0Pricing
R Academy · レッスン

LDA によるトピックモデリング

topicmodels パッケージを使って、文書コーパスに潜在するトピックを発見します。

「LDA によるトピックモデリング」はCoddyKit上の無料R Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはR Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 R Academyコースには全4レッスンが含まれています。

トピックモデリングとは

トピックモデリングは、コーパスに潜在するテーマ構造を発見する教師なし機械学習手法です。Latent Dirichlet Allocation(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にはDocument-Term Matrix(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)は、tidy形式の単語カウント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))は、ギブスサンプリングまたはVEMを使って、K個のトピックを持つLDAモデルを適合します。再現性を確保するため、必ずseedを設定してください。LDAは確率的な手法なので、固定したseedがないと実行ごとに結果が変わります。

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) perplexity:低いほど良いものの、Kの増加に伴って単調に低下します。(2) coherence score(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では、bag-of-wordsモデル(単語の順序は考慮しない)、コーパス全体でトピックは静的であること、Kは事前に選択する必要があることを仮定します。動的または階層的なトピックを扱う場合は、stmパッケージで利用できるSTM(Structural Topic Model)またはCTM(Correlated Topic Model)を検討してください。

# 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個の潜在トピックを発見します
  • DTMは、cast_dtm(document, word, n)(tidytext)またはDocumentTermMatrix()(tm)で構築します
  • LDA(dtm, k = K, control = list(seed = 42))でモデルを適合します
  • tidy(lda, 'beta') = 単語ごとのトピック確率(各トピックを定義するもの)
  • tidy(lda, 'gamma') = 文書ごとのトピック割合(各文書の内容)
  • perplexityまたはcoherence scoreを使って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 によるトピックモデリング」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、R Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 R Academyコースには全4レッスンが含まれています。

「LDA によるトピックモデリング」で何を学びますか?

topicmodels パッケージを使って、文書コーパスに潜在するトピックを発見します。 ブラウザで直接実行するハンズオンコードでR Academyを演習し、24時間対応の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に戻る