当散点图不够用时:用 t-SNE 可视化多维数据

从散点图的局限说起

在数据分析的工作中,我们经常会遇到这样一个问题:如何把数据的"样子"展示给别人看?

如果数据只有两个维度,事情很简单------画一张散点图就好了。

横轴一个变量,纵轴一个变量,每个点代表一条数据,分布一目了然。

如果数据有三个维度呢?也还行------散点图加上颜色,用不同颜色代表第三个维度的取值。

比如用散点图展示城市的人口、GDP和面积,点的颜色深浅表示面积大小。

如果数据有四个维度呢?可以再加一个点的大小(半径),用点的大小表示第四个维度。

比如散点图展示学生的学习时间、考试成绩、出勤率和作业完成度------横轴是学习时间,纵轴是考试成绩,颜色表示出勤率,点的大小表示作业完成度。

那五个维度呢?六个呢?十个呢?

散点图到此为止了。 当数据的维度超过4个,传统的散点图就再也无能为力了。我们无法在二维平面上用有限的视觉通道(位置、颜色、大小、形状)去表达十几个甚至上百个维度的信息。

但现实世界中的数据,往往就是高维的。一份用户画像可能有几十个特征,一篇文档的向量表示可能有几百维,一张图片经过神经网络提取特征后可能有上千维。这些数据里藏着丰富的结构和模式,但我们看不见。

直到我看到了 t-SNE

什么是 t-SNE?

t-SNEt-Distributed Stochastic Neighbor Embeddingt-分布随机邻域嵌入 )是一种非线性降维技术 ,它的目标是把高维空间中的数据点映射到二维或三维空间,同时尽可能保留高维空间中数据点之间的局部邻域关系

说人话就是:在高维空间中离得近的点,在 t-SNE 图上也会离得近;在高维空间中离得远的点,在 t-SNE 图上也会离得远。

t-SNE 的核心思想是概率的------它把数据点之间的相似度转化为概率分布,然后在低维空间中寻找一个分布,使得这两个分布尽可能接近。

它不需要预先指定簇的数量,能够自动揭示数据中潜在的聚类结构。

t-SNE 的优点

  • 擅长展示聚类效果:t-SNE 能够很好地将数据中的自然分组在二维平面上呈现出来。
  • 保留局部结构:在高维空间中相近的数据点在低维空间中仍然会靠在一起。
  • 无需预先定义簇数量:不像 K-Means 等聚类算法需要指定分成几类。

t-SNE 的缺点(⚠️ 重要!)

  • 计算成本高:尤其是在处理大型数据集时,t-SNE 的运行时间可能很长。
  • 结果不稳定:不同次运行可能得到不同的结果。
  • 坐标轴不可解释:t-SNE 的两个轴不代表任何原始特征的逻辑组合,不像 PCA 的主成分那样可以解释含义。
  • 参数敏感:困惑度(perplexity)等参数的选择会显著影响结果。
  • 可能产生误导:t-SNE 有时会夸大簇的大小差异,或者把本不相关的点凑到一起。

尤其需要注意的是,t-SNE 的全局距离(不同簇之间的距离)是没有意义的。

它擅长展示"谁和谁是一伙的 ",但不擅长展示"这两个群体之间到底有多远"。

什么时候用 t-SNE?

t-SNE 最适合的场景是:你想探索高维数据中是否存在自然的聚类结构

典型的使用场景包括:

  • 文本数据的可视化:将文档的向量表示(Embedding)降维到二维,观察不同主题的文档是否自然聚在一起。
  • 图像数据的可视化:将图片的特征向量降维,观察不同类别的图片在空间中的分布。
  • 用户行为分析:将用户的多维行为特征降维,识别不同的用户群体。
  • 模型输出的解释:可视化深度学习模型中间层的特征表示,帮助理解模型学到了什么。

动手实践:两个生活案例

光说不练假把式。接下来我们用两个贴近生活的例子,看看 t-SNE 到底怎么用。

案例一:葡萄酒品鉴数据可视化

假设你是一个葡萄酒爱好者,收集了 200 款红葡萄酒的 13 个特征数据(酒精含量、苹果酸含量、灰分含量、镁含量、总酚含量等)。

你想看看这些葡萄酒是否可以按照产地自然地分成几类。

这个数据集是 scikit-learn 这个机器学习库中自带的。

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler

# 加载葡萄酒数据集(13个特征,3个产地类别)
wine = load_wine()
X = wine.data  # 13个特征
y = wine.target  # 产地标签(0, 1, 2)

# 标准化数据(t-SNE 对尺度敏感)
X_scaled = StandardScaler().fit_transform(X)

# 使用 t-SNE 降维到 2 维
tsne = TSNE(n_components=2, perplexity=30, random_state=42, max_iter=1000)
X_tsne = tsne.fit_transform(X_scaled)

# 可视化
plt.figure(figsize=(10, 8))
colors = ['red', 'green', 'blue']
for i, label in enumerate(np.unique(y)):
    mask = y == label
    plt.scatter(X_tsne[mask, 0], X_tsne[mask, 1], 
                c=colors[i], label=f'产地 {i}', alpha=0.7, s=60)
plt.title('t-SNE 可视化:葡萄酒按产地聚类', fontsize=14)
plt.xlabel('t-SNE 维度 1')
plt.ylabel('t-SNE 维度 2')
plt.legend()
plt.show()

运行这段代码,你会看到不同产地的葡萄酒在二维平面上自然地分成了几个簇。

原本 13 个维度的数据,现在可以直观地看到聚类结构------这就是t-SNE的魔力。

案例二:电影推荐系统中的用户画像

假设你有一个电影推荐系统,收集了 500 个用户的观影偏好数据。每个用户有 20 个特征:对不同类型电影(动作、喜剧、科幻、爱情、恐怖、纪录片等)的偏好程度。

你想看看这些用户是否可以自然地分成不同的"观影人群",以便做更精准的推荐。

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# 模拟数据:500个用户,20个电影类型偏好特征
np.random.seed(42)
n_users = 500
n_features = 20

# 生成模拟数据:假设存在4种不同的用户类型
X = np.random.randn(n_users, n_features)

# 为不同用户类型添加不同的偏好模式
# 类型0:喜欢动作和科幻
X[:125, 0:5] += 2.0
# 类型1:喜欢爱情和喜剧
X[125:250, 5:10] += 2.0
# 类型2:喜欢恐怖和悬疑
X[250:375, 10:15] += 2.0
# 类型3:喜欢纪录片和历史
X[375:500, 15:20] += 2.0

# 标准化
X_scaled = StandardScaler().fit_transform(X)

# 先用 K-Means 聚类(这里只是为了给点打标签做可视化)
kmeans = KMeans(n_clusters=4, random_state=42)
labels = kmeans.fit_predict(X_scaled)

# t-SNE 降维
tsne = TSNE(n_components=2, perplexity=30, random_state=42, max_iter=1000)
X_tsne = tsne.fit_transform(X_scaled)

# 可视化
plt.figure(figsize=(10, 8))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], 
                      c=labels, cmap='viridis', alpha=0.6, s=50)
plt.colorbar(scatter, label='用户类型')
plt.title('t-SNE 可视化:电影用户聚类', fontsize=14)
plt.xlabel('t-SNE 维度 1')
plt.ylabel('t-SNE 维度 2')
plt.show()

在这个例子中,t-SNE 帮助我们直观地看到了 500 个用户在偏好空间中的分布------哪些用户是"动作片爱好者",哪些是"爱情片爱好者",一目了然。

一个完整的代码模板

如果你想把 t-SNE 应用到自己的数据上,下面这个模板可以直接套用:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler

# 1. 准备数据
# X 是你的高维数据,形状为 (n_samples, n_features)
# labels 是每个样本的类别标签(可选,用于着色)
# X = ...

# 2. 标准化(重要!)
X_scaled = StandardScaler().fit_transform(X)

# 3. t-SNE 降维
tsne = TSNE(
    n_components=2,          # 降维到2维
    perplexity=30,           # 困惑度,通常 5-50
    random_state=42,         # 固定随机种子,保证结果可复现
    max_iter=1000            # 迭代次数,确保收敛
)
X_tsne = tsne.fit_transform(X_scaled)

# 4. 可视化
plt.figure(figsize=(10, 8))
if labels is not None:
    scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], 
                          c=labels, cmap='tab10', alpha=0.6, s=50)
    plt.colorbar(scatter)
else:
    plt.scatter(X_tsne[:, 0], X_tsne[:, 1], alpha=0.6, s=50)
plt.title('t-SNE Visualization')
plt.xlabel('t-SNE dimension 1')
plt.ylabel('t-SNE dimension 2')
plt.show()

使用 t-SNE 的几个关键点

1. 困惑度(Perplexity)怎么选?

困惑度可以理解为"考虑多少个近邻"。这个参数对结果影响很大。

  • 小数据集(几百个点) :困惑度设为 5-30
  • 中等数据集(几千个点) :困惑度设为 30-50
  • 大数据集(上万个点) :困惑度可以更大

一个实用的建议是:尝试多个困惑度值,观察结果是否稳定。如果不同困惑度下呈现的聚类结构一致,说明结果是可靠的。

2. 一定要标准化数据

t-SNE 基于距离计算,如果不同特征的尺度差异很大(比如一个特征范围是 0-1,另一个是 0-10000),尺度大的特征会主导距离计算。用 StandardScaler 做标准化是必要的步骤

3. 多次运行,观察稳定性

t-SNE 的优化过程是随机的,不同次运行可能得到不同的结果。

建议多次运行,观察聚类结构是否一致。

4. 不要过度解读

t-SNE 图上的簇间距离没有定量意义。两个簇离得远,不一定意味着它们在原始空间中真的离得很远;两个簇离得近,也不一定意味着它们真的很相似。

t-SNE 主要告诉我们的是:哪些点倾向于聚在一起

5. 迭代要够

确保 max_iter 足够大,让算法收敛到稳定状态。通常 1000 次迭代是起步。

小结

从散点图的 4 维天花板,到 t-SNE 可以处理成百上千维的数据------这确实是一个巨大的进步。

t-SNE 不是万能的,它有计算成本高、结果不稳定、坐标不可解释等缺点。

但在探索性数据分析的场景下,当我们面对一堆高维数据不知所措时,t-SNE 提供了一个非常直观的入口:把数据"画"出来,看看它长什么样。

记住 t-SNE 最核心的价值:它不告诉你"为什么",但它告诉你"谁和谁在一起" 。至于为什么它们在一起,那就是你作为数据分析师需要进一步探究的了。

下次当你面对十几维、几十维的数据不知从何下手时,不妨试试 t-SNE------让数据自己"说话"。

相关推荐
2501_933670794 小时前
电子信息工程零项目经验求职破局
数据分析
我的xiaodoujiao6 小时前
快速学习Python基础知识详细图文教程9--函数进阶
开发语言·python·学习·测试工具
weixin_408099676 小时前
2026 图片去水印 API 接口完全指南:一键去除图片水印(附 Python/Java/PHP/C# 示例)
java·python·php·图片处理·api调用·图片去水印·石榴智能
去码头整点薯条ing7 小时前
某当网登录滑块【协议+OCR】
爬虫·python·ocr
赶紧写完去睡觉8 小时前
Anaconda 创建虚拟环境与使用
python·pycharm·conda
迷途呀8 小时前
Python:函数中的参数类型
开发语言·笔记·python·langchain·nlp
鱼毓屿御8 小时前
复习总结:把之前的所有 prompt 套路封装成可切换模板的 ask 函数
python
你驴我8 小时前
WhatsApp 多账号下消息已读回执的实时聚合与推送实践
后端·python
Strayer9 小时前
拓扑管网 3D 可视化大屏demo:科技感(地图 + 拓扑 + 3D 空间)
前端·three.js·数据可视化