Machine Learning Mastery

Clustering Unstructured Text with LLM Embeddings and HDBSCAN

8.5内容质量

TL;DR · AI 摘要

使用LLM嵌入和HDBSCAN聚类非结构化文本,可自动发现隐藏主题。

核心要点

  • 使用Sentence Transformers生成文本嵌入,可将非结构化文本转化为语义丰富的数学表示。
  • 通过UMAP降维处理嵌入,为聚类算法做准备。
  • HDBSCAN算法能够自动发现文本数据中的隐藏主题,无需人工标注。

结构提纲

按章节快速跳转。

  1. 介绍LLM和HDBSCAN在文本聚类中的应用,无需人工标注即可发现隐藏主题。

  2. 使用Sentence Transformers模型生成文本嵌入,将非结构化文本转化为语义丰富的数学表示。

  3. 使用UMAP算法对嵌入进行降维,为后续聚类算法做准备。

  4. 使用HDBSCAN算法对降维后的嵌入进行聚类,自动发现文本数据中的隐藏主题。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 文本聚类
    • LLM嵌入
      • Sentence Transformers
      • 生成语义表示
    • 降维处理
      • UMAP算法
    • 聚类算法
      • HDBSCAN

金句 / Highlights

值得收藏与分享的关键句。

  • LLM可以将非结构化文本转化为语义丰富的数学表示,为后续聚类分析提供基础。

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • HDBSCAN是一种基于密度的聚类算法,能够自动发现文本数据中的隐藏主题,无需人工标注。

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 使用UMAP算法对嵌入进行降维,可以有效减少计算复杂度,提高聚类效率。

    第 3 段

    ⬇︎ 下载 PNG𝕏 分享到 X
#NLP#机器学习#聚类算法#文本挖掘
打开原文

使用 LLM 嵌入和 HDBSCAN 对非结构化文本进行聚类 - MachineLearningMastery.com

使用 LLM 嵌入和 HDBSCAN 对非结构化文本进行聚类

作者:

Iván Palomares Carrascosa

2026 年 6 月 23 日

语言模型

2

分享

文章

在本文中,你将学习如何通过结合大型语言模型嵌入和 HDBSCAN(一种基于密度的聚类算法),构建一个文本聚类流水线,以自动发现未标记文本数据中的主题。

我们将涵盖的主题包括:

  • 如何使用预训练的 sentence-transformers 模型为原始文档生成文本嵌入。
  • 如何使用 UMAP 降低这些嵌入的维度,为聚类做准备。
  • 如何应用 HDBSCAN 自动发现主题聚类并可视化结果。

使用 LLM 嵌入和 HDBSCAN 对非结构化文本进行聚类

介绍

当前生成式人工智能的时代似乎主要关注聊天界面和提示,但大型语言模型(简称 LLM)的应用范围并不局限于这些。事实上,它们最强大的下游能力之一是将原始的、混乱的、非结构化文本转换为称为嵌入的语义丰富的数学表示。一旦完成,我们就可以将这些文本表示用于各种机器学习用例,聚类也不例外。

特别是,嵌入可以与先进的基于密度的聚类技术(如 HDBSCAN)结合使用,从而在你的文本文档集合中发现隐藏的主题、模式或类别:这一切都不需要预先标记。

本文展示了如何从头开始构建一个基于文本的聚类流水线。我们将使用一个包含文本实例的免费可用数据集,以及一个用于生成嵌入的开源 LLM(即所谓的嵌入模型)。锦上添花的是,我们将使用免费且实用的现代 Python 库,这些库提供了 HDBSCAN 等聚类算法的实现。

分步操作指南

首先,让我们开始安装我们需要的关键 Python 库:

  • Sentence transformers,用于从 Hugging Face 加载预训练的 LLM 以生成嵌入——你需要一个 Hugging Face API 密钥(也称为访问令牌)才能加载该模型。
  • Umap-learn,用于应用一种降低嵌入维度的算法。

同样,如果你在本地 IDE 上工作而不是在云笔记本环境中,并且没有安装 scikit-learn 和 pandas,你可能也需要安装它们。

!pip install sentence-transformers umap-learn

1

!

pip

install

sentence

-

transformers

umap

learn

现在我们通过获取一些新鲜数据来开始编码部分。fetch_20newsgroups 函数可以获取一个包含分类新闻文章文本的数据集。请注意,尽管数据集包含标签,但我们将会忽略它们,因为我们假装不知道这些信息,以便根据相似性将这些数据实例聚类到组中。此外,我们将数据集采样到 150 个实例,这将足以代表我们的示例。

导入 pandas 作为 pd,从 sklearn.datasets 导入 fetch_20newsgroups # 获取一个高度针对性的数据子集(约 150-200 个文档) categories = ['sci.space', 'sci.med', 'rec.autos'] newsgroups = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes')) # 抽样到一个具有代表性和说明性的子集 df = pd.DataFrame({'text': newsgroups.data, 'true_label': newsgroups.target}) df = df[df['text'].str.strip().str.len() > 100].sample(150, random_state=42).reset_index(drop=True) print(f"加载了 {len(df)} 个文本文档。") print("\n示例文档:") print(df['text'].iloc[0][:150] + "...")

3

4

5

6

7

8

9

10

11

12

13

14

导入

pandas

作为

pd

sklearn

.

datasets

fetch

_

20newsgroups

获取一个高度针对性的数据子集(约 150-200 个文档)

categories

=

[

'sci.space'

,

'sci.med'

'rec.autos'

]

newsgroups

fetch_20newsgroups

(

subset

'train'

remove

'headers'

'footers'

'quotes'

)

抽样到一个具有代表性和说明性的子集

df

DataFrame

{

'text'

:

data

'true_label'

target

}

str

strip

len

100

sample

150

random_state

42

reset_index

drop

True

print

f

"加载了 {len(df)} 个文本文档。"

"\n示例文档:"

iloc

0

+

"..."

输出:

加载了 150 个文本文档。 示例文档: Okay Mr. Dyer, we're properly impressed with your philosophical skills and ability to insult people. You're a wonderful speaker and an adept politic...

加载

文本

文档

文档

Okay

Mr

Dyer

we

're properly impressed with your philosophical skills and

ability to insult people. You'

re

a

wonderful

speaker

and

an

adept

politic

下一步是从原始文本中获取嵌入。为此,我们从 Hugging Face 的 sentence-transformers 库中加载 all-MiniLM-L6-v2 模型。这是一个轻量但有效的模型,可以快速获取嵌入。

from sentence_transformers import SentenceTransformer # 加载免费的开源模型 model = SentenceTransformer('all-MiniLM-L6-v2') # 将文本文档编码为密集向量嵌入 print("生成嵌入...") embeddings = model.encode(df['text'].tolist(), show_progress_bar=True) print(f"嵌入矩阵形状: {embeddings.shape}")

sentence_transformers

SentenceTransformer

加载免费的开源模型

model

'all-MiniLM-L6-v2'

将文本文档编码为密集向量嵌入

"生成嵌入..."

embeddings

encode

tolist

show_progress_bar

"嵌入矩阵形状: {embeddings.shape}"

由于嵌入维度对于聚类目的来说最初太高,我们现在使用之前安装的同名库中的 UMAP 算法进行降维:

import umap # 将嵌入维度减少到 5,以保留足够的密度信息用于聚类 reducer = umap.UMAP(n_neighbors=15, n_components=5, min_dist=0.0, random_state=42) reduced_embeddings = reducer.fit_transform(embeddings) print(f"降维后的矩阵形状: {reduced_embeddings.shape}")

将嵌入维度减少到 5,以保留足够的密度信息用于聚类

reducer

n_neighbors

15

n_components

min_dist

0.0

reduced_embeddings

fit_transform

"降维后的矩阵形状: {reduced_embeddings.shape}"

现在,我们与新闻文章相关的数值嵌入向量仅包含五个维度(属性)。让我们看看这种紧凑的表示是否足够有意义,以通过应用 HDBSCAN 算法获得有洞察力的聚类。HDBSCAN 是一种基于密度的聚类方法:

from sklearn.cluster import HDBSCAN # 初始化 HDBSCAN # min_cluster_size=8: 我们指定每个聚类必须至少包含 8 篇文档 clusterer = HDBSCAN(min_cluster_size=8, min_samples=3, store_centers='centroid') df['cluster'] = clusterer.fit_predict(reduced_embeddings) # 统计每个聚类的实例数量 cluster_counts = df['cluster'].value_counts() print("\nCluster Distribution:") print(cluster_counts)

cluster

HDBSCAN

初始化 HDBSCAN

min_cluster_size=8: 我们指定每个聚类必须至少包含 8 篇文档

clusterer

min_cluster_size

min_samples

store_centers

'centroid'

'cluster'

fit_predict

统计每个聚类的实例数量

cluster_counts

value_counts

"\nCluster Distribution:"

重要的是,聚类结果部分受到我们为 HDBSCAN 定义的超参数设置的影响。我建议你尝试其他最小聚类大小和其他超参数的配置,以探索这如何影响结果。

结果:

Cluster Distribution: cluster 0 101 1 49 Name: count, dtype: int64

Distribution

101

49

Name

count

dtype

int64

看起来 HDBSCAN 检测到了与数据空间中高密度区域相关的两个聚类。是否也会有未被分配到这两个聚类中的噪声点?让我们检查一下:

for cluster_id in sorted(df['cluster'].unique()): if cluster_id == -1: print("\n=== CLUSTER: NOISE / UNCLASSIFIED ===") else: print(f"\n=== CLUSTER: Discovered Topic #{cluster_id} ===") # 从该聚类中获取最多 3 个样本文本 samples = df[df['cluster'] == cluster_id]['text'].head(3).tolist() for i, sample in enumerate(samples, 1): clean_sample = " ".join(sample.split())[:120] print(f" {i}. {clean_sample}...")

for

cluster_id

sorted

unique

if

==

"\n=== CLUSTER: NOISE / UNCLASSIFIED ==="

else

"\n=== CLUSTER: Discovered Topic #{cluster_id} ==="

从该聚类中获取最多 3 个样本文本

samples

head

i

enumerate

clean_sample

" "

join

split

120

" {i}. {clean_sample}..."

=== CLUSTER: Discovered Topic #0 === 1. Okay Mr. Dyer, we're properly impressed with your philosophical skills and ability to insult people. You're a wonderful ... 2. I was at an interesting seminar at work (UK's R.A.L. Space Science Dept.) on this subject, specifically on a small-scale... 3. This is the second post which seems to be blurring the distinction between real disease caused by Candida albicans and t... === CLUSTER: Discovered Topic #1 === 1. It's great that all these other cars can out-handle, out-corner, and out- accelerate an Integra. But, you've got to ask ... 2. l diamond star cars (Talon/Eclipse/Laser) put out 190 hp in the turbo models, and 195 hp in the AWD turbo models, These ... 3. Sorry for the mis-spelling, but I forgot how to spell it after my series of exams and NO-on hand reference here. Is it s...

===

Discovered

Topic

#0 ===

1.

're properly impressed with your philosophical skills and ability to insult people. You'

2.

was

at

interesting

seminar

work

UK

's R.A.L. Space Science Dept.) on this subject, specifically on a small-scale...

  1. 这是第二篇帖子,似乎模糊了由白色念珠菌引起的真正疾病与其他疾病之间的区别...

=== CLUSTER: 发现的主题 #1 ===

  1. 很高兴看到其他车型都能轻松应对弯道加速,比如Integra。但你必须问一下,像钻石星Talon/Eclipse Laser这样的车型,搭载了190马力的涡轮增压版本甚至195马力的AWD版本。
  1. 对不起,拼写错误,考试之后我忘了怎么拼写,这里没有手边的参考资料。

看起来样本中所有150个数据点都被分配到了两个已识别的聚类之一,这暗示了新闻文章可能很容易根据主题进行分离。

为了获得额外的见解,我们可以借助下面提供的补充代码,展示一些聚类可视化,该代码为描述每个数据点的五个现有组件的每对组合生成散点图:

import matplotlib.pyplot as plt import seaborn as sns import itertools # 创建一个包含5个降维嵌入和聚类标签的DataFrame reduced_df = pd.DataFrame(reduced_embeddings, columns=[f'UMAP_D{i+1}' for i in range(reduced_embeddings.shape[1])]) reduced_df['cluster'] = df['cluster'] # 获取5个维度的所有唯一成对组合 dim_pairs = list(itertools.combinations(reduced_df.columns[:-1], 2)) num_plots = len(dim_pairs) num_cols = 3 num_rows = (num_plots + num_cols - 1) // num_cols plt.figure(figsize=(num_cols * 5, num_rows * 4)) for i, (dim1, dim2) in enumerate(dim_pairs): plt.subplot(num_rows, num_cols, i + 1) sns.scatterplot( x=dim1, y=dim2, hue='cluster', data=reduced_df, palette='viridis', s=70, alpha=0.7, legend='full' ) plt.title(f'{dim1} vs {dim2}') plt.xlabel(dim1) plt.ylabel(dim2) plt.grid(True, linestyle='--', alpha=0.6) plt.tight_layout() plt.show()

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

matplotlib

pyplot

plt

seaborn

sns

itertools

创建一个包含5个降维嵌入和聚类标签的DataFrame

reduced_df

columns

'UMAP_D{i+1}'

range

shape

获取5个维度的所有唯一成对组合

dim_pairs

list

combinations

num_plots

num_cols

num_rows

// num_cols

figure

figsize

num_cols *

num_rows *

dim1

dim2

subplot

scatterplot

x

y

hue

palette

'viridis'

70

alpha

0.7

legend

'full'

title

'{dim1} vs {dim2}'

xlabel

ylabel

grid

linestyle

'--'

0.6

tight_layout

show

通过尝试不同的HDBSCAN配置,你可能会发现识别出的聚类数量可能与两个不同。不妨一试!

总结

一旦我们完成了基于文本的聚类流水线的构建过程,值得指出将LLM嵌入与HDBSCAN结合在一起的几个关键原因。这些原因包括,由于通过sentence-transformers获得的嵌入所固有的属性,能够保留并部分捕捉原始文本的真实语义意义和语言细微差别。此外,HDBSCAN能够自动确定最佳聚类数量,并能够检测可能为噪声或异常值的离群点,这些点可能会扭曲群体层面的统计数据。

关于此主题的更多信息

  • 无结构文本数据的3种特征工程技巧
  • 使用LLM嵌入在Scikit-learn中进行文档聚类
  • 文本数据的7种高级特征工程技巧…
  • OpenCV 中的 K-Means 聚类及其应用…
  • 项目聚焦:使用…对 Stack Exchange 进行聚类
  • 使用 Python 的 10 种聚类算法

/.entry