主题模型


文档摘要

主题模型 本节摘要:LDA 把文档看作话题的混合,把话题看作词上的分布;BERTopic 让文档在嵌入空间里聚类,把簇当作话题。目标相同,分解方式不同。你手上有一万张客服工单、五万篇新闻或二十万条推文,需要在不读的前提下知道这批材料在讲什么——没有标注类别,甚至连有几类都不知道。主题模型在无监督下回答这个问题:喂语料,取回一小组连贯话题,并给每篇文档一个在这些话题上的分布。两大算法族主导。LDA(2003)用贝叶斯推断把每篇文档建模为隐话题的混合、每个话题建模为词上的分布,至今在生产里仍用于需要混合归属和可解释词级概率的场景。

主题模型

本节摘要:LDA 把文档看作话题的混合,把话题看作词上的分布;BERTopic 让文档在嵌入空间里聚类,把簇当作话题。目标相同,分解方式不同。你手上有一万张客服工单、五万篇新闻或二十万条推文,需要在不读的前提下知道这批材料在讲什么——没有标注类别,甚至连有几类都不知道。主题模型在无监督下回答这个问题:喂语料,取回一小组连贯话题,并给每篇文档一个在这些话题上的分布。两大算法族主导。LDA(2003)用贝叶斯推断把每篇文档建模为隐话题的混合、每个话题建模为词上的分布,至今在生产里仍用于需要混合归属和可解释词级概率的场景。BERTopic(2020)用 BERT 编码文档、UMAP 降维、HDBSCAN 聚类、再用类内 TF-IDF 抽话题词,在短文本、社交媒体以及语义相似比词重叠更重要的任何场合都胜出——代价是一篇文档只属一个话题,对长文是个限制。本节为两者建立直觉,并点明给定语料时该选哪个。

对应原课程:Phase 5 · Lesson 15 · topic-modeling(原英文 phases/05-nlp-foundations-to-advanced/15-topic-modeling/docs/en.md)。前置依赖:第 02 节(词袋与 TF-IDF)、第 03 节(Word2Vec)。

学习目标

阅读完本节,你应当能够:

  1. 复述 LDA 的生成式故事(文档=话题混合,话题=词分布)与**推断**方向(给定词反推两套分布)。
  2. 说清 BERTopic 流水线的四个阶段(嵌入→UMAP→HDBSCAN→类内 TF-IDF)各自解决什么。
  3. c_v 主题连贯性主题多样性量化评估两种方法的话题质量。
  4. 给定文档长度、算力与语义需求,在 LDA 与 BERTopic 间做出正确取舍

一、问题与直觉

你有一万张客服工单、五万篇新闻或二十万条推文。需要在不读的前提下知道这批材料在讲什么。你没有标注类别,甚至不知道有几类。

主题模型在无监督下回答这个问题:给语料,取回一小组连贯话题,并给每篇文档一个在这些话题上的分布。两大算法族主导:

LDA 的生成式故事。 每个话题是词上的分布,每篇文档是话题的混合。要在文档里生成一个词,先从文档的混合里抽一个话题,再从该话题的词分布里抽一个词。推断反过来:给定观测到的词,反推每篇文档的话题分布和每个话题的词分布。坍缩 Gibbs 采样或变分贝叶斯做这套数学。

LDA 的两个关键输出:

  • doc_topic:矩阵 (n_docs, n_topics),每行求和为 1(文档的话题混合)。
  • topic_word:矩阵 (n_topics, vocab_size),每行求和为 1(话题的词分布)。

BERTopic 流水线。 先用句子编码器(如 all-MiniLM-L6-v2)把每篇文档编码成 384 维向量;BERT 嵌入对聚类维度太高,用 UMAP 降到约 5 维;再用 HDBSCAN 聚类,密度法产出变长簇和一个「离群」标签;最后对每个簇,在其文档上算类内 TF-IDF 抽出最高词。

输出是「一篇文档一个话题」(外加 -1 离群标签);可选地通过 HDBSCAN 的概率向量得到软归属。

二、从零实现

第 1 步:用 scikit-learn 跑 LDA

from sklearn.feature_extraction.text import CountVectorizer from sklearn.decomposition import LatentDirichletAllocation import numpy as np def fit_lda(documents, n_topics=5, max_features=1000): cv = CountVectorizer( max_features=max_features, stop_words="english", min_df=2, max_df=0.9, ) X = cv.fit_transform(documents) lda = LatentDirichletAllocation( n_components=n_topics, random_state=42, max_iter=50, learning_method="online", ) doc_topic = lda.fit_transform(X) feature_names = cv.get_feature_names_out() return lda, cv, doc_topic, feature_names def print_top_words(lda, feature_names, n_top=10): for idx, topic in enumerate(lda.components_): top_idx = np.argsort(-topic)[:n_top] words = [feature_names[i] for i in top_idx] print(f"topic {idx}: {' '.join(words)}")

注意:去停用词;min_dfmax_df 过滤罕见词与无处不在的词;用 CountVectorizer(不是 TfidfVectorizer),因为 LDA 要的是原始计数。

第 2 步:BERTopic(生产)

from bertopic import BERTopic topic_model = BERTopic( embedding_model="sentence-transformers/all-MiniLM-L6-v2", min_topic_size=15, verbose=True, ) topics, probs = topic_model.fit_transform(documents) info = topic_model.get_topic_info() print(info.head(20)) valid_topics = info[info["Topic"] != -1]["Topic"].tolist() for topic_id in valid_topics[:5]: print(f"topic {topic_id}: {topic_model.get_topic(topic_id)[:10]}")

Topic != -1 的过滤丢掉 BERTopic 的离群桶(HDBSCAN 聚不进去的文档)。min_topic_size 控制 HDBSCAN 的最小簇规模;BERTopic 库默认 10,本例为本节规模显式设 15。语料超过一万篇时,提到 50 或 100。

第 3 步:评估

两种方法都输出话题词。问题在于这些词连不连贯

  • 主题连贯性(c_v)。 把顶部词对的 NPMI(归一化点互信息)在滑动窗口上下文上组合,聚合成话题向量,再用余弦相似比较这些向量。越高越好。用 gensim.models.CoherenceModelcoherence="c_v"
  • 主题多样性。 所有话题顶部词里不重复词的比例。越高越好(话题不重叠)。
  • 定性审视。 读每个话题的顶部词,看它们是否指代一个真实的东西。人的判断仍是最后一道防线。

三、框架对比

情形
短文本(推文、评论、标题) BERTopic
长文档、话题混合 LDA
无 GPU / 算力受限 LDA 或 NMF
需要文档级多话题分布 LDA
用 LLM 给话题打标签 BERTopic(原生支持)
资源受限的边缘部署 LDA
最大化语义连贯 BERTopic

最大的实战考量是文档长度。BERT 嵌入会截断;LDA 的计数对任意长度都工作。文档比嵌入模型上下文长时,要么分块再聚合,要么用 LDA。

2026 的栈:

  • BERTopic:短文本以及任何语义重要的场景的默认。
  • gensim.models.LdaModel:经典生产 LDA,成熟、久经考验。
  • sklearn.decomposition.LatentDirichletAllocation:实验用的简易 LDA。
  • NMF(非负矩阵分解):LDA 的快速替代,在短文本上质量相当。
  • Top2Vec:设计与 BERTopic 相似,社区较小但在某些基准上不错。
  • FASTopic:更新,在超大语料上比 BERTopic 快。
  • 基于 LLM 的标注:跑任意聚类,再提示模型给每个簇命名。

四、可复用产物

保存为 outputs/skill-topic-picker.md:

--- name: topic-picker description: Pick LDA or BERTopic for a corpus. Specify library, knobs, evaluation. version: 1.0.0 phase: 5 lesson: 15 tags: [nlp, topic-modeling] --- Given a corpus description (document count, avg length, domain, language, compute budget), output: 1. Algorithm. LDA / NMF / BERTopic / Top2Vec / FASTopic. One-sentence reason. 2. Configuration. Number of topics: `recommended = max(5, round(sqrt(n_docs)))`, clamped to 200 for corpora under 40,000 docs; permit >200 only when the corpus is genuinely large (>40k) and note the increased compute cost. `min_df` / `max_df` filters and embedding model for neural approaches also belong here. 3. Evaluation. Topic coherence (c_v) via `gensim.models.CoherenceModel`, topic diversity, and a 20-sample human read. 4. Failure mode to probe. For LDA, "junk topics" absorbing stopwords and frequent terms. For BERTopic, the -1 outlier cluster swallowing ambiguous documents. Refuse BERTopic on documents longer than the embedding model's context window without a chunking strategy. Refuse LDA on very short text (tweets, reviews under 10 tokens) as coherence collapses. Flag any n_topics choice below 5 as likely wrong; flag >200 on corpora under 40k docs as likely over-splitting.

五、练习

  1. 基础:在 20 Newsgroups 数据集上用 5 个话题跑 LDA,打印每话题前 10 词,手工给每个话题命名。算法找到真实类别了吗?
  2. 进阶:在同一 20 Newsgroups 子集上跑 BERTopic,对比找到的话题数、顶部词、定性连贯性与 LDA。哪个更干净地浮现真实类别?
  3. 挑战:在你的语料上对 LDA 与 BERTopic 都算 c_v 连贯性,各跑 5、10、20、50 话题,绘制连贯性 vs 话题数曲线,报告哪种方法在不同话题数下更稳定。

本节要点回顾

  1. 主题模型无监督回答「这批文档在讲什么」:无标注、未知类别数,也能取回一小组话题与每篇文档的分布。
  2. LDA 是生成式贝叶斯:文档=话题混合,话题=词分布,推断用坍缩 Gibbs 或变分贝叶斯反推两套分布。
  3. LDA 输出两矩阵:doc_topic(行和为 1)与 topic_word(行和为 1),天然支持混合归属。
  4. BERTopic 是四阶段流水线:BERT 编码→UMAP 降维→HDBSCAN 聚类→类内 TF-IDF 抽词。
  5. 一篇文档一个话题是 BERTopic 的限制:对长文要么分块聚合,要么换 LDA。
  6. c_v 连贯性用顶部词对的 NPMI 在滑动窗口上组合,是话题质量的头号量化指标。
  7. 主题多样性度量话题间是否重叠,越高越好。
  8. LDA 要 CountVectorizer:要原始计数而非 TF-IDF,且需去停用词与 min/max_df 过滤。
  9. min_topic_size 随语料调:BERTopic 默认 10,过万文档提到 50~100。
  10. 文档长度是头号取舍:短文本/语义重要选 BERTopic,长文/多话题分布选 LDA。

下一节,我们换到「生成」一侧——进入「Transformer 前的文本生成」,看 n-gram、RNN/LSTM、seq2seq 这些前 Transformer 时代的方法如何一个 token 一个 token 地吐出文本。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U