อัลกอริทึมที่แข็งแกร่งสำหรับ


26

อัลกอริธึมง่ายๆสำหรับการคำนวณ SVD ของเมทริกซ์คืออะไร?2×2

เป็นการดีที่ฉันต้องการอัลกอริทึมที่มีประสิทธิภาพเป็นตัวเลข แต่ฉันต้องการเห็นการใช้งานที่เรียบง่ายและไม่ง่าย ยอมรับรหัส C

การอ้างอิงใด ๆ กับเอกสารหรือรหัส?


5
Wikipediaแสดงโซลูชันแบบปิด 2x2 แต่ฉันไม่ทราบคุณสมบัติเชิงตัวเลข
ดาเมียน

เป็นข้อมูลอ้างอิง "สูตรอาหารเชิงตัวเลข", กดและคณะ, Cambridge Press หนังสือค่อนข้างแพง แต่คุ้มค่าทุกสตางค์ นอกจากโซลูชั่น SVD คุณจะพบอัลกอริทึมที่มีประโยชน์อื่น ๆ อีกมากมาย
ม.ค. Hackenberg

คำตอบ:


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+G2sx=Q+R;sy=QRa1=atan2(G,F);a2=atan2(H,E)θ=a2a12;ϕ=a2+a12

นั่นสลายเมทริกซ์ดังนี้

M=(ม.00ม.01ม.10ม.11)=(cosφ-บาปφบาปφcosφ)(sx00sY)(cosθ-บาปθบาปθcosθ)

สิ่งเดียวที่จะป้องกันด้วยวิธีนี้คือG=F=0หรือH=E=0สำหรับ atan2ฉันสงสัยว่ามันจะแข็งแกร่งกว่านั้น( อัปเดต:ดูคำตอบของ Alex Eftimiades!)

การอ้างอิงคือ: http://dx.doi.org/10.1109/38.486688 (ให้โดยราหุลที่นั่น) ซึ่งมาจากด้านล่างของโพสต์บล็อกนี้: http://metamerist.blogspot.com/2006/10/linear-algebra เผื่อกราฟิก geeks-svd.html

อัปเดต:ตามที่ระบุไว้โดย @VictorLiu ในความคิดเห็นsYอาจเป็นค่าลบ ที่เกิดขึ้นถ้าหากปัจจัยของเมทริกซ์อินพุตเป็นลบเช่นกัน หากเป็นกรณีและคุณต้องการค่าเอกพจน์บวกเพียงแค่ใช้ค่าสัมบูรณ์ของsY Y


1
ดูเหมือนว่าสามารถเป็นเชิงลบหากQ < R สิ่งนี้ไม่ควรเป็นไปได้ sYQ<R
Victor Liu

@VictorLiu หากเมทริกซ์อินพุตพลิกสถานที่เดียวที่สามารถสะท้อนได้คือเมทริกซ์สเกลเนื่องจากเมทริกซ์การหมุนไม่สามารถพลิกได้ อย่าป้อนข้อมูลเมทริกซ์ที่พลิก ฉันยังไม่ได้ทำคณิตศาสตร์ แต่ฉันเดิมพันว่าเครื่องหมายของดีเทอร์มีแนนต์ของเมทริกซ์อินพุตจะกำหนดว่าหรือRนั้นยิ่งใหญ่กว่า QR
Pedro Gimeno

@VictorLiu ฉันได้ทำคณิตศาสตร์ทันทีและยืนยันว่าจริง ๆ แล้วลดความซับซ้อนของm 00 m 11 - m 01 m 10นั่นคือดีเทอร์มีแนนต์ของเมทริกซ์อินพุต Q2-R2ม.00ม.11-ม.01ม.10
Pedro Gimeno

9

@Pedro Gimeno

"ฉันสงสัยว่ามันจะแข็งแกร่งกว่านี้อีกแล้ว"

รับคำท้า.

ฉันสังเกตเห็นวิธีการปกติคือการใช้ฟังก์ชั่นตรีโกณมิติเช่น atan2 ไม่จำเป็นต้องใช้ฟังก์ชันตรีโกณมิติ อันที่จริงผลลัพธ์ทั้งหมดจบลงด้วยการเป็นไซน์และอาร์คตินของอาร์คตัน - ซึ่งสามารถทำให้ฟังก์ชั่นพีชคณิตง่ายขึ้น ใช้เวลาสักครู่ แต่ฉันจัดการเพื่อทำให้อัลกอริทึมของ Pedro ง่ายขึ้นเพื่อใช้ฟังก์ชันพีชคณิตเท่านั้น

โค้ดไพ ธ อนต่อไปนี้ใช้เคล็ดลับ

จาก 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มี SVD 2 โดย 2 แก้พื้นฐานส่วนหนึ่ง QR การสลายตัวของอัลกอริทึม SVD gsl_linalg_SV_decompหลักสำหรับ ดูsvdstep.cไฟล์และมองหาsvd2ฟังก์ชั่น ฟังก์ชั่นนี้มีกรณีพิเศษไม่กี่เรื่องเล็กน้อยและดูเหมือนจะทำสิ่งต่าง ๆ ให้ระมัดระวังเป็นตัวเลข (เช่นใช้hypotเพื่อหลีกเลี่ยงการล้น)


1
ฟังก์ชั่นนี้มีเอกสารหรือไม่? ฉันต้องการทราบว่าพารามิเตอร์อินพุตคืออะไร
Victor Liu

@VictorLiu: น่าเสียดายที่ฉันไม่ได้เห็นอะไรเลยนอกจากความเห็นเพียงเล็กน้อยในไฟล์ มีChangeLogไฟล์อยู่เล็กน้อยถ้าคุณดาวน์โหลด GSL และคุณสามารถดูsvd.cรายละเอียดของอัลกอริทึมโดยรวมได้ เอกสารที่แท้จริงเท่านั้นดูเหมือนว่าจะเป็นระดับสูงฟังก์ชั่นการใช้งาน callable gsl_linalg_SV_decompเช่น
horchler

7

เมื่อเราพูดว่า "มีประสิทธิภาพเชิงตัวเลข" เรามักจะหมายถึงอัลกอริทึมที่เราทำสิ่งต่าง ๆ เช่นหมุนเพื่อหลีกเลี่ยงข้อผิดพลาดการแพร่กระจาย อย่างไรก็ตามสำหรับเมทริกซ์ 2x2, คุณสามารถเขียนลงผลในแง่ของสูตรอย่างชัดเจน - คือการเขียนลงสูตรสำหรับองค์ประกอบ SVD ที่รัฐผลที่ได้เพียง แต่ในแง่ของปัจจัยการผลิตมากกว่าในแง่ของค่ากลางคำนวณก่อนหน้านี้ ซึ่งหมายความว่าคุณอาจมีการยกเลิก แต่ไม่มีการเผยแพร่ข้อผิดพลาด

ประเด็นก็คือสำหรับระบบ 2x2 ไม่จำเป็นต้องกังวลเกี่ยวกับความทนทาน


มันสามารถขึ้นอยู่กับเมทริกซ์ ฉันได้เห็นวิธีการที่พบมุมซ้ายและขวาแยกกัน (แต่ละอันผ่าน arctan2 (y, x)) ซึ่งโดยทั่วไปแล้วก็ใช้งานได้ดี แต่เมื่อค่าเอกพจน์ใกล้กันอาร์คตินแต่ละอันมีแนวโน้มที่จะ 0/0 ดังนั้นผลลัพธ์อาจไม่ถูกต้อง ในวิธีการที่กำหนดโดย Pedro Gimeno การคำนวณของ a2 จะถูกกำหนดอย่างดีในกรณีนี้ในขณะที่ a1 กลายเป็นไม่ชัดเจน; คุณยังคงมีผลลัพธ์ที่ดีเนื่องจากความถูกต้องของการแบ่งแยกนั้นมีความอ่อนไหวต่อ theta + phi เมื่อ s.vals อยู่ใกล้กันไม่ใช่กับ theta-phi
greggo

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 บิต

เราต้องการคำนวณและσ 2ดังต่อไปนี้:c1,s1,c2,s2,σ1σ2

ซึ่งสามารถขยายได้เช่น:A=USV

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

แนวคิดหลักคือการหาเมทริกซ์การหมุนที่ทแยงมุมA T Aนั่นคือV A T A V T = Dเป็นแนวทแยงมุมVATAVATAVT=D

จำได้ว่า

USV=A

(เนื่องจาก Vเป็นมุมฉาก)US=AV1=AVTV

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

การคูณทั้งสองข้างด้วยเราได้S1

(STST)UTU(SS1)=UTU=STDS1

เนื่องจากเป็นแนวทแยงตั้งค่าSเป็นDSจะให้เราUTU=ฉันdentฉันtYหมายถึงUเป็นเมทริกซ์หมุนSเป็นเมทริกซ์ทแยงมุมVเป็นเมทริกซ์วาระและUSV=เพียงสิ่งที่เรากำลังมองหาDUTU=IdentityUSVUSV=A

การคำนวณการหมุนในแนวทแยงสามารถทำได้โดยการแก้สมการต่อไปนี้:

t22βαγt21=0

ที่ไหน

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

t2VVATAVT

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

S +DD

610-7อีRRโอR=||ยูSV-M||/||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

ฉันใช้คำอธิบายที่http://www.lucidarme.me/?p=4624เพื่อสร้างรหัส C ++ นี้ เมทริกซ์เป็นของไลบรารี 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;
}

ซึ่งส่งผลให้ค่าเดียวกันกับ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 ซึ่งอาจไม่แน่นอนและอาจต้องใช้ห้องสมุดภายนอก (typ. math.h)


3
เนื่องจากลิงก์นั้นไม่ค่อยถาวรจึงเป็นสิ่งสำคัญที่จะสรุปวิธีการแทนการให้ลิงก์เป็นคำตอบ
เปาโล

นอกจากนี้หากคุณกำลังโพสต์ลิงค์ไปยังบล็อกของคุณเองโปรด (ก) เปิดเผยว่าเป็นบล็อกของคุณ (ข) ยิ่งไปกว่านั้นคือการสรุปหรือตัดและวางแนวทางของคุณ (รูปภาพของสูตรสามารถ ถูกแปลเป็น raw LaTeX และเรนเดอร์โดยใช้ MathJax) คำตอบที่ดีที่สุดสำหรับสูตรสถานะคำถามชนิดนี้จัดเตรียมการอ้างอิงสำหรับสูตรดังกล่าวจากนั้นทำรายการสิ่งต่าง ๆ เช่นข้อบกพร่องกรณีขอบและตัวเลือกที่อาจเกิดขึ้น
Geoff Oxberry

1

นี่คือการดำเนินการแก้ไข 2x2 SVD ฉันใช้มันจากรหัสของวิกเตอร์หลิว รหัสของเขาไม่ทำงานสำหรับเมทริกซ์บางตัว ผมใช้ทั้งสองเอกสารอ้างอิงทางคณิตศาสตร์เพื่อแก้: pdf1และpdf2

setDataวิธีการเมทริกซ์อยู่ในลำดับที่สำคัญแถว ภายในผมเป็นตัวแทนของข้อมูลเมทริกซ์เป็น array 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.