[转载]用UMAP进行特征可视化

原始博文地址:https://mp.weixin.qq.com/s/nlw7nN7Hiel2O8BoCRKbgw

UMAP,全称,Uniform Manifold Approximation and Projection。

它的目标很朴素:把高维数据折叠到2D/3D里,同时尽量不破坏谁和谁更像的局部邻里关系。

首先,为什么要降维,为什么常用 UMAP?

高维数据人眼看不见,难以直观理解样本之间像不像;降维到2D/3D是探索式数据分析(EDA)最常用的入口。

PCA 适合线性结构,但遇到月牙形、瑞士卷这类非线性结构就容易压坏;t-SNE 的可视化很漂亮,但全局结构常常不可靠、参数难调、扩展到超大数据有点慢。

UMAP 结合了速度快结构保真度高可扩展参数直觉更强等优点,已经成为数据科学家在可视化和表征学习里的首发阵容

01UMAP 核心逻辑

把 UMAP 想成三个步骤:交朋友 -> 拼地图 -> 折到纸上。

先交朋友:

  • 对每个点,找出它最要好的  个邻居(参数 n_neighbors,比如15)。

  • 朋友不是非黑即白,而是亲密度打分:越近越,越远越普通朋友

再拼地图:

  • 每个人手里有一张小范围邻里地图(谁和谁亲密)。

  • 把全体人的小地图糊成一本网络地图;这时候你得到的是一个模糊的、带权重的邻接图

最后折到纸上:

  • 在2D/3D的纸上摆点,让铁哥们更靠近;不太熟的,尽量别挤在一起。

  • 用一种交叉熵的目标函数,让高维的亲密度与低维的亲密度尽量一致。

  • 有个关键参数 min_dist,控制铁哥们在2D里能靠多近,避免团成死疙瘩。

就是说,UMAP 就是在做把朋友网络画在纸上,既保留谁和谁是一伙的(局部结构),也尽可能让不同团体之间的相对远近有点道理(某种全局结构)。

02一个通俗例子

就好比说,教室里有一群小朋友,每两个人的亲密度不同:一起玩耍多的更亲、很少一起玩的不太熟。

第一步:老师让每个小朋友写下最要好的前15位朋友名单,并给每位朋友打分(比如共同游戏次数越多,分越高)。

第二步:老师把所有人的好朋友名单合在一起,做成一张友谊网。

第三步:要在地板上摆位置:

  • 给每一对要好的小朋友之间拴一根橡皮筋,越要好橡皮筋越紧(想把他们拽近一点)。

  • 同时准备一些回形针,对不太熟的一些人对进行轻微推开(不让他们全都挤在一团)。

  • 放手之后,让橡皮筋和回形针在地上拉扯一会儿,大家就形成了比较舒服的坐姿,这就是UMAP的2D坐标。

min_dist 就像防挤条款:再怎么要好,也要留一点缝,不许坐在同一个小板凳上。

03核心原理

1) 邻域距离与亲密度(条件隶属度)

对于样本 ,通过  近邻搜索得到它的邻居集合,并计算距离 。

UMAP会估计一个本地连通阈值\rho _{i}  和\sigma _{i}缩放 ,将距离转为亲密度(条件概率/隶属度):

2) 模糊集合的对称化(像或运算)

把两个方向的亲密度合成一个无向的边权:

3) 低维空间的相似度模型

在2D/3D中,UMAP用一条重尾曲线建模两点距离  的亲密度:

其中  由 min_dist 参数通过拟合确定(保证距离很近时曲线陡、远时尾巴长)。

4) 目标函数(交叉熵)

在低维中,希望 p_{ij}(y) 跟高维的 \mu _{ij} 尽量一致,优化的目标是:

实现上通过负采样高效近似第二项,从而有很强的扩展性。

5) 参数直观

  • n_neighbors:控制朋友圈多大;小一点更强调局部结构,大一点更看全局形状。

  • min_dist:控制在2D里的最小拥挤度;越小团越紧,越大团越松。

  • metric:距离度量(欧氏、余弦等);文本/推荐等可考虑余弦。

04完整案例

我们构造一个多形状、非线性的数据集:瑞士卷、双月牙、S曲线、同心圆,各取若干样本,然后升维到50维,添加非线性特征与噪声,再用 UMAP 还原到2D。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_swiss_roll, make_moons, make_s_curve, make_circles
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score
from sklearn.manifold import trustworthiness, TSNE

import umap

import warnings
warnings.filterwarnings("ignore", category=FutureWarning)
np.random.seed(42)

# 1) 数据集:4种形状,各自添加非线性特征与噪声,升维到50维
def make_virtual_dataset(n_each=1000, noise_dim=44, random_state=42):
    rs = np.random.RandomState(random_state)

    # 瑞士卷(3D)
    X_sw, t_sw = make_swiss_roll(n_samples=n_each, noise=0.05, random_state=rs)
    y_sw = np.array(["SwissRoll"]*n_each)

    # 双月牙(2D);补一个零维拼成3D再处理
    X_mo, y_dummy = make_moons(n_samples=n_each, noise=0.05, random_state=rs)
    X_mo = np.c_[X_mo, np.zeros(n_each)]
    y_mo = np.array(["Moons"]*n_each)

    # S曲线(3D)
    X_sc, t_sc = make_s_curve(n_samples=n_each, noise=0.05, random_state=rs)
    y_sc = np.array(["SCurve"]*n_each)

    # 同心圆(2D);补一个零维拼成3D
    X_ci, y_dummy2 = make_circles(n_samples=n_each, noise=0.05, factor=0.5, random_state=rs)
    X_ci = np.c_[X_ci, np.zeros(n_each)]
    y_ci = np.array(["Circles"]*n_each)

    X3 = np.vstack([X_sw, X_mo, X_sc, X_ci])  # 基础3维
    labels = np.concatenate([y_sw, y_mo, y_sc, y_ci])

    # 构造非线性高维特征:sin/cos 的随机投影 + 高斯噪声
    W1 = rs.normal(size=(3, noise_dim//2))
    W2 = rs.normal(size=(3, noise_dim - noise_dim//2))
    nonlin1 = np.sin(X3 @ W1)
    nonlin2 = np.cos(X3 @ W2)
    noise = 0.2 * rs.normal(size=(X3.shape[0], 3))

    X_high = np.c_[X3 + noise, nonlin1, nonlin2]  # 3 + noise_dim ≈ 47 ~ 50维
    scaler = StandardScaler()
    X_high = scaler.fit_transform(X_high)

    return X_high, labels

X, y = make_virtual_dataset(n_each=1000, noise_dim=47, random_state=42)
print("数据集维度:", X.shape, "类别分布:", pd.Series(y).value_counts().to_dict())

palette = {
    "SwissRoll": "#e41a1c",
    "Moons": "#377eb8",
    "SCurve": "#4daf4a",
    "Circles": "#984ea3"
}
colors = pd.Series(y).map(palette).values

# 2) 图1:PCA三维投影,观察高维数据的一个窗口
pca3 = PCA(n_components=3, random_state=42)
X_pca3 = pca3.fit_transform(X)

fig = plt.figure(figsize=(9, 7))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X_pca3[:, 0], X_pca3[:, 1], X_pca3[:, 2], c=colors, s=6, alpha=0.8)
ax.set_title("图1:高维数据的PCA三维投影", fontsize=14)
ax.set_xlabel("PC1"); ax.set_ylabel("PC2"); ax.set_zlabel("PC3")
plt.tight_layout()
plt.show()

# 3) 训练UMAP
umap_model = umap.UMAP(
    n_neighbors=15,
    min_dist=0.05,
    n_components=2,
    metric="euclidean",
    random_state=42
)
X_umap = umap_model.fit_transform(X)

plt.figure(figsize=(8, 7))
plt.scatter(X_umap[:, 0], X_umap[:, 1], c=colors, s=6, alpha=0.85)
plt.title("图2:UMAP二维嵌入(n_neighbors=15, min_dist=0.05)", fontsize=14)
plt.xlabel("UMAP-1"); plt.ylabel("UMAP-2")
plt.tight_layout()
plt.show()

# 4) 参数网格对比:不同n_neighbors与min_dist组合的效果
def plot_umap_grid(X, y, palette, nn_list=(5, 15, 50), md_list=(0.0, 0.3, 0.8), random_state=42):
    fig, axes = plt.subplots(len(nn_list), len(md_list), figsize=(14, 12))
    for i, nn in enumerate(nn_list):
        for j, md in enumerate(md_list):
            um = umap.UMAP(n_neighbors=nn, min_dist=md, n_components=2, random_state=random_state)
            emb = um.fit_transform(X)
            ax = axes[i, j]
            cols = pd.Series(y).map(palette).values
            ax.scatter(emb[:, 0], emb[:, 1], c=cols, s=6, alpha=0.85)
            ax.set_xticks([]); ax.set_yticks([])
            ax.set_title(f"n_neighbors={nn}, min_dist={md}")
    plt.suptitle("图3:UMAP参数网格可视化", fontsize=16, y=1.02)
    plt.tight_layout()
    plt.show()

plot_umap_grid(X, y, palette)

# 5) 信任度(trustworthiness)对比:UMAP vs PCA vs t-SNE
def compute_embeddings(X, random_state=42):
    pca2 = PCA(n_components=2, random_state=random_state).fit_transform(X)
    # 为了运行时间,t-SNE迭代步数不设太大
    tsne2 = TSNE(n_components=2, perplexity=30, n_iter=600, init='pca', random_state=random_state).fit_transform(X)
    umap2 = umap.UMAP(n_neighbors=15, min_dist=0.05, n_components=2, random_state=random_state).fit_transform(X)
    return pca2, tsne2, umap2

pca2, tsne2, umap2 = compute_embeddings(X, random_state=42)

k_list = [5, 10, 15, 30, 50, 100]
tw_pca = [trustworthiness(X, pca2, n_neighbors=k) for k in k_list]
tw_tsne = [trustworthiness(X, tsne2, n_neighbors=k) for k in k_list]
tw_umap = [trustworthiness(X, umap2, n_neighbors=k) for k in k_list]

plt.figure(figsize=(8, 6))
plt.plot(k_list, tw_pca, marker='o', color="#1f77b4", label="PCA-2D")
plt.plot(k_list, tw_tsne, marker='o', color="#ff7f0e", label="t-SNE-2D")
plt.plot(k_list, tw_umap, marker='o', color="#2ca02c", label="UMAP-2D")
plt.xlabel("k(评估近邻数)")
plt.ylabel("Trustworthiness")
plt.title("图4:信任度对比", fontsize=14)
plt.legend()
plt.tight_layout()
plt.show()

# 6) 轮廓系数对比:量化类内紧凑和类间分离
def algo_silhouette(X_orig, y, random_state=42):
    res = {}
    algo = {
        "PCA-2D": PCA(n_components=2, random_state=random_state).fit_transform(X_orig),
        "t-SNE-2D": TSNE(n_components=2, perplexity=30, n_iter=600, init='pca', random_state=random_state).fit_transform(X_orig),
        "UMAP-2D": umap.UMAP(n_neighbors=15, min_dist=0.05, n_components=2, random_state=random_state).fit_transform(X_orig)
    }
    # 将类别转为整数标签计算轮廓系数
    label_to_id = {c: i for i, c in enumerate(np.unique(y))}
    y_id = np.array([label_to_id[c] for c in y])
    for name, emb in algo.items():
        res[name] = silhouette_score(emb, y_id, metric="euclidean")
    return res

sil = algo_silhouette(X, y, random_state=42)
plt.figure(figsize=(7, 5))
method_names = list(sil.keys())
scores = [sil[m] for m in method_names]
bar_colors = ["#1f77b4", "#ff7f0e", "#2ca02c"]
plt.bar(method_names, scores, color=bar_colors)
plt.ylabel("Silhouette Score(越高越好)")
plt.title("图5:聚类轮廓系数对比(PCA vs t-SNE vs UMAP)", fontsize=14)
for i, v in enumerate(scores):
    plt.text(i, v + 0.01, f"{v:.3f}", ha='center', va='bottom')
plt.tight_layout()
plt.show()

PCA三维投影:

图片

这是把50维高维数据压到3维后的一个窗口,颜色代表四类不同形状的来源。你会看到不同颜色的大致簇,但它们之间仍交织,说明线性投影难以完全展开复杂的非线性结构。

UMAP二维嵌入主图:

这是 UMAP 的成品地图。同一来源形状的点大多聚到一起,不同颜色之间的相对位置也更有地理意义

图片

这张图很适合:

  • 初探数据:有几类?是否有子团?

  • 发现离群点:落在团之外的奇怪点

  • 观察局部结构:同一类内部是否有分层(比如S曲线中的不同段)

参数网格:n_neighbors × min_dist:

图片

行方向(n_neighbors从5到50):越小越强调各自抱团,越大越地理统一(全局结构更连续)。

列方向(min_dist从0.0到0.8):越小团越紧密,越大更松散、透气

Trustworthiness 对比:

图片

Trustworthiness 衡量低维结果保留了多少高维的近邻关系,取值越高表明局部结构保真度越好。UMAP 通常在一系列 k 上都能保持较高的 trustworthiness,说明它对邻居关系的还原普遍更稳健。

Silhouette 轮廓系数对比:

图片

衡量类内紧凑、类间分离的综合指标。UMAP 的分数通常较高,说明它在2D里把同类拉近、异类拉开做得不错(当然具体结果与数据分布相关)

核心处理逻辑

第0步:数据准备

合成四种非线性形状,升维到50维,添加随机非线性特征与噪声,模拟真实世界高维+非线性+噪声的复杂场景。

标准化(StandardScaler)让不同特征不至于因量纲差异而主导距离。

第1步:先来个PCA-3D探路

低成本看个轮廓,辅助理解数据是否适合更复杂的非线性降维。

第2步:核心 UMAP 降维(2D)

关键参数:n_neighbors=15, min_dist=0.05;平衡局部与全局、适度紧凑。

结果:二维可视化,可肉眼判断簇结构、离群点、子结构。

第3步:参数网格扫描

选几个典型组合,观察形状随参数的演变,确定符合你业务解释的形态(比如需要更紧的团还是更平滑的地貌)。

第4步:定量指标评估

Trustworthiness(局部结构保真),Silhouette(类内紧凑 vs 类间分离)

可以加上KNN在嵌入空间的分类精度、邻域召回等,作为辅助指标(本文示例已足够)。

UMAP vs t-SNE

t-SNE:特别擅长把团块可视化得很清楚,但不同团块之间的远近不要太认真。参数 perplexity 影响很大;大数据上速度一般,需要近似或子采样。

UMAP:把数据当作流形上的点,先构造模糊的邻接图,再在低维用目标函数拟合,既看重局部,也努力给全局一点秩序。近似KNN和负采样优化让 UMAP 更快、更可扩展。

总结

UMAP 的直觉其实很朴素:把谁和谁更像的信息装进一个友谊网,再在平面上尽量画出这个网的结构。理解交朋友 + 拼地图 + 折到纸上这三个关键步骤,你就抓住了 UMAP 的灵魂。

大家在实战中,UMAP 不仅是可视化工具,还是下游机器学习的表征引擎,比如把高维特征压到一小撮维度喂给KNN/聚类/检索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值