主题
奇异值分解
奇异值分解(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 在模型压缩中的应用。