跳至主要内容

理解 UMAP:降维的全面指南

通过详细讲解、实用用例,以及与 t-SNE 和 PCA 等其他降维方法的对比,了解 UMAP 如何简化高维数据的可视化。
· 12分钟 阅读

使用 AI 探索

在机器学习问题中,我们经常会遇到拥有数百甚至数千个特征的数据集,这种规模常常给有意义的数据可视化与分析带来挑战。传统绘图方法一次只能展示二维或三维,使我们几乎无法理解高维数据的结构。这正是 UMAP(Uniform Manifold Approximation and Projection,统一流形近似与投影)等降维技术大显身手的地方。

随着时间推移,UMAP 已成为多个领域中可视化复杂数据集的首选方法。本教程将带您从理论基础到实践落地与常见难题,全面了解 UMAP,帮助您从现在开始在项目中运用这一技术。

什么是 UMAP?

UMAP 是一种非线性的降维算法,可在尽可能保留数据本质结构的同时,将高维数据投影到低维空间(通常为 2D 或 3D)。UMAP 的全称是 Uniform Manifold Approximation and Projection,其理论基础源于流形学习与拓扑数据分析。

其他降维方法(如 t-SNE)擅长保留局部邻域,但往往会扭曲全局关系。UMAP 的关键优势在于它在局部邻域与全局关系之间取得平衡。它假设数据位于黎曼流形上,并认为该流形可在局部用模糊的拓扑结构建模。这一数学基础使 UMAP 能够生成更有意义的可视化:相似的数据点会聚集在一起,同时维持数据集整体的拓扑结构。

该算法通过在高维空间中构建数据的图表示,然后优化一个低维图,使其在结构上尽可能相似。除了用于可视化,UMAP 还可作为聚类算法的预处理步骤,或作为机器学习流水线中的通用非线性降维技术。

UMAP 的工作原理

我们将把流程拆解为几个关键步骤来理解 UMAP 的工作方式。完整的数学理论涉及拓扑与拓扑数据分析中的高级概念,因此本教程将以直观方式讲解核心算法。

步骤 1:构建高维图

UMAP 首先构建一个加权图来表示高维空间中的关系。对于每个数据点,它会找到其 k 个最近邻(k 是可调参数)。不同于简单的 k-NN 图,UMAP 采用概率式方法:它根据距离计算两点相连的概率,并使用依赖于每个点局部密度的平滑近似。

这里的关键洞见在于 UMAP 会自适应我们的数据局部结构。在密集区域,点需要非常接近才被视为邻居;而在稀疏区域,算法会扩大搜索半径。 

这种自适应行为由如下公式控制:

p(i,j) = exp(-(d(xi, xj) - ρi) / σi)

其中 d(xi, xj) 为两点间距离,ρi 是到最近邻的距离,σi 为对每个点计算的缩放因子。

步骤 2:构建模糊拓扑表示

随后,UMAP 将这些局部概率分布转换为来自拓扑数据分析的模糊拓扑结构。它通过合并两个方向的概率对图进行对称化:

p(i,j) = p(i|j) + p(j|i) - p(i|j) * p(j|i)

这会创建所谓的模糊单纯形集合(fuzzy simplicial set),用于刻画高维数据的流形结构。

步骤 3:优化低维表示

最后一步是寻找一个能保留该模糊拓扑结构的低维表示。UMAP 先在低维空间中初始化点(使用谱嵌入),然后通过梯度下降最小化高维与低维模糊拓扑表示之间的交叉熵。

优化中使用了吸引力与排斥力:应当靠近的点会被拉近(基于高维结构),应当远离的点会被推开。这种拉推动态形成了 UMAP 的典型可视化特征:簇之间分隔良好,同时保留内部结构。

UMAP 的实践应用

UMAP 在众多需要理解高维数据的领域中均有应用。 

一些真实世界的应用包括:

  • 单细胞基因组学与生物信息学:在基因组学研究中,UMAP 已成为可视化单细胞 RNA 测序数据的标准工具。研究人员利用它识别细胞类型、追踪发育轨迹并理解疾病进展。通过将上千个基因表达测量降到 2D 图上,UMAP 能揭示在原始数据中难以观察的不同细胞群体及其关系。
  • 自然语言处理:当文本数据被转换为高维嵌入后,可以用 UMAP 进行可视化以理解语义关系。它常用于可视化词向量、文档簇,并通过展示不同概念在嵌入空间中的关系来调试语言模型。
  • 计算机视觉与图像分析:UMAP 有助于可视化神经网络学习到的特征表示,帮助理解模型捕捉到了哪些模式。它也用于人脸识别系统中,直观展示人脸如何基于特征聚类;在医学影像中用于发现相似病例或异常。
  • 数据探索与离群点检测:UMAP 通过将复杂数据集投射到可解读的可视化空间,帮助揭示隐藏模式并检测离群点。当传统统计方法难以处理高维交互时,这种可视化方式的离群点检测尤为有价值。
  • 客户细分与市场分析:营销团队使用 UMAP 基于多种特征(购买历史、人口统计、参与度指标等)可视化客户行为模式,以识别不同客户细分并理解群体之间的关系。
  • 药物发现:制药研究人员利用 UMAP 可视化化合物空间,通过观察化合物是否聚集在已知有效药物附近且远离有毒化合物,从而识别潜在的候选药物。

在理解了 UMAP 的概念、数学基础与实践应用后,我们继续进入实现部分。

在 Python 中实现 UMAP

我们将在真实数据集上实现 UMAP,看看它在实践中的效果。这里使用 Olivetti Faces 数据集,该数据集包含来自 40 位不同人的人脸灰度图像。可以从 Kaggle 下载 Olivetti 数据集,或通过 scikit-learn Python 库获取。

Olivetti Faces 数据集包含:

  • 400 张灰度图像(64x64 像素 = 4096 维)
  • 40 位不同个体
  • 每人 10 张图像(表情、光照条件不同)

这非常适合 UMAP,因为我们预期同一个人的图像会聚在一起,尽管表情和光照存在变化。

步骤 1:导入库并加载数据

首先导入必要的库并加载数据集:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from sklearn.datasets import fetch_olivetti_faces
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import umap
import warnings
warnings.filterwarnings('ignore')

# Load the Olivetti faces dataset
faces = fetch_olivetti_faces(shuffle=True, random_state=42)
X = faces.data
y = faces.target

print(f"Dataset shape: {X.shape}")
print(f"Number of individuals: {len(np.unique(y))}")
print(f"Number of images per individual: {X.shape[0] // len(np.unique(y))}")

# Display sample faces
fig, axes = plt.subplots(2, 5, figsize=(12, 6))
for i, ax in enumerate(axes.flat):
   ax.imshow(X[i].reshape(64, 64), cmap='gray')
   ax.set_title(f'Person {y[i]}')
   ax.axis('off')
plt.suptitle('Sample Faces from the Dataset')
plt.tight_layout()
plt.show()

通过上述代码,我们可以如下方式可视化数据集:

Sample faces from the Olivetti dataset.

步骤 2:使用默认参数应用 UMAP

Olivetti faces 数据集的像素值已预归一化到 0 到 1 之间,因此我们可以直接应用 UMAP:

# Create UMAP instance with default parameters
reducer = umap.UMAP(random_state=42)

# Fit and transform the data
embedding = reducer.fit_transform(X)

# Create a custom colormap for 40 distinct classes
colors = cm.get_cmap('hsv', 40)  

# Create visualization
plt.figure(figsize=(12, 10))
scatter = plt.scatter(embedding[:, 0], embedding[:, 1], c=y,
                    cmap=colors, s=50, alpha=0.8, edgecolors='black', linewidth=0.5)
plt.colorbar(scatter, label='Person ID', ticks=np.arange(0, 40, 5))
plt.title('UMAP Projection of Olivetti Faces (Default Parameters)')
plt.xlabel('UMAP 1')
plt.ylabel('UMAP 2')
plt.grid(True, alpha=0.3)
plt.show()

我们会看到一张彩色散点图作为 UMAP 可视化:

UMAP Projection of Olivetti Faces with default parameters

为更好地理解该图,请注意以下几点:

  • 每个点代表一张人脸图像
  • 颜色代表不同的人(Person ID 0–39)
  • 接近意味着相似:彼此靠近的点是在 4096 维空间中被 UMAP 认为相似的人脸

尽管理想情况下我们会希望同一个人的 10 张图像(同一颜色)都聚在一起,但现实中很少如此。在此图中,使用默认参数构建 UMAP 后,可以看到一些相同颜色的点已聚成簇。

步骤 3:探索 UMAP 的关键参数

UMAP 有若干重要参数控制其行为。我们来看看它们如何影响结果:

# Create subplots for different parameter settings
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
axes = axes.ravel()

# Different parameter configurations
param_configs = [
   {'n_neighbors': 5, 'min_dist': 0.1},
   {'n_neighbors': 15, 'min_dist': 0.1},
   {'n_neighbors': 50, 'min_dist': 0.1},
   {'n_neighbors': 15, 'min_dist': 0.0},
   {'n_neighbors': 15, 'min_dist': 0.5},
   {'n_neighbors': 15, 'min_dist': 0.99}
]

# Apply UMAP with different parameters
for idx, params in enumerate(param_configs):
   reducer = umap.UMAP(random_state=42, **params)
   embedding = reducer.fit_transform(X_scaled)
  
   ax = axes[idx]
   scatter = ax.scatter(embedding[:, 0], embedding[:, 1], c=y,
                       cmap='tab20', s=30, alpha=0.8)
   ax.set_title(f"n_neighbors={params['n_neighbors']}, "
                f"min_dist={params['min_dist']}")
   ax.set_xlabel('UMAP 1')
   ax.set_ylabel('UMAP 2')
   ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

输出可视化如下:

Exploring UMAP parameters.

上面的网格展示了参数如何影响可视化。观察顶行 n_neighbors 的效果:

  • n_neighbors=5:非常局部的视角,会产生许多小而紧的簇。
  • n_neighbors=15:平衡视角,在局部与全局结构之间取得较好折中。
  • n_neighbors=50:更偏全局的视角,关注整体结构,可能会将相似的个体合并。

观察底行 min_dist 的效果:

  • min_dist=0.0:点极为接近,如图所示,形成非常紧密、密集的簇。
  • min_dist=0.5:中等间距,使得簇更为分散。
  • min_dist=0.99:最大间距,使点更均匀分布,但会丢失簇结构。

通常建议针对手头问题尝试这些参数,以获得最佳聚类效果。

UMAP 的替代方案:其他降维方法

我们继续使用 Olivetti 数据集,对比 UMAP 与 PCA、t-SNE,并理解它们的差异:

from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

# Create figure with subplots
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

colors = cm.get_cmap('hsv', 40)

# PCA
pca = PCA(n_components=2, random_state=42)
pca_result = pca.fit_transform(X)
axes[0].scatter(pca_result[:, 0], pca_result[:, 1], c=y,
               cmap=colors, s=50, alpha=0.8)
axes[0].set_title('PCA Projection')
axes[0].set_xlabel('PC 1')
axes[0].set_ylabel('PC 2')
axes[0].grid(True, alpha=0.3)

# t-SNE
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
tsne_result = tsne.fit_transform(X)
axes[1].scatter(tsne_result[:, 0], tsne_result[:, 1], c=y,
               cmap=colors, s=50, alpha=0.8)
axes[1].set_title('t-SNE Projection')
axes[1].set_xlabel('t-SNE 1')
axes[1].set_ylabel('t-SNE 2')
axes[1].grid(True, alpha=0.3)

# UMAP
umap_reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, random_state=42)
umap_result = umap_reducer.fit_transform(X)
scatter = axes[2].scatter(umap_result[:, 0], umap_result[:, 1], c=y,
                        cmap=colors, s=50, alpha=0.8)
axes[2].set_title('UMAP Projection')
axes[2].set_xlabel('UMAP 1')
axes[2].set_ylabel('UMAP 2')
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

比较图如下所示:

PCA vs t-SNE vs UMAP

PCA 投影(左图)呈现出围绕原点的高密度点云,不同颜色的点大量重叠。这正是我们对 PCA 的预期——它捕捉数据中方差最大的方向,但难以有效区分不同的人。可以看到来自不同个体(不同颜色)的人脸完全混在一起,几乎无法识别出清晰的群组。

t-SNE 投影(中图)有所改观,簇分离良好。每种颜色(代表不同的人)形成紧凑的独立群组,群组之间边界清晰。注意 t-SNE 几乎能形成完美的局部分组:同一人的人脸被强力拉到一起,同时与其他群组推开。这正是 t-SNE 的强项:擅长保留局部邻域并形成视觉上清晰的簇。 

UMAP 投影(右图)采取了不同的策略。虽然与 t-SNE 相比,簇更为分散,但这反映了 UMAP 同时保留局部与全局结构的尝试。可以看到每个人仍形成可辨识的群组,但内部变化更大。一些簇略有重叠或彼此更近,可能表明这些个体之间确有相似性。

这一比较说明了一个重要观点:何为“更好”取决于目标。如果需要用于展示或初步探索的最清晰的视觉分离,对于本数据集 t-SNE 的紧密簇表现出色。然而,如果希望理解数据的整体结构以及不同群组之间的关系,那么 UMAP 对全局关系的保留使其更适合本数据集。

UMAP 的常见挑战与解决方法

与所有算法和技术一样,在将 UMAP 应用于真实数据时也会面临若干挑战。 

基于大量实践,以下是我反复遇到的一些问题:

1. 选择合适的参数

最常见的挑战是为 n_neighbors 和 min_dist 选择合适的取值。这些参数会显著影响可视化效果,且没有放之四海而皆准的方案。 

参数 n_neighbors 用于平衡局部与全局结构的保留。较小取值(5–15)侧重保留非常局部的邻域,能捕捉精细结构,但可能打碎更大的模式。较大取值(30–100)强调更广泛的连通模式,能揭示全局结构,但可能掩盖局部细节。 

参数 min_dist 控制点在低维布局中的紧凑程度,但不影响高维图的构建。接近 0 的取值允许点紧密堆叠,形成视觉上紧凑的簇;接近 1 的取值会让点在投影空间更均匀分布。

最佳实践是从默认值(n_neighbors=15、min_dist=0.1)开始,并根据数据特性进行调整:

  • 对于簇清晰、分离良好的数据集,可使用较小的 n_neighbors(5–15)以保留局部结构
  • 对于更连续或层级化结构的数据集,可使用较大的 n_neighbors(30–100)以捕捉全局关系
  • 将 min_dist 降低(趋近 0)以获得更紧凑的可视化簇
  • 将 min_dist 提高(趋近 0.5–1.0)以获得更均匀分布、视觉上更易区分的点

2. 大型数据集上的计算性能

对于超大规模数据集(数百万点),UMAP 可能较慢,影响交互式探索。我们可以尝试以下方法提升性能:

  • 减少训练轮数以更快收敛: umap.UMAP(n_epochs=200)(默认 None,会基于数据规模自动选择)
  • 对于超大数据集使用随机初始化替代谱初始化: umap.UMAP(init='random')
  • 在初步探索时对数据进行子采样,然后再应用到全量数据
  • 使用 n_jobs 参数进行并行处理: umap.UMAP(n_jobs=-1)
  • 如果内存充足并希望以更高内存占用换取更快计算,可设置 low_memory=False
  • 使用 RAPIDS cuML 在兼容硬件上进行 GPU 加速。
  • 对于大型数据集,使用 densmap=False(默认设置),因为 DensMAP 的密度保真会增加计算开销

3. 解读 UMAP 投影中的距离

不同于 PCA,UMAP 投影中的距离没有直接含义。看起来接近的点未必在原始空间中相似。为缓解这一限制,请务必:

  • 使用原始特征验证在 UMAP 中发现的簇
  • 主要将 UMAP 用于可视化与假设生成
  • 结合其他验证技术,如 轮廓系数(silhouette score)
  • 为分析任务保留更多维度(3D 或 4D)

4. 可复现性问题

UMAP 使用随机初始化,可能导致不同运行间结果不同,难以复现实验可视化。可通过以下做法提升可复现性:

  • 始终设置 random_state 参数:umap.UMAP(random_state=42)
  • 对于关键分析,多次运行 UMAP 并观察是否存在一致模式
  • 如需严格复现,可使用 init 参数指定初始位置

5. 处理混合数据类型

UMAP 的默认距离度量假定连续数值型数据,这对包含分类型或混合特征的数据集可能不理想。处理混合数据类型时,可考虑:

  • 对分类变量进行恰当预处理(独热编码或嵌入)
  • 使用可处理混合数据类型的自定义距离度量
  • 在 metric 参数中选择适用于二值数据的 'hamming' 等选项
  • 分别对不同特征类型应用 UMAP,再综合洞见

6. 离群点扭曲可视化

极端离群点会压缩主要数据分布,使大多数点的结构难以观察。可采用以下技术应对:

  • 在应用 UMAP 前识别并移除极端离群点
  • 使用鲁棒缩放替代标准缩放
  • 调整 local_connectivity 参数以增强对离群点的鲁棒性
  • 在干净数据上拟合后,使用 UMAP 的 transform 方法再投影离群点

虽然上述清单并不穷尽,但涵盖了使用 UMAP 时最常见的问题。理解这些问题与对应解法将帮助我们更好地发挥 UMAP 的价值。

总结

通过本教程,我们从概念与实践两个层面探讨了 UMAP 的工作原理,在真实数据上完成了实现,与其他方法进行了对比,并讨论了常见挑战。核心结论是:UMAP 能在保留局部与全局结构之间取得平衡,使其在处理复杂高维数据时非常适合用于探索性数据分析。

若想进一步深入学习降维技术,欢迎探索我们的 Python 降维课程,在其中您将亲手实践 UMAP 以及 PCA、t-SNE 等技术。若想从更宏观的角度了解降维在机器学习工作流中的位置, Machine Learning Scientist with Python 学习路径将教授监督学习与无监督学习,帮助您为真实世界的数据挑战构建端到端解决方案。

UMAP 常见问答

UMAP 是什么的缩写?

UMAP 是 Uniform Manifold Approximation and Projection(统一流形近似与投影)的缩写,反映了其在流形学习与拓扑数据分析方面的数学基础。

UMAP 最重要的两个参数是什么?

两个关键参数是 n_neighbors(控制局部与全局结构的权衡)和 min_dist(控制点簇的紧密程度)。默认值分别为 15 和 0.1。

UMAP 能处理分类数据吗?

并不能直接处理。您需要对分类变量进行独热编码或嵌入,或使用如 'hamming' 这类适用于二值数据的自定义距离度量。

UMAP 可以降到多少维?

UMAP 通常将数据降到 2D 或 3D 用于可视化,但也可以降到任何小于原始维度的维数。

UMAP 与 t-SNE 有何不同?

UMAP 通常保留局部结构,并且比 t-SNE 更倾向于保留全局关系。

Arunn Thevapalan

作者

Arunn Thevapalan

作为一名高级数据科学家,我设计、开发并部署大规模机器学习解决方案,帮助企业做出更明智的数据驱动决策。作为数据科学写作者,我分享所学心得、职业建议以及深入的实践型教程。

主题