Який простий алгоритм для обчислення SVD матриць ?
В ідеалі мені хотілося б чисельно надійний алгоритм, але я хотів би бачити як прості, так і не дуже прості реалізації. C код прийнято.
Будь-які посилання на папери чи код?
Який простий алгоритм для обчислення SVD матриць ?
В ідеалі мені хотілося б чисельно надійний алгоритм, але я хотів би бачити як прості, так і не дуже прості реалізації. C код прийнято.
Будь-які посилання на папери чи код?
Відповіді:
Дивіться /math/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation (вибачте, я б це сказав у коментарі, але я зареєструвався просто опублікувати це, щоб я ще не міг публікувати коментарі).
Але оскільки я пишу це як відповідь, я також напишу метод:
Це розкладає матрицю так:
Єдине, що слід захистити за допомогою цього методу, це те, що або для atan2.Я сумніваюся, що це може бути більш надійним, ніж це( Оновлення: див. Відповідь Алекса Ефтіміадеса!).
Посилання: http://dx.doi.org/10.1109/38.486688 (надане там Рахулом), яке походить з нижньої частини цього повідомлення в блозі: http://metamerist.blogspot.com/2006/10/linear-algebra -для графіки-geeks-svd.html
Оновлення: Як зазначив @VictorLiu в коментарі, може бути негативним. Це відбувається тоді і лише тоді, коли визначник вхідної матриці також негативний. Якщо це так, і ви хочете отримати позитивні значення однини, просто візьміть абсолютне значення .
@Pedro Gimeno
"Я сумніваюся, що це може бути більш надійним за це".
Виклик прийнято.
Я зауважив, що звичайний підхід - це використання триггерних функцій, таких як atan2. Інтуїтивно зрозуміло, що не потрібно використовувати функції тригерів. Дійсно, всі результати закінчуються як синуси та косинуси арктанів - які можна спростити до алгебраїчних функцій. Минуло досить багато часу, але мені вдалося спростити алгоритм Педро, щоб використовувати лише алгебраїчні функції.
Наступний код 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 має СВД 2-на-2 решателя , що лежить в основі частина QR - розкладу основного алгоритму SVD для gsl_linalg_SV_decomp
. Перегляньте svdstep.c
файл і шукайте svd2
функцію. Функція має кілька особливих випадків, не зовсім тривіальна, і, схоже, виконує кілька речей, щоб бути чисельно обережними (наприклад, використовуючи hypot
для уникнення переповнення).
ChangeLog
файлі є трохи, якщо ви завантажите GSL. І ви можете подивитися svd.c
деталі загального алгоритму. Єдиний справжня документація , здається, для призначених для користувача викликаються функцій високого рівня, наприклад, gsl_linalg_SV_decomp
.
Коли ми говоримо "чисельно надійний", ми зазвичай маємо на увазі алгоритм, в якому ми робимо такі дії, як поворотні, щоб уникнути поширення помилок. Однак для матриці 2x2 ви можете записати результат за допомогою явних формул - тобто записати формули для елементів SVD, які констатують результат лише з точки зору вхідних даних , а не з точки зору раніше прорахованих проміжних значень . Це означає, що у вас може бути скасування, але відсутність поширення помилок.
Справа просто в тому, що для 2x2 систем турбуватися про надійність не потрібно.
Цей код заснований на папері 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]);
}
Мені потрібен був алгоритм, який є
Нагадаємо, що
Обчислення діагоналізуючого обертання можна здійснити, розв’язавши наступне рівняння:
де
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/
Я використовував опис за адресою http://www.lucidarme.me/?p=4624, щоб створити цей код C ++. Матриці - це бібліотеки 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. Я думаю, це, мабуть, одне з найпростіших і швидких рішень. Деталі ви можете знайти в моєму особистому блозі: http://lucidarme.me/?p=4624 .
Переваги: простий, швидкий, і ви можете обчислити одну або дві з трьох матриць (S, U або D), якщо три матриці вам не потрібні.
Зворотний зв'язок використовує atan2, який може бути неточним і може зажадати зовнішньої бібліотеки (тип.
Ось реалізація рішення 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
);
}
}
}