行列の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(そこにRahulによって与えられた)これは、このブログ投稿の下部にあります:http : //metamerist.blogspot.com/2006/10/linear-algebra -for-graphics-geeks-svd.html
更新: @VictorLiuがコメントで述べたように、は負になる場合があります。入力行列の行列式も負の場合にのみ発生します。その場合、正の特異値が必要な場合は、絶対値を取得します。
@ペドロ・ジメノ
「それ以上の堅牢性があるとは思わない。」
勝負を受けて立つ。
通常のアプローチは、atan2のようなトリガー関数を使用することです。直感的には、トリガー関数を使用する必要はありません。実際、すべての結果はアークタンの正弦および余弦になります。これは代数関数に簡略化できます。かなり時間がかかりましたが、代数関数のみを使用するようにPedroのアルゴリズムを単純化することができました。
次のpythonコードがトリックを行います。
numpy import 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アルゴリズムの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]);
}
私は持っているアルゴリズムが必要でした
それを思い出します
対角化回転の計算は、次の方程式を解くことで実行できます。
どこで
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;
}
これにより、https://eigen.tuxfamily.org/dox-devel/classEigen_1_1JacobiSVD.htmlをEigen::JacobiSVD
参照)とまったく同じ値が得られます。
S2 = hypot( a*a + b*b - c*c - d*d, 2*(a*c + b*d))
私の個人的なニーズのために、2x2のsvdの最小計算を分離しようとしました。おそらく最も簡単で最速のソリューションの1つだと思います。詳細については、個人のブログhttp://lucidarme.me/?p=4624をご覧ください。
利点:シンプルで高速、3つのマトリックス(S、UまたはD)のうち1つまたは2つしか計算できないのは、3つのマトリックスが必要ない場合のみです。
欠点はatan2を使用することです。これは不正確で、外部ライブラリ(typ。math.h)を必要とする場合があります。
以下は、2x2 SVDソルバーの実装です。私は、Victor Liuのコードに基づいています。彼のコードは、一部のマトリックスでは機能していませんでした。解決のための数学的なリファレンスとして、これら2つのドキュメントを使用しました: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
);
}
}
}