UMAP 数学基础:流形学习、图拉普拉斯与拓扑结构保持
很多人在做单细胞或宏基因组的时候,一听说 UMAP 比 t-SNE 快、还能保留更多全局结构,就直接调包用了。我以前也是这么干的,直到有次在一个包含 50 万细胞的数据集上,我发现 UMAP 跑出来的结果跟生物学注释对不上——嗯,不是工具不行,是我没搞懂它背后在干什么。
说实话,UMAP 的数学推导不算简单,但核心思想其实就三条:假设数据在流形上、构建图拉普拉斯、保持拓扑结构。这一章我们就掰开聊聊这三块。
1. 数据真的在高维空间里均匀分布吗?—— 流形假设
先想一个问题:你手里有 100 个基因的表达量,每个细胞就是一个 100 维的点。这些点真的把整个 100 维空间填满了?
不可能。那么多维度,实际情况是细胞只沿着少数几个生物学过程变化(比如分化轨迹、应激响应)。这些点其实躺在一个低维流形上——流形说白了就是高维空间里弯曲的“低维面片”。
我记得第一次接触这个概念是在做单细胞轨迹推断时。当时 PCA 降维后能看到一条“尾巴”,那就是一个一维流形嵌入在二维投影里。流形假设的核心就是:高维数据点都集中在某个低维曲面上,采样密度可以反映该区域的概率分布。
一图总结 UMAP 的数学链路
2. 怎么把流形变成图?—— k 近邻图与模糊单纯集
有了流形假设,下一步就是把连续流形离散化。UMAP 的做法是先对每个点找 k 个最近邻(默认 k=15),然后在这些邻居之间连边。得到的就是一张 kNN 图。
但问题来了:欧氏距离在高维空间会膨胀(俗称“维度灾难”),直接使用原始距离会连到一堆不相关的点。UMAP 在这里做了一个关键操作——它使用局部距离归一化,让每个点自己定义“多远算邻居”。
| 组件 | 作用 | 我踩过的坑 |
|---|---|---|
| k 近邻图 | 建立局部连通关系 | k 太小图不连通,k 太大噪声混入 |
| 模糊单纯集 | 将二元邻接变成概率化的模糊团 | min_dist 设太小会导致嵌入簇堆叠 |
| 交叉熵损失 | 优化低维嵌入与高维拓扑的差异 | 调 spread 直接改变点云紧凑度 |
模糊单纯集是 UMAP 的灵魂。我个人的理解是:它把“两个点是不是邻居”这个布尔值换成一个0到1之间的概率。距离越近概率越接近1,越远则平滑衰减。这样图就变成了一个加权图,而且每个点的局部感受野可以自适应。
3. 图拉普拉斯到底在做什么?—— 谱嵌入的前世今生
有了带权的kNN图,接下来就是 UMAP 的第二步:图拉普拉斯。你可能会想,拉普拉斯算子不是处理连续函数的吗?对,但图上的拉普拉斯就是它的离散版本。
定义一个图 G = (V, E, W),其中 W 是模糊单纯集的权值。那么非归一化拉普拉斯:
L = D - W
这里 D 是度矩阵,对角线上是 D_ii = ∑_j W_ij。
但 UMAP 用的是对称归一化拉普拉斯:
L_sym = D^(-1/2) * L * D^(-1/2) = I - D^(-1/2) * W * D^(-1/2)
为什么要用归一化的?因为不同区域的采样密度不一样,归一化可以消除密度对特征向量幅值的影响。我以前用非归一化拉普拉斯做谱聚类,结果在稀疏采样区域,前几个特征向量完全被局部度数高的节点带偏了——换用归一化后结果正常多了。
然后我们求解 L_sym 的前 d 个最小特征值对应的特征向量(去掉零特征值对应的平凡解),这就得到了一个 n×d 的嵌入矩阵。这个过程叫做谱嵌入。本质上是在找一个低维线性空间,让原来在图中相邻的点在空间中也靠近。
一个小提示
UMAP 并没有直接使用谱嵌入结果作为输出,而是拿这个嵌入作为初始坐标去优化交叉熵。我试过跳过初始嵌入(用随机初始化),结果收敛非常慢而且容易陷入局部最小值。所以这步别省。
4. 拓扑结构保持 —— 一个交叉熵的故事
最后一步也是最漂亮的一步:把高维空间的模糊单纯集和低维空间的模糊单纯集对比,让它们尽量一致。
在高维空间,我们定义了一个概率分布 μ(基于模糊单纯集)。在低维嵌入空间,我们用类似的方法计算一个概率分布 ν(但是权重函数不同,UMAP 用了不同的曲线)。然后最小化这两个分布的交叉熵:
CE(μ, ν) = -∑_ij [ μ_ij * log(ν_ij) + (1 - μ_ij) * log(1 - ν_ij) ]
仔细看这个公式,它分两部分:
- 第一部分:高维中存在的边(μ_ij 接近1),要让低维也尽量保持(ν_ij 接近1)。
- 第二部分:高维中不存在的边(μ_ij 接近0),要让低维也尽量没有(ν_ij 接近0)。
有意思的是,第二部分是 t-SNE 不具备的。t-SNE 只关心第一个部分(吸引项),所以它会把相似的点使劲拉近,但对不相似的点没有排斥项(或者说排斥项很弱)。UMAP 的两个项平衡了远近距离,所以全局结构更完整。
注意避坑
交叉熵的优化用随机梯度下降,批量一般设成 batch_size=1(即逐点更新)。我曾经偷懒用全批量梯度下降,结果内存爆了还跑不动——高维空间里 n×n 的邻接矩阵存不下。UMAP 通过负采样加速,默认 n_epochs=500,如果你数据超过10万点,可以考虑降到200,效果差别不大但快很多。
5. 三个核心参数在说什么?
理解数学后,UMAP 那几个重要参数就不再是玄学了:
| 参数 | 数学含义 | 实际效果 | 我个人常用的建议 |
|---|---|---|---|
n_neighbors | kNN 图中的 k,控制局部邻域大小 | 小值强调局部细节,大值保留全局轮廓 | 15(通用)、5(精细结构)、50(大趋势) |
min_dist | 低维空间中最近点的最小距离 | 小值簇更紧凑,大值点分布更散开 | 0.1 ~ 0.5,可视化推荐 0.05 ~ 0.3 |
spread | 低维权值函数的尺度参数 | 与 min_dist 配合控制点云整体半径 | 默认为1,可以不改;min_dist 缩小时可调大 spread |
总结一下 UMAP 的完整数学链条:
- 对每个点计算 k 近邻,构建模糊单纯集(加权图)。
- 构造对称归一化图拉普拉斯,计算前 d 个特征向量作为初始嵌入。
- 在低维空间定义另一个模糊单纯集,并用交叉熵衡量它与高维拓扑的差距。
- 用 SGD 优化初始嵌入,使交叉熵最小化。
这一套下来,你得到的低维坐标既保留了局部邻域结构(因为 kNN 图),又维持了全局拓扑骨架(通过图拉普拉斯的谱分解)。下次你跑 UMAP 的时候,脑子里可以过一遍这个流程,参数调起来就有底气了。