Saya membutuhkan algoritma yang dimiliki
- sedikit percabangan (semoga CMOVs)
- tidak ada panggilan fungsi trigonometri
- akurasi numerik tinggi bahkan dengan floats 32 bit
Kami ingin menghitung dan sebagai berikut:c1,s1,c2,s2,σ1σ2
A=USV , yang dapat diperluas seperti:
[acbd]=[c1−s1s1c1][σ100σ2][c2s2−s2c2]
Gagasan utamanya adalah menemukan matriks rotasi yang mendiagonalisasi , yaitu adalah diagonal.VATAVATAVT=D
Ingat itu
USV=A
US=AV−1=AVT (karena adalah orthogonal)V
VATAVT=(AVT)TAVT=(US)TUS=STUTUS=D
Mengalikan kedua sisi dengan kita dapatkanS−1
(S−TST)UTU(SS−1)=UTU=S−TDS−1
Karena adalah diagonal, pengaturan ke akan memberi kita , artinya adalah matriks rotasi, adalah matriks diagonal, adalah matriks rotasi dan , seperti yang kita cari untuk.DSD−−√UTU=IdentityUSVUSV=A
Menghitung rotasi diagonalisasi dapat dilakukan dengan menyelesaikan persamaan berikut:
t22−β−αγt2−1=0
dimana
ATA=[abcd][acbd]=[a2+c2ab+cdab+cdb2+d2]=[αγγβ]
dan adalah tangen dari sudut . Ini dapat diturunkan dengan memperluas dan membuat elemen off-diagonal sama dengan nol (mereka sama satu sama lain).t2VVATAVT
Masalah dengan metode ini adalah bahwa ia kehilangan presisi floating point yang signifikan ketika menghitung dan untuk matriks tertentu, karena pengurangan dalam perhitungan. Solusi untuk ini adalah untuk melakukan dekomposisi RQ ( , segitiga atas dan orthogonal) pertama, kemudian menggunakan algoritma untuk pd . Hal ini memberikan . Perhatikan bagaimana pengaturan to 0 (seperti pada ) menghilangkan beberapa penambahan / pengurangan. (Dekomposisi RQ cukup sepele dari perluasan produk matriks).β−αγA=RQRQUSV′=RUSV=USV′Q=RQ=AdR
Algoritme yang diimplementasikan secara naif dengan cara ini memiliki beberapa anomali numerik dan logis (misalnya atau ), yang saya perbaiki dalam kode di bawah ini.S +D−−√−D−−√
Saya melemparkan sekitar 2000 juta matriks acak pada kode, dan kesalahan numerik terbesar yang dihasilkan adalah sekitar (dengan floats 32 bit, ). Algoritma ini berjalan di sekitar 340 siklus clock (MSVC 19, Ivy Bridge).6⋅10−7error=||USV−M||/||M||
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);
}
Gagasan dari:
http://www.cs.utexas.edu/users/inderjit/public_papers/HLA_SVD.pdf
http://www.math.pitt.edu/~sussmanm/2071Spring08/lab09/index.html
http: // www.lucidarme.me/singular-value-decomposition-of-a-2x2-matrix/