SVDの堅牢なアルゴリズム


26

行列のSVDを計算するための簡単なアルゴリズムは何ですか?2×2

理想的には、数値的に堅牢なアルゴリズムが欲しいのですが、単純な実装とそれほど単純ではない実装の両方を見てみたいと思います。Cコードが受け入れられました。

論文やコードへの参照はありますか?


5
ウィキペディアには2x2の閉じた形式のソリューションがリストされていますが、その数値的性質についてはわかりません。
ダミアン

参考として、「Numerical Recipes」、Press et al。、Cambridge Press。非常に高価な本ですが、1セントに値します。SVDソリューションの他にも、他の多くの便利なアルゴリズムがあります。
ヤンハッケンバーグ

回答:


19

https://math.stackexchange.com/questions/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotationを参照してください(申し訳ありませんが、コメントに入れていましたが、登録しましたまだコメントを投稿できないようにこれを投稿するだけです)。

しかし、私は答えとしてそれを書いているので、メソッドも書きます:

E=m00+m112;F=m00m112;G=m10+m012;H=m10m012Q=E2+H2;R=F2+G2sバツ=Q+R;sy=QRa1=atan2GF;a2=atan2HEθ=a2a12;ϕ=a2+a12

それは次のようにマトリックスを分解します:

M=m00m01m10m11=cosϕϕϕcosϕsバツ00sycosθθθcosθ

この方法で保護する唯一のことは、atan2 に対してG=F=0またはH=E=0であることです。それよりも堅牢なものになるとは思わない更新: 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がコメントで述べたように、syは負になる場合があります。入力行列の行列式も負の場合にのみ発生します。その場合、正の特異値が必要な場合は、sy絶対値を取得します。


1
Q < Rの場合、は負になる可能性があるようです。これは不可能なはずです。syQ<R
ビクター

@VictorLiu入力行列が反転する場合、回転行列は反転できないため、反映できる唯一の場所はスケーリング行列内です。反転する入力行列を与えないでください。まだ計算はしていませんが、入力行列の行列式の符号がまたはRのどちらが大きいかを決定すると確信しています。QR
ペドロジメノ14

@VictorLiuこれで計算が完了し、実際、m 00 m 11 - m 01 m 10、つまり入力行列の行列式に単純化されることを確認しました。Q2R2m00m11m01m10
ペドロジメノ14

9

@ペドロ・ジメノ

「それ以上の堅牢性があるとは思わない。」

勝負を受けて立つ。

通常のアプローチは、atan2のようなトリガー関数を使用することです。直感的には、トリガー関数を使用する必要はありません。実際、すべての結果はアークタンの正弦および余弦になります。これは代数関数に簡略化できます。かなり時間がかかりましたが、代数関数のみを使用するようにPedroのアルゴリズムを単純化することができました。

次のpythonコードがトリックを行います。

numpy import 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


1
コードが間違っているようです。2x2の単位行列を考えます。その後、y1= 0、x1= 0、h1= 0、およびt1= 0/0 =となりNaNます。
ユグ

8

GSLはのための主要なSVDアルゴリズムのQR分解部の下にあるソルバー2×2のSVDを有していますgsl_linalg_SV_decompsvdstep.cファイルを参照して、svd2関数を探してください。この関数にはいくつかの特殊なケースがあり、厳密ではありません。数値的に注意するためにいくつかのことを行うように見えます(たとえば、hypotオーバーフローを避けるために使用します)。


1
この関数にはドキュメントがありますか?入力パラメータが何であるか知りたいです。
ビクター

@VictorLiu:残念なことに、ファイル自体にあるわずかなコメント以外は見ていません。ChangeLogGSLをダウンロードする場合、ファイルに少しあります。またsvd.c、アルゴリズム全体の詳細を確認できます。唯一の真のドキュメントは、たとえば、などの高レベルのユーザー呼び出し可能関数に関するものgsl_linalg_SV_decompです。
-horchler

7

「数値的に堅牢」と言うとき、通常は、エラー伝播を回避するためにピボットなどのことを行うアルゴリズムを意味します。ただし、2x2行列の場合は、明示的な式で結果を書き留めることができます。つまり、以前に計算された中間値ではなく、入力のみで結果を述べるSVD要素の式を書き留めることができます。つまり、キャンセルはできてもエラーは伝播しない可能性があります。

ポイントは、2x2システムの場合、堅牢性を心配する必要がないということです。


マトリックスに依存します。私は、左と右の角度を別々に見つける方法を見てきました(それぞれarctan2(y、x)を使用)。ただし、特異値が互いに近い場合、これらの各アークタンは0/0になる傾向があるため、結果が不正確になる可能性があります。Pedro Gimenoの方法では、この場合a2の計算は明確に定義されますが、a1は不明確になります。s.valsがtheta-phiではなく互いに近接している場合にのみ、分解の有効性がtheta + phiにのみ敏感であるため、依然として良好な結果が得られます。
グレゴ

5

このコードは、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]);
}

5

私は持っているアルゴリズムが必要でした

  • 小さな分岐(できればCMOV)
  • 三角関数呼び出しなし
  • 32ビット浮動小数点でも高い数値精度

c1s1c2s2σ1σ2

A=うんSV

[abcd]=[c1s1s1c1][σ100σ2][c2s2s2c2]

VATAVATAVT=D

それを思い出します

うんSV=A

うんS=AV1=AVTV

VATAVT=AVTTAVT=うんSTうんS=STうんTうんS=D

S1

STSTうんTうんSS1=うんTうん=STDS1

DSDうんTうん=denttyうんSVうんSV=A

対角化回転の計算は、次の方程式を解くことで実行できます。

t22βαγt21=0

どこで

ATA=[acbd][abcd]=[a2+c2ab+cdab+cdb2+d2]=[αγγβ]

t2VVATAVT

βαγA=RQRQうんSV=RうんSV=うんSVQ=RQ=AdR

S +DD

6107error=||うんSVM||/||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);
}

:アイデア
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/


3

このC ++コードを作成するには、http://www.lucidarme.me/?p = 4624の説明を使用しました。行列はEigenライブラリのものですが、この例から独自のデータ構造を簡単に作成できます。

A=うんΣVT

#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参照)とまったく同じ値が得られます


1
S2 = hypot( a*a + b*b - c*c - d*d, 2*(a*c + b*d))
グレゴ


2

私の個人的なニーズのために、2x2のsvdの最小計算を分離しようとしました。おそらく最も簡単で最速のソリューションの1つだと思います。詳細については、個人のブログhttp://lucidarme.me/?p=4624をご覧ください

利点:シンプルで高速、3つのマトリックス(S、UまたはD)のうち1つまたは2つしか計算できないのは、3つのマトリックスが必要ない場合のみです。

欠点はatan2を使用することです。これは不正確で、外部ライブラリ(typ。math.h)を必要とする場合があります。


3
リンクが永続的となることはめったにないので、答えとして単にリンクを提供するのではなく、アプローチを要約することが重要です。
ポール

また、自分のブログへのリンクを投稿する場合は、(a)自分のブログであることを開示してください。(b)実際にアプローチを要約またはカットアンドペーストすることをお勧めします(数式の画像は生のLaTeXに変換され、MathJaxを使用してレンダリングされます)。この種の質問状態式の最良の答えは、その式の引用を提供し、次に欠点、エッジケース、および潜在的な代替案などをリストします。
ジェフオックスベリー14

1

以下は、2x2 SVDソルバーの実装です。私は、Victor Liuのコードに基づいています。彼のコードは、一部のマトリックスでは機能していませんでした。解決のための数学的なリファレンスとして、これら2つのドキュメントを使用しました:pdf1pdf2

行列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
            );
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.