BERTopic 模型教程:从零把文档变成可解释的主题簇
简介这份资源是面向自然语言处理初学者与主题建模实践者的BERTopic入门教程配套代码包帮助读者理解如何将BERT嵌入与传统主题模型结合从文本中提取语义连贯的主题。包内共14个文件以4个csv结果数据、3个Python脚本、3张可视化图表为主另含txt样本、md说明与inscode配置压缩包约906KB结构紧凑便于按模块查阅。代码覆盖文本预处理、嵌入生成、UMAP降维、HDBSCAN聚类到主题表示全流程并附离线演示脚本与交互式演示方便对照运行。csv文件记录文档主题、主题关键词与主题摘要等输出png图表直观呈现主题分布、关键词热力图与分析结果可辅助调参与效果验证。目前已有151人学习适合希望快速上手BERTopic、掌握参数调节与主题优化思路的读者参考。1. BERTopic 模型教程从零把一篇篇文档变成能解释的主题簇你手上有一堆文本——用户反馈、工单、论文摘要、评论几千到几万条不等。老板要你看看大家都在说什么你第一反应是 LDA跑完发现主题词全是的、了、是调参调到怀疑人生。BERTopic 就是冲着这个场景来的它用预训练 embedding 把文档变成向量降维后用密度聚类分组再用 c-TF-IDF 给每组抽关键词最后还能接一个大模型给主题起名字。整条链路不需要你从头训练任何模型CPU 也能跑几万条文档十几分钟出结果。这篇教程面向两类人一类是刚装完 Python、想拿真实数据跑通一遍的新手跟着代码块一步步来就行另一类是用过 LDA 或 Top2Vec、想知道 BERTopic 的边界在哪、参数怎么调、什么时候会翻车的老手。我会把安装、最小可跑示例、关键参数、中文场景的坑、以及怎么验证主题质量全部讲清楚代码都能直接抄。核心词 BERTopic、embedding、c-TF-IDF、UMAP、HDBSCAN 会反复出现因为它们就是这条流水线的四个关节缺一个都跑不动。2. BERTopic 的四个关节embedding、UMAP、HDBSCAN、c-TF-IDF 各自在干什么2.1 为什么不是 LDA从词袋到语义向量的代差LDA 把文档当成词袋靠词共现统计推断主题。它有两个硬伤一是短文本上词共现太稀疏主题糊成一团二是它不理解同义和语序手机卡顿和电话很卡在它眼里是两组不同的词。BERTopic 换了思路先用 sentence-transformers 这类 embedding 模型把整句话压成一个稠密向量语义相近的句子在向量空间里距离就近然后再聚类。这个代差带来的直接好处是你不需要自定义停用词表去清洗的了吗因为 embedding 模型本身在预训练阶段就学会了忽略这些高频虚词。代价是你要接受一个黑匣子——embedding 模型的质量直接决定上限选错模型后面怎么调都救不回来。常见做法是中文用paraphrase-multilingual-MiniLM-L12-v2或BAAI/bge-small-zh-v1.5英文用all-MiniLM-L6-v2。低显存运行模型时优先选 small 级别384 维足够跑通大部分场景。2.2 UMAP 降维把 384 维压到 5 维再聚类embedding 出来是 384 维甚至 768 维直接上 HDBSCAN 会因为维度灾难导致所有点距离都差不多聚类退化成一个大簇。UMAP 的作用是在保留局部邻域结构的前提下把维度压到 5 维左右。BERTopic 默认n_neighbors15, n_components5, min_dist0.0。n_neighbors控制 UMAP 看多远调大更关注全局结构主题更少更粗调小更关注局部主题更多更细。n_components一般设 5设 2 是为了可视化聚类效果反而差。min_dist0.0是让同簇的点尽量挤在一起方便 HDBSCAN 识别密度。这三个参数里n_neighbors是最值得动的数据量小几百条就调到 510数据量大几万条可以到 3050。2.3 HDBSCAN 聚类为什么它比 KMeans 更适合主题发现KMeans 要你预先指定簇数量 K但文本主题你根本不知道有几个。HDBSCAN 基于密度自动决定簇数量还能把不属于任何簇的点标为 -1离群点。BERTopic 默认min_cluster_size10意思是少于 10 篇文档的组不算主题。min_cluster_size是最关键的参数调大主题更少更稳调小主题更多更碎。min_samples影响离群点判定默认等于min_cluster_size调大能让更多边缘点变成 -1。实际调参时先固定min_cluster_size看输出的主题数量和 -1 占比-1 占比超过 40% 说明聚类太严要么降min_cluster_size要么回去调 UMAP 的n_neighbors。2.4 c-TF-IDF给每个簇抽关键词的算法聚类完你得到的是第 3 簇有 47 篇文档但你需要的是第 3 簇在讲退款流程。c-TF-IDF 把每个簇当成一篇大文档计算词在簇内频率与跨簇频率的比值。一个词如果在某簇高频、在其他簇低频它的 c-TF-IDF 就高就被选为该簇的代表词。它和 TF-IDF 的区别在于TF-IDF 是词对文档的重要性c-TF-IDF 是词对簇的重要性。BERTopic 默认取每个簇 top 10 词你可以通过top_n_words调整。如果关键词里混进了无意义的高频词可以用CountVectorizer的stop_words参数过滤中文场景建议自己维护一个停用词表传进去。3. 用 BERTopic 在本地跑通第一个主题模型安装到出图3.1 环境安装与版本兼容BERTopic 依赖链比较长sentence-transformers拉torchumap-learn拉numbahdbscan拉scikit-learn。版本不匹配是新手翻车重灾区。我一般用 conda 建一个干净环境Python 3.10 最稳。conda create -n bertopic python3.10 -y conda activate bertopic pip install bertopic # 中文场景额外装一个中文 embedding 模型 pip install sentence-transformers装完先验证核心依赖能不能 import这一步能提前暴露 90% 的版本冲突import bertopic from bertopic import BERTopic from sentence_transformers import SentenceTransformer import umap, hdbscan print(bertopic, bertopic.__version__) print(umap, umap.__version__) print(hdbscan, hdbscan.__version__)如果hdbscan报编译错误多半是缺 C 编译工具链Windows 上装 Visual Studio Build ToolsLinux 上apt install build-essential。如果numba报错通常是 numpy 版本太新降到numpy1.24能解决大部分玄学问题。3.2 最小可跑示例20 条文档看完整流程先用一小撮数据把流程跑通确认每一步输出符合预期再上真实数据。下面这段代码从 embedding 到可视化一条龙from bertopic import BERTopic from sentence_transformers import SentenceTransformer docs [ 手机电池续航太差了一天要充三次, 充电速度很慢两个小时才充满, 电池不耐用出门必须带充电宝, 屏幕显示效果很好色彩很鲜艳, 屏幕亮度不够阳光下看不清, 显示屏有坏点刚买就发现了, 物流很快第二天就收到了, 快递包装破损盒子都压扁了, 发货速度慢等了一周才到, 客服态度很好问题很快解决, 售后响应慢三天没人回复, 客服专业帮我解决了退款问题, 系统更新后卡顿严重, App 经常闪退没法用, 软件运行流畅体验不错, 价格有点贵性价比不高, 优惠活动力度大很划算, 同价位里算便宜的, 外观设计漂亮手感好, 做工精致细节到位, ] embedding_model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) topic_model BERTopic( embedding_modelembedding_model, languagechinese, min_cluster_size3, verboseTrue, ) topics, probs topic_model.fit_transform(docs) print(topic_model.get_topic_info())跑完get_topic_info()会返回一个表列包括 Topic主题编号-1 是离群、Count文档数、Name代表词拼接。20 条数据min_cluster_size3大概能分出 46 个主题电池、屏幕、物流、客服、系统、价格、外观各成一簇。如果 -1 特别多说明min_cluster_size相对数据量还是太大降到 2 再试。3.3 关键参数怎么设一张表说清默认值和调整方向参数默认值作用调大调小n_neighbors(UMAP)15邻域范围主题更少更粗主题更多更细n_components(UMAP)5降维目标维度信息保留多但慢快但可能丢结构min_cluster_size10最小簇文档数主题少而稳主题多而碎min_samples同左离群判定离群点更多离群点更少top_n_words10每簇关键词数关键词更全关键词更精nr_topicsNone合并后主题数-指定数字自动合并调参顺序建议先定 embedding 模型再调min_cluster_size看主题数量是否合理最后微调n_neighbors控制粒度。不要一上来就同时动三个参数否则你根本不知道是哪个起了作用。3.4 可视化与主题合并BERTopic 内置了几种图最常用的是visualize_barchart和visualize_topicsfig topic_model.visualize_barchart(top_n_topics6) fig.write_html(topics_barchart.html) fig2 topic_model.visualize_topics() fig2.write_html(topics_map.html)visualize_topics用的是 Plotly交互式能看主题之间的语义距离。如果两个主题挨得很近说明它们可能该合并。合并用merge_topicstopics_to_merge [1, 3] topic_model.merge_topics(docs, topics_to_merge)合并后主题编号会重排记得重新get_topic_info()确认。这一步在真实项目里很常用因为 HDBSCAN 有时会把一个语义主题拆成两个相近的簇。4. 中文场景的坑分词、停用词、embedding 模型选型4.1 中文分词与 CountVectorizer 配置BERTopic 抽关键词那一步默认用CountVectorizer英文按空格切词没问题中文不切词的话整句话会被当成一个 token关键词就废了。解决办法是传一个用 jieba 分词的CountVectorizerfrom sklearn.feature_extraction.text import CountVectorizer import jieba def chinese_tokenizer(text): return jieba.lcut(text) vectorizer_model CountVectorizer( tokenizerchinese_tokenizer, stop_words[的, 了, 是, 在, 我, 有, 和, 就], min_df2, max_df0.9, ) topic_model BERTopic( embedding_modelembedding_model, vectorizer_modelvectorizer_model, min_cluster_size5, )min_df2过滤只出现一次的词max_df0.9过滤出现在 90% 以上文档里的词。停用词表建议自己维护通用停用词表在垂直领域往往不够用。注意tokenizer返回的必须是词列表不能返回字符串。4.2 embedding 模型选型多语言还是中文专用中文场景有两个选择多语言模型如paraphrase-multilingual-MiniLM-L12-v2和中文专用模型如BAAI/bge-small-zh-v1.5。多语言模型胜在通用中英混排数据不用换模型中文专用模型在纯中文语料上语义区分度更好尤其是短文本。选型建议数据里中英混杂超过 20%用多语言纯中文且句子短评论、标题用 bge 系列。切换模型只需换SentenceTransformer的模型名但换完要重新跑一遍因为向量空间变了之前的聚类结果不能复用。embedding 模型排行里 bge 系列在中文榜单上长期靠前但排行是通用榜单你的领域数据上不一定最优有条件的话拿 500 条标注数据对比两个模型的聚类纯度。4.3 短文本主题太碎怎么办短文本微博、评论、弹幕是 BERTopic 最容易翻车的场景每条就十几个字embedding 信息量少HDBSCAN 容易分出几十个小簇每个簇就三五条。三个应对手段第一把min_cluster_size调大比如从 10 调到 20强制合并小簇。第二把n_neighbors从 15 调到 30让 UMAP 更关注全局结构。第三如果同一用户或同一会话有多条短文本先按会话拼接成一条长文本再喂进去语义信息更完整。我一般先试拼接效果最明显。4.4 增量更新与在线场景BERTopic 支持partial_fit做增量更新但要注意它只更新聚类和关键词不重新训练 embedding。新数据来的时候new_docs [新买的手机发热严重, 充电时手机很烫] topics, probs topic_model.transform(new_docs)transform把新文档映射到已有主题上如果新文档和所有已有主题都不像会被标为 -1。要真正把新主题加进去得用partial_fit或定期全量重跑。在线场景下我一般每周全量重跑一次日常用transform做实时打标这样主题编号稳定下游系统不用频繁改映射。5. 避坑与排查BERTopic 跑不通时先看这五条5.1 现象所有文档都被分到 -1一个主题都没有原因min_cluster_size相对数据量太大或者 UMAP 降维后所有点挤在一起。数据只有 100 条却用默认min_cluster_size10HDBSCAN 找不到密度足够的区域。解决先把min_cluster_size降到数据量的 1%2%100 条数据设 23。如果还是全 -1检查 embedding 模型是否加载成功——模型名写错时 sentence-transformers 会静默返回随机向量聚类自然全乱。打印几条 embedding 看数值是否正常。5.2 现象主题数量几百个每个主题就几条文档原因min_cluster_size太小加上n_neighbors太小UMAP 过度关注局部把噪声也当成了结构。解决min_cluster_size调到 1520n_neighbors调到 30。如果数据本身噪声大比如爬虫抓的网页正文混了导航栏先做数据清洗把长度少于 20 字的文档删掉。5.3 现象关键词全是的了吗是看不出主题在讲什么原因中文没配分词器或者停用词表没生效。默认CountVectorizer对中文按空格切整句变成一个 tokenc-TF-IDF 算出来全是整句显示时截断成乱码。解决按 4.1 节配 jieba 分词器和停用词表。配完检查topic_model.get_topics()返回的词是不是正常的中文词如果还是整句说明tokenizer没传对。5.4 现象换了个 embedding 模型主题编号全变了原因不同 embedding 模型的向量空间不同聚类结果不可比。这不是 bug是预期行为。解决主题编号只在本轮模型内有意义不要跨模型对比编号。如果下游系统依赖编号把编号到主题名的映射存下来换模型时重建映射。我一般把topic_model.get_topic_info()存成 CSV作为版本快照。5.5 现象内存爆了几万条文档跑到一半 OOM原因embedding 阶段把所有向量存在内存里384 维 × 10 万条 ≈ 150MB本身不大但 UMAP 的n_neighbors大时构建近邻图会吃大量内存。解决分批计算 embedding用embedding_model.encode(docs, batch_size64)控制批次。UMAP 阶段如果内存还是不够把n_neighbors降到 10或者先用 PCA 把 384 维降到 50 维再喂给 UMAP。低显存运行模型时这些手段都用得上。6. 主题质量怎么验证三个可量化的检查手段跑出主题不等于主题有用。我见过太多人跑完get_topic_info()看一眼关键词就交差结果主题里混了一半不相关的文档。下面三个检查手段是我每次交付前必做的。手段一主题一致性c_v。用 gensim 的CoherenceModel算每个主题的关键词一致性c_v 低于 0.4 的主题基本可以判定为噪声簇。计算时把 BERTopic 的get_topics()输出转成 gensim 需要的格式from gensim.models.coherencemodel import CoherenceModel from gensim.corpora.dictionary import Dictionary import jieba tokenized_docs [jieba.lcut(d) for d in docs] dictionary Dictionary(tokenized_docs) topic_words [ [word for word, _ in topic_model.get_topic(tid)] for tid in topic_model.get_topics() if tid ! -1 ] cm CoherenceModel( topicstopic_words, textstokenized_docs, dictionarydictionary, coherencec_v, ) print(整体一致性:, cm.get_coherence())c_v 在 0.5 以上算合格0.6 以上算好。注意这个指标对分词质量敏感分词没做好 c_v 会虚低。手段二人工抽检每个主题的 top 5 文档。随机抽每个主题的 5 篇文档自己读一遍判断是否真的属于同一主题。这一步没有捷径但最可靠。我一般抽 10 个主题 × 5 篇 50 篇半小时能看完能发现 c_v 发现不了的语义漂移。手段三主题稳定性。把数据随机分成两半各跑一次 BERTopic对比两次的主题关键词重合度。重合度高说明主题稳定重合度低说明参数对数据扰动太敏感需要调大min_cluster_size。这个检查在数据量小于 1000 条时尤其重要。三个手段里人工抽检是后悔药前面两个指标再好看人工一看发现主题混了就得回去调参。我的习惯是c_v 先筛掉明显噪声簇人工抽检确认语义稳定性检查决定参数是否要固化。这套流程跑下来交付的主题列表基本不会被业务方打回来。希望帮到你。本文还有配套的精品资源点击获取