LDA를 활용한 토픽 모델링
topicmodels 패키지를 사용해 문서 말뭉치에 잠재된 주제를 발견합니다.
LDA를 활용한 토픽 모델링은(는) CoddyKit의 무료 R Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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)는 정돈된 단어 개수 티블을 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')는 단어별 토픽 확률인 베타 행렬을 추출합니다. 각 행은 특정 단어가 특정 토픽에서 생성되었을 확률을 나타냅니다. 확률이 높은 단어들이 각 토픽의 주제를 정의합니다.
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()를 사용해 각 토픽에서 베타 값에 따라 정렬하면 면마다 독립적으로 올바른 순서를 적용할 수 있습니다.
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')는 문서별 토픽 확률인 감마 행렬을 추출합니다. 각 행은 특정 문서가 특정 토픽에서 비롯된 것으로 추정되는 비율을 나타냅니다. 토픽 2에 대한 감마 값이 높은 문서는 토픽 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) 응집도 점수(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를 적합한 다음, 베타 행렬을 추출하고 토픽별 상위 단어를 표시합니다.
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에서 베타 행렬은 무엇을 나타낼까요?
복습: 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를 활용한 토픽 모델링” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 R Academy 강의 전체를 잠금 해제할 수 있습니다. R Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“LDA를 활용한 토픽 모델링”에서 뭘 배우나요?
topicmodels 패키지를 사용해 문서 말뭉치에 잠재된 주제를 발견합니다. 브라우저에서 직접 실행하는 실습 코드로 R Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
R Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 R Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“LDA를 활용한 토픽 모델링” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 R Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 R Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 토큰화와 불용어 제거
- TF-IDF와 단어 빈도 분석
- R에서 감성 분석
- LDA를 활용한 토픽 모델링