Надійний алгоритм для


26

Який простий алгоритм для обчислення SVD матриць ?2×2

В ідеалі мені хотілося б чисельно надійний алгоритм, але я хотів би бачити як прості, так і не дуже прості реалізації. C код прийнято.

Будь-які посилання на папери чи код?


5
У Вікіпедії перелічено рішення 2х2 закритої форми, але я не маю уявлення про його числові властивості.
Дамієн

В якості довідки, "Числові рецепти", Press et al., Cambridge Press. Досить дорога книга, але варта кожного цента. Крім рішень SVD, ви знайдете ще багато корисних алгоритмів.
Ян Хакенберг

Відповіді:


19

Дивіться /math/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation (вибачте, я б це сказав у коментарі, але я зареєструвався просто опублікувати це, щоб я ще не міг публікувати коментарі).

Але оскільки я пишу це як відповідь, я також напишу метод:

Е=м00+м112;Ж=м00-м112;Г=м10+м012;Н=м10-м012Q=Е2+Н2;R=Ж2+Г2сх=Q+R;су=Q-Rа1=атан2(Г,Ж);а2=атан2(Н,Е)θ=а2-а12;ϕ=а2+а12

Це розкладає матрицю так:

М=(м00м01м10м11)=(cosϕ-гріхϕгріхϕcosϕ)(сх00су)(cosθ-гріхθгріхθcosθ)

Єдине, що слід захистити за допомогою цього методу, це те, що або для atan2.Г=Ж=0Н=Е=0Я сумніваюся, що це може бути більш надійним, ніж це( Оновлення: див. Відповідь Алекса Ефтіміадеса!).

Посилання: http://dx.doi.org/10.1109/38.486688 (надане там Рахулом), яке походить з нижньої частини цього повідомлення в блозі: http://metamerist.blogspot.com/2006/10/linear-algebra -для графіки-geeks-svd.html

Оновлення: Як зазначив @VictorLiu в коментарі, може бути негативним. Це відбувається тоді і лише тоді, коли визначник вхідної матриці також негативний. Якщо це так, і ви хочете отримати позитивні значення однини, просто візьміть абсолютне значення .сусу


1
Здається, що може бути негативним, якщо Q < R . Це не повинно бути можливим. суQ<R
Віктор Лю

@VictorLiu Якщо вхідна матриця відвертається, єдине місце, яке може відображатися, - це матриця масштабування, оскільки матриці обертання неможливо перевернути. Просто не подайте вхідні матриці, які перевертаються. Я не зробив математику , але поки я тримав парі , що знак визначника вхідний матриці буде визначити , є чи або R більше. QR
Педро Гімено

@VictorLiu Я вже зробив математику і підтвердив, що дійсно спрощує m 00 m 11 - m 01 m 10, тобто визначник вхідної матриці. Q2-R2м00м11-м01м10
Педро Гімено

9

@Pedro Gimeno

"Я сумніваюся, що це може бути більш надійним за це".

Виклик прийнято.

Я зауважив, що звичайний підхід - це використання триггерних функцій, таких як atan2. Інтуїтивно зрозуміло, що не потрібно використовувати функції тригерів. Дійсно, всі результати закінчуються як синуси та косинуси арктанів - які можна спростити до алгебраїчних функцій. Минуло досить багато часу, але мені вдалося спростити алгоритм Педро, щоб використовувати лише алгебраїчні функції.

Наступний код 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.
Hugues

8

GSL має СВД 2-на-2 решателя , що лежить в основі частина QR - розкладу основного алгоритму SVD для gsl_linalg_SV_decomp. Перегляньте svdstep.cфайл і шукайте svd2функцію. Функція має кілька особливих випадків, не зовсім тривіальна, і, схоже, виконує кілька речей, щоб бути чисельно обережними (наприклад, використовуючи hypotдля уникнення переповнення).


1
Чи є ця функція документацією? Я хотів би знати, які його вхідні параметри.
Віктор Лю

@VictorLiu: На жаль, я не бачив нічого, крім мізерних коментарів у самому файлі. У ChangeLogфайлі є трохи, якщо ви завантажите GSL. І ви можете подивитися svd.cдеталі загального алгоритму. Єдиний справжня документація , здається, для призначених для користувача викликаються функцій високого рівня, наприклад, gsl_linalg_SV_decomp.
хорхлер

7

Коли ми говоримо "чисельно надійний", ми зазвичай маємо на увазі алгоритм, в якому ми робимо такі дії, як поворотні, щоб уникнути поширення помилок. Однак для матриці 2x2 ви можете записати результат за допомогою явних формул - тобто записати формули для елементів SVD, які констатують результат лише з точки зору вхідних даних , а не з точки зору раніше прорахованих проміжних значень . Це означає, що у вас може бути скасування, але відсутність поширення помилок.

Справа просто в тому, що для 2x2 систем турбуватися про надійність не потрібно.


Це може залежати від матриці. Я бачив метод, який знаходить лівий і правий кути окремо (кожен через arctan2 (y, x)), який, як правило, працює добре. Але коли значення однини близькі між собою, кожен з цих арктанів має тенденцію до 0/0, тому результат може бути неточним. У способі, поданому Педро Гімено, обчислення a2 в цьому випадку буде чітко визначено, тоді як a1 стає неправильним; у вас все ще хороший результат, тому що обгрунтованість розкладу чутлива до тета + фі, коли с.вал розташовані близько один до одного, а не до тета-фі.
greggo

5

Цей код заснований на папері Blinn в , Елліс паперу , СВД лекції і додаткових розрахунків. Алгоритм підходить для регулярних і сингулярних реальних матриць. Усі попередні версії працюють на 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-бітових поплавках

c1,с1,c2,с2,σ1σ2

А=USV

[абcг]=[c1с1-с1c1][σ100σ2][c2-с2с2c2]

VАТАVАТАVТ=D

Нагадаємо, що

USV=А

US=АV-1=АVТV

VАТАVТ=(АVТ)ТАVТ=(US)ТUS=SТUТUS=D

S-1

(S-ТSТ)UТU(SS-1)=UТU=S-ТDS-1

DSDUТU=ЯгентiтуUSVUSV=А

Обчислення діагоналізуючого обертання можна здійснити, розв’язавши наступне рівняння:

т22-β-αγт2-1=0

де

АТА=[аcбг][абcг]=[а2+c2аб+cгаб+cгб2+г2]=[αγγβ]

т2VVАТАVТ

β-αγА=RQRQUSV'=RUSV=USV'Q=RQ=АгR

S +D-D

610-7еrrоr=||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/


3

Я використовував опис за адресою http://www.lucidarme.me/?p=4624, щоб створити цей код C ++. Матриці - це бібліотеки Eigen, але ви можете легко створити власну структуру даних з цього прикладу:

А=UΣVТ

#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 ).


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


2

Для моєї особистої потреби я намагався ізолювати мінімальний обчислення для 2x2 svd. Я думаю, це, мабуть, одне з найпростіших і швидких рішень. Деталі ви можете знайти в моєму особистому блозі: http://lucidarme.me/?p=4624 .

Переваги: простий, швидкий, і ви можете обчислити одну або дві з трьох матриць (S, U або D), якщо три матриці вам не потрібні.

Зворотний зв'язок використовує atan2, який може бути неточним і може зажадати зовнішньої бібліотеки (тип.


3
Оскільки посилання рідко є постійними, важливо узагальнити підхід, а не просто надавати посилання як відповідь.
Пол

Крім того, якщо ви збираєтесь розмістити посилання на свій власний блог, будь ласка, (а) розкрийте, що це ваш блог, (b) ще краще було б фактично узагальнити або вирізати і вставити свій підхід (зображення формул можуть переводиться в сирий LaTeX і виводиться за допомогою MathJax). Найкращі відповіді на подібні формули стану запитань, надайте цитати для цих формул, а потім перераховуйте такі речі, як недоліки, кращі випадки та можливі альтернативи.
Джефф Оксберрі

1

Ось реалізація рішення 2x2 SVD. Я грунтувався на коді Віктора Лю. Його код не працював для деяких матриць. Я використовував ці два документи як математичні орієнтири для вирішення: pdf1 та pdf2 .

Метод матриці setDataзнаходиться в рядково-головному порядку. Внутрішньо я представляю матричні дані як двовимірний масив, заданий 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
            );
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.