首先需要介绍一个经典的工具SVD奇异值分解,他是很多降维算法的基础。
在考研数学中,特征值分解(Eigenvalue Decomposition)相关的内容通常出现在线性代数部分,常见题目类型包括求矩阵的特征值和特征向量、判断矩阵是否可对角化、以及利用特征值分解进行矩阵的对角化。这些题目往往以计算题或证明题的形式出现。
一、线性代数:特征值分解相关
考研数学中与特征值分解相关的题目主要集中在以下几类:
-
计算特征值和特征向量:通过特征方程求解。
-
矩阵对角化:判断矩阵是否可对角化,并求对角化矩阵。
-
利用特征值分解求矩阵幂或函数:通过分解简化高次幂计算。
-
证明题:如证明某些矩阵性质与特征值的关系。
总结与联系
-
正交矩阵:列向量正交且单位化,在 SVD 中用于旋转或反射(U和V)。
-
特征值与特征向量:描述矩阵在某些方向上的缩放特性,是计算奇异值的基础。
-
对称矩阵:具有实特征值和正交特征向量,SVD 通过构造 A的转置乘A 和 A乘A的转置 利用其性质。
-
矩阵分解:将复杂矩阵分解为简单矩阵乘积,是降维和数据分析的核心工具。
二、奇异值分解(SVD)的输入和输出:
-
输入:一个任意的矩阵 A,尺寸为 m * n(其中 m 是行数,n 是列数,可以是矩形矩阵,不必是方阵)。
奇异值分解(SVD)得到的三个矩阵U、Σ和VT各有其特定的意义和用途,下面我简要说明它们的作用:
-
U(左奇异向量矩阵):
-
是一个m×m的正交矩阵,列向量是矩阵 A*AT 的特征向量。
-
作用:表示原始矩阵 A 一个在行空间(样本空间)中的主方向或基向量。简单来说,U的列向量描述了数据在行维度上的“模式”或“结构”。
-
应用:在降维中,U的前几列可以用来投影数据到低维空间,保留主要信息(如在图像处理中提取主要特征)。
-
-
Σ(奇异值矩阵):
-
是一个m×n的对角矩阵,对角线上的值是奇异值(singular values),按降序排列,非负。
-
作用:奇异值表示原始矩阵一个在每个主方向上的“重要性”或“能量”。较大的奇异值对应更重要的特征,较小的奇异值对应噪声或次要信息。
-
应用:通过选择前k个较大的奇异值,可以实现降维,丢弃不重要的信息(如数据压缩、去噪)。
-
-
VT(右奇异向量矩阵的转置):
-
是V的转置,V是一个n×n的正交矩阵,列向量是矩阵 AT*A 的特征向量。
-
作用:表示原始矩阵 A 在列空间(特征空间)中的主方向或基向量。简单来说,V的列向量描述了数据在列维度上的“模式”或“结构”。
-
应用:类似U,V的前几列可以用来投影数据到低维空间,提取主要特征。
-
整体作用:
-
结合起来,A=U*Σ*VT 意味着原始矩阵一个可以被分解为一系列主方向(U和V)和对应的权重(Σ)的组合。这种分解揭示了数据的内在结构。
- 主要应用:
-
降维:通过保留前k个奇异值及其对应的U和V的列向量,可以近似重建 A,减少数据维度(如 PCA 的基础)。
-
数据压缩:如图像压缩,丢弃小的奇异值以减少存储空间。
-
去噪:小的奇异值往往对应噪声,丢弃它们可以提高数据质量。
-
推荐系统:如矩阵分解,用于预测用户评分矩阵中的缺失值。
-
简单来说,U、Σ和VT提供了数据的核心结构信息,帮助我们在保留主要信息的同时简化数据处理。
三、奇异值的应用
奇异值分解(SVD)后,原始矩阵 A 被分解为 A=U*Σ*VT,这种分解是等价的,意味着通过U、Σ和VT的乘积可以完全重构原始矩阵 A ,没有任何信息损失。
但在实际应用中,我们通常不需要保留所有的奇异值和对应的向量,而是可以通过筛选规则选择排序靠前的奇异值及其对应的向量来实现降维或数据压缩。以下是这个过程的核心思想:
-
奇异值的排序:
-
在Σ矩阵中,奇异值(对角线上的值)是按降序排列的。靠前的奇异值通常较大,代表了数据中最重要的信息或主要变化方向;靠后的奇异值较小,代表次要信息或噪声。
-
奇异值的大小反映了对应向量对原始矩阵 A 的贡献程度。
-
-
筛选规则:
-
我们可以根据需求选择保留前k个奇异值(k是一个小于原始矩阵秩的数),并丢弃剩余的较小奇异值。
- 常见的筛选规则包括:
-
固定数量:直接选择前k个奇异值(例如,前 10 个)。
-
累计方差贡献率:计算奇异值的平方(代表方差),选择累计方差贡献率达到某个阈值(如 95%)的前k个奇异值。
-
奇异值下降幅度:观察奇异值下降的“拐点”,在下降明显变缓的地方截断。
-
-
-
-
对应的向量:
-
U 的列向量和 V 的列向量分别对应左右奇异向量。保留前k个奇异值时,Uk的列向量代表数据在行空间中的主要方向,Vk的列向量代表数据在列空间中的主要方向。
-
这些向量与奇异值一起,构成了数据的主要“模式”或“结构”。
-
总结:SVD 分解后原始矩阵是等价的,但通过筛选排序靠前的奇异值和对应的向量,我们可以实现降维,保留数据的主要信息,同时减少计算量和噪声影响。这种方法是许多降维算法(如 PCA)和数据处理技术的基础。
四、实际案例
输出解释:
-
原始矩阵一个被分解为U、Σ(由 sigma 表示)和VT。
-
通过U*Σ*VT的矩阵乘法,可以完全重构原始矩阵一个,几乎没有信息损失(可能有非常小的数值误差)。
矩阵降维案例
import numpy as np
# 创建一个矩阵 A (5x3)
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
[14, 14, 15] ])
print("原始矩阵 A:\n", A)
# 进行 SVD 分解
U, sigma, Vt = np.linalg.svd(A, full_matrices=False)
print("\n奇异值 sigma:", sigma)
# 保留前 k=1 个奇异值进行降维
k = 1
U_k = U[:, :k] # 取 U 的前 k 列,保持行数不变
sigma_k = sigma[:k] # 取前k个奇异值
Vt_k = Vt[:k, :] # 取Vt的前k行,保持列数不变
# 近似重构矩阵 A,常用于信号or图像筛除噪声
A_approx = U_k @ np.diag(sigma_k) @ Vt_k
print("\n保留前{k}个奇异值后的近似矩阵 A_approx:\n", A_approx)
# 计算近似误差
error = np.linalg.norm(A - A_approx, 'fro') / np.linalg.norm(A, 'fro')
print("\n近似误差(Frobenius 范数相对误差):", error)
输出:
原始矩阵 A:
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]
[14 14 15]]
奇异值 sigma: [35.54818284 1.81199142 0.20828726]
保留前{k}个奇异值后的近似矩阵 A_approx:
[[ 1.89372058 2.02092145 2.21781775]
[ 4.6611723 4.9742624 5.45889967]
[ 7.42862401 7.92760334 8.69998159]
[10.19607572 10.88094429 11.94106351]
[13.24839303 14.13828519 15.51576379]]
近似误差(Frobenius 范数相对误差): 0.05124108101316206
可以看到,再保留了k=1个奇异值后,近似误差仅为 5.12%,表明这种降维方式非常有效,丢失的信息很少。
在机器学习中,如果对训练集进行 SVD 降维后训练模型,而测试集的特征数量与降维后的训练集不一致(测试集仍保持原始特征数量),该如何处理?
1、问题分析
-
训练集降维:假设训练集有 1000 个样本,50 个特征,通过 SVD 降维后保留k=10个特征,得到形状为(1000, 10) 的新数据。模型基于这 10 个特征进行训练。
-
测试集问题:测试集假设有 200 个样本,仍然是 50 个特征。如果直接输入测试集到模型中,特征数量不匹配(模型期望 10 个特征,测试集有 50 个),会导致错误。
-
核心问题:如何确保测试集也能被正确地降维到与训练集相同的k个特征空间?
2、解决方案:对测试集应用相同的变换
在机器学习中,降维(如 SVD、PCA 等)是一种数据预处理步骤。训练集和测试集必须经过相同的变换,以确保数据分布一致。具体到 SVD,步骤如下:
-
训练阶段:对训练集 X_train 进行 SVD 分解,得到U,Σ, 和VT,并保存VT矩阵(或其前k行)用于降维变换。
-
测试阶段:使用从训练集得到的VT矩阵,将测试集 X_test 投影到相同的低维空间,得到降维后的测试数据。
-
原因:VT矩阵定义了从原始特征空间到低维特征空间的映射关系,测试集必须使用相同的映射以保持一致性。
3、为什么不能对测试集单独做 SVD?
-
如果对测试集单独进行 SVD,会得到不同的VT矩阵,导致测试集和训练集的低维空间不一致,模型无法正确处理测试数据。
-
训练集的VT矩阵代表了训练数据的特征映射规则,测试集必须遵循相同的规则,否则会引入数据泄漏或不一致性问题。
4、代码示例:训练集和测试集的 SVD 降维
以下是一个完整的代码示例,展示如何对训练集进行 SVD 降维,训练模型,并对测试集应用相同的降维变换。
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 设置随机种子以便结果可重复
np.random.seed(42)
# 模拟数据: 1000个样本, 50 个特征
n_samples = 1000
n_features = 50
X = np.random.randn(n_samples, n_features) * 10 # 随机生成特征数据
y = (X[:, 0] + X[:, 1] > 0).astype(int) # 模拟二分类标签
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"训练集形状:{X_train.shape}")
print(f"测试集形状:{X_test.shape}")
# 对训练集进行 SVD 分解
U_train, sigma_train, Vt_train = np.linalg.svd(X_train, full_matrices=False)
print(f"Vt_train 矩阵形状:{Vt_train.shape}")
# 选择保留的奇异值数量 k,选择前k个最重要的奇异值对应的右奇异向量,用于降维。
k = 10
Vt_k = Vt_train[:k, :] # 保留前 k 行,形状为 (k, 50)
print(f"保留 k={k} 后的 Vt_k 矩阵形状:{Vt_k.shape}")
# 降维训练集
X_train_reduced = X_train @ Vt_k.T
print(f"降维后训练集形状:{X_train_reduced.shape}")
# 使用相同的 Vt_k 对测试集进行降维
X_test_reduced = X_test @ Vt_k.T
print(f"降维后测试集形状:{X_test_reduced.shape}")
# 训练模型(以逻辑回归为例)
model = LogisticRegression(random_state=42)
model.fit(X_train_reduced, y_train)
# 预测并评估
model_pred = model.predict(X_test_reduced)
accuracy = accuracy_score(y_test, model_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}")
输出:
训练集形状:(800, 50)
测试集形状:(200, 50)
Vt_train 矩阵形状:(50, 50)
保留 k=10 后的 Vt_k 矩阵形状:(10, 50)
降维后训练集形状:(800, 10)
降维后测试集形状:(200, 10)
测试集准确率:0.595
训练集近似误差 (Frobenius 范数相对误差): 0.8524799295115592
5、实际操作中的注意事项
1.标准化数据:在进行 SVD 之前,通常需要对数据进行标准化(均值为 0,方差为 1),以避免某些特征的量纲差异对降维结果的影响。可以使用 sklearn.preprocessing.StandardScaler。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
注意: scaler 必须在训练集上 fit,然后对测试集只用transform ,以避免数据泄漏。
2.选择合适的k:可以通过累计方差贡献率(explained variance ratio)选择 k,通常选择解释 90%-95% 方差的 k 值。代码中可以计算:
explained_variance_ratio = np.cumsum(sigma_train**2) / np.sum(sigma_train**2)
print(f"前 {k} 个奇异值的累计方差贡献率: {explained_variance_ratio[k-1]}")
3.使用 sklearn 的 TruncatedSVD:sklearn 提供了 TruncatedSVD 类,专门用于高效降维,尤其适合大规模数据。它直接计算前k个奇异值和向量,避免完整 SVD 的计算开销。
from sklearn.decomposition import TruncatedSVD
svd = TruncatedSVD(n_components=k, random_state=42)
X_train_reduced = svd.fit_transform(X_train)
X_test_reduced = svd.transform(X_test)
print(f"累计方差贡献率: {sum(svd.explained_variance_ratio_)}")