降维方法:PCA、t-SNE 与 UMAP 实战拆解

RNA-seq 表达数据有个老毛病——维度远远多于样本。动不动两万多个基因,样本才几十个。你说这怎么玩?直接扔进模型里,不仅慢,还容易被噪声带偏。降维就成了绕不开的一步。

我个人做单细胞项目时踩过不少坑。说实话,选错降维方法,后续聚类画出来的图自己都不敢认。今天咱们就把三种最常用的方法——PCA、t-SNE、UMAP——挨个掰开揉碎,看看它们到底怎么用,又该避开哪些雷区。

为什么先降维?

表达矩阵本质上是个高维稀疏空间。两万个基因里,真正有差异表达的也许只有几百个。直接做聚类或可视化,计算量大不说,还特别容易受到“维数灾难”影响——距离度量失效,样本在空间中越来越“远”。

降维的核心目标有两个:

  • 去除冗余噪声:把那些表达量稳定、不提供区分信息的基因过滤掉
  • 提取有效特征:在低维空间里保留数据的主要结构,便于后续建模或可视化
要点:降维不是扔掉信息,而是把信息重新组合成更有意义的“成分”或“嵌入”。

PCA —— 最老牌,也最稳

主成分分析(PCA)估计大家都认识。它的数学原理其实很直白:找一组正交方向,让投影后的数据方差最大化。

你想想看,第一个主成分就是数据变化最剧烈的方向,第二个与第一个正交,再找剩下方差最大的方向……以此类推。最后只用前几个主成分就能解释大部分方差。

PCA 在表达数据上的应用

  • 样本聚类:PC1 vs PC2 散点图一看,不同处理组基本能分开
  • 批次效应检测:如果不同批次样本在 PC 图上各自抱团,说明有批次效应
  • 去噪:去掉尾部低方差主成分,保留前 10–50 个作为后续 t-SNE/UMAP 的输入
我的一点经验:批次效应用 PCA 一眼就能瞧出来。如果你看到某个主成分(比如 PC3)上样本完全按“建库日期”聚在一起,那赶紧做批次校正吧。

实际代码走走

from sklearn.decomposition import PCA
import numpy as np

# exp_matrix shape: (n_samples, n_genes)
pca = PCA(n_components=50)
pca_result = pca.fit_transform(exp_matrix)

# 方差解释比例
print(pca.explained_variance_ratio_[:5])  
# 累计方差
print(np.cumsum(pca.explained_variance_ratio_)[:5])

通常我会选累计方差达到 70%–80% 的主成分数。不过也别太死板,先看 elbow 图再拍板。

t-SNE —— 局部结构看得清

PCA 是线性方法,碰到非线性关系就有点吃力了。t-SNE 就不一样了——它通过概率分布把高维空间的相似性映射到低维空间,而且特意用 t 分布来处理“拥挤问题”。

核心思想很简单:高维空间中相近的点,在低维空间中也尽量靠近;高维空间中远离的点,在低维空间中也可以远一点。但它的损失函数特别强调局部结构——所以你用 t-SNE 画出来的图,聚类边界特别清晰,但全局距离没什么意义。

参数踩坑指南

我曾经在处理一个 8 万细胞的数据集时,默认 perplexity=30,结果跑了一晚上没出来。后来才发现,perplexity 建议取样本数的 5%–10%,样本太大要降采样或先跑 PCA。
参数默认值经验建议
perplexity30≈ 样本数的 5%,范围 5–50
learning_rate200太小会拥挤,太大则像散点
n_iter1000≥250 才能收敛
random_state无固定住,否则每次结果不一样

应用场景

  • 单细胞 RNA-seq 亚群可视化
  • 探索性分析,看是否有罕见亚群
  • 样本量 ≤ 1 万时效果较好

但它有个毛病——非凸优化,每次跑结果都不一样。我建议固定 random_state,或者多跑几次看哪些聚类是稳定的。

UMAP —— 快且全局兼顾

UMAP 近几年火得不行。它的原理基于流形学习和图论:先在高维空间构建 k-近邻图,再在低维空间优化一个相似的图结构。相比 t-SNE,UMAP 不仅保留了局部结构,还能大致维持全局拓扑——也就是类群之间的相对位置有一定意义。

更关键的是,它跑得快。t-SNE 在 5 万样本上能跑哭你,UMAP 十几秒就完事。

关键参数

参数作用常用值
n_neighbors局部邻域大小15–30(小=更局部,大=更全局)
min_dist低维点之间的最小距离0.1–0.5(小=聚集,大=松散)
metric距离度量euclidean(表达数据常用)
我的习惯:先用 PCA 降到 30–50 维,再用 UMAP 做二维可视化。这样既去掉了噪声,又保留了结构,效果最稳。

代码示例

import umap

# 先用 PCA 降维
pca_50 = PCA(n_components=50).fit_transform(exp_matrix)

# UMAP 嵌入
reducer = umap.UMAP(n_neighbors=20, min_dist=0.3, random_state=42)
umap_embedding = reducer.fit_transform(pca_50)

三大方法对比

特性PCAt-SNEUMAP
类型线性非线性非线性(图基)
速度极快慢(O(n²))快
全局结构保留(方差排序)几乎失去部分保留
局部结构一般非常好很好
可重现性100%需固定种子需固定种子
用于输入模型常用不推荐(结构扭曲)可选但谨慎

一个完整流程建议

拿单细胞数据举例,我一般这么做:

  1. 质量控制、标准化、对数归一化
  2. 筛选高变基因(HVG),约 2000 个
  3. PCA 降维到 50 维
  4. 用 PCA 的前 50 个成分做 UMAP 可视化,同时用 PCA 的前 20 个成分做聚类(比如 KNN + Louvain)
  5. 最后用 t-SNE 验证一下聚类边界
# 官方教学中的典型组合
import scanpy as sc
adata = sc.read("my_data.h5ad")
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
sc.pp.pca(adata, n_comps=50)
sc.pp.neighbors(adata, n_pcs=20)
sc.tl.umap(adata)
sc.tl.tsne(adata, n_pcs=50)
sc.pl.umap(adata, color="cluster")

一张图看懂整体脉络

RNA-seq 降维方法选择流程 高维表达矩阵 PCA(线性,方差最大化) 用于去噪、批次检测、作为后续输入 PCA 主成分用作特征 适合线性模型、聚类 t-SNE(非线性,局部优先) 可视化亚群边界,不适合建模 UMAP(图基,兼顾全局) 大规模数据首选,可做嵌入 实战流程:高维数据 → PCA (50维) → UMAP 可视化 + PCA 特征用于聚类

写在最后的小提醒

  • 别把 t-SNE 或 UMAP 的坐标直接喂进模型。它们的低维嵌入扭曲了距离,模型容易过拟合。
  • 样本量超大(10 万+)时,先跑 PCA 再降采样,否则 t-SNE 内存会爆炸。
  • 用 PCA 检测离群样本也很管用——PC1 上离群太远的点,十有八九是建库失败。
一句话总结:PCA 做粗降维和去噪,t-SNE 做精细可视化看局部,UMAP 当主力嵌入工具——三个组合起来,基本能搞定 90% 的 RNA-seq 降维需求。