机器学习降维技术深度解析:PCA、LDA与t-SNE原理及实战
一、降维技术概述
1.1 为什么需要降维?
在机器学习领域,维度灾难(Curse of Dimensionality) 是一个核心挑战。随着特征维度的增加,数据变得稀疏,模型容易过拟合,计算成本也急剧上升。
降维的核心价值:
- 📦 数据压缩:减少存储空间,提高计算效率
- 🎯 避免过拟合:降低模型复杂度,提升泛化能力
- 📊 数据可视化:将高维数据映射到2D/3D空间
- 🔍 特征提取:发现数据中的潜在结构
1.2 特征选择 vs 特征提取
| 对比维度 | 特征选择(Feature Selection) | 特征提取(Feature Extraction) |
|---|---|---|
| 核心思想 | 选择原始特征的子集 | 将数据转换到新的特征空间 |
| 特征来源 | 保留原始特征 | 创造新的特征组合 |
| 代表算法 | SBS、L1正则化、随机森林 | PCA、LDA、t-SNE |
| 可解释性 | 高(保留原始特征名) | 低(新特征是线性/非线性组合) |
二、主成分分析(PCA)------无监督降维之王
2.1 PCA核心原理
PCA(Principal Component Analysis) 是一种无监督线性变换方法,目标是找到数据中方差最大的方向(主成分),并将数据投影到这些方向上实现降维。
关键洞察:方差最大的方向包含最多的信息。PCA通过保留最大方差的方向,实现信息损失最小的压缩。
2.2 PCA算法流程(七步法)
md
┌─────────────────────────────────────────────────────────┐
│ Step 1: 标准化数据 │
│ ───────────────── │
│ 使用StandardScaler将特征缩放到零均值、单位方差 │
│ x_std = (x - μ) / σ │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 2: 构造协方差矩阵 │
│ ───────────────────── │
│ Σ = 1/(n-1) * X^T * X │
│ 协方差矩阵Σ[d×d]存储特征间的相关性 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 3: 特征分解 │
│ ───────────── │
│ 计算特征值和特征向量:Σv = λv │
│ 使用np.linalg.eig或np.linalg.eigh │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 4: 特征值排序 │
│ ───────────────── │
│ 按特征值降序排列:λ₁ ≥ λ₂ ≥ ... ≥ λ_d │
│ 特征值越大,对应主成分包含的方差越多 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 5: 选择前k个主成分 │
│ ───────────────────── │
│ 根据方差解释比选择k: │
│ 方差解释比 = λ_j / Σλ_i │
│ 累积方差解释比 = Σ(λ₁到λ_k) / Σλ_i │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 6: 构造投影矩阵W │
│ ─────────────────── │
│ W = [v₁, v₂, ..., v_k] (d×k矩阵) │
│ 每列是一个主成分(特征向量) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 7: 数据投影变换 │
│ ───────────────── │
│ X' = X · W (n×k矩阵) │
│ 原始d维数据 → 新的k维特征子空间 │
└─────────────────────────────────────────────────────────┘
2.3 方差解释比可视化
PCA降维的关键是确定保留多少个主成分。通过方差解释比(Explained Variance Ratio) 可以量化信息保留程度:
python
import numpy as np
import matplotlib.pyplot as plt
# 计算方差解释比
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)]
cum_var_exp = np.cumsum(var_exp)
# 可视化
plt.bar(range(1, 14), var_exp, align='center',
label='Individual explained variance')
plt.step(range(1, 14), cum_var_exp, where='mid',
label='Cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal component index')
plt.legend(loc='best')
plt.show()
葡萄酒数据集示例结果:
- 第1主成分:~40%方差
- 前2主成分:~60%方差
- 前3主成分:~70%方差
2.4 Scikit-Learn实战代码
python
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 1. 数据准备
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, stratify=y, random_state=0
)
# 2. 标准化(PCA对尺度敏感!)
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.transform(X_test)
# 3. PCA降维到2维
pca = PCA(n_components=2)
X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)
# 4. 查看方差解释比
print(pca.explained_variance_ratio_)
# 输出: [0.36951469, 0.18434927] # 约55%方差
# 5. 降维后分类
lr = LogisticRegression(multi_class='ovr', random_state=1)
lr.fit(X_train_pca, y_train)
print(f'Accuracy: {lr.score(X_test_pca, y_test):.3f}')
2.5 PCA特征贡献度分析
理解原始特征如何贡献于主成分,可通过载荷(Loadings) 分析:
python
# 获取PCA载荷(特征与主成分的相关性)
sklearn_loadings = pca.components_.T * np.sqrt(pca.explained_variance_)
# 可视化第1主成分的载荷
fig, ax = plt.subplots()
ax.bar(range(13), sklearn_loadings[:, 0], align='center')
ax.set_ylabel('Loadings for PC 1')
ax.set_xticks(range(13))
ax.set_xticklabels(feature_names, rotation=90)
plt.ylim([-1, 1])
plt.show()
关键发现:载荷绝对值越大,该特征对主成分的贡献越大。
三、线性判别分析(LDA)------监督降维利器
3.1 LDA vs PCA:核心区别
md
┌────────────────────────────────────────────────────────────┐
│ PCA(无监督) │
├────────────────────────────────────────────────────────────┤
│ 目标:最大化投影后方差 │
│ 输入:仅特征X │
│ 优化:寻找数据分布最分散的方向 │
│ 假设:方差大的方向信息多 │
│ 适用:数据探索、压缩、去噪 │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ LDA(监督) │
├────────────────────────────────────────────────────────────┤
│ 目标:最大化类间可分性 │
│ 输入:特征X + 标签y │
│ 优化:类间距离大,类内距离小 │
│ 假设:数据服从正态分布,各类协方差相同 │
│ 适用:分类任务预处理、特征提取 │
└────────────────────────────────────────────────────────────┘
3.2 LDA几何直观
md
┌─────────────────────────────────────────────────────────┐
│ Step 1: 计算各类别均值向量 │
│ ───────────────────────── │
│ μ_c = 1/n_c * Σx_i (c=1,2,...,C类别数) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 2: 计算类内散布矩阵S_W │
│ ───────────────────────── │
│ S_W = Σ_c Σ_{x∈D_c} (x - μ_c)(x - μ_c)^T │
│ 衡量每个类别内部的离散程度 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 3: 计算类间散布矩阵S_B │
│ ───────────────────────── │
│ S_B = Σ_c n_c(μ_c - μ)(μ_c - μ)^T │
│ 衡量不同类别均值之间的离散程度 │
│ (μ为全局均值) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 4: 求解广义特征值问题 │
│ ───────────────────────── │
│ S_W^(-1) * S_B * w = λ * w │
│ 等价于:找到使 J(w) = (w^T S_B w) / (w^T S_W w) 最大的w │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 5: 选择特征向量构造投影矩阵 │
│ ─────────────────────────────── │
│ 最大特征值对应的特征向量 = 最佳判别方向 │
│ 最多有C-1个非零特征值(C为类别数) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 6: 数据投影 │
│ ───────────── │
│ X' = X · W (投影到C-1维判别空间) │
└─────────────────────────────────────────────────────────┘
3.4 Scikit-Learn实战
python
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
# LDA降维(注意:n_components ≤ min(n_features, n_classes-1))
lda = LDA(n_components=2)
X_train_lda = lda.fit_transform(X_train_std, y_train) # 需要标签!
X_test_lda = lda.transform(X_test_std)
# 训练分类器
lr = LogisticRegression(multi_class='ovr', random_state=1)
lr.fit(X_train_lda, y_train)
# 在3分类葡萄酒数据集上,LDA通常比PCA分类效果更好
print(f'LDA+LR Accuracy: {lr.score(X_test_lda, y_test):.3f}')
3.5 判别能力分析
python
# 计算线性判别式的判别能力比例
tot = sum(eigen_vals.real)
discr = [(i / tot) for i in sorted(eigen_vals.real, reverse=True)]
cum_discr = np.cumsum(discr)
plt.bar(range(1, 14), discr, align='center', label='Individual')
plt.step(range(1, 14), cum_discr, where='mid', label='Cumulative')
plt.ylabel('"Discriminability" ratio')
plt.xlabel('Linear Discriminants')
plt.legend()
plt.show()
关键特性:对于C类问题,LDA最多产生C-1个非零判别式。
四、t-SNE------非线性降维与可视化神器
4.1 为什么需要非线性降维?
python
线性降维的局限:
┌────────────────────────────────────────┐
│ 线性可分数据 非线性可分数据 │
│ │
│ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ │
│ \ +++++++ │
│ \ +++++++ ○ +++++++ ○ │
│ \+++++++ ○ +++++++ ○ │
│ ○ ○ ○ ○ ○ ○ +++++++ ○ │
│ │
│ PCA/LDA: ✓ 完美 PCA/LDA: ✗ 失败 │
│ 一条直线可分离 需要非线性边界 │
│ │
│ 解决方案:t-SNE、UMAP等流形学习方法 │
└────────────────────────────────────────┘
4.2 t-SNE核心原理
t-SNE(t-Distributed Stochastic Neighbor Embedding) 的核心思想:
高维空间:计算样本间的相似度概率分布P
低维空间:构建对应的相似度概率分布Q
优化目标:最小化P与Q之间的KL散度
md
高维空间(原始数据) 低维空间(2D/3D可视化)
┌─────────────────┐ ┌─────────────────┐
│ │ │ ● ● │
│ ● ● │ t-SNE优化 │ ● ● ● │
│ ● │ ───────────→ │ ● │
│ ● ● │ 保持局部结构 │ ● ● │
│ │ │ ● ● │
└─────────────────┘ └─────────────────┘
相似样本距离近 相似样本在2D中仍靠近
不相似样本距离远 不相似样本在2D中远离
4.3 t-SNE关键特性
| 特性 | 说明 |
|---|---|
| 非线性 | 能捕捉复杂的非线性流形结构 |
| 局部性 | 主要保持局部邻域结构,全局结构可能失真 |
| 随机性 | 不同运行可能产生不同结果 |
| 不可投影 | 不能直接应用于新数据(需重新训练) |
| 超参数敏感 | perplexity、learning rate等影响大 |
4.4 实战:手写数字可视化
python
from sklearn.manifold import TSNE
from sklearn.datasets import load_digits
# 加载手写数字数据集(64维:8×8像素)
digits = load_digits()
X_digits, y_digits = digits.data, digits.target # (1797, 64)
# t-SNE降维到2D(关键参数设置)
tsne = TSNE(
n_components=2, # 降维到2维
init='pca', # 使用PCA初始化(推荐!)
random_state=123,
perplexity=30, # 困惑度(通常5-50)
learning_rate=200 # 学习率
)
# 执行降维(注意:t-SNE计算较慢)
X_digits_tsne = tsne.fit_transform(X_digits)
# 可视化结果
import matplotlib.patheffects as PathEffects
def plot_projection(X, colors):
plt.figure(figsize=(8, 8))
for i in range(10): # 10个数字类别
plt.scatter(X[colors == i, 0], X[colors == i, 1],
label=f'Digit {i}')
# 在类别中心添加数字标签
xtext, ytext = np.median(X[colors == i, :], axis=0)
txt = plt.text(xtext, ytext, str(i), fontsize=24)
txt.set_path_effects([
PathEffects.Stroke(linewidth=5, foreground="w"),
PathEffects.Normal()
])
plt.legend()
plt.show()
plot_projection(X_digits_tsne, y_digits)
可视化效果:相同数字形成紧密的簇,不同数字明显分离,即使原始数据是64维!
4.5 t-SNE最佳实践
md
┌─────────────────────────────────────────────────────────┐
│ ✅ DO(推荐做法) │
├─────────────────────────────────────────────────────────┤
│ • 先用PCA降维到50维左右,再应用t-SNE(加速+去噪) │
│ • 使用PCA初始化(init='pca') │
│ • 多次运行取最佳结果(因随机性) │
│ • 调整perplexity(5-50,数据集越大可越大) │
│ • 主要用于可视化,不用于特征工程 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ ❌ DON'T(避免做法) │
├─────────────────────────────────────────────────────────┤
│ • 直接在高维数据(>100维)上运行t-SNE │
│ • 用于训练机器学习模型(无法transform新数据) │
│ • 过度解读全局距离(t-SNE主要保持局部结构) │
│ • 固定random_state后认为结果是确定的 │
└─────────────────────────────────────────────────────────┘
五、算法对比与选择指南
5.1 综合对比表
| 维度 | PCA | LDA | t-SNE |
|---|---|---|---|
| 学习类型 | 无监督 | 监督 | 无监督 |
| 线性/非线性 | 线性 | 线性 | 非线性 |
| 主要目标 | 最大方差 | 最大类间可分性 | 保持局部邻域结构 |
| 输出维度限制 | 无(≤原维度) | ≤C-1(C为类别数) | 通常2或3(可视化) |
| 新数据投影 | ✅ 可以 | ✅ 可以 | ❌ 不可行 |
| 计算复杂度 | O(d³)或O(nd²) | O(d³) | O(n²) ~ O(n log n) |
| 主要用途 | 压缩、去噪、预处理 | 分类预处理、特征提取 | 可视化、探索性分析 |
| 对异常值敏感 | 中等 | 高 | 高 |
5.2 选择决策树
md
开始
│
▼
需要用于训练模型? ──Yes──→ 需要标签信息?
│ No │
▼ Yes ├─→ LDA(监督分类任务)
t-SNE/UMAP No │
(仅可视化) ▼
PCA
(通用降维)
5.3 实际应用建议
| 场景 | 推荐算法 | 理由 |
|---|---|---|
| 高维数据预处理(>100维) | PCA → 50维 | 去噪、加速后续处理 |
| 分类任务特征提取 | LDA | 利用标签信息,提升可分性 |
| 数据探索与展示 | t-SNE/UMAP | 直观展示数据结构 |
| 实时系统需要transform新数据 | PCA/LDA | t-SNE无法直接投影新数据 |
| 图像/文本非线性结构 | UMAP | 比t-SNE更快,可投影新数据 |
六、总结与进阶
6.1 核心要点回顾
- PCA是降维的瑞士军刀,无监督、线性、可投影,适合预处理
- LDA利用标签信息,最大化类间可分性,适合分类任务
- t-SNE专注于可视化,非线性、保持局部结构,但不适合特征工程
6.2 进阶学习方向
- UMAP:t-SNE的升级版,更快且可投影新数据
- Kernel PCA:非线性PCA,使用核技巧
- Autoencoder:基于神经网络的非线性降维
- Factor Analysis:概率化的PCA变体
本文来自《智能系统与技术丛书 Python机器学习 基于PyTorch和Scikit-Learn_(美)塞巴斯蒂安・拉施卡》的学习与理解,仅供学习使用,请勿用于商业用途