Machine Learning: 降维 — PCA与t-SNE高维数据降维与可视化指南

高维空间里,所有点都离得很远——降维不只是压缩,更是发现数据真实结构。

1. 你将学到


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通过特征值分解找到方差最大的方向(主成分),将数据投影到低维空间。

100%
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_)判断。

📖 小节


📝 作业

  1. 基础题(难度⭐):对Iris数据集做PCA降维到2维,绘制散点图,标注方差解释比例。提示:PCA(n_components=2) + scatter。
  2. 进阶题(难度⭐⭐):对load_digits(64维)数据,先用PCA降到30维,再用t-SNE降到2维,对比直接t-SNE和PCA+t-SNE的计算时间与可视化效果。提示:time.time()计时。
  3. 挑战题(难度⭐⭐⭐):生成50维3群用户数据,用PCA/t-SNE/UMAP分别降维到2D,用K-Means聚类评估(ARI/NMI)哪种降维方法更好地保留原始分组结构。提示:sklearn.metrics.adjusted_rand_score

← 上一课:集成学习 | 下一课:NLP基础 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏