☰
BERTopic模型教程:从零把长文档变成可解释主题簇
2026/10/12 17:51:06 网站建设 项目流程

简介:这份资源是面向自然语言处理初学者与主题建模实践者的BERTopic入门教程配套代码包,围绕BERT嵌入与传统主题模型结合的核心思路,帮助读者理解从文本嵌入、降维、聚类到主题表示的完整流程,并解决传统LDA语义连贯性不足的问题。压缩包共14个文件,约906KB,包含4个csv结果数据、3个Python脚本、3张可视化图表、2个txt说明文件及md文档等,分别用于演示运行、结果分析与主题分布展示。目前已有153人学习下载。读者可借助其中的离线演示脚本、交互式示例与参数调节代码,快速上手主题提取、关键词热力图与层次主题分析,并参考README与示例数据完成从环境配置到结果解读的实践,适合希望系统掌握BERTopic建模方法的中级学习者。

1. BERTopic 模型教程:从零把一篇长文档变成可解释的主题簇

手里有一批用户反馈、工单记录或者论文摘要,想快速知道“这批文本到底在讲什么”,人工读三天也读不完,关键词词频统计又只能给出零散的高频词,看不出主题结构。BERTopic 就是为这个场景设计的:它把预训练语言模型的语义嵌入、UMAP 降维、HDBSCAN 密度聚类和 c-TF-IDF 关键词提取串成一条流水线,输入一列文本,输出若干主题簇,每个簇带一组可读的关键词,还能把文档反查回主题。它适合做文本探索、内容分类预处理、舆情粗筛、知识库标签生成。这篇教程按“先跑通最小例子,再调参,再避坑”的顺序走,代码可以直接抄。

2. 把 BERTopic 的流水线拆开看:每一步在干什么

2.1 四个组件各管一段,别把它们当成黑匣子

BERTopic 的默认流程是四段式。第一段是嵌入,把每条文本转成一个稠密向量,默认用 sentence-transformers 的 all-MiniLM-L6-v2,输出 384 维。第二段是降维,用 UMAP 把 384 维压到 5 维左右,目的是让密度聚类在高维空间里不至于失效——高维空间里距离度量会退化,这是很多人第一次跑出来全是离群点的原因。第三段是聚类,用 HDBSCAN 做基于密度的聚类,它不需要预先指定簇数量,还能把不属于任何簇的点标成 -1。第四段是主题表示,用 c-TF-IDF 把每个簇内的文档拼成一个“类文档”,再算词权重,得到每个主题的关键词。

理解这条链路的意义在于:调参时你知道该动哪一段。主题太碎,多半是 UMAP 的 n_neighbors 太小或 HDBSCAN 的 min_cluster_size 太小;主题太粗,反过来调大;关键词不相关,换嵌入模型或者调 c-TF-IDF 的向量化参数。

2.2 最小可运行例子:20 条文本跑通全流程

先装依赖。BERTopic 对版本比较敏感,建议在干净虚拟环境里装。

python -m venv venv source venv/bin/activate pip install bertopic sentence-transformers umap-learn hdbscan scikit-learn

下面是最小代码。文本用虚构的客服反馈示例,实际用时替换成你自己的数据。

from bertopic import BERTopic # 虚构示例:一批客服反馈短文本 docs = [ "登录之后一直转圈,进不去首页", "密码重置邮件收不到,试了三次", "首页加载特别慢,要等十几秒", "付款页面卡住,点了没反应", "手机端打开就闪退,安卓系统", "搜索结果不准确,找不到想要的商品", "搜索关键词没结果,明明有货", "筛选条件点了没生效,列表没变", "订单状态一直显示待发货,实际已收到", "物流信息不更新,好几天没动静", "退款申请提交后没有进度", "客服电话打不通,一直占线", "在线客服排队太久,等了半小时", "发票申请入口找不到", "优惠券领取后账户里没有", "会员权益和页面描述不一致", "消息推送重复,同一条推了五次", "夜间模式切换后字体看不清", "字体太小,调大之后布局错乱", "多语言切换后部分文案还是中文", ] topic_model = BERTopic(language="chinese") topics, probs = topic_model.fit_transform(docs) # 查看主题概览 print(topic_model.get_topic_info()) # 查看某个主题的关键词 print(topic_model.get_topic(0))

这段代码里,language="chinese"会切换 c-TF-IDF 阶段的分词策略,但嵌入模型默认还是英文的 all-MiniLM-L6-v2,对中文语义捕捉有限。真正做中文,嵌入模型要换成中文或多语言的,下一节讲。

fit_transform返回两个东西:topics是每条文档的主题编号,-1 表示离群;probs是归属概率。get_topic_info()给出每个主题的文档数、关键词和代表文档,这是你判断结果好坏的第一入口。

2.3 中文场景必须换嵌入模型,否则主题会漂

默认嵌入模型对中文支持一般,跑出来的主题关键词经常是“的”“了”这类无意义词,或者把语义不相关的文档聚在一起。常见做法是换成多语言或中文句向量模型。

from sentence_transformers import SentenceTransformer from bertopic import BERTopic # 换用多语言句向量模型,对中文语义更友好 embedding_model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") topic_model = BERTopic( embedding_model=embedding_model, language="chinese", calculate_probabilities=True, verbose=True, ) topics, probs = topic_model.fit_transform(docs) print(topic_model.get_topic_info())

参数说明:embedding_model接收任何 sentence-transformers 兼容对象,也可以传字符串让 BERTopic 自己加载。calculate_probabilities=True会算每条文档属于各主题的概率,代价是速度变慢,数据量大时先关掉。verbose=True打印进度,调试阶段开着。

换模型后如果主题质量还是差,先别急着调 UMAP,检查文本预处理:短文本、口语化、夹杂表情符号的语料,嵌入质量本身就受限,这时候可以考虑先做一轮清洗,或者把短文本按会话合并成更长的段落再嵌入。

3. 关键参数怎么设:UMAP 和 HDBSCAN 的调参顺序

3.1 UMAP 的 n_neighbors 和 n_components 决定主题粒度

UMAP 在 BERTopic 里负责降维,两个参数最影响结果。n_neighbors控制局部与全局的平衡,值小偏向局部结构,主题更碎;值大偏向全局结构,主题更粗。默认 15,中文短文本常见调到 10 到 20 之间。n_components是降维后的维度,默认 5,一般 5 到 10 够用,太小会丢信息,太大密度聚类效果下降。

from umap import UMAP from bertopic import BERTopic umap_model = UMAP( n_neighbors=15, # 局部与全局平衡,短文本可降到 10 n_components=5, # 降维目标维度,5~10 常见 min_dist=0.0, # 允许点更紧密,利于密度聚类 metric="cosine", # 文本嵌入用余弦距离更合适 random_state=42, # 固定随机种子,保证可复现 ) topic_model = BERTopic(umap_model=umap_model, language="chinese") topics, probs = topic_model.fit_transform(docs)

min_dist=0.0是 BERTopic 官方示例里的常见设置,让降维后的点更聚集,HDBSCAN 更容易形成簇。metric="cosine"对文本嵌入比欧氏距离更合理。random_state一定要固定,否则每次跑出来的主题编号和边界都会变,没法对比调参效果。

3.2 HDBSCAN 的 min_cluster_size 是控制主题数量的主开关

HDBSCAN 不需要指定簇数量,但min_cluster_size直接决定多小的簇会被当成噪声。默认值跟数据量有关,BERTopic 内部会取一个自适应值,但显式设置更可控。数据量几百条时,min_cluster_size设 5 到 10;几千条时设 20 到 50;几万条时可以到 100 以上。

from hdbscan import HDBSCAN from bertopic import BERTopic hdbscan_model = HDBSCAN( min_cluster_size=10, # 小于这个规模的簇归为噪声 min_samples=5, # 核心点邻域内的最小样本数 metric="euclidean", # 在 UMAP 降维后的空间里用欧氏距离 cluster_selection_method="eom", prediction_data=True, # 允许对新文档做增量预测 ) topic_model = BERTopic(hdbscan_model=hdbscan_model, language="chinese") topics, probs = topic_model.fit_transform(docs)

min_samples影响噪声比例,值越大噪声越多,簇越保守。cluster_selection_method用默认的eom即可,改成leaf会得到更多更细的簇。prediction_data=True在你要用transform对新文档做主题推断时必须开,否则会报错。

调参顺序建议:先固定 UMAP 默认值,只调min_cluster_size,看主题数量和噪声比例;主题数量合适后再微调n_neighbors控制粒度;最后看关键词质量,不行再换嵌入模型。一次只动一个参数,否则你分不清是哪个起了作用。

3.3 用层次聚类把碎主题合并回去

跑完经常出现主题太碎的情况,几十个主题里很多只差一两个关键词。BERTopic 提供层次聚类,可以把相似主题合并。

# 先看当前主题数量 print(len(topic_model.get_topics())) # 按关键词相似度合并,nr_topics 可以指定目标数量或设为 "auto" topic_model.reduce_topics(docs, nr_topics=8) # 合并后再看主题概览 print(topic_model.get_topic_info())

nr_topics设成整数表示合并到目标数量,设成"auto"让算法自己判断。合并的依据是主题间 c-TF-IDF 向量的相似度。注意合并会改变主题编号,之前保存的topics列表要重新取。如果数据量大,reduce_topics会比较慢,可以先在小样本上试出合适的nr_topics,再全量跑。

4. 避坑与排查:跑 BERTopic 最常见的五个翻车点

4.1 主题全是 -1,一条都没聚上

现象:get_topic_info()里只有主题 -1,文档数等于总数。原因通常是降维后点太分散,HDBSCAN 找不到密度足够的区域。解决:先把min_cluster_size降到 3 到 5,再把 UMAP 的n_neighbors降到 5 到 10,同时确认min_dist=0.0。如果还不行,检查嵌入向量是不是全零或者维度异常,打印embedding_model.encode(docs[:5]).shape看一眼。

4.2 关键词里全是停用词和标点

现象:主题关键词是“的”“了”“是”“,”。原因是 c-TF-IDF 阶段没有过滤停用词,或者中文分词没生效。解决:在CountVectorizer里显式传停用词表,并确认language="chinese"已设置。

from sklearn.feature_extraction.text import CountVectorizer from bertopic import BERTopic vectorizer_model = CountVectorizer( stop_words=["的", "了", "是", "在", "和", "就", "都", "而", "及", "与"], ngram_range=(1, 2), # 同时考虑单字和双字词 min_df=2, # 至少在两个文档里出现才算 ) topic_model = BERTopic( vectorizer_model=vectorizer_model, language="chinese", )

ngram_range=(1, 2)能捞出“加载慢”“收不到”这类双字组合,比单字更有信息量。min_df=2过滤只出现一次的词,减少噪声。

4.3 每次跑结果都不一样,没法对比

现象:同样的数据和参数,两次运行主题编号和边界都不同。原因是 UMAP 和 HDBSCAN 都有随机性。解决:UMAP 设random_state=42,HDBSCAN 本身确定性较强,但降维结果变了它也会变。另外嵌入模型如果用了 GPU 且开了非确定性算子,也可能引入差异,调试阶段可以强制 CPU 或固定种子。

4.4 新文档用 transform 报错

现象:训练完想对新文档做主题推断,调用topic_model.transform(new_docs)报错。原因是 HDBSCAN 没开prediction_data=True。解决:初始化 HDBSCAN 时加上这个参数,重新训练。注意开了之后模型体积会变大,因为要保存预测所需的结构。

4.5 数据量上万后内存爆掉

现象:几万条文本跑fit_transform时内存飙升甚至被系统杀掉。原因是嵌入和 UMAP 都是内存密集型。解决:分批算嵌入,或者换更小的嵌入模型;UMAP 可以设low_memory=True;如果只是探索,先随机采样几千条跑通流程,确定参数后再全量。全量时把calculate_probabilities关掉,能省不少内存。

5. 进阶技巧:用可视化验证主题质量,别只看关键词

5.1 三个图快速判断聚类是否合理

BERTopic 内置了几种可视化,最有用的是主题距离图、主题词条图和文档散点图。主题距离图看簇之间是否分得开,词条图看每个主题的关键词权重,散点图看文档在降维空间的分布。

# 主题间距离图:看簇是否重叠 fig_distance = topic_model.visualize_topics() fig_distance.write_html("topics_distance.html") # 主题关键词条形图:看每个主题的词权重 fig_barchart = topic_model.visualize_barchart(top_n_topics=8) fig_barchart.write_html("topics_barchart.html") # 文档散点图:看文档分布和离群点 fig_documents = topic_model.visualize_documents( docs, reduced_embeddings=umap_model.fit_transform(embedding_model.encode(docs)), ) fig_documents.write_html("topics_documents.html")

visualize_topics返回的是主题间相似度的二维投影,簇之间距离远说明区分度好,重叠严重说明该合并或调参。visualize_barchart把每个主题的 top 词按 c-TF-IDF 权重画出来,权重断崖式下跌说明这个词之后的信息量骤减。visualize_documents需要传入降维后的嵌入,注意这里重新 fit 了一次 UMAP,实际项目中应该复用训练时的降维结果,避免不一致。

5.2 用代表文档做人工抽检

关键词只能给你一个印象,真正判断主题质量要看代表文档。get_representative_docs返回每个主题最典型的几条文档。

# 取主题 0 的代表文档 rep_docs = topic_model.get_representative_docs(0) for i, d in enumerate(rep_docs): print(f"[{i}] {d}") # 也可以按主题批量导出,人工标注用 topic_info = topic_model.get_topic_info() for tid in topic_info["Topic"].tolist(): if tid == -1: continue print(f"=== 主题 {tid} ===") for d in topic_model.get_representative_docs(tid)[:3]: print(" -", d)

抽检时重点看两件事:同一主题的代表文档是否真的在讲同一件事;不同主题的代表文档是否有明显区分。如果发现某个主题混了两类内容,说明min_cluster_size偏大或者嵌入模型区分度不够。如果两个主题代表文档几乎一样,用reduce_topics合并。

5.3 保存与加载模型,避免重复训练

调好参数后把模型存下来,下次直接加载做推断,省去重新训练的时间。

# 保存 topic_model.save("my_topic_model", serialization="safetensors", save_ctfidf=True) # 加载 from bertopic import BERTopic loaded_model = BERTopic.load("my_topic_model") new_topics, new_probs = loaded_model.transform(["新的反馈文本示例"])

serialization="safetensors"比默认的 pickle 更安全也更快,save_ctfidf=True把 c-TF-IDF 的向量化器一起存下来,否则加载后关键词提取会出问题。加载后对新文档做transform,前提是训练时 HDBSCAN 开了prediction_data=True。

我自己的习惯是:任何一次调参实验都固定random_state,把get_topic_info()导出成 CSV 存档,主题编号、关键词、文档数三列对齐。这样换参数重跑时能直接 diff,一眼看出哪个主题被拆了、哪个被合了。主题模型没有绝对正确的答案,只有“这批数据在当前参数下是否可解释”,所以人工抽检这一步省不掉。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询