본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
Latent Dirichlet allocation; LDA; 잠재 디리클레 할당
각 문서를 여러 토픽의 혼합으로, 각 토픽을 단어의 확률 분포로 보고, 두 분포에 디리클레 사전 분포를 둔 계층적 베이즈 생성 모형

잠재 디리클레 할당(Latent Dirichlet allocation, LDA)은 문서 묶음에서 숨은 주제(토픽)를 찾는 대표적인 토픽 모델링 기법이다. "문서는 몇 개 토픽이 섞인 것이고, 토픽은 특정 단어들이 자주 나오는 분포"라고 가정한 생성 모형을 세운 뒤, 관측된 단어만으로 토픽-단어 분포와 문서-토픽 비율을 거꾸로 추정한다. 예를 들어 뉴스 기사 묶음에서 "주식, 금리, 환율"이 높은 토픽과 "선수, 경기, 골"이 높은 토픽을 레이블 없이 찾아낸다.

2003년 데이비드 블레이(David Blei), 앤드루 응(Andrew Ng), 마이클 조던(Michael I. Jordan)이 JMLR에 발표했다.[1] 같은 구조의 모형은 2000년 프리처드(J. K. Pritchard) 등이 개체군 유전학에서 개체의 조상 집단 비율을 추정하는 모형으로 먼저 제안했다.[2] 텍스트 마이닝, 사회과학의 대량 문헌 분석, 계량서지학 등에서 쓰인다. 같은 약어를 쓰는 선형 판별 분석(Linear discriminant analysis)과는 다른 기법이다(판별 분석 참고).

  • 잠재 의미 분석(LSA): 문서 단어 행렬을 특이값 분해해 저차원 "의미 공간"을 얻는다. 확률 모형이 아니어서 성분 값에 음수가 섞이고 해석이 어렵다.
  • 확률적 잠재 의미 분석(pLSA): 1999년 토마스 호프만(Thomas Hofmann)이 문서마다 토픽 혼합 비율을 두는 확률 모형으로 바꿨다.[3] 하지만 문서별 혼합 비율이 학습 문서마다 따로 있는 매개변수라서 문서 수에 비례해 매개변수가 늘고 과적합되기 쉬우며, 새 문서에 확률을 매기는 방법이 자연스럽지 않다.
  • LDA: 문서별 혼합 비율 자체를 디리클레 분포에서 뽑히는 확률 변수로 만들어 pLSA를 완전한 베이즈 생성 모형으로 바꿨다. 매개변수 수가 문서 수와 무관해지고 새 문서도 같은 방식으로 다룬다.[1]
기호 뜻
K,V,D 토픽 수, 어휘 크기, 문서 수
θd∈ΔK−1 문서 d의 토픽 비율(합이 1인 K차원 벡터)
φk∈ΔV−1 토픽 k의 단어 분포
zdn,wdn 문서 d의 n번째 단어에 배정된 토픽, 그 단어
α,β 문서-토픽, 토픽-단어 디리클레 사전 분포의 집중 매개변수
  • 토픽마다 단어 분포를 뽑는다: φk∼Dir(β), k=1,…,K
  • 문서마다:
    • 토픽 비율을 뽑는다: θd∼Dir(α)
    • 각 단어 자리 n마다 토픽을 고르고 zdn∼Cat(θd), 그 토픽의 분포에서 단어를 뽑는다 wdn∼Cat(φzdn)

결합 분포는 다음과 같다.

p(w,z,θ,φ∣α,β)=∏k=1Kp(φk∣β)∏d=1Dp(θd∣α)∏n=1Ndp(zdn∣θd)p(wdn∣φzdn)

단어 순서는 무시하는 단어 가방 가정이다. 원 논문은 φ를 고정 매개변수로 두고 변분 EM으로 추정했으며, φ에도 디리클레 사전 분포를 둔 형태(평활화 LDA)를 함께 제시했다. 지금 보통 LDA라 하면 이 평활화 형태다.

디리클레 사전 분포의 역할

편집 원본 편집

디리클레 분포는 범주 분포의 켤레 사전 분포라서 사후 분포 계산이 깔끔해진다. 집중 매개변수가 1보다 작으면 확률 질량이 단체(simplex)의 모서리에 몰려, 대부분의 성분이 0에 가깝고 몇 개만 큰 희소한 벡터가 뽑힌다.

  • α가 작다: 문서 하나가 소수의 토픽만 다룬다고 가정한다.
  • β가 작다: 토픽 하나가 소수의 단어에 집중한다고 가정한다.
  • 대칭 사전 분포(모든 성분이 같은 값)가 기본이다. 월러크(Hanna Wallach) 등은 문서-토픽 쪽 사전 분포를 비대칭으로 두고 하이퍼파라미터를 학습하면 토픽 수나 치우친 단어 빈도에 덜 민감해지는 반면, 토픽-단어 쪽 비대칭은 이득이 없었다고 보고했다.[4] gensim의 alpha="auto"가 데이터에서 비대칭 사전 분포를 학습한다.

관측된 단어 w로 잠재 변수의 사후 분포 p(z,θ,φ∣w)를 구해야 하는데, 정규화 상수 계산에 KN개 토픽 배정을 모두 더해야 해서 정확한 계산은 불가능하다. 근사 방법이 여럿 있다.

방법 핵심 구현
변분 EM(평균장 변분 베이즈) 사후 분포를 서로 독립인 디리클레·범주 분포의 곱으로 근사하고 ELBO를 좌표 상승으로 최대화. E 단계에서 문서별 변분 매개변수, M 단계에서 토픽을 갱신[1] 원 논문 C 구현
붕괴 깁스 샘플링 θ,φ를 적분해 없애고 토픽 배정 z만 깁스 샘플링[5] MALLET 등
온라인 변분 베이즈 미니배치마다 토픽의 변분 매개변수를 확률적 자연 경사로 갱신. 문서를 한 번만 훑어도 되고 스트리밍 데이터에 맞다[6] gensim LdaModel, scikit-learn learning_method="online"
희소성 활용 샘플러 문서·단어마다 실제로 쓰이는 토픽만 훑어 깁스 샘플링을 빠르게 함 대규모 토픽 수 학습

붕괴 깁스 샘플링

편집 원본 편집

디리클레-범주 켤레성 덕분에 θ와 φ를 해석적으로 적분할 수 있다. 그 결과 단어 하나의 토픽을 나머지 배정이 주어졌을 때 다시 뽑는 완전 조건부 분포가 단순한 개수 비율이 된다.[5]

p(zdn=k∣z¬dn,w) ∝ (ndk¬dn+α)⋅nkwdn¬dn+βnk¬dn+Vβ

ndk는 문서 d에서 토픽 k에 배정된 단어 수, nkw는 토픽 k에 배정된 단어 w의 수, nk는 토픽 k에 배정된 전체 단어 수이고, ¬dn은 지금 다시 뽑는 단어를 뺀 개수라는 뜻이다. 첫 항은 "이 문서가 이미 많이 쓰는 토픽", 둘째 항은 "이 단어를 많이 내는 토픽"을 선호한다. 표본을 충분히 돌린 뒤 다음처럼 추정한다.

θ^dk=ndk+αNd+Kα,φ^kw=nkw+βnk+Vβ

그리피스와 스테이버스는 이 방법으로 PNAS 초록을 분석해 과학 분야별 토픽과 그 시대별 변화를 보였다.[5] 깁스 샘플링 일반론은 마르코프 연쇄 몬테카를로 문서 참고.

하이퍼파라미터와 토픽 수

편집 원본 편집
  • 토픽 수 K: 미리 정해야 한다. 여러 K로 학습해 아래 지표와 사람의 판단을 함께 보고 고른다. 계층적 디리클레 과정(HDP)은 토픽 수를 데이터에서 추론하는 비모수 확장이다.[7]
  • 퍼플렉서티: 떼어 둔 문서의 단어당 로그 우도로 계산한 exp⁡(−log⁡p(wtest)/Ntest). 낮을수록 좋지만, 창(Jonathan Chang) 등은 사람 대상 실험에서 보류 데이터 우도가 좋은 모형이 오히려 의미가 덜 분명한 토픽을 낼 수 있음을 보였다.[8]
  • 토픽 일관성(coherence): 토픽 상위 단어들이 실제 문서에서 함께 나타나는 정도로 해석 가능성을 근사한다. 문서 동시 출현 기반 UMass, 슬라이딩 창과 점별 상호 정보량(PMI) 기반 C_UCI·C_NPMI, 이를 조합한 C_V 등이 있고, 뢰더(Michael Röder) 등이 여러 측정을 체계적으로 비교했다.[9]
  • 전처리: 결과는 전처리에 크게 좌우된다. 불용어, 너무 드물거나 너무 흔한 단어를 지우고(gensim Dictionary.filter_extremes), 한국어는 형태소 분석으로 명사 등 내용어만 남겨 넣는 것이 일반적인 절차다. 어절 단위로 넣으면 "주식이", "주식을"이 다른 단어가 되어 토픽이 흩어진다.

NumPy로 붕괴 깁스 샘플링

편집 원본 편집

위 식을 그대로 옮긴 최소 구현이다. 문서 6개, 토픽 2개의 장난감 예다(NumPy 2.1에서 실행).

import numpy as np

def lda_gibbs(docs, V, K, alpha=0.1, beta=0.01, n_iter=500, seed=0):
    """docs: 단어 id 리스트의 리스트. 붕괴 깁스 샘플링(Griffiths & Steyvers, 2004)."""
    rng = np.random.default_rng(seed)
    n_dk = np.zeros((len(docs), K))   # 문서 d에서 토픽 k에 배정된 단어 수
    n_kw = np.zeros((K, V))           # 토픽 k에 배정된 단어 w의 수
    n_k = np.zeros(K)
    z = [rng.integers(K, size=len(d)) for d in docs]
    for d, doc in enumerate(docs):
        for w, k in zip(doc, z[d]):
            n_dk[d, k] += 1; n_kw[k, w] += 1; n_k[k] += 1
    for _ in range(n_iter):
        for d, doc in enumerate(docs):
            for i, w in enumerate(doc):
                k = z[d][i]                       # 현재 단어를 집계에서 뺀다
                n_dk[d, k] -= 1; n_kw[k, w] -= 1; n_k[k] -= 1
                p = (n_dk[d] + alpha) * (n_kw[:, w] + beta) / (n_k + V * beta)
                k = rng.choice(K, p=p / p.sum())  # 완전 조건부 분포에서 다시 뽑기
                z[d][i] = k
                n_dk[d, k] += 1; n_kw[k, w] += 1; n_k[k] += 1
    theta = (n_dk + alpha) / (n_dk + alpha).sum(axis=1, keepdims=True)
    phi = (n_kw + beta) / (n_kw + beta).sum(axis=1, keepdims=True)
    return theta, phi

texts = [
    "주식 금리 환율 주식 투자 금리".split(),
    "환율 투자 주식 채권 금리".split(),
    "축구 골 선수 경기 축구 감독".split(),
    "경기 선수 감독 골 야구".split(),
    "주식 채권 투자 환율".split(),
    "야구 선수 경기 골 축구".split(),
]
vocab = sorted({w for t in texts for w in t})
wid = {w: i for i, w in enumerate(vocab)}
docs = [list(map(wid.get, t)) for t in texts]
theta, phi = lda_gibbs(docs, len(vocab), K=2)
for k in range(2):
    print(f"토픽 {k}:", [vocab[i] for i in phi[k].argsort()[::-1][:4]])
print("문서별 토픽 비율:", theta.round(2)[:, 0])
토픽 0: ['주식', '투자', '환율', '금리']
토픽 1: ['축구', '골', '선수', '경기']
문서별 토픽 비율: [0.98 0.98 0.02 0.02 0.98 0.02]

실제 구현은 반복마다 단어 순서를 섞고, 초기 구간(번인)을 버린 뒤 여러 표본을 평균하며, 하이퍼파라미터도 함께 추정한다.

gensim의 LdaModel은 호프만 등의 온라인 변분 베이즈를 구현한다.[6] 아래는 gensim 4.4에서 실행했다. CoherenceModel은 기본으로 여러 프로세스를 띄우므로 윈도우에서는 if __name__ == "__main__": 안에서 실행해야 한다.

from gensim.corpora import Dictionary
from gensim.models import LdaModel, CoherenceModel

texts = [  # 형태소 분석 등으로 토큰화가 끝난 문서들
    "주식 금리 환율 주식 투자 금리".split(),
    "환율 투자 주식 채권 금리".split(),
    "축구 골 선수 경기 축구 감독".split(),
    "경기 선수 감독 골 야구".split(),
    "주식 채권 투자 환율".split(),
    "야구 선수 경기 골 축구".split(),
]
if __name__ == "__main__":
    dictionary = Dictionary(texts)                   # 단어 -> id
    # 실제 말뭉치에서는 dictionary.filter_extremes(no_below=5, no_above=0.5) 등으로 거른다
    corpus = [dictionary.doc2bow(t) for t in texts]  # 문서 -> [(id, 빈도), ...]
    lda = LdaModel(corpus, num_topics=2, id2word=dictionary, passes=50,
                   alpha="auto", eta="auto", random_state=0)
    for k, words in lda.show_topics(num_topics=2, num_words=4, formatted=False):
        print("토픽", k, [w for w, _ in words])
    print(lda.get_document_topics(corpus[0]))       # 문서 0의 토픽 비율
    cm = CoherenceModel(model=lda, texts=texts, dictionary=dictionary, coherence="c_v")
    print("C_V 일관성:", cm.get_coherence())
토픽 0 ['주식', '금리', '투자', '환율']
토픽 1 ['축구', '경기', '골', '선수']
[(0, np.float32(0.992028))]

일관성 값은 문서 6개짜리 장난감 예에서는 의미가 없어 출력을 생략했다. 실제 말뭉치에서는 여러 토픽 수로 학습해 일관성이 높고 사람이 보기에 해석이 되는 지점을 고른다.

scikit-learn의 LatentDirichletAllocation은 변분 베이즈를 쓰며 기본값은 전체 데이터를 쓰는 learning_method="batch"다(0.20부터). doc_topic_prior(α)와 topic_word_prior(β)를 비워 두면 1 / n_components가 된다. components_는 정규화되지 않은 유사 빈도라서 확률로 쓰려면 행 합으로 나눠야 한다.[10]

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation

docs = [
    "주식 금리 환율 주식 투자 금리", "환율 투자 주식 채권 금리",
    "축구 골 선수 경기 축구 감독", "경기 선수 감독 골 야구",
    "주식 채권 투자 환율", "야구 선수 경기 골 축구",
]
vec = CountVectorizer(tokenizer=str.split, token_pattern=None)  # 공백으로 이미 토큰화됨
X = vec.fit_transform(docs)                                     # 문서-단어 빈도 행렬
lda = LatentDirichletAllocation(n_components=2, max_iter=50, random_state=0).fit(X)
terms = vec.get_feature_names_out()
for k, comp in enumerate(lda.components_):
    print("토픽", k, [terms[i] for i in comp.argsort()[::-1][:4]])
print("문서 0의 토픽 비율:", lda.transform(X[:1]).round(2)[0])
print("퍼플렉서티:", round(lda.perplexity(X), 2))
토픽 0 ['축구', '골', '선수', '경기']
토픽 1 ['주식', '금리', '환율', '투자']
문서 0의 토픽 비율: [0.07 0.93]
퍼플렉서티: 13.12

LDA에는 단어 빈도를 넣는다. TF-IDF 가중치는 생성 모형의 가정(단어 개수)과 맞지 않는다.

모형 바꾼 점
상관 토픽 모형(CTM) 디리클레 대신 로지스틱 정규 분포로 토픽끼리의 상관(유전학 토픽과 질병 토픽이 함께 나옴)을 표현[11]
동적 토픽 모형(DTM) 시간 구간마다 토픽의 단어 분포가 조금씩 변하도록 상태 공간 모형으로 연결[12]
HDP 디리클레 과정으로 토픽 수를 데이터에서 정한다
지도 LDA(sLDA) 문서마다 붙은 반응 변수(평점 등)를 토픽 비율로 예측하도록 함께 학습
저자-토픽 모형, 구조적 토픽 모형(STM) 저자, 발행 시기 같은 문서 메타데이터가 토픽 비율에 영향을 주게 함
신경망 토픽 모형 변분 오토인코더 방식으로 추론망을 학습(변분 오토인코더)
BERTopic 사전 학습 문장 임베딩을 차원 축소·군집화한 뒤 군집별 c-TF-IDF로 토픽 단어를 뽑는다. 생성 모형이 아니다[13]

다른 토픽 모델링 기법과 비교

편집 원본 편집
기법 방식 장점 단점
LSA 문서-단어 행렬의 절단 특이값 분해 빠르고 결정적 음수 성분, 확률 해석 불가
NMF 문서-단어 행렬을 음이 아닌 두 행렬로 분해 빠르고 해석이 쉬움 확률 모형이 아님
LDA 계층적 베이즈 생성 모형 문서별 토픽 혼합 비율, 확장 모형이 많음 단어 가방 가정, 짧은 문서에 약함, 결과가 실행마다 달라짐
BERTopic 임베딩 + 군집화 + c-TF-IDF 문맥 의미 반영, 짧은 문서에 비교적 강함 기본 설정에서 문서 하나에 토픽 하나, 임베딩 계산 비용
  • 단어 순서와 문맥을 버린다. "은행"의 두 가지 뜻을 구분하지 못한다.
  • 트윗·댓글처럼 짧은 문서는 문서 안 동시 출현 정보가 부족해 토픽이 불안정하다.
  • 토픽 수와 전처리에 결과가 민감하고, 실행마다(초깃값, 무작위 시드) 토픽이 달라진다.
  • 토픽은 단어 확률 목록일 뿐이라 이름 붙이기와 해석은 사람의 몫이고 주관이 들어간다.
  • 대규모 언어 모델과 임베딩이 보급된 뒤로는 군집 기반 방법이나 언어 모델로 직접 주제를 요약하는 방법과 함께 비교해 쓴다.
  1. ↑ 1.0 1.1 1.2 David M. Blei, Andrew Y. Ng, Michael I. Jordan, 〈Latent Dirichlet Allocation〉, Journal of Machine Learning Research 3, 2003, pp. 993~1022. JMLR
  2. ↑ J. K. Pritchard, M. Stephens, P. Donnelly, 〈Inference of Population Structure Using Multilocus Genotype Data〉, Genetics 155(2), 2000, pp. 945~959. doi:10.1093/genetics/155.2.945
  3. ↑ Thomas Hofmann, 〈Probabilistic Latent Semantic Indexing〉, SIGIR 1999. doi:10.1145/312624.312649
  4. ↑ Hanna M. Wallach, David Mimno, Andrew McCallum, 〈Rethinking LDA: Why Priors Matter〉, NIPS 2009. NeurIPS
  5. ↑ 5.0 5.1 5.2 Thomas L. Griffiths, Mark Steyvers, 〈Finding scientific topics〉, PNAS 101(suppl. 1), 2004, pp. 5228~5235. doi:10.1073/pnas.0307752101
  6. ↑ 6.0 6.1 Matthew D. Hoffman, David M. Blei, Francis Bach, 〈Online Learning for Latent Dirichlet Allocation〉, NIPS 2010. NeurIPS
  7. ↑ Yee Whye Teh, Michael I. Jordan, Matthew J. Beal, David M. Blei, 〈Hierarchical Dirichlet Processes〉, JASA 101(476), 2006. doi:10.1198/016214506000000302
  8. ↑ Jonathan Chang, Sean Gerrish, Chong Wang, Jordan Boyd-Graber, David M. Blei, 〈Reading Tea Leaves: How Humans Interpret Topic Models〉, NIPS 2009. NeurIPS
  9. ↑ Michael Röder, Andreas Both, Alexander Hinneburg, 〈Exploring the Space of Topic Coherence Measures〉, WSDM 2015. doi:10.1145/2684822.2685324
  10. ↑ scikit-learn, LatentDirichletAllocation
  11. ↑ David M. Blei, John D. Lafferty, 〈A correlated topic model of Science〉, Annals of Applied Statistics 1(1), 2007. doi:10.1214/07-AOAS114
  12. ↑ David M. Blei, John D. Lafferty, 〈Dynamic Topic Models〉, ICML 2006. doi:10.1145/1143844.1143859
  13. ↑ Maarten Grootendorst, 〈BERTopic: Neural topic modeling with a class-based TF-IDF procedure〉, 2022. arXiv:2203.05794