DAY20 奇异值SVD分解
@浙大疏锦行
知识点:
- 线性代数概念回顾(可不掌握)
- 奇异值推导(可不掌握)
- 奇异值的应用
a. 特征降维:对高维数据减小计算量、可视化
b. 数据重构:比如重构信号、重构图像(可以实现有损压缩,k 越小压缩率越高,但图像质量损失越大)
c. 降噪:通常噪声对应较小的奇异值。通过丢弃这些小奇异值并重构矩阵,可以达到一定程度的降噪效果。
d. 推荐系统:在协同过滤算法中,用户-物品评分矩阵通常是稀疏且高维的。SVD (或其变种如 FunkSVD, SVD++) 可以用来分解这个矩阵,发现潜在因子 (latent factors),从而预测未评分的项。这里其实属于特征降维的部分。
作业:尝试利用svd来处理心脏病预测,看下精度变化
1 奇异值
奇异值分解(SVD)的输入和输出:
- 输入:一个任意的矩阵 AAA,尺寸为 m×nm \times nm×n(其中 mmm 是行数,nnn 是列数,可以是矩形矩阵,不必是方阵)。
- UUU(左奇异向量矩阵):m×mm \times mm×m 的正交矩阵
- Σ\SigmaΣ(奇异值矩阵):m×nm \times nm×n 的对角矩阵,对角线上的值是奇异值
- VTV^TVT(右奇异向量矩阵的转置):VVV 的转置,VVV 是一个 n×nn \times nn×n 的正交矩阵
结合起来,A=UΣVTA = U \Sigma V^TA=UΣVT 意味着原始矩阵 AAA 可以被分解为一系列主方向(UUU 和 VVV)和对应的权重(Σ\SigmaΣ)的组合。
主要作用于降维、数据压缩、去噪、推荐系统。
2 作业
前提是已经完成特征编码,划分好训练集和测试集。
# 标准化数据(需要标准化)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 对训练集进行 SVD 分解
U_train, sigma_train, Vt_train = np.linalg.svd(X_train, full_matrices=False)
# 计算每个奇异值的方差贡献率及累计方差贡献率
singular_values_squared = sigma_train ** 2
total_variance = np.sum(singular_values_squared)
explained_variance_ratio = np.cumsum(singular_values_squared) / total_variance# 根据目标贡献率自动选择k(例如选择累计贡献率达到95%的最小k)
target_ratio = 0.95 # 可以根据需要调整,如0.90, 0.99等
k = np.argmax(explained_variance_ratio >= target_ratio) + 1 # +1因为索引从0开始print(f"满足累计方差贡献率{target_ratio*100}%的最小k值为: {k}")
print(f"前{k}个奇异值的实际累计方差贡献率: {explained_variance_ratio[k-1]:.4f}")
得出最小k值为15:
Vt_k = Vt_train[:k, :] # 保留前 k 行# 降维训练集:X_train_reduced = X_train @ Vt_k.T
X_train_reduced = X_train @ Vt_k.T# 使用相同的 Vt_k 对测试集进行降维:X_test_reduced = X_test @ Vt_k.T
X_test_reduced = X_test @ Vt_k.Tfrom sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 训练模型(以逻辑回归为例)
model = LogisticRegression(random_state=42)
model.fit(X_train_reduced, y_train)# 预测并评估
y_pred = model.predict(X_test_reduced)
accuracy = accuracy_score(y_test, y_pred)
print(f"测试集准确率: {accuracy}")# 计算训练集的近似误差(可选,仅用于评估降维效果)
X_train_approx = U_train[:, :k] @ np.diag(sigma_train[:k]) @ Vt_k
error = np.linalg.norm(X_train - X_train_approx, 'fro') / np.linalg.norm(X_train, 'fro')
print(f"训练集近似误差 (Frobenius 范数相对误差): {error}")
最后结果如下,精度基本没什么变化: