_ मेट्रिसेस के SVD की गणना के लिए एक सरल एल्गोरिथ्म क्या है ?
आदर्श रूप में, मैं एक संख्यात्मक रूप से मजबूत एल्गोरिथम पसंद करूंगा, लेकिन मैं सरल और समान-सरल दोनों कार्यान्वयन देखना चाहूंगा। C कोड स्वीकार किया गया।
कागजात या कोड का कोई संदर्भ?
_ मेट्रिसेस के SVD की गणना के लिए एक सरल एल्गोरिथ्म क्या है ?
आदर्श रूप में, मैं एक संख्यात्मक रूप से मजबूत एल्गोरिथम पसंद करूंगा, लेकिन मैं सरल और समान-सरल दोनों कार्यान्वयन देखना चाहूंगा। C कोड स्वीकार किया गया।
कागजात या कोड का कोई संदर्भ?
जवाबों:
देखें /math/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation (क्षमा करें, मैं डाल दिया है कि एक टिप्पणी में, लेकिन मैं पंजीकृत कर लिया है बस इसे पोस्ट करने के लिए इसलिए मैं अभी तक टिप्पणी पोस्ट नहीं कर सकता)।
लेकिन जब से मैं इसे उत्तर के रूप में लिख रहा हूं, मैं विधि भी लिखूंगा:
मैट्रिक्स को निम्नानुसार विघटित करता है:
इस विधि के साथ रक्षा करने के लिए केवल एक चीज है कि atan2 के लिए या ।मुझे संदेह है कि यह इससे ज्यादा मजबूत हो सकता है( अपडेट: एलेक्स एफ़्टीमाईड्स का जवाब देखें!)।
संदर्भ है: http://dx.doi.org/10.1109/38.486688 (राहुल द्वारा दिया गया) जो इस ब्लॉग पोस्ट के नीचे से आता है: http://metamerist.blogspot.com/2006/10/linear-alnbra -किसी-ग्राफिक्स-गीक्स-svd.html
अपडेट: जैसा कि @VictorLiu ने एक टिप्पणी में कहा है, नकारात्मक हो सकता है। ऐसा तब होता है जब और केवल तभी इनपुट मैट्रिक्स का निर्धारक ऋणात्मक होता है। अगर ऐसा है और आप सकारात्मक एकवचन मूल्यों को चाहते हैं, तो बस का पूर्ण मूल्य ।
@Pedro Gimeno
"मुझे संदेह है कि यह उससे कहीं अधिक मजबूत हो सकता है।"
चुनौती स्वीकार की गई।
मैंने देखा कि सामान्य दृष्टिकोण atan2 जैसे ट्रिगर कार्यों का उपयोग करना है। सहज रूप से, ट्रिगर कार्यों का उपयोग करने की आवश्यकता नहीं होनी चाहिए। वास्तव में, सभी परिणाम आर्कन के साइन और कोजाइन के रूप में समाप्त होते हैं - जिन्हें बीजीय कार्यों के लिए सरल बनाया जा सकता है। इसमें काफी समय लगा, लेकिन मैंने केवल बीजीय कार्यों का उपयोग करने के लिए पेड्रो के एल्गोरिथ्म को सरल बनाने में कामयाबी हासिल की।
निम्नलिखित अजगर कोड चाल करता है।
सुन्न आयात asarray, diag सेdef svd2 (m):
y1, x1 = (m[1, 0] + m[0, 1]), (m[0, 0] - m[1, 1]) y2, x2 = (m[1, 0] - m[0, 1]), (m[0, 0] + m[1, 1]) h1 = hypot(y1, x1) h2 = hypot(y2, x2) t1 = x1 / h1 t2 = x2 / h2 cc = sqrt((1 + t1) * (1 + t2)) ss = sqrt((1 - t1) * (1 - t2)) cs = sqrt((1 + t1) * (1 - t2)) sc = sqrt((1 - t1) * (1 + t2)) c1, s1 = (cc - ss) / 2, (sc + cs) / 2, u1 = asarray([[c1, -s1], [s1, c1]]) d = asarray([(h1 + h2) / 2, (h1 - h2) / 2]) sigma = diag(d) if h1 != h2: u2 = diag(1 / d).dot(u1.T).dot(m) else: u2 = diag([1 / d[0], 0]).dot(u1.T).dot(m) return u1, sigma, u2
y1
= 0, x1
= 0, h1
= 0, और t1
= 0/0 = NaN
।
GSL एक 2-दर-2 SVD के लिए मुख्य SVD एल्गोरिथ्म के QR अपघटन हिस्सा अंतर्निहित solver है gsl_linalg_SV_decomp
। svdstep.c
फ़ाइल देखें और svd2
फ़ंक्शन देखें। समारोह में कुछ विशेष मामले हैं, बिल्कुल तुच्छ नहीं है, और संख्यात्मक रूप से सावधान रहने के लिए कई काम कर रहे हैं (जैसे, hypot
ओवरफ्लो से बचने के लिए उपयोग करना)।
ChangeLog
यदि आप जीएसएल डाउनलोड करते हैं तो फ़ाइल में थोड़ा सा है । और आप svd.c
समग्र एल्गोरिथ्म के विवरण के लिए देख सकते हैं । उच्च स्तर के उपयोगकर्ता-कॉल करने योग्य कार्यों, जैसे, के लिए एकमात्र सही दस्तावेज लगता है gsl_linalg_SV_decomp
।
जब हम कहते हैं "संख्यात्मक रूप से मजबूत" हम आम तौर पर एक एल्गोरिथ्म का मतलब है जिसमें हम त्रुटि प्रसार से बचने के लिए धुरी जैसी चीजें करते हैं। हालांकि, 2x2 मैट्रिक्स के लिए, आप परिणाम को स्पष्ट फॉर्मूलों के संदर्भ में लिख सकते हैं - यानी, SVD तत्वों के लिए फॉर्मूले लिखें, जो परिणाम को केवल इनपुट के संदर्भ में बताते हैं , बजाय पहले से गणना किए गए मध्यवर्ती मूल्यों के संदर्भ में । इसका मतलब है कि आपके पास रद्दीकरण हो सकता है लेकिन कोई त्रुटि नहीं है।
बस बात यह है कि 2x2 प्रणालियों के लिए, मजबूती के बारे में चिंता करना आवश्यक नहीं है।
यह कोड ब्लिन के पेपर , एलिस पेपर , एसवीडी व्याख्यान , और अतिरिक्त गणनाओं पर आधारित है। एक एल्गोरिथ्म नियमित और एकवचन वास्तविक मैट्रिक्स के लिए उपयुक्त है। पिछले सभी संस्करण 100% के साथ-साथ इस एक पर काम करते हैं।
#include <stdio.h>
#include <math.h>
void svd22(const double a[4], double u[4], double s[2], double v[4]) {
s[0] = (sqrt(pow(a[0] - a[3], 2) + pow(a[1] + a[2], 2)) + sqrt(pow(a[0] + a[3], 2) + pow(a[1] - a[2], 2))) / 2;
s[1] = fabs(s[0] - sqrt(pow(a[0] - a[3], 2) + pow(a[1] + a[2], 2)));
v[2] = (s[0] > s[1]) ? sin((atan2(2 * (a[0] * a[1] + a[2] * a[3]), a[0] * a[0] - a[1] * a[1] + a[2] * a[2] - a[3] * a[3])) / 2) : 0;
v[0] = sqrt(1 - v[2] * v[2]);
v[1] = -v[2];
v[3] = v[0];
u[0] = (s[0] != 0) ? (a[0] * v[0] + a[1] * v[2]) / s[0] : 1;
u[2] = (s[0] != 0) ? (a[2] * v[0] + a[3] * v[2]) / s[0] : 0;
u[1] = (s[1] != 0) ? (a[0] * v[1] + a[1] * v[3]) / s[1] : -u[2];
u[3] = (s[1] != 0) ? (a[2] * v[1] + a[3] * v[3]) / s[1] : u[0];
}
int main() {
double a[4] = {1, 2, 3, 6}, u[4], s[2], v[4];
svd22(a, u, s, v);
printf("Matrix A:\n%f %f\n%f %f\n\n", a[0], a[1], a[2], a[3]);
printf("Matrix U:\n%f %f\n%f %f\n\n", u[0], u[1], u[2], u[3]);
printf("Matrix S:\n%f %f\n%f %f\n\n", s[0], 0, 0, s[1]);
printf("Matrix V:\n%f %f\n%f %f\n\n", v[0], v[1], v[2], v[3]);
}
मुझे एक एल्गोरिथ्म की आवश्यकता है जो है
हम निम्न प्रकार से और गणना करना चाहते हैं:
, जिसे बढ़ाया जा सकता है जैसे:
मुख्य विचार एक घूर्णन मैट्रिक्स को खोजना है जो विकर्ण करता है, यह विकर्ण है।
याद करें कि
(चूंकि ऑर्थोगोनल है)
दोनों पक्षों को गुणा करके हम प्राप्त करते हैं
चूंकि विकर्ण है, को सेट करने से हमें मिलेगी , जिसका अर्थ है कि एक घूर्णन मैट्रिक्स है, एक विकर्ण मैट्रिक्स है, एक घूर्णन मैट्रिक्स और , बस जिसे हम देख रहे हैं। के लिये।
विकर्ण रोटेशन की गणना निम्नलिखित समीकरण को हल करके की जा सकती है:
कहा पे
और , के कोण का स्पर्शरेखा है । यह विस्तार करके और इसके ऑफ-विकर्ण तत्वों को शून्य के बराबर बनाकर (वे एक-दूसरे के बराबर हैं) हो सकते हैं।
इस विधि के साथ समस्या यह है कि यह महत्वपूर्ण फ्लोटिंग पॉइंट सटीक खो देता है जब गणना में घटाव के कारण कुछ मैट्रिसेस के लिए और की गणना की जाती है। इसका समाधान पहले एक आरक्यू अपघटन ( , ऊपरी त्रिकोणीय और ऑर्थोगोनल) करना है, फिर को फैक्ट करने के लिए एल्गोरिथ्म का उपयोग करें । यह । सूचना कैसे स्थापित करने 0 (के रूप में ) अतिरिक्त / subtractions से कुछ दूर करता है। (मैट्रिक्स उत्पाद के विस्तार से आरक्यू अपघटन काफी तुच्छ है)।
एल्गोरिथ्म ने इस तरह लागू किया कुछ संख्यात्मक और तार्किक विसंगतियाँ हैं (उदाहरण के लिए या ), जो मैंने नीचे दिए गए कोड में तय किया है।
मैंने लगभग 2000 मिलियन रैंडमाइज्ड मेट्रिसेस को कोड में फेंक दिया, और निर्मित सबसे बड़ी संख्यात्मक त्रुटि लगभग (32 बिट फ़्लोट, ) के साथ थी। एल्गोरिथ्म लगभग 340 घड़ी चक्र (MSVC 19, आइवी ब्रिज) में चलता है।
template <class T>
void Rq2x2Helper(const Matrix<T, 2, 2>& A, T& x, T& y, T& z, T& c2, T& s2) {
T a = A(0, 0);
T b = A(0, 1);
T c = A(1, 0);
T d = A(1, 1);
if (c == 0) {
x = a;
y = b;
z = d;
c2 = 1;
s2 = 0;
return;
}
T maxden = std::max(abs(c), abs(d));
T rcmaxden = 1/maxden;
c *= rcmaxden;
d *= rcmaxden;
T den = 1/sqrt(c*c + d*d);
T numx = (-b*c + a*d);
T numy = (a*c + b*d);
x = numx * den;
y = numy * den;
z = maxden/den;
s2 = -c * den;
c2 = d * den;
}
template <class T>
void Svd2x2Helper(const Matrix<T, 2, 2>& A, T& c1, T& s1, T& c2, T& s2, T& d1, T& d2) {
// Calculate RQ decomposition of A
T x, y, z;
Rq2x2Helper(A, x, y, z, c2, s2);
// Calculate tangent of rotation on R[x,y;0,z] to diagonalize R^T*R
T scaler = T(1)/std::max(abs(x), abs(y));
T x_ = x*scaler, y_ = y*scaler, z_ = z*scaler;
T numer = ((z_-x_)*(z_+x_)) + y_*y_;
T gamma = x_*y_;
gamma = numer == 0 ? 1 : gamma;
T zeta = numer/gamma;
T t = 2*impl::sign_nonzero(zeta)/(abs(zeta) + sqrt(zeta*zeta+4));
// Calculate sines and cosines
c1 = T(1) / sqrt(T(1) + t*t);
s1 = c1*t;
// Calculate U*S = R*R(c1,s1)
T usa = c1*x - s1*y;
T usb = s1*x + c1*y;
T usc = -s1*z;
T usd = c1*z;
// Update V = R(c1,s1)^T*Q
t = c1*c2 + s1*s2;
s2 = c2*s1 - c1*s2;
c2 = t;
// Separate U and S
d1 = std::hypot(usa, usc);
d2 = std::hypot(usb, usd);
T dmax = std::max(d1, d2);
T usmax1 = d2 > d1 ? usd : usa;
T usmax2 = d2 > d1 ? usb : -usc;
T signd1 = impl::sign_nonzero(x*z);
dmax *= d2 > d1 ? signd1 : 1;
d2 *= signd1;
T rcpdmax = 1/dmax;
c1 = dmax != T(0) ? usmax1 * rcpdmax : T(1);
s1 = dmax != T(0) ? usmax2 * rcpdmax : T(0);
}
से विचार:
http://www.cs.utexas.edu/users/inderjit/public_papers/HLA_SVD.pdf
http://www.math.pitt.edu/~sussmanm/2071Sprn08/lab09/index.html
http: // www.lucidarme.me/singular-value-decomposition-of-a-2x2-matrix/
मैंने इस C ++ कोड को बनाने के लिए http://www.lucidarme.me/?p=4624 पर विवरण का उपयोग किया है । मेट्रिसेस, ईगन लाइब्रेरी के हैं, लेकिन आप इस उदाहरण से आसानी से अपनी स्वयं की डेटा संरचना बना सकते हैं:
#include <cmath>
#include <Eigen/Core>
using namespace Eigen;
Matrix2d A;
// ... fill A
double a = A(0,0);
double b = A(0,1);
double c = A(1,0);
double d = A(1,1);
double Theta = 0.5 * atan2(2*a*c + 2*b*d,
a*a + b*b - c*c - d*d);
// calculate U
Matrix2d U;
U << cos(Theta), -sin(Theta), sin(Theta), cos(Theta);
double Phi = 0.5 * atan2(2*a*b + 2*c*d,
a*a - b*b + c*c - d*d);
double s11 = ( a*cos(Theta) + c*sin(Theta))*cos(Phi) +
( b*cos(Theta) + d*sin(Theta))*sin(Phi);
double s22 = ( a*sin(Theta) - c*cos(Theta))*sin(Phi) +
(-b*sin(Theta) + d*cos(Theta))*cos(Phi);
// calculate S
S1 = a*a + b*b + c*c + d*d;
S2 = sqrt(pow(a*a + b*b - c*c - d*d, 2) + 4*pow(a*c + b*d, 2));
Matrix2d Sigma;
Sigma << sqrt((S1+S2) / 2), 0, 0, sqrt((S1-S2) / 2);
// calculate V
Matrix2d V;
V << signum(s11)*cos(Phi), -signum(s22)*sin(Phi),
signum(s11)*sin(Phi), signum(s22)*cos(Phi);
मानक साइन फ़ंक्शन के साथ
double signum(double value)
{
if(value > 0)
return 1;
else if(value < 0)
return -1;
else
return 0;
}
यह बिल्कुल वैसा ही मान देता है जैसे Eigen::JacobiSVD
( https://eigen.tuxfamily.org/dox-devel/classEigen_1_1JacobiSVD.html देखें )।
S2 = hypot( a*a + b*b - c*c - d*d, 2*(a*c + b*d))
मैं यहाँ 2x2 असली SVD के लिए शुद्ध C कोड रखता हूँ । पंक्ति 559 देखें। यह अनिवार्य रूप से द्विघात को हल करके आइगेनवैल्यूज़ की गणना करता है , इसलिए यह आवश्यक रूप से सबसे मजबूत नहीं है, लेकिन यह व्यावहारिक रूप से भी-पैथोलॉजिकल मामलों के लिए अच्छा काम करता है। यह अपेक्षाकृत सरल है।
अपनी व्यक्तिगत ज़रूरत के लिए, मैंने 2x2 svd के लिए न्यूनतम गणना को अलग करने की कोशिश की। मुझे लगता है कि यह शायद सबसे सरल और सबसे तेज़ समाधान में से एक है। : तुम मेरी निजी ब्लॉग पर जानकारी प्राप्त कर सकते http://lucidarme.me/?p=4624 ।
लाभ: सरल, तेज और आप तीन मैट्रिसेस (एस, यू या डी) में से केवल एक या दो की गणना कर सकते हैं यदि आपको तीन मैट्रिस की आवश्यकता नहीं है।
ड्राबैक यह atan2 का उपयोग करता है, जो गलत हो सकता है और इसके लिए बाहरी लाइब्रेरी (टाइप। math.h) की आवश्यकता हो सकती है।
यहां एक 2x2 एसवीडी समाधान का कार्यान्वयन है। मैंने इसे विक्टर लियू के कोड से हटा दिया। कुछ मैट्रिसेस के लिए उनका कोड काम नहीं कर रहा था। : मैं हल के लिए गणितीय संदर्भ के रूप में इन दोनों दस्तावेजों का इस्तेमाल किया pdf1 और pdf2 ।
मैट्रिक्स setData
विधि पंक्ति-प्रमुख क्रम में है। आंतरिक रूप से, मैं मैट्रिक्स डेटा का प्रतिनिधित्व 2 डी सरणी के रूप में करता हूं data[col][row]
।
void Matrix2f::svd(Matrix2f* w, Vector2f* e, Matrix2f* v) const{
//If it is diagonal, SVD is trivial
if (fabs(data[0][1] - data[1][0]) < EPSILON && fabs(data[0][1]) < EPSILON){
w->setData(data[0][0] < 0 ? -1 : 1, 0, 0, data[1][1] < 0 ? -1 : 1);
e->setData(fabs(data[0][0]), fabs(data[1][1]));
v->loadIdentity();
}
//Otherwise, we need to compute A^T*A
else{
float j = data[0][0]*data[0][0] + data[0][1]*data[0][1],
k = data[1][0]*data[1][0] + data[1][1]*data[1][1],
v_c = data[0][0]*data[1][0] + data[0][1]*data[1][1];
//Check to see if A^T*A is diagonal
if (fabs(v_c) < EPSILON){
float s1 = sqrt(j),
s2 = fabs(j-k) < EPSILON ? s1 : sqrt(k);
e->setData(s1, s2);
v->loadIdentity();
w->setData(
data[0][0]/s1, data[1][0]/s2,
data[0][1]/s1, data[1][1]/s2
);
}
//Otherwise, solve quadratic for eigenvalues
else{
float jmk = j-k,
jpk = j+k,
root = sqrt(jmk*jmk + 4*v_c*v_c),
eig = (jpk+root)/2,
s1 = sqrt(eig),
s2 = fabs(root) < EPSILON ? s1 : sqrt((jpk-root)/2);
e->setData(s1, s2);
//Use eigenvectors of A^T*A as V
float v_s = eig-j,
len = sqrt(v_s*v_s + v_c*v_c);
v_c /= len;
v_s /= len;
v->setData(v_c, -v_s, v_s, v_c);
//Compute w matrix as Av/s
w->setData(
(data[0][0]*v_c + data[1][0]*v_s)/s1,
(data[1][0]*v_c - data[0][0]*v_s)/s2,
(data[0][1]*v_c + data[1][1]*v_s)/s1,
(data[1][1]*v_c - data[0][1]*v_s)/s2
);
}
}
}