跳转到内容

奇异值分解

奇异值分解(Singular Value Decomposition,简称 SVD)是线性代数中最重要的矩阵分解之一,被称为"数值线性代数的瑞士军刀"。与特征值分解不同,SVD 适用于任意形状的矩阵(不限于方阵)。任何一个 m×n 的矩阵 A 都可以分解为 A = UΣV^T,其中 U 和 V 是正交矩阵,Σ 是对角矩阵,对角线上的元素是奇异值。SVD 在数据压缩、降维、推荐系统、主成分分析等领域有广泛应用。在深度学习中,SVD 是 LoRA(Low-Rank Adaptation)等模型压缩技术的理论基础。

SVD 的定义

对于任意 m×n 的矩阵 A,SVD 分解为:

A = U Σ V^T

其中:

  • U 是 m×m 的正交矩阵(U^T U = I)
  • Σ 是 m×n 的对角矩阵,对角线上的元素是奇异值
  • V 是 n×n 的正交矩阵(V^T V = I)

np.linalg.svd:计算 SVD

python
import numpy as np

A = np.array([[1, 2], [3, 4], [5, 6]])

U, s, Vt = np.linalg.svd(A)

print(f"原始矩阵 A:\n{A}")
print(f"U 形状: {U.shape}")
print(f"奇异值 s: {s}")
print(f"Vt 形状: {Vt.shape}")

SVD 的性质

重构矩阵

使用奇异值可以重构原始矩阵:

python
# 重构矩阵(使用所有奇异值)
U, s, Vt = np.linalg.svd(A)
Sigma = np.zeros_like(A)
np.fill_diagonal(Sigma, s)
reconstructed = U @ Sigma @ Vt
print(f"重构矩阵:\n{reconstructed}")
print(f"重构误差: {np.linalg.norm(A - reconstructed):.10f}")

矩阵的秩

奇异值的数量等于矩阵的秩(非零奇异值的数量):

python
# 计算矩阵的秩
rank = np.sum(s > 1e-10)
print(f"矩阵的秩: {rank}")

低秩近似

SVD 最重要的应用之一是找到矩阵的最佳低秩近似。根据 Eckart-Young-Mirsky 定理,使用前 k 个最大的奇异值可以得到最佳的 k 秩近似:

python
def low_rank_approximation(A, k):
    """返回 A 的最佳 k 秩近似"""
    U, s, Vt = np.linalg.svd(A, full_matrices=False)

    # 只使用前 k 个奇异值
    U_k = U[:, :k]
    s_k = s[:k]
    Vt_k = Vt[:k, :]

    return U_k @ np.diag(s_k) @ Vt_k

# 测试低秩近似
A = np.random.randn(100, 50).astype(np.float32)
A_rank50 = low_rank_approximation(A, 50)
A_rank10 = low_rank_approximation(A, 10)

print(f"原始矩阵形状: {A.shape}")
print(f"原始矩阵的秩: {np.linalg.matrix_rank(A)}")
print(f"50秩近似形状: {A_rank50.shape}")
print(f"10秩近似形状: {A_rank10.shape}")

# 计算压缩比
print(f"10秩近似压缩比: {A.size / A_rank10.size:.1f}x")

在LLM场景中的应用

LoRA 原理

LoRA(Low-Rank Adaptation)是一种模型微调技术,其核心思想是假设大型权重矩阵 W 的更新 ΔW 可以用低秩矩阵来近似:

python
def lora_decomposition(W, rank=4):
    """LoRA 的低秩分解

    原始: Y = W @ X
    LoRA: Y = (W + ΔW) @ X,其中 ΔW = A @ B
    """
    # W: (d_model, d_model)
    # A: (d_model, rank)
    # B: (rank, d_model)

    # 使用 SVD 找到 ΔW 的低秩近似
    U, s, Vt = np.linalg.svd(W, full_matrices=False)

    # 只保留前 rank 个奇异值
    A = U[:, :rank] * np.sqrt(s[:rank])
    B = np.sqrt(s[:rank])[:, np.newaxis] * Vt[:rank, :]

    return A, B, s[:rank]

# 模拟大型权重矩阵
d_model = 768
W = np.random.randn(d_model, d_model).astype(np.float32) * 0.02

A, B, singular_values = lora_decomposition(W, rank=8)
print(f"LoRA A 形状: {A.shape}")
print(f"LoRA B 形状: {B.shape}")
print(f"前8个奇异值: {singular_values}")

矩阵分析

使用 SVD 分析神经网络权重矩阵的特性:

python
def analyze_weight_matrix_svd(W, name="权重"):
    """使用 SVD 分析权重矩阵"""
    U, s, Vt = np.linalg.svd(W, full_matrices=False)

    print(f"\n{name}:")
    print(f"  形状: {W.shape}")
    print(f"  秩: {np.sum(s > 1e-6)}")
    print(f"  前5个奇异值: {s[:5]}")
    print(f"  奇异值和: {s.sum():.2f}")
    print(f"  能量比(前10个/全部): {s[:10].sum() / s.sum():.4f}")

# 分析不同初始化的权重
np.random.seed(42)
W_xavier = np.random.randn(768, 768) * np.sqrt(2.0 / 768)
W_he = np.random.randn(768, 768) * np.sqrt(2.0 / 768)

analyze_weight_matrix_svd(W_xavier, "Xavier 初始化")
analyze_weight_matrix_svd(W_he, "He 初始化")

数据压缩

SVD 可以用于压缩神经网络权重:

python
def compress_weights_svd(weights, compression_ratio=0.5):
    """使用 SVD 压缩权重矩阵

    压缩后存储 U(:, :k), s[:k], Vt[:k, :]
    存储量从 d1*d2 减少到 k*(d1 + d2 + 1)
    """
    d1, d2 = weights.shape
    k = int(min(d1, d2) * compression_ratio)

    U, s, Vt = np.linalg.svd(weights, full_matrices=False)

    compressed = {
        'U': U[:, :k],
        's': s[:k],
        'Vt': Vt[:k, :]
    }

    original_size = weights.size
    compressed_size = k * (d1 + d2 + 1)

    return compressed, compressed_size / original_size

# 测试压缩
weights = np.random.randn(1000, 1000).astype(np.float32) * 0.02
compressed, ratio = compress_weights_svd(weights, compression_ratio=0.3)
print(f"原始大小: {weights.size}")
print(f"压缩后大小: {compressed['U'].size + compressed['s'].size + compressed['Vt'].size}")
print(f"压缩比: {ratio:.2%}")

常见误区与注意事项

误区一:混淆奇异值和特征值

python
A = np.array([[1, 2], [3, 4]])

# SVD 的奇异值不是特征值
_, s, _ = np.linalg.svd(A)
eigenvalues, _ = np.linalg.eig(A @ A.T)

print(f"奇异值: {s}")
print(f"A @ A.T 的特征值: {np.sqrt(eigenvalues)}")  # 奇异值的平方是特征值

误区二:full_matrices 参数的默认值

python
A = np.random.randn(5, 3)

# 默认 full_matrices=True,返回完整的 U 和 V
U_full, s_full, Vt_full = np.linalg.svd(A)
print(f"full U 形状: {U_full.shape}")  # (5, 5)
print(f"full Vt 形状: {Vt_full.shape}")  # (3, 3)

# 如果不需要完整矩阵,使用 full_matrices=False
U_small, s_small, Vt_small = np.linalg.svd(A, full_matrices=False)
print(f"small U 形状: {U_small.shape}")  # (5, 3)
print(f"small Vt 形状: {Vt_small.shape}")  # (3, 3)

小结

SVD 是最重要的矩阵分解之一,可以将任意矩阵分解为 UΣV^T。奇异值的大小反映了矩阵的信息量,低秩近似可以用于数据压缩和模型优化。在 LLM 场景中,SVD 是 LoRA 等模型压缩技术的理论基础,可用于分析权重矩阵的特性。

面试时需要能够解释 SVD 的定义和性质,理解低秩近似的原理,以及能够描述 SVD 在模型压缩中的应用。

基于 MIT 许可发布