0Pricing
R Academy · 课时

使用 LDA 进行主题建模

使用 topicmodels 软件包发现文档语料中的潜在主题

使用 LDA 进行主题建模 是 CoddyKit 上的免费 R Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。

什么是主题建模

主题建模是一种无监督机器学习技术,用于发现语料库中潜在的主题结构。潜在狄利克雷分配(LDA)假设每篇文档都是 K 个主题的混合,而每个主题都是关于词语的概率分布。

# LDA key assumptions:
# 1. Each document is a mixture of K topics
# 2. Each topic is a distribution over all vocabulary words
# 3. Words are generated by first picking a topic, then a word from that topic

# Example: K=2 topics in a corpus about tech and cooking
# Topic 1 (Tech):    neural(0.15) python(0.12) algorithm(0.10) ...
# Topic 2 (Cooking): recipe(0.14) flour(0.11) bake(0.10) ...

# A document about 'AI-generated recipes' might be:
# 70% Topic 1 (Tech) + 30% Topic 2 (Cooking)

cat('LDA parameters:\n')
cat('K = number of topics (you choose)\n')
cat('alpha = document-topic sparsity prior\n')
cat('beta  = topic-word sparsity prior\n')

来自 tm 的 DocumentTermMatrix

LDA 需要一个文档-词项矩阵(DTM):行表示文档,列表示词语,单元格表示词语计数。tm 程序包的 DocumentTermMatrix() 或 tidytext 的 cast_dtm() 都可以生成这种格式。

library(tm)

# Build a corpus from raw text
docs <- c(
  'machine learning neural networks training algorithms',
  'deep learning gradient backpropagation optimizer',
  'python scikit numpy pandas machine learning',
  'database sql tables indexes queries joins',
  'relational schema normalization foreign primary',
  'nosql mongodb document store redis queries'
)

corpus <- Corpus(VectorSource(docs))
corpus <- tm_map(corpus, removePunctuation)
corpus <- tm_map(corpus, removeWords, stopwords('english'))
corpus <- tm_map(corpus, stripWhitespace)

dtm <- DocumentTermMatrix(corpus)
cat('DTM shape:', nrow(dtm), 'docs x', ncol(dtm), 'terms\n')
cat('Sparsity: ', round(tm::sparsity(dtm) * 100, 1), '%\n')

来自 tidytext 的 cast_dtm

cast_dtm(document, term, value) 可以将整洁的词频 tibble 直接转换为与 topicmodels 程序包兼容的 DocumentTermMatrix。这样可以让您的处理流程保持 tidyverse 风格。

library(tidytext)
library(dplyr)

# Tidy corpus
corpus <- tibble::tibble(
  doc_id = c(rep(1, 3), rep(2, 3), rep(3, 3), rep(4, 3)),
  text   = c(
    'neural networks deep learning training',
    'gradient descent backpropagation optimizer',
    'machine learning algorithms classification',
    'database sql tables queries indexes',
    'relational schema normalization joins',
    'foreign primary key constraints transactions',
    'recipe bake flour butter oven',
    'cooking temperature simmer saute ingredients',
    'kitchen knife herbs spices garnish'
  )
)

dtm <- corpus |>
  unnest_tokens(word, text) |>
  anti_join(tidytext::stop_words, by = 'word') |>
  count(doc_id, word) |>
  cast_dtm(doc_id, word, n)

cat('DTM:', nrow(dtm), 'docs x', ncol(dtm), 'terms\n')

LDA(dtm, k = 5):拟合模型

LDA(dtm, k = K, control = list(seed = 42)) 使用 Gibbs 抽样或 VEM,拟合包含 K 个主题的 LDA 模型。请始终设置 seed 以确保结果可复现——LDA 具有随机性,如果不固定种子,不同运行结果可能会有所不同。

library(tidytext)
library(dplyr)
library(topicmodels)

# Build DTM
corpus <- tibble::tibble(
  doc_id = c(rep(1,3), rep(2,3), rep(3,3), rep(4,3), rep(5,3), rep(6,3)),
  text = c(
    'neural networks deep gradient','backpropagation training optimizer','learning model layers',
    'database sql queries indexes','relational tables joins foreign','schema normalization constraints',
    'recipe flour bake butter','cooking temperature oven simmer','kitchen knife herbs spices'
  )[rep(c(1,2,3,4,5,6), each=1)]
)

dtm <- corpus |>
  unnest_tokens(word, text) |>
  count(doc_id, word) |>
  cast_dtm(doc_id, word, n)

# Fit LDA with k=3 topics
lda_model <- LDA(dtm, k = 3, control = list(seed = 42))
cat('LDA model fitted: k=3 topics\n')
cat('Class:', class(lda_model), '\n')

tidy(lda, matrix = 'beta'):每个词的主题概率

tidy(lda_model, matrix = 'beta') 提取每个词对应的主题概率(即 beta 矩阵)。每一行给出特定词由特定主题生成的概率。概率较高的词可以定义每个主题所“讨论”的内容。

library(topicmodels)
library(tidytext)
library(dplyr)

# Using the built-in AssociatedPress dataset
data('AssociatedPress', package = 'topicmodels')

lda <- LDA(AssociatedPress[1:50, ], k = 4, control = list(seed = 1234))

# Per-word topic probabilities (beta)
beta_tbl <- tidy(lda, matrix = 'beta')
cat('Beta matrix rows:', nrow(beta_tbl), '\n')
cat('Columns:', names(beta_tbl), '\n')

# Top words per topic
top_terms <- beta_tbl |>
  group_by(topic) |>
  slice_max(beta, n = 5) |>
  ungroup()

cat('\nTop 5 words per topic:\n')
print(top_terms)

将主题词可视化

将每个主题排名靠前的 N 个词绘制为分面柱形图。使用 tidytext 中的 reorder_within() 和 scale_x_reordered(),按 beta 值在每个主题内排序,从而确保每个分面都能独立正确地排序。

library(topicmodels)
library(tidytext)
library(dplyr)
library(ggplot2)

data('AssociatedPress', package = 'topicmodels')
lda <- LDA(AssociatedPress[1:100, ], k = 4, control = list(seed = 42))

tidy(lda, matrix = 'beta') |>
  group_by(topic) |>
  slice_max(beta, n = 8) |>
  ungroup() |>
  mutate(term = reorder_within(term, beta, topic)) |>
  ggplot(aes(term, beta, fill = factor(topic))) +
    geom_col(show.legend = FALSE) +
    facet_wrap(~topic, scales = 'free') +
    scale_x_reordered() +
    coord_flip() +
    labs(x = NULL, y = 'Beta', title = 'Top Words per LDA Topic') +
    theme_minimal(base_size = 10)

tidy(lda, matrix = 'gamma'):每篇文档的主题概率

tidy(lda_model, matrix = 'gamma') 提取每篇文档对应的主题概率(即 gamma 矩阵)。每一行给出估计的某个主题在一篇文档中所占的比例。对于主题 2 的 gamma 值较高的文档,可以认为它们主要“讨论”主题 2。

library(topicmodels)
library(tidytext)
library(dplyr)

data('AssociatedPress', package = 'topicmodels')
lda <- LDA(AssociatedPress[1:30, ], k = 3, control = list(seed = 99))

# Per-document topic proportions (gamma)
gamma_tbl <- tidy(lda, matrix = 'gamma')
cat('Gamma matrix rows:', nrow(gamma_tbl), '\n')

# Dominant topic per document
dominant_topic <- gamma_tbl |>
  group_by(document) |>
  slice_max(gamma, n = 1) |>
  ungroup()

cat('\nDominant topic per document (first 8):\n')
print(head(dominant_topic, 8))

选择 K:主题数量

LDA 要求您预先指定 K。常见方法包括:(1) 困惑度——越低越好,但会随着 K 增大而单调下降;(2) 一致性分数(ldatuning 程序包);(3) 领域知识;(4) 尝试 K = 5、10、20,然后手动检查词语列表。

library(topicmodels)
library(tidytext)

data('AssociatedPress', package = 'topicmodels')

# Evaluate perplexity for K = 2, 3, 4, 5
k_values <- 2:5
perplexities <- vapply(k_values, function(k) {
  model <- LDA(AssociatedPress[1:50, ], k = k,
               control = list(seed = 42))
  perplexity(model)
}, numeric(1))

results <- data.frame(k = k_values, perplexity = round(perplexities, 1))
cat('Perplexity by K:\n')
print(results)
cat('\nNote: lower perplexity = better fit to training data\n')

主题标注

LDA 生成的是没有名称的主题(1、2、3……)。分析人员必须检查每个主题中排名靠前的词,为主题命名。您可以创建一个查找表,将主题编号映射到描述性名称,再将其连接到结果中。

library(topicmodels)
library(tidytext)
library(dplyr)

data('AssociatedPress', package = 'topicmodels')
lda <- LDA(AssociatedPress[1:100, ], k = 4, control = list(seed = 7))

# Top 5 words per topic for manual labelling
tidy(lda, matrix = 'beta') |>
  group_by(topic) |>
  slice_max(beta, n = 5) |>
  summarise(top_words = paste(term, collapse = ', ')) |>
  print()

# After inspecting top words, assign human-readable labels
topic_labels <- tibble::tibble(
  topic = 1:4,
  label = c('Politics', 'Economy', 'Sports', 'Science')  # your interpretation
)

cat('\nTopic labels assigned (example):\n')
print(topic_labels)

在自定义语料库上运行 LDA

下面是一个在自定义语料库上运行 LDA 的完整端到端处理流程:分词、构建 DTM、拟合 LDA、提取 beta 矩阵,并显示每个主题中排名靠前的词。

library(tidytext)
library(dplyr)
library(topicmodels)

# 9 documents across 3 latent topics
docs <- tibble::tibble(
  id   = 1:9,
  text = c(
    'machine learning neural deep training',
    'algorithm gradient backpropagation network',
    'python tensorflow keras model layers',
    'database sql relational tables queries',
    'joins indexes schema foreign primary',
    'transactions normalization constraints',
    'recipe ingredients bake flour butter',
    'cooking temperature simmer oven saute',
    'kitchen knife herbs spices garnish'
  )
)

dtm <- docs |>
  unnest_tokens(word, text) |>
  count(id, word) |>
  cast_dtm(id, word, n)

lda <- LDA(dtm, k = 3, control = list(seed = 123))

tidy(lda, 'beta') |>
  group_by(topic) |>
  slice_max(beta, n = 4) |>
  summarise(words = paste(term, collapse = ', ')) |>
  print()

LDA 的局限

LDA 假设使用词袋模型(词语顺序并不重要),假设主题在整个语料库中保持不变,并且必须提前选择 K。对于动态主题或层次主题,可以考虑 stm 程序包提供的 STM(结构主题模型)或 CTM(相关主题模型)。

# LDA assumptions and limitations
limitations <- data.frame(
  Assumption = c(
    'Bag of words', 'Fixed K', 'Topic independence',
    'Static topics', 'No metadata'
  ),
  Alternative = c(
    'word2vec / BERT',
    'ldatuning for K selection',
    'CTM (Correlated Topic Model)',
    'DTM (Dynamic Topic Model)',
    'STM (Structural Topic Model)'
  )
)

print(limitations, row.names = FALSE)

快速检查

在 LDA 中,beta 矩阵表示什么?

回顾:使用 LDA 进行主题建模

要点:

  • LDA 根据文档-词项矩阵,将 K 个潜在主题发现为词语分布
  • 使用 cast_dtm(document, word, n)(tidytext)或 DocumentTermMatrix()(tm)构建 DTM
  • LDA(dtm, k = K, control = list(seed = 42)) 拟合模型
  • tidy(lda, 'beta') = 每个词对应的主题概率(定义每个主题的内容)
  • tidy(lda, 'gamma') = 每篇文档对应的主题比例(每篇文档的主题内容)
  • 使用困惑度或一致性分数来辅助选择 K
  • 始终手动检查排名靠前的词,为主题标注名称
library(topicmodels)
library(tidytext)
library(dplyr)

data('AssociatedPress', package = 'topicmodels')
lda <- LDA(AssociatedPress[1:20, ], k = 2, control = list(seed = 1))

tidy(lda, 'beta') |>
  group_by(topic) |>
  slice_max(beta, n = 3) |>
  summarise(top_words = paste(term, collapse = ', ')) |>
  print()

常见问题解答

「使用 LDA 进行主题建模」课时是免费的吗?

是的 — 「使用 LDA 进行主题建模」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。

「使用 LDA 进行主题建模」这节课中我会学到什么?

使用 topicmodels 软件包发现文档语料中的潜在主题 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「使用 LDA 进行主题建模」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 R Academy 课中编写并运行代码吗?

能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 分词与停用词移除
  2. TF-IDF 与词频分析
  3. 在 R 中进行情感分析
  4. 使用 LDA 进行主题建模
← 返回 R Academy