อัลกอริธึมง่ายๆสำหรับการคำนวณ SVD ของเมทริกซ์คืออะไร?
เป็นการดีที่ฉันต้องการอัลกอริทึมที่มีประสิทธิภาพเป็นตัวเลข แต่ฉันต้องการเห็นการใช้งานที่เรียบง่ายและไม่ง่าย ยอมรับรหัส C
การอ้างอิงใด ๆ กับเอกสารหรือรหัส?
อัลกอริธึมง่ายๆสำหรับการคำนวณ SVD ของเมทริกซ์คืออะไร?
เป็นการดีที่ฉันต้องการอัลกอริทึมที่มีประสิทธิภาพเป็นตัวเลข แต่ฉันต้องการเห็นการใช้งานที่เรียบง่ายและไม่ง่าย ยอมรับรหัส C
การอ้างอิงใด ๆ กับเอกสารหรือรหัส?
คำตอบ:
ดูhttps://math.stackexchange.com/questions/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation-ขออภัย (ฉันจะใส่ไว้ในความคิดเห็น แต่ฉันลงทะเบียนแล้ว เพียงโพสต์สิ่งนี้ดังนั้นฉันยังไม่สามารถแสดงความคิดเห็นได้
แต่เมื่อฉันเขียนมันเป็นคำตอบฉันจะเขียนวิธี:
นั่นสลายเมทริกซ์ดังนี้
สิ่งเดียวที่จะป้องกันด้วยวิธีนี้คือหรือสำหรับ atan2ฉันสงสัยว่ามันจะแข็งแกร่งกว่านั้น( อัปเดต:ดูคำตอบของ Alex Eftimiades!)
การอ้างอิงคือ: http://dx.doi.org/10.1109/38.486688 (ให้โดยราหุลที่นั่น) ซึ่งมาจากด้านล่างของโพสต์บล็อกนี้: http://metamerist.blogspot.com/2006/10/linear-algebra เผื่อกราฟิก geeks-svd.html
อัปเดต:ตามที่ระบุไว้โดย @VictorLiu ในความคิดเห็นอาจเป็นค่าลบ ที่เกิดขึ้นถ้าหากปัจจัยของเมทริกซ์อินพุตเป็นลบเช่นกัน หากเป็นกรณีและคุณต้องการค่าเอกพจน์บวกเพียงแค่ใช้ค่าสัมบูรณ์ของ Y
@Pedro Gimeno
"ฉันสงสัยว่ามันจะแข็งแกร่งกว่านี้อีกแล้ว"
รับคำท้า.
ฉันสังเกตเห็นวิธีการปกติคือการใช้ฟังก์ชั่นตรีโกณมิติเช่น atan2 ไม่จำเป็นต้องใช้ฟังก์ชันตรีโกณมิติ อันที่จริงผลลัพธ์ทั้งหมดจบลงด้วยการเป็นไซน์และอาร์คตินของอาร์คตัน - ซึ่งสามารถทำให้ฟังก์ชั่นพีชคณิตง่ายขึ้น ใช้เวลาสักครู่ แต่ฉันจัดการเพื่อทำให้อัลกอริทึมของ Pedro ง่ายขึ้นเพื่อใช้ฟังก์ชันพีชคณิตเท่านั้น
โค้ดไพ ธ อนต่อไปนี้ใช้เคล็ดลับ
จาก asarray นำเข้าจำนวนมาก, diagdef 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มี SVD 2 โดย 2 แก้พื้นฐานส่วนหนึ่ง QR การสลายตัวของอัลกอริทึม SVD gsl_linalg_SV_decomp
หลักสำหรับ ดูsvdstep.c
ไฟล์และมองหาsvd2
ฟังก์ชั่น ฟังก์ชั่นนี้มีกรณีพิเศษไม่กี่เรื่องเล็กน้อยและดูเหมือนจะทำสิ่งต่าง ๆ ให้ระมัดระวังเป็นตัวเลข (เช่นใช้hypot
เพื่อหลีกเลี่ยงการล้น)
ChangeLog
ไฟล์อยู่เล็กน้อยถ้าคุณดาวน์โหลด GSL และคุณสามารถดูsvd.c
รายละเอียดของอัลกอริทึมโดยรวมได้ เอกสารที่แท้จริงเท่านั้นดูเหมือนว่าจะเป็นระดับสูงฟังก์ชั่นการใช้งาน callable gsl_linalg_SV_decomp
เช่น
เมื่อเราพูดว่า "มีประสิทธิภาพเชิงตัวเลข" เรามักจะหมายถึงอัลกอริทึมที่เราทำสิ่งต่าง ๆ เช่นหมุนเพื่อหลีกเลี่ยงข้อผิดพลาดการแพร่กระจาย อย่างไรก็ตามสำหรับเมทริกซ์ 2x2, คุณสามารถเขียนลงผลในแง่ของสูตรอย่างชัดเจน - คือการเขียนลงสูตรสำหรับองค์ประกอบ SVD ที่รัฐผลที่ได้เพียง แต่ในแง่ของปัจจัยการผลิตมากกว่าในแง่ของค่ากลางคำนวณก่อนหน้านี้ ซึ่งหมายความว่าคุณอาจมีการยกเลิก แต่ไม่มีการเผยแพร่ข้อผิดพลาด
ประเด็นก็คือสำหรับระบบ 2x2 ไม่จำเป็นต้องกังวลเกี่ยวกับความทนทาน
รหัสนี้ขึ้นอยู่กับกระดาษของ Blinn , กระดาษ Ellis , การบรรยาย SVDและการ คำนวณเพิ่มเติม อัลกอริทึมเหมาะสำหรับเมทริกซ์จริงปกติและเอกพจน์ ทุกรุ่นก่อนหน้านี้ใช้งานได้ 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]);
}
ฉันต้องการอัลกอริทึมที่มี
เราต้องการคำนวณและσ 2ดังต่อไปนี้:
ซึ่งสามารถขยายได้เช่น:
แนวคิดหลักคือการหาเมทริกซ์การหมุนที่ทแยงมุมA T Aนั่นคือV A T A V T = Dเป็นแนวทแยงมุม
จำได้ว่า
(เนื่องจาก Vเป็นมุมฉาก)
การคูณทั้งสองข้างด้วยเราได้
เนื่องจากเป็นแนวทแยงตั้งค่าSเป็น√จะให้เราUTU=ฉันdentฉันtYหมายถึงUเป็นเมทริกซ์หมุนSเป็นเมทริกซ์ทแยงมุมVเป็นเมทริกซ์วาระและUSV=เพียงสิ่งที่เรากำลังมองหา
การคำนวณการหมุนในแนวทแยงสามารถทำได้โดยการแก้สมการต่อไปนี้:
ที่ไหน
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/2071Spring08/lab09/index.html
http: // www.lucidarme.me/singular-value-decomposition-of-a-2x2-matrix/
ฉันใช้คำอธิบายที่http://www.lucidarme.me/?p=4624เพื่อสร้างรหัส C ++ นี้ เมทริกซ์เป็นของไลบรารี Eigen แต่คุณสามารถสร้างโครงสร้างข้อมูลของคุณเองจากตัวอย่างนี้:
#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 ฉันคิดว่ามันน่าจะเป็นวิธีแก้ปัญหาที่ง่ายและเร็วที่สุด คุณสามารถหารายละเอียดในบล็อกส่วนตัวของฉัน: http://lucidarme.me/?p=4624
ข้อดี:ง่ายรวดเร็วและคุณสามารถคำนวณได้เพียงหนึ่งหรือสองในสามเมทริกซ์ (S, U หรือ D) หากคุณไม่ต้องการเมทริกซ์สามตัว
ข้อเสียเปรียบมันใช้ atan2 ซึ่งอาจไม่แน่นอนและอาจต้องใช้ห้องสมุดภายนอก (typ. math.h)
นี่คือการดำเนินการแก้ไข 2x2 SVD ฉันใช้มันจากรหัสของวิกเตอร์หลิว รหัสของเขาไม่ทำงานสำหรับเมทริกซ์บางตัว ผมใช้ทั้งสองเอกสารอ้างอิงทางคณิตศาสตร์เพื่อแก้: pdf1และpdf2
setData
วิธีการเมทริกซ์อยู่ในลำดับที่สำคัญแถว ภายในผมเป็นตัวแทนของข้อมูลเมทริกซ์เป็น array 2D 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
);
}
}
}