09 - 降维算法:PCA 与 t-SNE

2 阅读17分钟

适合人群:零基础小白 | 核心思想:用更少的数字描述同样的事物

如果你觉得"100 个特征太多了,能不能只保留最重要的几个?"——恭喜你,你已经在想降维了。


目录

  1. 为什么需要降维?——维度灾难
  2. 降维的生活类比
  3. PCA 主成分分析
  4. t-SNE 可视化利器
  5. LDA 线性判别分析(简介)
  6. UMAP 简介
  7. 方法对比与选择指南
  8. 实战案例
  9. 总结

1. 为什么需要降维?——维度灾难

1.1 什么是"维度"?

想象你要描述一个苹果:

  • 只说颜色 → 1 维
  • 颜色 + 重量 → 2 维
  • 颜色 + 重量 + 甜度 + 大小 + 产地 + ... → 很多维

在机器学习中,每一个特征就是一个维度。一张 64×64 的灰度图片就有 4096 个维度!

1.2 维度灾难(Curse of Dimensionality)

维度越高,问题越大:

维度增加带来的问题
================================

  维度数    │  需要的数据量(指数增长!)
 ───────────┼──────────────────────────
    1 维    │  ■■ (10 个点就够)
    2 维    │  ■■■■■ (100 个点)
    3 维    │  ■■■■■■■■■■■ (1000 个点)
   10 维    │  ■■■■■■■■■■■■■■■... (100亿个点!)

  数据量跟不上维度增长 → 模型学不好 → 过拟合!

三大问题

  1. 数据稀疏:高维空间中数据点之间距离都差不多,"远近"变得没有意义
  2. 计算爆炸:特征越多,训练时间越长,内存消耗越大
  3. 过拟合:维度太高,模型容易"记住"噪声而非规律

1.3 降维能帮什么忙?

降维前:100 个特征 → 训练慢、容易过拟合、无法可视化
    │
    ▼  (降维算法)
    │
降维后:2~10 个特征 → 训练快、泛化好、可以画图看!

2. 降维的生活类比

2.1 PCA ≈ 影子投影

想象你手里拿着一个 3D 的铁丝模型(比如一只纸鹤),用手电筒从不同角度照它:

        手电筒角度 1          手电筒角度 2
        (信息多)              (信息少)

         ☀                        ☀
          \                       |
           \                      |
        ╱═══╲                 ╱═══╲
       ╱ 纸鹤 ╲              ╱ 纸鹤 ╲
      ╱═══════╲             ╱═══════╲
           \                      |
            \                     |
     ════════════          ════════════
     影子形状丰富            影子只是一条线
     (保留了最多信息)        (丢了太多信息)

PCA 做的事:找到那个"影子最丰富"的投影角度,让降维后保留尽量多的信息。

2.2 t-SNE ≈ 地图压缩

想象你有一张世界地图(高维数据),要把它压缩到一张名片大小(2维):

    真实世界(高维)              名片地图(2维)

    北京 ←近→ 天津              北京 · 天津    (近的还是近)
    北京 ←远→ 纽约              北京 ·              · 纽约(远的还是远)

    t-SNE 的目标:
    "邻居关系"在压缩前后保持不变!
    原来靠近的点,压缩后还是靠近
    原来远离的点,压缩后还是远离

3. PCA 主成分分析

3.1 核心思想:一句话总结

找到数据变化最大的方向,沿着这些方向投影。

3.2 用人话解释方差、特征值、特征向量

方差 = 数据的"分散程度"

方差小(数据挤在一起):     方差大(数据很分散):

    · · ·                   ·           ·
   · · · ·                    ·     ·
    · · ·                  ·     ·
                               ·        ·

 → 包含的信息少            → 包含的信息多

PCA 的原则:保留方差大的方向,丢弃方差小的方向。

特征向量 = "最佳投影方向"

二维数据的 PCA 过程

        y
        │      · ·
        │    · · ·  ← 数据呈椭圆形分布
        │  · · · · ·
        │    · · ·
        │      ·
        └──────────── x

  第 1 步:找到数据最分散的方向

        y
        │    ╱ · ·         ← PC1(第一主成分方向)
        │  ╱· · ·              数据在这个方向最分散
        │╱· · · · ·
        │  · · ·╱
        │    ·╱
        └──────────── x

  第 2 步:找到与 PC1 垂直的方向

        y
        │    ╱ · ·
        │  ╱· ·│·          ← PC2(第二主成分方向)
        │╱· · ·│· ·            与 PC1 垂直
        │  · · │·╱             数据在这个方向不太分散
        │    ·╱│
        └──────────── x

  如果只保留 PC1 → 2D 降到 1D!

人话来说:

  • 特征向量(Eigenvector):就是上图中的箭头方向,告诉你"往哪个方向看"
  • 特征值(Eigenvalue):就是那个方向上数据的分散程度,数值越大越重要
  • 主成分(Principal Component):按特征值从大到小排列的特征向量

类比:考试成绩

假设有 5 门课的成绩:语文、数学、英语、物理、化学

PCA 可能发现:
  PC1 = 0.5×数学 + 0.4×物理 + 0.3×化学   → "理科能力"
  PC2 = 0.6×语文 + 0.5×英语               → "文科能力"

只用 2 个主成分就概括了 5 门课的信息!

3.3 PCA 的数学步骤(简化版)

┌─────────────────────────────────────────────┐
│  PCA 算法步骤                                │
├─────────────────────────────────────────────┤
│                                             │
│  1. 数据标准化(让每个特征均值为0)            │
│     ↓                                       │
│  2. 计算协方差矩阵(衡量特征间的关联)         │
│     ↓                                       │
│  3. 求协方差矩阵的特征值和特征向量             │
│     ↓                                       │
│  4. 按特征值从大到小排序                      │
│     ↓                                       │
│  5. 选前 k 个特征向量组成投影矩阵              │
│     ↓                                       │
│  6. 用投影矩阵将原始数据变换到新空间           │
│                                             │
└─────────────────────────────────────────────┘

3.4 方差解释比(Explained Variance Ratio)

方差解释比告诉你:每个主成分保留了多少信息。

方差解释比(碎石图 / Scree Plot)

  方差解释比(%)
  10090│ ██
   80│ ██
   70│ ██
   60│ ██
   50│ ██
   40│ ██ ██
   30│ ██ ██
   20│ ██ ██ ██
   10│ ██ ██ ██ ██ ██                        ← "肘部"之后的成分
    0│ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██            贡献很小,可以丢弃
     └─────────────────────────────────
      PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 PC10

  累计方差解释比:

  100│                          ●──●──●──●  ← 趋于平坦
   90│                    ●──●
   80│              ●──●
   70│         ●
   60│       ●
   50│     ●
   40│   ●
   30│  ●
   20│ ●
   10│●
    0│
     └─────────────────────────────────
      PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 PC10

  经验法则:选择累计方差达到 85%~95% 时的主成分数量

3.5 如何选择主成分数量?

三种常用方法:

方法做法适合场景
碎石图法看曲线"肘部"拐点快速判断
累计方差阈值选累计方差 ≥ 90% 的数量通用
交叉验证看下游模型表现最严谨

3.6 Python 代码实战:PCA

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris

# ============ 第一步:加载数据 ============
iris = load_iris()
X = iris.data        # 4个特征:花萼长/宽、花瓣长/宽
y = iris.target      # 3种鸢尾花

print(f"原始数据形状: {X.shape}")  # (150, 4) → 150个样本,4个特征

# ============ 第二步:标准化 ============
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# ============ 第三步:PCA 降维 ============
pca = PCA(n_components=2)  # 降到2维
X_pca = pca.fit_transform(X_scaled)

print(f"降维后形状: {X_pca.shape}")  # (150, 2) → 150个样本,2个特征

# ============ 第四步:查看方差解释比 ============
print(f"各主成分方差解释比: {pca.explained_variance_ratio_}")
print(f"累计方差解释比: {sum(pca.explained_variance_ratio_):.2%}")
# 通常能达到 ~95%,说明 2 个主成分就够了!

# ============ 第五步:可视化 ============
plt.figure(figsize=(8, 6))
colors = ['red', 'green', 'blue']
names = iris.target_names

for i, (color, name) in enumerate(zip(colors, names)):
    mask = y == i
    plt.scatter(X_pca[mask, 0], X_pca[mask, 1],
                c=color, label=name, alpha=0.7)

plt.xlabel('第一主成分 (PC1)')
plt.ylabel('第二主成分 (PC2)')
plt.title('PCA 降维:鸢尾花数据集')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

绘制碎石图

# ============ 碎石图代码 ============
pca_full = PCA()  # 不指定 n_components,保留所有主成分
pca_full.fit(X_scaled)

plt.figure(figsize=(8, 4))

# 子图1:各成分方差比
plt.subplot(1, 2, 1)
plt.bar(range(1, 5), pca_full.explained_variance_ratio_, alpha=0.7)
plt.xlabel('主成分编号')
plt.ylabel('方差解释比')
plt.title('碎石图(Scree Plot)')

# 子图2:累计方差比
plt.subplot(1, 2, 2)
cumsum = np.cumsum(pca_full.explained_variance_ratio_)
plt.plot(range(1, 5), cumsum, 'bo-')
plt.axhline(y=0.9, color='r', linestyle='--', label='90% 阈值')
plt.xlabel('主成分数量')
plt.ylabel('累计方差解释比')
plt.title('累计方差解释比')
plt.legend()

plt.tight_layout()
plt.show()

3.7 PCA 的 3D 到 2D 投影可视化

  3D 空间中的数据点            PCA 投影到 2D

        z
        │   · ·
        │  ·····              ·  ·
        │ ·······        ·  · · ·  ·
        │  ·····    →      · · · · ·
        │   · ·              ·  · ·
        │                      ·  ·
   y ───┘
  ╱                        PC1 ──────→
 x                              │
                                ↓ PC2

 原始:3 个坐标描述         降维:2 个坐标描述
 每个点 (x, y, z)          每个点 (pc1, pc2)

 类比:把 3D 物体的影子投在最信息丰富的那面墙上

3.8 PCA 的优缺点

优点缺点
速度快,可处理大数据集只能发现线性关系
数学理论成熟主成分难以解释含义
可用于预处理提速对异常值敏感
降维同时去噪可能丢失非线性结构

4. t-SNE 可视化利器

4.1 PCA 的局限

PCA 只能找"直线方向",如果数据的结构是弯曲的呢?

PCA 看不到的结构(瑞士卷 Swiss Roll)

  真实结构(展开后):        PCA 投影(压扁了):

    A ─── B ─── C            A · B
    │           │              · ·
    │   瑞士卷   │        →    C · D
    │           │              · ·
    D ─── E ─── F            E · F

  A和F在卷上其实很远         A和F被投影到很近的位置
  但PCA把它们压到一起了!     结构被破坏了!

4.2 t-SNE 的核心思想

在高维空间中"亲近"的点,在低维空间中也要"亲近"。

t-SNE 的全称:t-distributed Stochastic Neighbor Embedding(t 分布随机邻居嵌入)

步骤(人话版)

┌──────────────────────────────────────────────┐
│  t-SNE 的工作流程                              │
├──────────────────────────────────────────────┤
│                                              │
│  1. 在高维空间中,计算每对点之间的"亲近程度"     │
│     (用高斯分布转化为概率)                     │
│     → 近的点概率高,远的点概率低                 │
│     ↓                                        │
│  2. 在低维空间中,随机放置所有点                 │
│     ↓                                        │
│  3. 也计算低维中每对点的"亲近程度"               │
│     (用 t 分布转化为概率)                      │
│     ↓                                        │
│  4. 调整低维中点的位置,让两个空间的              │
│     "亲近程度分布"尽可能一致                     │
│     (最小化 KL 散度)                          │
│     ↓                                        │
│  5. 反复迭代,直到收敛                          │
│                                              │
└──────────────────────────────────────────────┘

邻域保持的直觉

t-SNE 邻域保持示意图

高维空间:                    低维空间(t-SNE 输出):

    B                             B
    │                             │
    A ── C    E                   A ── C    E
    │       ╱                     │       ╱
    D    F ─ G                    D    F ─ G

  A 的邻居是 B,C,D            A 的邻居还是 B,C,D  ✓
  F 的邻居是 E,G              F 的邻居还是 E,G    ✓
  A 和 F 很远                 A 和 F 还是远       ✓

  邻居关系被完美保留了!

4.3 为什么用 t 分布?

高斯分布 vs t 分布

概率密度
  │
  │  ╱╲                     高斯分布:尾巴薄
  │ ╱  ╲                    → 远处的点概率太低
  │╱    ╲                   → 低维中点容易挤在一起
  └──────────── 距离

概率密度
  │
  │  ╱╲                     t 分布:尾巴厚("胖尾")
  │ ╱  ╲                    → 远处的点还有一定概率
  │╱    ╲___________        → 不同簇之间有更多空间
  └──────────── 距离         → 可视化效果更好!

4.4 Perplexity(困惑度)参数

Perplexity 是 t-SNE 最重要的超参数,可以理解为**"每个点关注多少个邻居"**。

Perplexity 对结果的影响

  Perplexity = 5              Perplexity = 30            Perplexity = 100
  (关注很少邻居)               (适中,常用默认值)          (关注很多邻居)

   ··  ··  ··                   ···    ···                ··········
   ··  ··  ··                  ·····  ·····               ··········
                               ·····  ·····               ··········
  很多小碎片                    清晰的簇结构               所有点混在一起
  过度关注局部                  局部+全局平衡              过度关注全局

  建议:尝试 5~50 的范围,常用 30
  数据量大时可以适当增大 perplexity

4.5 Python 代码实战:t-SNE

from sklearn.manifold import TSNE
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt

# ============ 加载手写数字数据集 ============
digits = load_digits()
X = digits.data    # (1797, 64) → 8x8的图片展平为64维
y = digits.target  # 0-9 的标签

print(f"数据形状: {X.shape}")  # 64维!无法直接可视化

# ============ t-SNE 降到 2 维 ============
tsne = TSNE(
    n_components=2,     # 降到2维
    perplexity=30,      # 困惑度,默认30
    random_state=42,    # 固定随机种子以便复现
    n_iter=1000,        # 迭代次数
    learning_rate='auto'
)
X_tsne = tsne.fit_transform(X)

# ============ 可视化 ============
plt.figure(figsize=(10, 8))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1],
                      c=y, cmap='tab10', alpha=0.6, s=10)
plt.colorbar(scatter, label='数字标签')
plt.title('t-SNE 可视化:手写数字数据集')
plt.xlabel('t-SNE 维度 1')
plt.ylabel('t-SNE 维度 2')
plt.show()

# 你会看到 0-9 各自聚成一团,效果非常惊艳!

尝试不同的 Perplexity

fig, axes = plt.subplots(1, 3, figsize=(18, 5))

for ax, perp in zip(axes, [5, 30, 100]):
    tsne = TSNE(n_components=2, perplexity=perp, random_state=42)
    X_embedded = tsne.fit_transform(X)
    ax.scatter(X_embedded[:, 0], X_embedded[:, 1],
               c=y, cmap='tab10', s=5, alpha=0.6)
    ax.set_title(f'Perplexity = {perp}')
    ax.set_xticks([])
    ax.set_yticks([])

plt.suptitle('不同 Perplexity 下的 t-SNE 效果对比')
plt.tight_layout()
plt.show()

4.6 t-SNE 使用注意事项

  ⚠ t-SNE 的常见陷阱
  ═══════════════════════════════════════

  1. 簇的大小不代表真实大小
     → t-SNE 会自动调整密度

  2. 簇之间的距离不代表真实距离
     → 只有"谁和谁是邻居"是可信的

  3. 每次运行结果可能不同
     → 设置 random_state 固定随机种子

  4. 不能用于新数据的变换
     → 没有 transform() 方法!
     → 只能 fit_transform()

  5. 速度慢
     → 大数据集先用 PCA 降到 50 维
     → 再用 t-SNE 降到 2 维

5. LDA 线性判别分析(简介)

5.1 LDA vs PCA

  PCA:无监督降维                LDA:有监督降维
  "不管标签,找最分散的方向"       "看着标签,找最容易分开的方向"

       · · ○ ○                      · · ○ ○
      · · · ○ ○                    · · · ○ ○
       · · ○ ○                      · · ○ ○

  PCA 投影方向:                LDA 投影方向:
  ────────→ (最分散)            ─────→ (最容易区分两类)

  两种方向可能不同!
  PCA 关注整体分散程度
  LDA 关注类间分离程度

5.2 LDA 的核心思想

组间距离最大化,组内距离最小化。

LDA 的目标

  类别 A          类别 B
  ·····          ○○○○○
  ·····          ○○○○○

       ←─ d1 ──→
       组间距离大 ✓

  ·····内部紧凑 ✓    ○○○○○内部紧凑 ✓

5.3 Python 代码

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

# LDA 降维(注意:需要标签 y)
lda = LinearDiscriminantAnalysis(n_components=2)
X_lda = lda.fit_transform(X_scaled, y)  # 注意这里传了 y!

# LDA 最多只能降到 (类别数 - 1) 维
# 比如 3 类数据,最多降到 2 维

6. UMAP 简介

UMAP(Uniform Manifold Approximation and Projection)是 t-SNE 的"进化版"。

6.1 UMAP vs t-SNE

特性t-SNEUMAP
速度快(快 10 倍以上)
全局结构保持较差保持较好
可扩展性万级数据百万级数据
新数据变换不支持支持 transform()
理论基础概率分布拓扑学

6.2 Python 代码

# 需要先安装:pip install umap-learn
import umap

reducer = umap.UMAP(
    n_components=2,
    n_neighbors=15,     # 类似 perplexity
    min_dist=0.1,       # 控制点的紧密程度
    random_state=42
)
X_umap = reducer.fit_transform(X)

# UMAP 支持 transform(),可以处理新数据!
# X_new_umap = reducer.transform(X_new)

7. 方法对比与选择指南

7.1 PCA vs t-SNE 效果对比

  同一份数据(手写数字 0-9),两种方法的可视化效果:

  PCA 结果:                         t-SNE 结果:

     2 2                              333
   2 2   5 5                         3 333
  2 2 2  5 5 5     1 1 1              3 3       111
   2 2    5 5     1 1 1 1                      1 1 1
     4 4     8 8    1 1            555   222     111
    4 4 4   8 8 8                 5 555 2 222
     4 4    8 8 8                  555   222   888
              3 3 3                            8 888
  7 7 7 7   3 3                   7777          888
   7 7 7     3 3                  7 777   444
    7 7                            7777  4 444
                                          444
  类别之间有重叠                    类别被清晰分开
  全局结构保留好                    局部结构保留好
  适合数据预处理                    适合数据探索可视化

7.2 选择指南

                    你的目标是什么?
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
       数据预处理      可视化探索     有监督分类
     (给模型用)     (给人看)       (有标签)
            │            │            │
            ▼            ▼            ▼
          PCA       数据量大吗?       LDA
                         │
                    ┌────┴────┐
                    ▼         ▼
                  >万条      <万条
                    │         │
                    ▼         ▼
                  UMAP      t-SNE

7.3 方法总结表

方法类型速度保留结构可用于新数据典型用途
PCA线性/无监督很快全局预处理、去噪
t-SNE非线性/无监督局部可视化
LDA线性/有监督类间分类预处理
UMAP非线性/无监督较快全局+局部可视化+预处理

8. 实战案例

8.1 案例一:人脸识别预处理(PCA 降维 = Eigenfaces)

from sklearn.datasets import fetch_lfw_people
from sklearn.decomposition import PCA
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

# ============ 加载人脸数据 ============
faces = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
X = faces.data
y = faces.target
target_names = faces.target_names

print(f"图片数量: {X.shape[0]}")
print(f"每张图片的像素数(维度): {X.shape[1]}")  # 可能是 1850 维!

# ============ PCA 降维 ============
# 1850 维 → 150 维(减少了 92% 的维度!)
pca = PCA(n_components=150, whiten=True, random_state=42)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42
)

X_train_pca = pca.fit_transform(X_train)
X_test_pca = pca.transform(X_test)

print(f"累计方差解释比: {sum(pca.explained_variance_ratio_):.2%}")
# 通常 150 个成分就能保留 ~90% 的信息

# ============ 用 SVM 分类 ============
clf = SVC(kernel='rbf', C=1000, gamma=0.005)
clf.fit(X_train_pca, y_train)
y_pred = clf.predict(X_test_pca)

print(classification_report(y_test, y_pred, target_names=target_names))
# 准确率通常能达到 80%+ ,而维度减少了 90%!

关键点:PCA 提取的前几个主成分被称为 Eigenfaces(特征脸),每个主成分长得像一张"幽灵脸",代表人脸的某种变化模式(如光照、表情、角度)。

8.2 案例二:基因表达数据可视化(t-SNE)

import numpy as np
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# ============ 模拟基因表达数据 ============
# 真实场景中:几千个基因(特征)× 几百个细胞(样本)
np.random.seed(42)
n_genes = 2000  # 2000 个基因
n_cells = 500   # 500 个细胞

# 模拟 3 种细胞类型
cell_type_A = np.random.randn(200, n_genes) + np.array([1]*500 + [0]*1500)
cell_type_B = np.random.randn(150, n_genes) + np.array([0]*500 + [1]*500 + [0]*1000)
cell_type_C = np.random.randn(150, n_genes) + np.array([0]*1000 + [1]*500 + [0]*500)

X = np.vstack([cell_type_A, cell_type_B, cell_type_C])
y = np.array([0]*200 + [1]*150 + [2]*150)

print(f"原始数据: {X.shape}")  # (500, 2000) → 2000维!

# ============ 先 PCA 降到 50 维(加速) ============
from sklearn.decomposition import PCA

pca = PCA(n_components=50)
X_pca = pca.fit_transform(StandardScaler().fit_transform(X))

# ============ 再 t-SNE 降到 2 维(可视化) ============
tsne = TSNE(n_components=2, perplexity=30, random_state=42)
X_tsne = tsne.fit_transform(X_pca)

# ============ 可视化 ============
plt.figure(figsize=(8, 6))
cell_types = ['A 型细胞', 'B 型细胞', 'C 型细胞']
colors = ['#e74c3c', '#2ecc71', '#3498db']

for i in range(3):
    mask = y == i
    plt.scatter(X_tsne[mask, 0], X_tsne[mask, 1],
                c=colors[i], label=cell_types[i], alpha=0.6, s=20)

plt.xlabel('t-SNE 1')
plt.ylabel('t-SNE 2')
plt.title('t-SNE 可视化:不同细胞类型的基因表达模式')
plt.legend()
plt.show()

# 2000 维的基因数据被映射到了 2D,
# 不同细胞类型形成了清晰的簇!

实际应用:在单细胞 RNA 测序(scRNA-seq)中,t-SNE/UMAP 是标准的可视化方法,帮助生物学家发现新的细胞类型。

8.3 案例三:图像压缩(PCA)

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

# ============ 加载图片(这里用 sklearn 的人脸数据) ============
from sklearn.datasets import fetch_olivetti_faces

faces = fetch_olivetti_faces()
X = faces.data  # (400, 4096) → 每张脸 64×64 = 4096 像素

# ============ 用不同数量的主成分重建图片 ============
n_components_list = [5, 20, 50, 100, 200]

fig, axes = plt.subplots(1, len(n_components_list) + 1, figsize=(16, 3))

# 原始图片
sample = X[0].reshape(64, 64)
axes[0].imshow(sample, cmap='gray')
axes[0].set_title(f'原图\n4096 维')
axes[0].axis('off')

for idx, n_comp in enumerate(n_components_list):
    pca = PCA(n_components=n_comp)
    X_compressed = pca.fit_transform(X)
    X_reconstructed = pca.inverse_transform(X_compressed)

    # 计算压缩率
    ratio = n_comp / 4096 * 100

    axes[idx + 1].imshow(X_reconstructed[0].reshape(64, 64), cmap='gray')
    axes[idx + 1].set_title(f'{n_comp} 个主成分\n(保留 {ratio:.1f}%)')
    axes[idx + 1].axis('off')

plt.suptitle('PCA 图像压缩:不同主成分数量下的重建效果')
plt.tight_layout()
plt.show()

# 结果你会发现:
# 50 个主成分(仅 1.2% 的维度)就能较好还原人脸!
  PCA 图像压缩效果示意

  原图        5个PC      20个PC     50个PC     200个PC
  (4096维)    (模糊)     (轮廓清晰)  (比较清楚)  (几乎一样)

  ┌────┐    ┌────┐    ┌────┐    ┌────┐    ┌────┐
  │☺   │    │▓▓  │    │☺   │    │☺   │    │☺   │
  │    │    │▓▓▓ │    │    │    │    │    │    │
  │    │    │▓▓  │    │    │    │    │    │    │
  └────┘    └────┘    └────┘    └────┘    └────┘

  压缩率:   99.9%      99.5%      98.8%      95.1%
  质量:     很差        一般       较好        优秀

9. 总结

9.1 核心知识点回顾

  降维算法知识地图
  ═══════════════════════════════════════════════

  为什么降维?
  └── 维度灾难:数据稀疏、计算慢、过拟合

  线性方法
  ├── PCA(无监督)
  │   ├── 找方差最大的方向投影
  │   ├── 关键概念:特征值、特征向量、方差解释比
  │   ├── 用碎石图选择主成分数量
  │   └── 适合:预处理、去噪、图像压缩
  │
  └── LDA(有监督)
      ├── 最大化类间距离,最小化类内距离
      └── 适合:分类任务的预处理

  非线性方法
  ├── t-SNE
  │   ├── 保持邻居关系的概率分布
  │   ├── perplexity:控制关注的邻居数量
  │   ├── 结果不可复现、不能变换新数据
  │   └── 适合:高维数据的 2D/3D 可视化
  │
  └── UMAP
      ├── t-SNE 的改进版,更快、更好
      ├── 支持 transform() 变换新数据
      └── 适合:大规模数据可视化 + 预处理

9.2 实用口诀

  ┌───────────────────────────────────────────┐
  │                                           │
  │   降维口诀(小白版)                        │
  │                                           │
  │   数据太多维度高,降维算法来帮忙             │
  │   PCA 投影找方差,线性快速好预处理           │
  │   t-SNE 邻居要保持,可视化效果最惊艳        │
  │   UMAP 速度更加快,大数据集首选它           │
  │   LDA 分类需标签,类间最远类内紧             │
  │                                           │
  │   先问自己要干啥?                          │
  │   预处理选 PCA,看图用 t-SNE/UMAP          │
  │   有标签就试 LDA,没标签就用 PCA            │
  │                                           │
  └───────────────────────────────────────────┘

9.3 常见面试题

Q1:PCA 和 t-SNE 的核心区别?

PCA 是线性方法,最大化方差(全局结构);t-SNE 是非线性方法,保持邻居关系(局部结构)。

Q2:什么时候用 PCA,什么时候用 t-SNE?

需要降维后继续建模 → PCA;需要可视化高维数据 → t-SNE/UMAP。

Q3:PCA 之前需要标准化吗?

需要!如果特征量纲不同(如身高 cm 和体重 kg),不标准化会导致大量纲特征主导结果。

Q4:t-SNE 的 perplexity 怎么选?

一般 5~50,默认 30。数据量越大可以适当增大。建议多试几个值比较效果。

Q5:为什么 t-SNE 不能用于预处理?

因为 t-SNE 没有显式的变换公式,不能对新数据做 transform();而且它优化的是可视化效果而非保留最多信息。


下一篇预告:聚类算法(K-Means、DBSCAN)—— 让机器自动发现数据中的群组


本文适合初学者入门,如有疑问或建议,欢迎交流讨论!