这篇笔记整理 SVD 在机器学习中的几个典型用法:低秩近似做图像压缩、矩阵分解做推荐系统、PCA 做降维,以及 SVM 中常见 kernel 的选择。主线是同一个:把高维数据表示成更少、更重要的方向。
1. SVD 的直觉
对一个矩阵
X∈Rm×n
奇异值分解写作:
X=UΣVT
其中:
- U:左奇异向量,描述行空间方向。
- V:右奇异向量,描述列空间方向。
- Σ:奇异值矩阵,对角线上是 σ1,σ2,…,通常按从大到小排列。
大的奇异值对应数据中最重要的结构,小的奇异值往往对应细节、噪声或较弱的变化。因此,只保留前 k 个奇异值,就可以得到一个低秩近似:
X≈UkΣkVkT
其中:
Uk=[u1,…,uk],Vk=[v1,…,vk]
这就是很多 SVD 应用的核心。
2. 图像压缩:低秩近似
灰度图像可以看成一个矩阵,每个像素值就是矩阵中的一个元素。彩色图像本质上是三通道数组,可以把 RGB 分别处理,也可以先转成灰度图。
对灰度图矩阵 X 做 SVD:
X=UΣVT
只保留前 k 个奇异值:
Xk=UkΣkVkT
k 越小,压缩越强,但图像越模糊;k 越大,保留的信息越多,但存储成本也越高。
原图

Rank 5

Rank 5 只保留很少的主方向,所以能看出大概轮廓,但人脸和背景细节已经明显丢失。
Rank 25

Rank 25 已经能恢复主要结构,人脸、栏杆和海面轮廓都清楚很多,但仍然有明显的模糊和纹理损失。
Rank 50

Rank 50 保留了更多奇异值,图像细节明显增强,但相比原图仍然有压缩痕迹。
Python 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| from PIL import Image import numpy as np import matplotlib.pyplot as plt from scipy.linalg import svd
def compress_image(A, k): U, s, Vt = svd(A, full_matrices=False) A_k = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :] return A_k
image = Image.open("your_image_path.png").convert("L") A = np.array(image)
for k in [5, 25, 50]: A_k = compress_image(A, k) plt.imshow(A_k, cmap="gray") plt.title(f"rank-{k}") plt.axis("off") plt.show()
|
这段代码的关键是:
Ak=U:,1:kΣ1:k,1:kV1:k,:T
也就是只拿前 k 个奇异值和对应方向重构图片。
3. 推荐系统:矩阵分解
推荐系统里常见的数据形式是用户-物品评分矩阵:
R∈Rm×n
其中 m 是用户数,n 是物品数,Rij 表示用户 i 对物品 j 的评分。
但真实推荐数据通常很稀疏:大部分用户没有给大部分物品打分。矩阵分解的目标是学习:
R≈UVT
其中:
- U∈Rm×k:用户隐向量矩阵。
- V∈Rn×k:物品隐向量矩阵。
- k 是隐因子数量。
预测评分为:
R^ij=uivjT
训练目标常写成:
U,Vmin(i,j)∈Ω∑(Rij−uivjT)2+λ(∥U∥F2+∥V∥F2)
其中 Ω 是已知评分的位置,λ 是正则化系数,用来减少过拟合。
4. ALS:Alternating Least Squares
ALS 的思想是交替优化:
- 固定 U,求最优 V。
- 固定 V,求最优 U。
- 重复直到收敛。
固定 U 时,对第 j 个物品向量 vj,有:
vj=(UΩjTUΩj+λI)−1UΩjTrj
其中 Ωj 表示给物品 j 打过分的用户集合。
一个简化版 ALS 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import numpy as np
def als(R, num_factors=3, num_iterations=10, lambda_reg=0.1): num_users, num_items = R.shape U = np.random.rand(num_users, num_factors) V = np.random.rand(num_items, num_factors)
for _ in range(num_iterations): for i in range(num_users): rated = R[i, :] > 0 if rated.any(): V_i = V[rated] R_i = R[i, rated] U[i] = np.linalg.solve( V_i.T @ V_i + lambda_reg * np.eye(num_factors), V_i.T @ R_i )
for j in range(num_items): rated = R[:, j] > 0 if rated.any(): U_j = U[rated] R_j = R[rated, j] V[j] = np.linalg.solve( U_j.T @ U_j + lambda_reg * np.eye(num_factors), U_j.T @ R_j )
return U, V
R = np.array([ [4, 0, 0, 5, 1], [5, 5, 4, 0, 0], [0, 3, 0, 4, 0], [0, 0, 5, 3, 2], ])
U, V = als(R) R_pred = U @ V.T print(R_pred)
|
ALS 的优点是每一步都是一个相对清楚的最小二乘问题;缺点是如果矩阵很大,求解线性方程也会有计算成本。
5. Gradient Descent 做矩阵分解
也可以用梯度下降直接优化:
U,Vmin(i,j)∈Ω∑[(Rij−uivjT)2+λ(∥ui∥2+∥vj∥2)]
令误差:
eij=Rij−uivjT
更新方式可以写成:
ui←ui+α(eijvj−λui)
vj←vj+α(eijui−λvj)
其中 α 是学习率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import numpy as np
def matrix_factorization_gd(R, num_factors=3, num_iterations=100, alpha=0.01, lambda_reg=0.1): num_users, num_items = R.shape U = np.random.rand(num_users, num_factors) V = np.random.rand(num_items, num_factors)
for _ in range(num_iterations): for i in range(num_users): for j in range(num_items): if R[i, j] > 0: error = R[i, j] - U[i] @ V[j].T U_old = U[i].copy() U[i] += alpha * (error * V[j] - lambda_reg * U[i]) V[j] += alpha * (error * U_old - lambda_reg * V[j])
return U, V
|
梯度下降更像深度学习里的训练过程,适合扩展到更复杂的模型。
6. WALS:Weighted ALS
普通 ALS 只在已知评分上训练。如果把未知位置简单当成 0,可能会引入误导,因为“没评分”不等于“不喜欢”。
WALS 引入权重矩阵:
wij={1,0,Rij 已知Rij 未知
目标函数变为:
U,Vmini,j∑wij(Rij−uivjT)2+λ(∥U∥F2+∥V∥F2)
固定 U 时:
vj=(UTWjU+λI)−1UTWjrj
固定 V 时:
ui=(VTWiV+λI)−1VTWiri
WALS 的核心是:让模型知道哪些评分可信,哪些位置只是缺失。
7. PCA 与 SVD
PCA,Principal Component Analysis,主成分分析,是一种经典降维方法。它希望找到新的坐标轴,使得:
- 数据在前几个方向上的方差尽可能大。
- 不同方向之间的协方差尽可能小。
- 用更少的维度保留尽可能多的信息。
设数据矩阵为:
X=[a1b1a2b2⋯⋯ambm]
协方差矩阵可以写作:
m1XXT=[Cov(a,a)Cov(b,a)Cov(a,b)Cov(b,b)]
PCA 本质上就是找这个协方差矩阵的特征向量,把数据投影到方差最大的方向。
用 SVD 做 PCA 的常见写法:
1 2 3 4 5 6 7 8 9 10
| import numpy as np from sklearn.datasets import load_iris
iris = load_iris() X = iris.data X_centered = X - X.mean(axis=0)
U, S, Vt = np.linalg.svd(X_centered / np.sqrt(X.shape[0] - 1), full_matrices=False) Y = X_centered @ Vt.T
|
这里 Vt.T 的列就是主成分方向,Y 是原始数据投影后的低维表示。
8. SVM Kernel 简记
SVM 的核心思想是找到一个分类边界,让不同类别之间的 margin 尽量大。当数据在原始空间里不容易线性分开时,可以用 kernel trick 把数据映射到更高维的特征空间。
常见 kernel:
Linear Kernel
K(x,y)=xTy
Polynomial Kernel
K(x,y)=(xTy+c)d
Gaussian / RBF Kernel
K(x,y)=exp(−2σ2∥x−y∥2)
Sigmoid Kernel
K(x,y)=tanh(αxTy+c)
一个粗略经验:
- 特征维度很高、样本数相对少时,可以先试 linear kernel。
- 特征维度较低、样本数较多、边界明显非线性时,可以试 RBF kernel。
- Polynomial kernel 对参数更敏感,通常需要更细的调参。
SVR,Support Vector Regression,是 SVM 的回归版本:
1 2 3 4 5
| from sklearn.svm import SVR
model = SVR(kernel="rbf", gamma="scale", C=1.0) model.fit(X_train, y_train)
|
9. 总结
这篇笔记可以连成一条线:
- SVD 把矩阵拆成重要方向和奇异值。
- 低秩近似用前 k 个奇异值保留主要信息,可以做图像压缩。
- 推荐系统矩阵分解用 UVT 学用户和物品的隐向量。
- PCA 可以通过 SVD 得到主成分方向。
- SVM kernel 解决的是“在什么空间里更容易分开数据”的问题。
从线性代数角度看,这些方法都在回答同一个问题:如何用更合适的坐标系和更少的维度表示数据。