Machine Learning: 降维 — PCA与t-SNE高维数据降维与可视化指南
高维空间里,所有点都离得很远——降维不只是压缩,更是发现数据真实结构。
1. 你将学到
- 维度灾难:高维空间的距离失效与数据稀疏性
- PCA主成分分析:协方差矩阵、特征值分解、方差解释比例
- t-SNE流形可视化:perplexity参数、KL散度、高维到2D/3D映射
- UMAP简介:比t-SNE更快的替代方案,保留全局结构
- 电商用户行为降维:50维行为特征 → 2D可视化,发现用户自然分群
2. 一个数据科学家的真实故事
(1) 痛点:50维用户行为数据无法可视化和理解
Bob收集了50维用户行为特征(浏览时长、点击次数、各类别互动比例等),但无法直观理解用户分布——表格看不了、散点图最多2维、聚类结果难以验证。高维数据是人类直觉的黑箱。
(2) PCA + t-SNE的解法
PCA压缩到2维保留最大方差,t-SNE将局部结构映射到2D平面,让高维数据"看得见"。
PYTHON
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
X_scaled = StandardScaler().fit_transform(X_50d)
# PCA: fast, preserves global variance
X_pca = PCA(n_components=2).fit_transform(X_scaled)
# t-SNE: slow, preserves local structure
X_tsne = TSNE(n_components=2, perplexity=30).fit_transform(X_scaled)
(3) 收益:2D图直接揭示3个自然用户群体
降维后Bob一眼看到3个清晰的用户群体——高价值/活跃/沉睡——与聚类结果高度吻合,让业务团队秒懂数据分布。
3. 维度灾难
(1) 高维空间的反直觉现象
- 距离失效:高维空间中,最近点和最远点的距离趋于相同
- 数据稀疏:体积随维度指数增长,固定样本数越来越稀疏
- 过拟合风险:特征数 >> 样本数时,模型容易"记住噪声"
▶ 示例:维度灾难实验
PYTHON
import numpy as np
rng = np.random.default_rng(42)
for dim in [2, 10, 50, 100, 500]:
n = 1000
X = rng.uniform(0, 1, (n, dim))
# Compute pairwise distances
from sklearn.metrics import pairwise_distances
dists = pairwise_distances(X)
np.fill_diagonal(dists, np.inf)
min_dist = dists.min()
max_dist = dists.max()
ratio = (max_dist - min_dist) / max_dist
print(f"Dim={dim:4d}: min={min_dist:.3f}, max={max_dist:.3f}, "
f"relative_range={ratio:.4f}")
输出:
TEXT
📖 仅展示
# 执行成功
| 维度 | 最近距离 | 最远距离 | 相对范围 | 含义 |
|---|---|---|---|---|
| 2 | 0.02 | 1.41 | 0.99 | 距离有效 |
| 10 | 0.78 | 1.87 | 0.58 | 距离开始失效 |
| 50 | 2.42 | 3.28 | 0.26 | 距离几乎无效 |
| 100 | 3.54 | 4.05 | 0.13 | 所有距离趋同 |
| 500 | 8.11 | 8.47 | 0.04 | 完全失效 |
4. PCA主成分分析
(1) PCA原理
PCA通过特征值分解找到方差最大的方向(主成分),将数据投影到低维空间。
graph TB
INPUT[Standardized Data] --> COV[Compute Covariance Matrix]
COV --> EIG[Eigenvalue Decomposition]
EIG --> SELECT[Select Top K Eigenvectors]
SELECT --> PROJECT[Project Data to K Dimensions]
PROJECT --> OUTPUT[Low-Dimensional Representation]
▶ 示例:PCA降维与方差解释比例
PYTHON
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np
X, y = load_iris(return_X_y=True)
# Standardize first (PCA is sensitive to scale)
X_scaled = StandardScaler().fit_transform(X)
# PCA with all components to see explained variance
pca_full = PCA()
pca_full.fit(X_scaled)
print("Explained variance ratio:")
for i, (evr, cum) in enumerate(zip(pca_full.explained_variance_ratio_,
pca_full.explained_variance_ratio_.cumsum())):
print(f" PC{i+1}: {evr:.3f} (cumulative: {cum:.3f})")
# Reduce to 2D
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(f"\n2D PCA preserves {pca.explained_variance_ratio_.sum():.1%} variance")
# Visualize
fig, ax = plt.subplots(figsize=(8, 6))
for target in np.unique(y):
mask = y == target
ax.scatter(X_pca[mask, 0], X_pca[mask, 1], label=load_iris().target_names[target], s=40)
ax.set_xlabel(f"PC1 ({pca.explained_variance_ratio_[0]:.1%})")
ax.set_ylabel(f"PC2 ({pca.explained_variance_ratio_[1]:.1%})")
ax.set_title("PCA of Iris Dataset")
ax.legend()
plt.tight_layout()
plt.savefig("pca_iris.png", dpi=150)
输出:
TEXT
📖 仅展示
Explained variance ratio:
(2) 选择保留多少主成分
▶ 示例:方差解释比例累计图
PYTHON
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(42)
n, p = 500, 50
X = rng.standard_normal((n, p))
# Make first 5 dimensions have more signal
X[:, :5] *= 3
X_scaled = StandardScaler().fit_transform(X)
pca = PCA().fit(X_scaled)
fig, ax = plt.subplots(figsize=(10, 5))
cumvar = pca.explained_variance_ratio_.cumsum()
ax.plot(range(1, len(cumvar)+1), cumvar, "b-o", markersize=3)
ax.axhline(0.95, color="red", linestyle="--", label="95% variance")
ax.axhline(0.90, color="orange", linestyle="--", label="90% variance")
ax.set_xlabel("Number of Components")
ax.set_ylabel("Cumulative Explained Variance")
ax.set_title("PCA Explained Variance")
ax.legend()
n_95 = (cumvar < 0.95).sum() + 1
print(f"Components for 95% variance: {n_95}")
plt.tight_layout()
plt.savefig("pca_variance.png", dpi=150)
输出:
TEXT
📖 仅展示
# 执行成功
| 阈值 | 含义 | 典型结果 |
|---|---|---|
| 90% | 保留主要信号 | 5-15个主成分(50维) |
| 95% | 保留更多细节 | 10-25个主成分(50维) |
| 99% | 几乎完整保留 | 20-40个主成分(50维) |
5. t-SNE流形可视化
(1) t-SNE原理
t-SNE通过最小化高维和低维空间中点对相似度的KL散度,保持局部邻居关系。
▶ 示例:t-SNE可视化
PYTHON
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
import numpy as np
X, y = load_digits(return_X_y=True)
X_scaled = StandardScaler().fit_transform(X)
# t-SNE with different perplexity
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
for ax, perplexity in zip(axes, [5, 30, 50]):
tsne = TSNE(n_components=2, perplexity=perplexity, random_state=42, init="pca")
X_tsne = tsne.fit_transform(X_scaled)
scatter = ax.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap="tab10", s=5, alpha=0.7)
ax.set_title(f"t-SNE (perplexity={perplexity})")
ax.set_xlabel("t-SNE 1")
ax.set_ylabel("t-SNE 2")
plt.tight_layout()
plt.savefig("tsne_perplexity.png", dpi=150)
输出:
TEXT
📖 仅展示
# 执行成功
(2) t-SNE注意事项
| 注意点 | 说明 |
|---|---|
| perplexity | 典型5-50,小→关注近邻,大→关注全局 |
| 随机种子 | 不同种子结果可能不同,固定random_state |
| 不可用于下游 | t-SNE不能保留距离,不适合作为特征输入模型 |
| 计算慢 | 大数据集(>10k)先用PCA降维再用t-SNE |
| 簇大小无意义 | t-SNE可能放大或缩小簇的相对大小 |
▶ 示例:PCA预处理加速t-SNE
PYTHON
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import numpy as np
rng = np.random.default_rng(42)
X = rng.standard_normal((5000, 50))
# Direct t-SNE (slow)
# tsne = TSNE(n_components=2, random_state=42)
# X_tsne = tsne.fit_transform(X) # Very slow on 5000x50!
# Best practice: PCA first, then t-SNE
X_scaled = StandardScaler().fit_transform(X)
X_pca_30 = PCA(n_components=30).fit_transform(X_scaled) # Fast PCA to 30D
X_tsne = TSNE(n_components=2, perplexity=30, random_state=42,
init="pca", learning_rate="auto").fit_transform(X_pca_30)
print(f"PCA→t-SNE shape: {X_tsne.shape}")
输出:
TEXT
📖 仅展示
# 执行成功
6. 电商用户行为降维实战
▶ 示例:50维用户特征 → 2D可视化
PYTHON
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(42)
n = 3000
# Generate 50-dim user behavior data with 3 natural groups
group1 = rng.normal(loc=2, scale=1, size=(800, 50)) # High-value users
group2 = rng.normal(loc=0, scale=1.5, size=(1200, 50)) # Regular users
group3 = rng.normal(loc=-2, scale=0.8, size=(1000, 50)) # Dormant users
X = np.vstack([group1, group2, group3])
# Add noise dimensions
X[:, 20:] += rng.normal(0, 3, (n, 30))
# Standardize
X_scaled = StandardScaler().fit_transform(X)
# PCA to 2D
X_pca = PCA(n_components=2).fit_transform(X_scaled)
# t-SNE to 2D (with PCA preprocessing)
X_pca30 = PCA(n_components=30).fit_transform(X_scaled)
X_tsne = TSNE(n_components=2, perplexity=30, random_state=42,
init="pca", learning_rate="auto").fit_transform(X_pca30)
# K-Means labels for reference
labels = KMeans(n_clusters=3, random_state=42, n_init=10).fit_predict(X_scaled)
# Visualize
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
for ax, X_2d, title in [(axes[0], X_pca, "PCA"), (axes[1], X_tsne, "t-SNE")]:
scatter = ax.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap="Set1", s=8, alpha=0.6)
ax.set_title(title)
ax.set_xlabel(f"{title} 1")
ax.set_ylabel(f"{title} 2")
plt.tight_layout()
plt.savefig("user_behavior_dim_reduction.png", dpi=150)
输出:
TEXT
📖 仅展示
# 执行成功
(1) PCA vs t-SNE vs UMAP
| 维度 | PCA | t-SNE | UMAP |
|---|---|---|---|
| 保留信息 | 全局方差 | 局部邻居 | 全局+局部 |
| 计算速度 | 快(<1s) | 慢(分钟级) | 中等(秒级) |
| 可复现性 | 确定性 | 受种子影响 | 受种子影响 |
| 可解释性 | 高(方差解释) | 低 | 中 |
| 适合下游 | ✅ 可做特征 | ❌ 只能可视化 | ⚠️ 需验证 |
| 新数据映射 | ✅ transform | ❌ 需重跑 | ✅ transform |
❓ 常见问题
Q PCA降维后能用于模型训练吗?
A 能。PCA降维后的主成分可作为模型输入,尤其适合高维小样本场景。注意:PCA在训练集fit,测试集只transform。
Q t-SNE为什么不能用于模型特征?
A t-SNE不保留全局距离关系,且不能transform新数据(每次需重新计算)。它只适合可视化,不适合作为模型输入。
Q perplexity参数怎么选?
A 典型范围5-50。数据量小时(1k)用5-20,大数据(10k+)用30-50。建议试几个值,看哪个分群最清晰。
Q PCA需要标准化吗?
A 必须。PCA最大化方差,如果特征量纲不同(如年龄vs收入),大方差特征会主导主成分。先StandardScaler再PCA。
Q UMAP比t-SNE好在哪?
A 三个优势——1) 速度快10-100倍;2) 保留更多全局结构;3) 支持transform新数据。但需要额外安装
pip install umap-learn。Q 降维会丢失多少信息?
A 取决于保留的主成分数。保留95%方差通常只损失5%信息,但可能去掉的是噪声。用cumsum(explained_variance_ratio_)判断。
📖 小节
- 维度灾难:高维空间距离失效、数据稀疏、过拟合风险,降维是刚需
- PCA:保留最大方差方向,适合快速降维+模型输入,需先标准化
- t-SNE:保留局部邻居关系,适合可视化,不可用于模型特征,计算慢
- UMAP:t-SNE替代方案,更快、保留全局结构、支持新数据映射
- 降维流程:StandardScaler → PCA(加速+去噪) → t-SNE/UMAP(可视化)
- PCA选主成分数:看累计方差解释比例,90-95%是常用阈值
📝 作业
- 基础题(难度⭐):对Iris数据集做PCA降维到2维,绘制散点图,标注方差解释比例。提示:PCA(n_components=2) + scatter。
- 进阶题(难度⭐⭐):对load_digits(64维)数据,先用PCA降到30维,再用t-SNE降到2维,对比直接t-SNE和PCA+t-SNE的计算时间与可视化效果。提示:
time.time()计时。 - 挑战题(难度⭐⭐⭐):生成50维3群用户数据,用PCA/t-SNE/UMAP分别降维到2D,用K-Means聚类评估(ARI/NMI)哪种降维方法更好地保留原始分组结构。提示:
sklearn.metrics.adjusted_rand_score。