행렬 의 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 -for-graphics-geeks-svd.html
업데이트 : 의견에 @VictorLiu가 언급했듯이 는 음수 일 수 있습니다. 입력 행렬의 행렬식이 음수 인 경우에만 발생합니다. 이 경우 양의 특이 값을 원하면 의 절대 값을 사용하십시오 .
@ 페드로 기 메노
"나는 그것보다 더 강력 할 수 있을지 의심 스럽다."
도전은 받아 들였다.
일반적인 접근 방식은 atan2와 같은 삼각 함수를 사용하는 것입니다. 직관적으로 삼각 함수를 사용할 필요는 없습니다. 실제로 모든 결과는 대수 함수로 단순화 될 수있는 아크 탄의 사인과 코사인으로 끝납니다. 꽤 오랜 시간이 걸렸지 만 대수 함수 만 사용하도록 Pedro의 알고리즘을 단순화했습니다.
다음 파이썬 코드가 트릭을 수행합니다.
numpy import asarray, diag에서데프 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 알고리즘의 QR 분해 부 밑에 솔버 2 바이 2 SVD를 갖는다 gsl_linalg_SV_decomp
. 참고 항목 svdstep.c
파일을하고 찾는 svd2
기능. 이 함수에는 몇 가지 특별한 경우가 있으며 정확하게 사소하지는 않으며 숫자로주의 해야하는 몇 가지 작업을 수행하는 것처럼 보입니다 (예 : hypot
오버플로를 피하기 위해 사용 ).
ChangeLog
GSL을 다운로드하면 파일에 비트가 있습니다 . svd.c
전체 알고리즘 에 대한 자세한 내용을 볼 수 있습니다 . 유일한 진정한 문서는 높은 수준의 사용자 호출 가능 기능에 대한 것 같습니다 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]);
}
나는 가지고있는 알고리즘이 필요했다
주요 아이디어는 하는 회전 행렬 를 찾는 것입니다. 는 대각선입니다.
기억해
입니다.
대각선 회전 계산은 다음 방정식을 해결하여 수행 할 수 있습니다.
어디에
과 각도의 접선 . 이것은 확장하여 파생 될 수 있습니다 대각선 이외의 요소를 0으로 만듭니다 (서로 같음).
이 방법의 문제점은 계산할 때 부동 소수점 정밀도가 크게 떨어진다는 것입니다 과 계산에서 빼기 때문에 특정 행렬의 경우 이에 대한 해결책은 RQ 분해를 수행하는 것입니다., 상단 삼각형 직교) 먼저 알고리즘을 사용하여 분해 . 이것은 준다. 설정 방법에 주목 ~ 0 )는 일부 덧셈 / 뺄셈을 제거합니다. (RQ 분해는 매트릭스 제품의 확장으로 인해 사소한 것입니다).
이 방법으로 순진하게 구현 된 알고리즘에는 수치 적, 논리적 이상이 있습니다 (예 : 또는 ), 아래 코드에서 수정했습니다.
코드에서 약 2 억 개의 무작위 행렬을 던졌고 가장 큰 수치 오류는 약 (32 비트 수레로, ). 이 알고리즘은 약 340 클럭 사이클 (MSVC 19, Ivy Bridge)로 실행됩니다.
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/
이 C ++ 코드를 작성하기 위해 http://www.lucidarme.me/?p=4624 의 설명을 사용했습니다 . 행렬은 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에 대한 순수한 C 코드가 있습니다 . 559 행을 참조하십시오. 기본적으로 고유 값을 계산합니다.이차 문제를 해결함으로써 반드시 가장 강건한 것은 아니지만 병리학 적이 지 않은 경우에는 실제로 잘 작동하는 것 같습니다. 비교적 간단합니다.
개인적인 필요를 위해 2x2 svd에 대한 최소 계산을 분리하려고했습니다. 아마도 가장 간단하고 빠른 솔루션 중 하나 일 것입니다. 내 개인 블로그 ( http://lucidarme.me/?p=4624) 에서 세부 정보를 찾을 수 있습니다 .
장점 : 간단하고 빠르며 세 개의 매트릭스가 필요하지 않은 경우 세 개의 매트릭스 (S, U 또는 D) 중 하나 또는 두 개만 계산할 수 있습니다.
단점 은 atan2를 사용하는데, 이는 부정확하고 외부 라이브러리 (일반적으로 math.h)가 필요할 수 있습니다.
다음은 2x2 SVD 솔버의 구현입니다. Victor Liu의 코드를 기반으로했습니다. 그의 코드는 일부 행렬에서 작동하지 않았습니다. 이 두 문서를 해결을위한 수학적 참조로 사용했습니다 : pdf1 및 pdf2 .
행렬 setData
방법은 주요 행 순서입니다. 내부적으로는 행렬 데이터를로 지정된 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
);
}
}
}