好的,我们开始今天的讲座。今天的主题是Python中的特征分解与奇异值分解(SVD),以及它们在降维和推荐系统中的底层实现。
1. 线性代数基础回顾
在深入探讨特征分解和SVD之前,我们需要回顾一些线性代数的基础概念。
- 向量 (Vector): 一个有序的数值列表。在Python中,通常用NumPy数组表示。
- 矩阵 (Matrix): 一个二维的数值数组。同样,在Python中,也用NumPy数组表示。
- 转置 (Transpose): 矩阵的行和列互换。
- 内积 (Dot Product): 两个向量对应元素相乘再求和。
- 线性无关 (Linear Independence): 一组向量中,没有一个向量可以表示为其他向量的线性组合。
- 正交 (Orthogonal): 两个向量的内积为零。
- 正交矩阵 (Orthogonal Matrix): 一个方阵,其列向量是单位正交向量。正交矩阵的转置等于其逆矩阵。
2. 特征分解 (Eigen Decomposition)
特征分解是一种将矩阵分解为一组特征向量和特征值的技术。只有方阵才能进行特征分解。
- 特征向量 (Eigenvector): 对于给定的方阵 A,一个非零向量 v 满足 Av = λv,其中 λ 是一个标量。这个 v 就是 A 的特征向量。
- 特征值 (Eigenvalue): λ 就是对应于特征向量 v 的特征值。
数学公式:
对于一个 n x n 的方阵 A,如果存在 n 个线性无关的特征向量 v1, v2, …, vn,以及对应的特征值 λ1, λ2, …, λn,那么 A 可以被分解为:
A = VΛV⁻¹
其中:
- V 是一个 n x n 的矩阵,其列向量是 A 的特征向量 v1, v2, …, vn。
- Λ 是一个 n x n 的对角矩阵,对角线上的元素是对应的特征值 λ1, λ2, …, λn。
Python 实现:
我们可以使用NumPy库的linalg.eig函数来进行特征分解。
import numpy as np
# 定义一个方阵
A = np.array([[4, 1],
[2, 3]])
# 进行特征分解
eigenvalues, eigenvectors = np.linalg.eig(A)
print("特征值:", eigenvalues)
print("特征向量:n", eigenvectors)
# 验证分解结果
V = eigenvectors
Lambda = np.diag(eigenvalues)
A_reconstructed = V @ Lambda @ np.linalg.inv(V) # 使用 @ 运算符进行矩阵乘法
print("重构后的矩阵:n", A_reconstructed)
代码解释:
- 我们首先定义了一个2×2的方阵 A。
- 使用
np.linalg.eig(A)函数计算 A 的特征值和特征向量。 eigenvalues存储了特征值,eigenvectors存储了特征向量,其中每一列对应一个特征向量。- 我们使用特征向量构建矩阵 V,并使用特征值构建对角矩阵 Lambda。
- 最后,我们使用公式 A = VΛV⁻¹ 重构矩阵 A,并打印结果。由于浮点数精度问题,重构后的矩阵可能与原始矩阵略有不同,但非常接近。
特征分解的应用:降维
特征分解可以用于降维,尤其是在主成分分析 (PCA) 中。 PCA 的核心思想是选择具有最大特征值的特征向量作为主成分,这些主成分代表了数据中最重要的方向。
假设我们有一个数据矩阵 X,我们可以计算 X 的协方差矩阵 C,然后对 C 进行特征分解。选择前 k 个最大的特征值对应的特征向量,构成一个转换矩阵 W。 将原始数据 X 投影到 W 上,得到降维后的数据 Y:
Y = XW
import numpy as np
# 假设我们有以下数据矩阵
X = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 计算协方差矩阵
C = np.cov(X.T) # 注意要转置,因为np.cov默认计算列之间的协方差
# 进行特征分解
eigenvalues, eigenvectors = np.linalg.eig(C)
# 选择前 k 个最大的特征值对应的特征向量 (例如,k=1)
k = 1
top_k_eigenvectors = eigenvectors[:, :k]
# 将数据投影到选定的特征向量上
Y = X @ top_k_eigenvectors
print("降维后的数据:n", Y)
代码解释:
- 我们首先定义了一个数据矩阵 X。
- 计算 X 的协方差矩阵 C。 注意,
np.cov默认计算列之间的协方差,因此我们需要转置 X。 - 对协方差矩阵 C 进行特征分解。
- 选择前 k 个最大的特征值对应的特征向量。
- 将原始数据 X 投影到选定的特征向量上,得到降维后的数据 Y。
3. 奇异值分解 (Singular Value Decomposition – SVD)
奇异值分解是一种将矩阵分解为三个矩阵的技术。与特征分解不同,SVD 可以应用于任何矩阵,不要求是方阵。
数学公式:
对于一个 m x n 的矩阵 A,SVD 将 A 分解为:
A = UΣVᵀ
其中:
- U 是一个 m x m 的正交矩阵,称为左奇异向量矩阵。
- Σ 是一个 m x n 的对角矩阵,对角线上的元素称为奇异值。奇异值通常按降序排列。
- V 是一个 n x n 的正交矩阵,称为右奇异向量矩阵。
Python 实现:
我们可以使用NumPy库的linalg.svd函数来进行奇异值分解。
import numpy as np
# 定义一个矩阵
A = np.array([[1, 2, 3],
[4, 5, 6]])
# 进行奇异值分解
U, S, V = np.linalg.svd(A)
print("U:n", U)
print("S:n", S) # SVD返回的S是一个一维数组,包含奇异值
print("V:n", V)
# 验证分解结果
Sigma = np.zeros((A.shape[0], A.shape[1]))
Sigma[:A.shape[0], :A.shape[0]] = np.diag(S) # 构建完整的奇异值矩阵
A_reconstructed = U @ Sigma @ V
print("重构后的矩阵:n", A_reconstructed)
代码解释:
- 我们首先定义了一个2×3的矩阵 A。
- 使用
np.linalg.svd(A)函数计算 A 的奇异值分解。 U存储了左奇异向量矩阵,S存储了奇异值(一个一维数组),V存储了右奇异向量矩阵的转置。- 我们使用奇异值构建对角矩阵 Sigma。 需要注意的是,
np.linalg.svd返回的S只是奇异值的一维数组,我们需要手动构建完整的对角矩阵。 - 最后,我们使用公式 A = UΣVᵀ 重构矩阵 A,并打印结果。
SVD 的应用:降维
SVD 也可以用于降维。类似于 PCA,我们可以选择前 k 个最大的奇异值对应的奇异向量,来近似原始矩阵。
假设我们有一个数据矩阵 A,我们可以对 A 进行 SVD 分解。选择前 k 个最大的奇异值,以及对应的左奇异向量和右奇异向量,构成近似矩阵 Ak:
Ak = UkΣkVkᵀ
其中:
- Uk 是 U 的前 k 列。
- Σk 是 Σ 的左上角 k x k 的子矩阵,包含前 k 个奇异值。
- Vk 是 V 的前 k 列。
降维后的表示可以是 UkΣk 或者 ΣkVᵀ,具体取决于应用场景。
import numpy as np
# 假设我们有以下数据矩阵
A = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
# 进行奇异值分解
U, S, V = np.linalg.svd(A)
# 选择前 k 个最大的奇异值对应的奇异向量 (例如,k=2)
k = 2
Uk = U[:, :k]
Sk = np.diag(S[:k])
Vk = V[:k, :]
# 构建近似矩阵
Ak = Uk @ Sk @ Vk
print("降维后的表示 Uk*Sk:n", Uk @ Sk)
print("降维后的表示 Sk*VkT:n", Sk @ Vk)
print("近似矩阵 Ak:n", Ak)
代码解释:
- 我们首先定义了一个数据矩阵 A。
- 对 A 进行 SVD 分解。
- 选择前 k 个最大的奇异值,以及对应的左奇异向量和右奇异向量。
- 构建近似矩阵 Ak。
- 打印降维后的表示 Uk*Sk 和 Sk*VkT,以及近似矩阵 Ak。
SVD 的应用:推荐系统
SVD 在推荐系统中有着广泛的应用,尤其是在协同过滤中。
假设我们有一个用户-物品评分矩阵 R,其中 R[i][j] 表示用户 i 对物品 j 的评分。 矩阵 R 可能非常稀疏,因为用户不可能对所有物品都进行评分。
我们可以使用 SVD 对 R 进行分解,然后使用分解后的矩阵来预测用户未评分的物品的评分。
具体步骤如下:
- 对评分矩阵 R 进行 SVD 分解: R = UΣVᵀ
- 选择前 k 个最大的奇异值,以及对应的左奇异向量和右奇异向量。
- 使用分解后的矩阵来预测用户 i 对物品 j 的评分: R[i][j] ≈ U[i, :] @ Σ @ V[:, j]
import numpy as np
# 用户-物品评分矩阵 (缺失评分用 0 表示)
R = np.array([[5, 3, 0, 1],
[4, 0, 0, 1],
[1, 1, 0, 5],
[1, 0, 0, 4],
[0, 1, 5, 4]])
# 进行奇异值分解
U, S, V = np.linalg.svd(R)
# 选择前 k 个最大的奇异值 (例如,k=2)
k = 2
Uk = U[:, :k]
Sk = np.diag(S[:k])
Vk = V[:k, :]
# 预测用户 1 对物品 3 的评分
user_id = 0 # 用户ID 从0开始
item_id = 2 # 物品ID 从0开始
predicted_rating = Uk[user_id, :] @ Sk @ Vk[:, item_id]
print("预测的用户", user_id+1, "对物品", item_id+1, "的评分:", predicted_rating)
# 完整预测所有缺失值(将原始矩阵中的0替换为预测值)
R_predicted = Uk @ Sk @ Vk
R_filled = R.copy()
for i in range(R.shape[0]):
for j in range(R.shape[1]):
if R[i, j] == 0:
R_filled[i, j] = R_predicted[i, j]
print("填充后的矩阵:n", R_filled)
代码解释:
- 我们首先定义了一个用户-物品评分矩阵 R。
- 对 R 进行 SVD 分解。
- 选择前 k 个最大的奇异值,以及对应的左奇异向量和右奇异向量。
- 使用分解后的矩阵来预测用户 1 对物品 3 的评分。
- 循环遍历整个矩阵,将所有评分0的位置替换为预测评分。
需要注意的是:
- 实际应用中,评分矩阵通常非常稀疏,直接使用 SVD 可能会导致性能问题。可以使用一些优化的 SVD 算法,例如 Truncated SVD。
- 在推荐系统中,通常需要对数据进行一些预处理,例如填充缺失值、归一化评分等。
- SVD 只是推荐系统中的一种技术,还可以结合其他技术,例如基于内容的推荐、混合推荐等。
表格总结:特征分解与 SVD 的比较
| 特征 | 特征分解 (Eigen Decomposition) | 奇异值分解 (Singular Value Decomposition – SVD) |
|---|---|---|
| 适用矩阵 | 方阵 | 任意矩阵 |
| 分解形式 | A = VΛV⁻¹ | A = UΣVᵀ |
| 特征向量/奇异向量 | 特征向量 | 左奇异向量 (U), 右奇异向量 (V) |
| 特征值/奇异值 | 特征值 | 奇异值 (Σ) |
| 应用 | PCA降维,图算法等 | 降维,推荐系统,图像压缩等 |
| 是否要求矩阵可逆 | V 需要可逆 | 无要求 |
| 与数据矩阵的关系(降维时) | 基于协方差矩阵 | 直接分解原矩阵 |
4. 实际应用中的考量
在实际应用中,特征分解和SVD都需要考虑一些性能和精度的问题。
- 大规模矩阵: 对于大规模矩阵,直接进行特征分解或SVD的计算成本非常高。 可以使用一些优化的算法,例如Lanczos算法, Power Iteration等。
- 稀疏矩阵: 对于稀疏矩阵,可以使用专门针对稀疏矩阵的SVD算法,例如IRLBA (implicitly restarted Lanczos bidiagonalization algorithm)。
- 增量更新: 当数据发生变化时,重新计算分解结果的成本很高。 可以使用一些增量更新的算法,例如Online SVD。
- 数值稳定性: 在进行特征分解和SVD时,可能会遇到数值稳定性问题。 可以使用一些正则化技术来提高数值稳定性。
特征分解与SVD:总结
特征分解只能用于方阵,而SVD可以用于任意矩阵。两者都可以用于降维,在推荐系统中SVD应用广泛。
降维与推荐:核心要点
无论是特征分解还是SVD,降维的核心都是找到数据中最主要的成分;推荐系统则利用降维后的信息,预测用户对未评分物品的喜好。
实际应用的优化:性能与稳定性
实际应用中,要考虑大规模矩阵、稀疏矩阵以及数值稳定性等问题,选择合适的优化算法和技术。
更多IT精英技术系列讲座,到智猿学院