聚类并可视化嵌入
对一组嵌入应用 k-means 聚类,并使用 UMAP 将其可视化为二维图,以发现数据中的自然主题分组。
聚类并可视化嵌入 是 CoddyKit 上的免费 AI Engineering Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Engineering Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Engineering Academy 课程共包含 4 节课。
为什么要对嵌入进行聚类
当您拥有数百或数千份文档时,通常希望在不逐份阅读的情况下,发现其中有哪些主题。对嵌入进行聚类可以自动将语义相似的文档归为一组,从而揭示数据的自然结构。
常见应用包括:自动标记支持工单、发现内容类别、查找冗余文档,以及了解用户最常询问的内容。
K 均值聚类概述
K 均值通过反复将每个数据点分配给最近的质心,再将质心重新计算为所分配数据点的均值,把 n 个数据点划分为 k 个簇。当分配结果不再变化时,算法便会收敛。
对于嵌入,K 均值会找出在高维嵌入空间中彼此接近的文档,从而有效地按照语义相似度将它们分组。
from sklearn.cluster import KMeans
import numpy as np
# corpus_embeddings: (n_docs, 1536) — pre-computed
corpus_embeddings = np.random.randn(200, 1536) # placeholder
kmeans = KMeans(n_clusters=5, random_state=42, n_init='auto')
kmeans.fit(corpus_embeddings)
labels = kmeans.labels_
print(f'Cluster assignments: {labels[:10]}')
print(f'Unique clusters: {set(labels)}')选择合适的簇数量
选择 k(簇的数量)是最困难的部分。肘部法针对不同的 k 值绘制惯性(到质心的距离平方和),并寻找这样一个点:继续增加簇的数量后,惯性的下降幅度不再明显。
轮廓系数衡量一个数据点与自身簇的匹配程度相对于与最近其他簇的匹配程度有多大优势——越接近 1 越好。请尝试从 3 到 20 的 k 值,并选择得分最高的值。
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
import numpy as np
corpus_embeddings = np.random.randn(200, 50) # placeholder (50D for speed)
best_k, best_score = 2, -1
for k in range(2, 11):
km = KMeans(n_clusters=k, random_state=42, n_init='auto')
labels = km.fit_predict(corpus_embeddings)
score = silhouette_score(corpus_embeddings, labels, sample_size=100)
print(f'k={k}: silhouette={score:.3f}')
if score > best_score:
best_score, best_k = score, k
print(f'Best k: {best_k}')使用 LLM 为簇标记
聚类完成后,您可以将该簇中的几份代表性文档传给模型,请 LLM 为每个簇生成一个易于人类理解的标签。这样便能自动将原始的簇编号转换为有意义的类别名称。
from openai import OpenAI
client = OpenAI()
def label_cluster(cluster_docs, n_examples=3):
examples = '\n'.join(f'- {d}' for d in cluster_docs[:n_examples])
prompt = f'Given these documents, provide a 3-word category label:\n{examples}\nLabel:'
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
max_tokens=20
)
return response.choices[0].message.content.strip()
# Example usage:
# cluster_0_docs = [documents[i] for i in range(len(documents)) if labels[i] == 0]
# print(label_cluster(cluster_0_docs))使用 UMAP 降低维度
无法直接绘制 1536 维向量。UMAP(统一流形近似与投影)可以在保留局部邻域结构的同时,将高维数据降至二维或三维。与 PCA 不同,UMAP 是非线性的,在保留簇方面表现好得多。
UMAP 是可视化文本嵌入的标准选择,因为相似的文档在二维图中仍会彼此接近。
import umap
import numpy as np
corpus_embeddings = np.random.randn(200, 1536) # placeholder
reducer = umap.UMAP(
n_components=2,
metric='cosine', # important for text embeddings
random_state=42
)
embeddings_2d = reducer.fit_transform(corpus_embeddings)
print(f'Reduced shape: {embeddings_2d.shape}') # (200, 2)使用 Matplotlib 可视化簇
获得 UMAP 生成的二维坐标和 K 均值生成的簇标签后,简单的散点图就能直观呈现簇的结构。根据簇标签为每个点着色,并添加代表性文档标题作为文本注释,使图表更易理解。
import matplotlib.pyplot as plt
import numpy as np
# Assume: embeddings_2d (n, 2), labels (n,), documents list
# Placeholder data:
np.random.seed(42)
embeddings_2d = np.random.randn(50, 2)
labels = np.random.randint(0, 5, 50)
fig, ax = plt.subplots(figsize=(10, 7))
scatter = ax.scatter(
embeddings_2d[:, 0],
embeddings_2d[:, 1],
c=labels,
cmap='tab10',
s=60,
alpha=0.8
)
plt.colorbar(scatter, label='Cluster')
ax.set_title('Document Embedding Clusters (UMAP)')
plt.tight_layout()
plt.savefig('/tmp/clusters.png', dpi=150)
print('Saved cluster plot')影响 UMAP 的重要参数
影响可视化质量的 UMAP 关键超参数:
- n_neighbors(默认值为 15):较大的值捕捉更多全局结构,较小的值揭示局部簇
- min_dist(默认值为 0.1):控制点聚集的紧密程度;值越小,簇越紧密,但簇之间的重叠也越多
- metric:文本嵌入始终使用
'cosine'——欧氏距离并不适用
请在 5 到 50 之间调整 n_neighbors,找到最能展现数据结构的可视化效果。
层次聚类替代方案
凝聚层次聚类会构建一棵嵌套簇树,无需您预先指定 k。您可以在选定的距离阈值处切割这棵树,以获得最终的簇。当您不知道数据中自然主题的数量时,这种方法非常有用。
from sklearn.cluster import AgglomerativeClustering
import numpy as np
corpus_embeddings = np.random.randn(100, 50) # placeholder
cluster = AgglomerativeClustering(
n_clusters=None,
distance_threshold=1.5,
metric='cosine',
linkage='average'
)
labels = cluster.fit_predict(corpus_embeddings)
n_clusters = len(set(labels))
print(f'Found {n_clusters} natural clusters')查找离群点和近重复文档
嵌入也非常适合查找近重复文档。计算所有文档之间的两两余弦相似度,并将相似度高于 0.95 的文档对标记为潜在重复项。与文本差异比较相比,这种方法更加稳健,因为它也能发现改写后的重复内容。
import numpy as np
def find_near_duplicates(corpus_vecs, threshold=0.95):
# corpus_vecs assumed L2-normalized
sim_matrix = corpus_vecs @ corpus_vecs.T # (n, n)
duplicates = []
n = len(corpus_vecs)
for i in range(n):
for j in range(i + 1, n):
if sim_matrix[i, j] >= threshold:
duplicates.append((i, j, float(sim_matrix[i, j])))
return duplicates
# pairs = find_near_duplicates(corpus_embeddings)
# print(f'Found {len(pairs)} near-duplicate pairs')实际的聚类工作流
实际的聚类工作流按以下顺序进行:
- 使用
text-embedding-3-small为所有文档生成嵌入 - 可选:在聚类前使用 UMAP 降至 50 维(加快 K 均值处理)
- 使用 K 均值进行聚类,遍历 k 以找到最佳轮廓系数
- 使用 5 份代表性文档,通过 LLM 为每个簇标记
- 绘制二维 UMAP 散点图,并按簇着色
- 检查离群点(远离所有质心的点)
聚类的局限性
请注意,对嵌入进行聚类存在以下重要局限:
- K 均值假设簇呈球形——细长或形状不规则的主题组可能会被错误拆分
- 嵌入会压缩含义——两份文档在语义上可能很接近,但属于不同的可执行类别
- 簇标签需要验证——请始终从每个簇中抽取一些文档,进行合理性检查,验证 LLM 生成的标签
请将聚类用作探索工具,而不是用作绝对正确的分类系统。
快速检查
测试您对本课中 AI 工程概念的理解。
课程回顾
本课中,您学到了:k-means 按嵌入空间中的语义邻近度对文档进行分组,UMAP 使用余弦距离将高维嵌入降至二维以便可视化,以及轮廓系数可以在没有标注数据的情况下帮助您选择合适的聚类数量。接下来,我们将超越内存中搜索,探索生产环境中的向量数据库。
常见问题解答
「聚类并可视化嵌入」课时是免费的吗?
是的 — 「聚类并可视化嵌入」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Engineering Academy 课程的其余内容,请升级到 CoddyKit PRO。 AI Engineering Academy 课程共包含 4 节课。
「聚类并可视化嵌入」这节课中我会学到什么?
对一组嵌入应用 k-means 聚类,并使用 UMAP 将其可视化为二维图,以发现数据中的自然主题分组。 你通过在浏览器中直接运行的动手代码来练习 AI Engineering Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Engineering Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Engineering Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「聚类并可视化嵌入」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Engineering Academy 课中编写并运行代码吗?
能。每节 AI Engineering Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。