现代数据集常常包含成百上千甚至上万个特征。图像可能有数百万像素,基因数据可能包含数万个基因表达值,文本数据可能有数万个词汇维度。这种高维数据给机器学习带来了巨大挑战——计算开销大、存储需求高、可视化困难、容易过拟合(维度灾难)。
降维(Dimensionality Reduction)技术能够将高维数据映射到低维空间,同时尽可能保留数据的重要信息。降维不仅能加速算法、节省存储,更重要的是能够帮助我们理解数据的内在结构,发现数据中真正重要的模式。
这节课我们将学习最著名的降维算法——主成分分析(PCA)。PCA通过找到数据变化最大的方向,用少数几个主成分来表示原始的高维数据。我们会理解PCA的原理,学习如何实现和应用它,以及何时应该(和不应该)使用降维。
动机一:数据压缩
假设我们测量了飞行员的技能,用两个高度相关的特征:用厘米表示的身高和用英寸表示的身高。这两个特征包含的信息几乎完全重复。我们可以用一个特征(比如厘米)来替代两个,几乎不损失信息。
更通常地来说,高维数据中常常存在冗余。不同特征可能高度相关,实际的有效维度可能远低于名义维度。降维能够移除这种冗余,提取数据的“本质”维度。
好处:
动机二:可视化
人类很难直观理解超过3维的数据。但通过降维,我们可以把高维数据投影到2维或3维,画出散点图,直观地观察数据的分布、聚类、异常值等。

例如,我们有50个国家的统计数据,每个国家有100个特征(GDP、人口、教育水平等)。这是100维数据,无法直接可视化。但如果我们用PCA降到2维,就可以在平面上画出50个点,每个点代表一个国家。我们可能会发现发达国家聚在一起,发展中国家聚在另一边,揭示了数据的结构。
降维是有损的——我们会损失一些信息。关键是找到一个平衡:用尽可能少的维度保留尽可能多的信息。PCA通过数学方法找到这个最优的权衡。
PCA是最常用的降维算法。它的核心思想是:找到数据变化最大的方向,把数据投影到这些方向上。
想象你有一堆三维空间中的点,大致分布在一个平面附近(想象一张纸在空间中)。这些点的z坐标变化很小,主要变化在xy平面内。PCA会发现这个平面,把数据投影到这个平面上,从3维降到2维,几乎不损失信息。
更形式化地说,PCA寻找k个方向(主成分),使得数据投影到这k个方向后,方差最大。方差大意味着数据在这个方向上“伸展”得厉害,包含更多信息。
PCA的数学表述:
给定数据集 ,,目标是找到 个方向 (),使得:
步骤0:数据预处理
import numpy as np
def pca(X, k):
"""
主成分分析
X: 数据矩阵 (m x n)
k: 目标维度
返回: 投影矩阵U_reduce, 降维后的数据Z
"""
m, n = X.shape
# 步骤0:均值归一化
mu = np.mean(X, axis=0)
X_norm = X - mu
# 可选:特征缩放
sigma = np.std(X, axis
使用scikit-learn:
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
# 创建PCA对象
pca = PCA(n_components=2)
# 拟合并转换数据
Z = pca.fit_transform(X)
# 查看每个主成分解释的方差比例
print("解释方差比:", pca.explained_variance_ratio_)
print("累积解释方差:", np.cumsum(pca.explained_variance_ratio_))
# 可视化降维后的数据
plt.scatter(Z[:, 0], Z[:, 1])
plt.xlabel(
如何决定保留多少个主成分?这是PCA中的关键问题。
方法1:解释方差比例
每个主成分解释了原始数据中一定比例的方差。前k个主成分累计解释的方差比例:
其中 是第i个特征值(代表第i个主成分的方差)。
我们通常选择k使得累计解释方差达到一定阈值,比如95%或99%。
# 计算不同k值下的累计解释方差
pca_full = PCA()
pca_full.fit(X)
cumsum = np.cumsum(pca_full.explained_variance_ratio_)
# 找到达到95%的k
k_95 = np.argmax(cumsum >= 0.95) + 1
print(f"保留95%方差需要 {k_95} 个主成分")
# 画出累计方差图
plt.plot(range(1, len(cumsum)+1), cumsum)
plt.axhline(

方法2:碎石图(Scree Plot)
画出每个主成分解释的方差。寻找"肘部"——方差下降突然变缓的地方。
plt.plot(range(1, len(pca_full.explained_variance_)+1),
pca_full.explained_variance_, 'o-')
plt.xlabel('主成分')
plt.ylabel('解释方差')
plt.title('碎石图')
plt.show()方法3:基于下游任务
如果降维是为了后续的监督学习,可以尝试不同的k,看哪个在验证集上表现最好。
降维后的数据可以近似重建回原始维度。虽然不能完全恢复(有信息损失),但可以得到一个近似。
重建误差反映了降维损失了多少信息:
def reconstructData(Z, U_reduce, mu, sigma):
"""
从降维数据重建原始数据
"""
X_approx = Z @ U_reduce.T
X_approx = X_approx * sigma + mu # 反归一化
return X_approx
# 重建
X_approx = reconstructData(Z, U_reduce, mu, sigma)
# 计算重建误差
reconstruction_error = np.mean(np.sum((X - X_approx)**2, axis=1
应用1:图像压缩
灰度图像可以看作向量(每个像素一个维度)。对一组图像应用PCA,可以用少数主成分表示每张图像,实现压缩。
# 假设有1000张28x28的人脸图像
# X是1000x784的矩阵
pca = PCA(n_components=50) # 用50个主成分
Z = pca.fit_transform(X) # 从784维降到50维
# 压缩率:50/784 ≈ 6.4%
# 重建图像
X_approx = pca.inverse_transform(Z)
# 显示原始和重建的图像
fig, axes = plt.subplots(2, 5, figsize=(15, 6))
for i in range
应用2:数据可视化
将高维数据降到2D或3D进行可视化。
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
# 加载手写数字数据(64维)
digits = load_digits()
X, y = digits.data, digits.target
# PCA降到2维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
# 可视化
plt.figure(figsize=(10, 8))
scatter =
应用3:噪声过滤
PCA可以用来去除数据中的噪声。思想是:信号主要在前几个主成分中,噪声分散在所有维度。保留主要成分、丢弃后面的成分,可以去噪。
应用4:加速学习
在训练监督学习模型前,先用PCA降维可以:
# 降维 + 分类
from sklearn.linear_model import LogisticRegression
# 原始数据训练
clf = LogisticRegression()
clf.fit(X_train, y_train)
score_original = clf.score(X_test, y_test)
# PCA降维后训练
pca = PCA(n_components=0.95) # 保留95%方差
X_train_pca = pca.fit_transform(X_train)
X_test_pca = pca.transform(X_test)
clf_pca = LogisticRegression()
clf_pca.fit(X_train_pca, y_train)
score_pca = clf_pca.score(X_test_pca, y_test)
PCA降维会损失信息。不要期望降维后的性能总是更好。有时候,那些看似不重要的小方差成分中也包含对任务有用的信息。应该通过实验来验证降维是否有益。
降维是数据科学工具箱中的重要工具。PCA作为最经典的降维方法,简单、高效、理论基础扎实。掌握PCA,你就能处理高维数据的诸多挑战。但记住,降维是手段不是目的,应该根据具体问题决定是否使用、如何使用。
在下一节课中,我们将学习异常检测——识别数据中不寻常的模式。异常检测在很多领域有重要应用,从欺诈检测到设备故障预警。我们会看到,异常检测也是一种无监督学习任务,它与聚类、降维等技术可以结合使用。
问题诊断和解决方案:根据以下情况,诊断问题并提出解决方案。
你训练了一个模型,得到:
这是什么问题?应该如何解决?
答案:
诊断:高方差(过拟合)
判断依据:
解决方案(按优先级):
获取更多训练数据 ✓ 最有效
减少特征数量 ✓
增加正则化参数λ ✓
使用更简单的模型 ✓
不应该做的:
Python诊断代码:
def diagnose_model(train_error, val_error):
gap = val_error - train_error
if train_error > 0.15: # 高偏差
if gap < 0.05:
return "欠拟合(高偏差)"
else:
学习曲线分析:分析以下学习曲线,判断问题类型。
随着训练样本数量增加:
这是什么问题?解决方案是什么?
答案:
诊断:轻度高方差(过拟合),可能混合轻度高偏差
学习曲线特征分析:
训练误差上升(10% → 18%)
验证误差下降(60% → 20%)
曲线趋于平缓
判断:
解决方案:
如果目标误差可接受(如15%):
如果目标误差更低(如5%):
典型学习曲线模式:
高方差(过拟合):
训练误差:很低且平
验证误差:高且有大gap
解决:更多数据有帮助
高偏差(欠拟合):
训练误差:高且平
验证误差:高且gap小
解决:更多数据帮助不大,需要更复杂模型
理想状态:
两条曲线都低且接近
差距很小
其中
其中 是特征 的标准差。
步骤1:计算协方差矩阵
其中 是 的数据矩阵(每行一个样本)。 是 的矩阵。
步骤2:计算协方差矩阵的特征向量
对 进行特征值分解(或奇异值分解SVD):
是 的矩阵,列是特征向量; 是对角矩阵,对角线是特征值。
步骤3:选择前k个主成分
的前k列就是我们要找的k个主方向。记为 ( 矩阵)。
步骤4:投影数据
将数据投影到k维空间:
是降维后的表示。