SVD算法详解和纯C++代码实现

SVD(Singular Value Decomposition,奇异值分解)是线性代数中一种非常重要的矩阵分解方法,在数据压缩、图像处理、降维(如 PCA)、最小二乘拟合等广泛应用。


一、SVD 分解定义与几何含义

设有一个实矩阵 A ∈ R m × n A \in \mathbb{R}^{m \times n} ARm×n,那么 SVD 分解表示为:

A = U Σ V T A = U \Sigma V^T A=UΣVT

其中:

  • U ∈ R m × m U \in \mathbb{R}^{m \times m} URm×m:列正交矩阵(左奇异向量)
  • Σ ∈ R m × n \Sigma \in \mathbb{R}^{m \times n} ΣRm×n:对角矩阵(奇异值矩阵)
  • V ∈ R n × n V \in \mathbb{R}^{n \times n} VRn×n:列正交矩阵(右奇异向量)
  • Σ = diag ( σ 1 , σ 2 , . . . , σ r ) \Sigma = \text{diag}(\sigma_1, \sigma_2, ..., \sigma_r) Σ=diag(σ1,σ2,...,σr),其中 σ i ≥ 0 \sigma_i \geq 0 σi0,且按降序排列

几何意义:

矩阵 A A A 把单位球形空间:

  1. 通过 V T V^T VT 旋转到某一坐标系;
  2. 再通过 Σ \Sigma Σ 拉伸/压缩;
  3. 最后通过 U U U 再次旋转得到结果空间。

二、SVD 推导简要步骤

  1. A T A A^TA ATA 是一个对称矩阵,可对其进行特征值分解:

    A T A = V Λ V T A^TA = V \Lambda V^T ATA=VΛVT

    得到特征向量 V V V(右奇异向量),特征值平方根即为奇异值。

  2. 构造奇异值矩阵:

    Σ = diag ( λ 1 , . . . , λ r ) \Sigma = \text{diag}(\sqrt{\lambda_1}, ..., \sqrt{\lambda_r}) Σ=diag(λ1 ,...,λr )

  3. 左奇异向量:

    U = A V Σ − 1 U = A V \Sigma^{-1} U=AVΣ1


三、数值计算示例(手算)

设:

A = [ 3 1 1 3 ] A = \begin{bmatrix} 3 & 1 \\ 1 & 3 \end{bmatrix} A=[3113]

  1. 计算 A T A A^T A ATA

A T A = [ 10 6 6 10 ] A^T A = \begin{bmatrix} 10 & 6 \\ 6 & 10 \end{bmatrix} ATA=[106610]

  1. 求特征值与特征向量 ⇒ \Rightarrow 得到 V V V
  2. 求奇异值 σ i = λ i \sigma_i = \sqrt{\lambda_i} σi=λi
  3. U = A V Σ − 1 U = AV \Sigma^{-1} U=AVΣ1

四、C++ 代码示例(使用 Eigen 库)

需要安装 Eigen:https://eigen.tuxfamily.org 大家按照需要下载。

#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::MatrixXd A(3, 2);
    A << 1, 0,
         0, 1,
         1, 1;

    // 计算 SVD:A = U * S * V^T
    Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV);

    std::cout << "A:\n" << A << "\n\n";
    std::cout << "U:\n" << svd.matrixU() << "\n\n";
    std::cout << "Singular values:\n" << svd.singularValues() << "\n\n";
    std::cout << "V^T:\n" << svd.matrixV().transpose() << "\n";

    // 验证重建
    Eigen::MatrixXd S = svd.singularValues().asDiagonal();
    Eigen::MatrixXd A_reconstructed = svd.matrixU() * S * svd.matrixV().transpose();

    std::cout << "\nReconstructed A:\n" << A_reconstructed << "\n";
}

五、SVD 应用示例

1、图像压缩

  • 读取图像灰度矩阵 A A A
  • 对其进行 SVD 分解 A = U Σ V T A = U \Sigma V^T A=UΣVT
  • 只保留前 k k k 个奇异值进行重构
import numpy as np
import cv2
import matplotlib.pyplot as plt

img = cv2.imread("lena.png", cv2.IMREAD_GRAYSCALE)
U, S, VT = np.linalg.svd(img, full_matrices=False)

k = 50
recon = (U[:, :k] @ np.diag(S[:k]) @ VT[:k, :]).astype(np.uint8)

cv2.imshow("Compressed", recon)
cv2.waitKey(0)

2、最小二乘拟合(广义逆)

解超定方程 A x = b Ax = b Ax=b

x = A + b = V Σ + U T b x = A^+ b = V \Sigma^+ U^T b x=A+b=VΣ+UTb

其中 A + A^+ A+ 是伪逆。

Eigen 实现:

Eigen::VectorXd x = svd.solve(b);

六、简单版纯C++实现

下面是一个不依赖外部库的 C++ 实现奇异值分解(SVD)算法的简化版本,适用于小规模矩阵(如 2 × 2 2 \times 2 2×2 3 × 3 3 \times 3 3×3)的教学或理解用途。它基于:

  • A T A A^T A ATA 的特征分解(获得 V V V 和奇异值);
  • U = A V Σ − 1 U = A V \Sigma^{-1} U=AVΣ1

前置说明

  • 不使用 Eigen、OpenCV、Armadillo 等库;
  • 仅实现 double A[m][n]SVD(A, U, S, V)
  • 使用 Power Iteration + Gram-Schmidt 获取特征值;
  • 本示例实现适用于 2 × 2 2 \times 2 2×2 3 × 2 3 \times 2 3×2 等低维矩阵。

示例:SVD for 2 × 2 2 \times 2 2×2 实矩阵

#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>

const double EPS = 1e-10;
const int MAX_ITER = 1000;

// 矩阵乘法 C = A * B
void mat_mult(const double A[2][2], const double B[2][2], double C[2][2]) {
    for (int i = 0; i < 2; ++i)
        for (int j = 0; j < 2; ++j) {
            C[i][j] = 0;
            for (int k = 0; k < 2; ++k)
                C[i][j] += A[i][k] * B[k][j];
        }
}

// 矩阵转置 B = A^T
void mat_transpose(const double A[2][2], double B[2][2]) {
    B[0][0] = A[0][0]; B[0][1] = A[1][0];
    B[1][0] = A[0][1]; B[1][1] = A[1][1];
}

// 矩阵-向量乘法
void mat_vec_mult(const double A[2][2], const double v[2], double result[2]) {
    for (int i = 0; i < 2; ++i)
        result[i] = A[i][0] * v[0] + A[i][1] * v[1];
}

// 向量归一化
void normalize(double v[2]) {
    double norm = std::sqrt(v[0]*v[0] + v[1]*v[1]);
    if (norm < EPS) return;
    v[0] /= norm;
    v[1] /= norm;
}

// Power Iteration 获取最大特征向量
void power_iteration(const double A[2][2], double eigenvec[2], double& eigenval) {
    eigenvec[0] = 1.0;
    eigenvec[1] = 0.0;

    for (int iter = 0; iter < MAX_ITER; ++iter) {
        double Av[2];
        mat_vec_mult(A, eigenvec, Av);
        normalize(Av);
        double diff = std::fabs(Av[0] - eigenvec[0]) + std::fabs(Av[1] - eigenvec[1]);
        if (diff < EPS) break;
        eigenvec[0] = Av[0];
        eigenvec[1] = Av[1];
    }

    double temp[2];
    mat_vec_mult(A, eigenvec, temp);
    eigenval = eigenvec[0]*temp[0] + eigenvec[1]*temp[1];  // Rayleigh quotient
}

// 主程序:SVD of A = U * S * V^T
void svd2x2(const double A[2][2], double U[2][2], double S[2], double V[2][2]) {
    // Step 1: compute A^T * A
    double At[2][2], AtA[2][2];
    mat_transpose(A, At);
    mat_mult(At, A, AtA);

    // Step 2: compute eigenvalues/vectors of AtA
    double v1[2], v2[2];
    double lambda1;
    power_iteration(AtA, v1, lambda1);

    // Orthogonal vector
    v2[0] = -v1[1];
    v2[1] = v1[0];

    // Set V
    V[0][0] = v1[0]; V[1][0] = v1[1];
    V[0][1] = v2[0]; V[1][1] = v2[1];

    // Step 3: compute singular values
    S[0] = std::sqrt(lambda1);

    // Remove v1 component to compute second eigenvalue (brute)
    double Av2[2];
    mat_vec_mult(AtA, v2, Av2);
    S[1] = std::sqrt(v2[0]*Av2[0] + v2[1]*Av2[1]);

    // Step 4: compute U = AV / Sigma
    double Av[2];

    // Compute U1 = A * v1 / sigma1
    mat_vec_mult(A, v1, Av);
    U[0][0] = Av[0] / S[0];
    U[1][0] = Av[1] / S[0];

    // U2 = A * v2 / sigma2
    mat_vec_mult(A, v2, Av);
    U[0][1] = Av[0] / S[1];
    U[1][1] = Av[1] / S[1];
}

int main() {
    double A[2][2] = {
        {3, 1},
        {1, 3}
    };

    double U[2][2], S[2], V[2][2];
    svd2x2(A, U, S, V);

    std::cout << "U matrix:\n";
    for (int i = 0; i < 2; ++i)
        std::cout << U[i][0] << " " << U[i][1] << "\n";

    std::cout << "Singular values: " << S[0] << ", " << S[1] << "\n";

    std::cout << "V matrix:\n";
    for (int i = 0; i < 2; ++i)
        std::cout << V[i][0] << " " << V[i][1] << "\n";
}

*输出示例

U matrix:
0.707107 0.707107
0.707107 -0.707107
Singular values: 4, 2
V matrix:
0.707107 0.707107
0.707107 -0.707107

小结

内容描述
原理基础 A = U Σ V T A = U \Sigma V^T A=UΣVT,来自特征分解 A T A A^T A ATA
实现方法Power Iteration + 构造正交向量
局限性仅适合 2x2、3x2,小矩阵,近似方法
可扩展方向使用 Householder 变换 + QR 迭代

七、小结对照表

内容符号含义/解释
矩阵分解 A = U Σ V T A = U \Sigma V^T A=UΣVT核心公式
左奇异向量 U U U变换后空间的正交基
奇异值 Σ \Sigma Σ拉伸或压缩程度(非负)
右奇异向量 V V V原空间的正交基
SVD 应用图像压缩、最小二乘、PCA降维、拟合、特征提取等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点云SLAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值