SVD için sağlam algoritma


26

matrisin SVD'sini hesaplamak için basit bir algoritma nedir ?2×2

İdeal olarak, sayısal olarak sağlam bir algoritma istiyorum, ancak hem basit hem de basit olmayan uygulamaları görmek istiyorum. C kodu kabul edildi.

Makalelere veya kodlara referans var mı?


5
Wikipedia 2x2 kapalı formlu bir çözüm sunuyor, ancak sayısal özellikleri hakkında hiçbir fikrim yok.
Damien

Referans olarak, "Nümerik Tarifler", Press ve diğerleri, Cambridge Press. Oldukça pahalı kitap ama her kuruş. SVD çözümlerinin yanı sıra birçok başka yararlı algoritma bulacaksınız.
Jan Hackenberg,

Yanıtlar:


19

Bkz. Https://math.stackexchange.com/questions/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation (üzgünüm, bunu bir yoruma koyardım ama kayıt oldum sadece bunu göndermek için henüz yorum gönderemiyorum).

Ancak cevap olarak yazdığımdan, yöntemi de yazacağım:

E=m00+m112;F=m00m112;G=m10+m012;H=m10m012Q=E2+H2;R=F2+G2sx=Q+R;sy=QRa1=atan2(G,F);a2=atan2(H,E)θ=a2a12;ϕ=a2+a12

Bu matrisi şu şekilde ayrıştırır:

M=(m00m01m10m11)=(cosϕsinϕsinϕcosϕ)(sx00sy)(cosθsinθsinθcosθ)

Bu yöntemle korunacak tek şey atan2 için veya .G=F=0H=E=0Bundan daha güçlü olabileceğinden şüpheliyim( Güncelleme: Alex Eftimiades'in cevabına bakınız!).

Referans: Bu blog yazısının altından gelen http://dx.doi.org/10.1109/38.486688 (burada Rahul tarafından verilen): http://metamerist.blogspot.com/2006/10/linear-algebra -için-grafik inekler-svd.html

Güncelleme: Bir yorumda tarafından belirtildiği gibi, negatif olabilir. Bu, eğer sadece giriş matrisinin determinantı da negatifse olur. Durum buysa ve pozitif tekil değerleri istiyorsanız, sadece mutlak değerini .sysy


1
olursa , negatif olabilir . Bu mümkün olmamalı. Q < RsyQ<R
Victor Liu

@VictorLiu Girdi matrisi dönerse, yansıtılabilecek tek yer, döndürme matrislerinin büyük olasılıkla çeviremeyeceği şekilde ölçeklendirme matrisindedir. Sadece çevirerek açılan giriş matrislerini beslemeyin. Henüz matematiği yapmadım ama bahse girerim giriş matrisinin determinantının işareti veya daha büyük olup olmadığını belirleyecektir . R,QR
Pedro Gimeno

@VictorLiu Ben şimdi matematiği yaptım ve gerçekten, basitleştirdiğini, yani giriş matrisinin determinantını . m 00 m 11 - m 01 m 10Q2R2m00m11m01m10
Pedro Gimeno

9

@Pedro Gimeno

“Bundan daha güçlü olabileceğinden şüpheliyim.”

Meydan okuma kabul edildi.

Genel yaklaşımın atan2 gibi trig işlevlerini kullanmak olduğunu fark ettim. Sezgisel olarak, trig işlevlerini kullanmaya gerek olmamalıdır. Aslında, tüm sonuçlar cebirsel fonksiyonlarla basitleştirilebilen artansların sinüs ve kosinüsleri olarak sonuçlanır. Biraz zaman aldı, ancak Pedro'nun algoritmasını sadece cebirsel fonksiyonları kullanacak şekilde basitleştirmeyi başardım.

Aşağıdaki python kodu hile yapar.

Numpy ithalat asarray gelen 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
Kod yanlış görünüyor. 2x2 kimlik matrisini düşünün. Ardından y1= 0, x1= 0, h1= 0 ve t1= 0/0 = NaN.
Hugues

8

GSL ana SVD algoritmasının QR ayrışma kısmını temel çözücü, bir 2-by-2 SVD sahiptir gsl_linalg_SV_decomp. svdstep.cDosyaya bakın ve svd2işlevi arayın . Fonksiyonun birkaç özel durumu vardır, tamamen önemsiz değildir ve sayısal olarak dikkatli olmak için birkaç şey yapıyor gibi görünmektedir (örneğin hypottaşmaları önlemek için kullanılır ).


1
Bu işlevin herhangi bir belgesi var mı? Girdi parametrelerinin ne olduğunu bilmek istiyorum.
Victor Liu,

@VictorLiu: Ne yazık ki dosyadaki yetersiz yorumlardan başka bir şey görmedim. ChangeLogGSL'yi indirirseniz dosyada bir bit var . Ve svd.cgenel algoritmanın ayrıntılarına bakabilirsiniz . Tek gerçek dokümantasyon, yüksek düzeyde kullanıcı tarafından çağrılabilir fonksiyonlar için olduğu gibi, örn gsl_linalg_SV_decomp.
horchler

7

"Nümerik olarak sağlam" derken genellikle hata yayılmasını önlemek için dönmeyi sevdiğimiz şeyleri yaptığımız bir algoritmayı kastediyoruz. Bununla birlikte, 2x2'lik bir matris için, sonucu açık formüller cinsinden yazabilirsiniz - yani sonucu daha önce hesaplanan ara değerler yerine yalnızca girdiler cinsinden ifade eden SVD öğeleri için formüller yazabilirsiniz . Bu, iptal edebileceğiniz, ancak hata yayma yapamayacağınız anlamına gelir.

Mesele şu ki 2x2 sistemler için sağlamlık endişesi gerekli değil.


Bu matrise bağlı olabilir. Sol ve sağ açıları ayrı ayrı bulan bir yöntem gördüm (her biri arctan2 (y, x)). Ancak tekil değerler birbirine yaklaştığında, bu arktanların her biri 0/0 eğilimindedir, bu nedenle sonuç yanlış olabilir. Pedro Gimeno tarafından verilen yöntemde a2'nin hesaplanması bu durumda iyi tanımlanırken, a1 kötü tanımlanmış olur; hala iyi bir sonuç elde edersiniz, çünkü ayrışmanın geçerliliği, s.val'ler teta-phi'ye değil, birbirine yakın olduğunda sadece teta + phi'ye duyarlıdır.
greggo

5

Bu kod Blinn'in makalesine , Ellis makalesine , SVD dersine ve ek hesaplamalara dayanmaktadır . Bir algoritma normal ve tekil gerçek matrisler için uygundur. Önceki tüm sürümler bunun yanı sıra% 100 de çalışıyor.

#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

Bir algoritmaya ihtiyacım vardı.

  • küçük dallanma (umarım CMOV)
  • trigonometrik fonksiyon çağrısı yok
  • 32 bitlik yüzerlerde bile yüksek sayısal hassasiyet

c1,s1,c2,s2,σ1σ2

A=USV

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

VATAVATAVT=D

Hatırlamak

USV=A

US=AV1=AVTV

VATAVT=(AVT)TAVT=(US)TUS=STUTUS=D

S1

(STST)UTU(SS1)=UTU=STDS1

DSDUTU=IdentityUSVUSV=A

Köşegenleştirme rotasyonunun hesaplanması aşağıdaki denklemi çözerek yapılabilir:

t22βαγt21=0

nerede

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

t2VVATAVT

βαγA=RQRQUSV=RUSV=USVQ=RQ=AdR

S +DD

6107error=||USVM||/||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);
}

Fikirler:
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

Bu C ++ kodunu oluşturmak için http://www.lucidarme.me/?p=4624 adresindeki açıklamayı kullandım . Matrisler, Eigen kütüphanesine aittir, ancak bu örnekten kendi veri yapınızı kolayca oluşturabilirsiniz:

A=UΣ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);

Standart işareti fonksiyonu ile

double signum(double value)
{
    if(value > 0)
        return 1;
    else if(value < 0)
        return -1;
    else
        return 0;
}

Bu, tam olarak aynı değerlerle sonuçlanır Eigen::JacobiSVD(bkz. Https://eigen.tuxfamily.org/dox-devel/classEigen_1_1JacobiSVD.html ).


1
S2 = hypot( a*a + b*b - c*c - d*d, 2*(a*c + b*d))
greggo

2

ATA


Kodunuzun matrisin özdeğerleri negatif olduğunda işe yaradığını sanmıyorum. [[1 1] [1 0]] 'i deneyin ve u * s * vt, m'ye eşit değildir ...
Carlos Scheidegger

2

Kişisel ihtiyacım için 2x2 svd için minimum hesaplamayı izole etmeye çalıştım. Sanırım muhtemelen en basit ve en hızlı çözümlerden biri. Ayrıntıları kişisel blogumda bulabilirsiniz: http://lucidarme.me/?p=4624 .

Avantajları: basit, hızlı ve üç matrise ihtiyacınız yoksa, sadece üç matrisin bir veya ikisini (S, U veya D) hesaplayabilirsiniz.

Dezavantajı , hatalı olabilen ve harici bir kütüphane (tip.


3
Bağlantılar nadiren kalıcı olduğu için, bir bağlantıyı sadece bir cevap olarak sunmak yerine, yaklaşımı özetlemek önemlidir.
Paul

Ayrıca, kendi blogunuza bir link gönderecekseniz, lütfen (a) blogunuzun bu olduğunu açıklayın, (b) daha iyi, yaklaşımınızı gerçekten özetlemek veya kesip yapıştırmak (formüller görüntüleri olabilir) Ham LaTeX'e çevrilmiş ve MathJax kullanılarak hazırlanmıştır). Bu tür soru formülleri için en iyi yanıtlar, söz konusu formüller için alıntılar sağlar ve ardından sakıncaları, son durumları ve potansiyel alternatifleri listeler.
Geoff Oxberry

1

İşte 2x2 SVD çözünürlüğünün bir uygulaması. Victor Liu'nun koduna dayandırdım. Onun kodu bazı matrisler için çalışmıyordu. Bu iki belgeyi çözmek için matematiksel referans olarak kullandım: pdf1 ve pdf2 .

Matris setDatayöntemi, satır ana sıradadır. Dahili olarak, matris verilerini 2B dizisi olarak verdim 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
            );
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.