愛林

[Text Mining] 텍스트 마이닝 - Scikit learn 을 이용한 토픽 모델링(Topic Modeling), LDA 본문

Data Science/Text Mining, 자연어처리

[Text Mining] 텍스트 마이닝 - Scikit learn 을 이용한 토픽 모델링(Topic Modeling), LDA

愛林 2022. 8. 28. 01:24
반응형

이전에는 단어 빈도 분석법을 알아보았다.

단어 빈도 분석은 문서 내의 대체적인 내용은 짐작할 수 있지만,

여러 문서에 포함된 구체적인 내용은 알기가 어렵다.

또한, 눈, 벌 같은 동음 이의어가 쓰여진 경우 그 의미를 찾기는 더 어려워진다.

우리는 전반적으로 앞뒤의 내용을 듣고 내용을 지레짐작할 수 있으나,

컴퓨터는 바보라서 이를 파악할 수 없다.

그래서 사용하는 토픽 모델링은 컴퓨터가 주제를 파악할 수 있도록 도와준다.

 

이번에는 토픽모델링에 대해 알아보자.

https://wndofla123.tistory.com/72

 

[Text Mining] 텍스트 마이닝 - 카운트 기반 문서 표현

거의 한 달만에 해보는 텍스트 마이닝 .. 카운트 기반의 문서 표현 컴퓨터는 바보다. 그래서 바로바로 글자를 읽지 못해서, 우리가 숫자로 이루어진 벡터로 바꾸어주어야 안다. 그래서 이전에는

wndofla123.tistory.com


토픽 모델링

 

토픽 모델링은 여러 문서에 공통적으로 내재된 주제를 분석해준다.

내재된 주제는 추상적이고, 명시되어 있지 않다.

예를 들어 대통령 연설문 100개가 있을때, 어떤주제에 대해 전반적으로 애기하는 지가

명시되어 있지 않다는 말이다.

 

주제는 단어들의 집합으로 표현되었을 때, 그 단어가 쓰인 비중을 통해 판단한다.

그래서 분석가의 주관적인 해석이 많이 들어간다.

주관적인 해석이지만, 다른 사람이 보았을 때도 그렇구나 하는 주제로 선택해야 한다.

 

토픽모델링은 단어 빈도 분석보다 더 구체적이고 깊이 있는 내용의 분석이 가능하게 만들어준다.

아래는 청와대 청원 데이터에 대해서 단어 빈도만 본 것과 주제를 분석한 것의 차이이다.

왼쪽보다 오른쪽이 훨씬 더 주제를 파악하기가 쉽다.

오른쪽처럼 저렇게 그룹을 지어주면, 분석가가 각 단어를 보고 주제명을 붙여주어야 한다.

위의 '외교' 주제에서

'북한, 한국, 우리, 대통령, 정부, 미국, 대한민국, 일본 ...' 

등의 그룹은 컴퓨터가 그룹지어준 것이지만,

'외교' 라는 주제명은 분석이 끝난 후 분석가가 지어준 것이다.

분석가가 붙여준 주제명은, 다른 사람도 단어들을 보았을 때 주제와 연관이 된다고

생각하는 주제명을 붙여주어야 한다.

 

 


LDA (Latent Dirichlet Allocation)

 

LDA 는 이러한 토픽 모델링에서 가장 많이 사용되는 모형이다.

토픽 모델링의 기본 가정은

 

문서들이 쓰여질때 그 문서를 구성하는 몇 개의 토픽이 존재하며,
각 토픽은 단어의 집합으로 구성된다.

 

이다.

그리고 각 토픽들은 비중이 있으며, 토픽 내 단어에도 비중이 있다.

예를 들어, 청와대 청원 글에는 육아, 주택, 일자리 등의 주제가 있다.

그리고 이 주제 중 '육아' 를 서술하기 위해서

'아이', '부모'. '어린이집', '교사' 등과 같은 단어를 사용하게 된다.

 

이러한 주제들은 문서 내에서 명시적으로 드러나지는 않고, (물론 단어가 사용될 수는 있지만)

내재되어 있다. 우리는 LDA를 사용하여 이렇게 내재된 토픽을

유추하고자 할 것이다.

LDA 는 이에 필요한 통계적 방법론이다.

 


문서와 토픽, 단어의 관계

 

문서는 여러 개의 토픽(주제) 로 구성된다. 그리고 한 토픽은 여러 문서에

공통으로 존재할 수 있다.

 

토픽은 여러 개의 단어로 구성된다.

그리고 한 단어는 여러 토픽에 공통으로 존재할 수 있다.

동일한 토픽은 동일한 단어로 구성된다.

 

 

θ : 문서의 토픽 분포

 

문서를 구성하는 K개 토픽의 비중(확률)

말뭉치의 전체 토픽이 3개이면 문서는 [0.7, 0.2, 0.1] 과 같은 토픽 비중을 가질 수 있다.

이 때 첫째 토픽의 비중이 70%라는 것이다.

각 토픽의 비중을 모두 합치면 1이 되어야 한다.

 

Ex: 청원 A의 토픽 분포가 [0.7, 0.2, 0.1] 일 때,

A문서는 육아에 대한 주제를 70%정도로 다루고, 주택에 대해서는 20%,

일자리에 대해서 10%를 다루는 것이다.

 

 

φ : 토픽의 단어 분포

 

토픽을 구성하는 n개 단어(말뭉치의 모든 단어)의 비중이다.

단어가 5개라고 하면, 토픽은 [0.4, 0.3, 0, 0, 0.3] 과 같은 값을 가질 수 있다.

이 경우 이 토픽은 3,4번째 단어와는 관계가 없고 나머지 단어로

구성되며, 토픽에서 각각 40%, 30%, 30% 를 차지한다고 볼 수 있다.

 

Ex: 어떤 토픽의 단어 분포가 [아이 : 0.4, 부모 :0.3, 주택:0, 부동산:0, 어린이집:0.3] 일 때,

이 토픽은 아이, 부모, 어린이집과 관계가 있고, 주택과 부동산과는

관련이 없다. 이를 통해서 이 토픽은 '육아' 에 관련된 것이라고 짐작할 수 있다.

 

 

디리클레 분포의 정의

 

디리클레 분포는 연속 확률분포의 하나로, n차원의 실수 벡터 중 벡터의 요소가 양수이며

모든 요소를 더한 값이 1인 경우에 대해서 확률값이 정의되는 분포이다.

 

토픽의 수를 n개라고 하면 , 문서의 토픽 분포와 일치하는 형태를 가진다.

문서에서 각 토픽의 비중은 합이 1이 되어야한다. 확률이니까.

모든 문서의 토픽 분포는 디리클레 분포의 모양을 결정하는 파라미터(α) 에 영향을 받는다.

 

단어의 수를 n개라고 하면, 토픽의 단어 분포와 일치하는 형태를 가진다.

토픽에서 각 단어의 비중은 합이 1이 되어야 한다.

모든 토픽의 단어 분포는 디리클레 분포의 모양을 결정하는 파라미터(β) 에 영향을 받는다.

 

 


LDA 모형의 구조

 

 

LDA 모형은 M개의 문서가 있고,

K개의 토픽으로 구성되며,

N개의 단어 W로 이루어져 있다.

 

이 때 문서는 말뭉치 전체 단어에 대한 카운트 벡터로 표현이 가능하다.

 

θ : 문서의 토픽 분포, 문서를 구성하는 K개 토픽의 비중. 매개변수 α에 영향 

φ : 토픽의 단어 분포, 토픽을 구성하는 N개의 단어의 비중, 매개변수 β에 영향

z : 문서에 있는 단어 W가 속한 토픽.

 

여기서, 토픽의 수 K가 가장 중요하다.

LDA 모형의 구조

 

 

LDA 모형의 목표는

 

문서의 단어 분포(카운트 벡터) 를 이용해 문서의 토픽 분포와 토픽의 단어 분포를
동시에 추정하는 것

 

이다.

 

LDA 모형의 입력 정보는 말뭉치의 특성 벡터(카운트 벡터) 이고,

출력은 문서와 토픽의 분포가 출력된다.

매개변수는 위에서 말한 것과 같이  α,  β, 토픽의 수(K) 가 있다.

매개변수들은 적절하게 잘 선택하는 것이 중요하며,

설정된 값에 따라서 LDA  모형의 성능이 결정된다.

 

 

모형의 평가 방법은

혼잡도와 토픽 응집도가 있다.

 

 

혼잡도. 혼란도 (Perplexity)

 

혼잡도는 특정한 확률 모형이실제로 관측되는 값을 얼마나 유사하게 예측해내는지는 평가한다.추정한 모형이 문서 집합을 얼마나 유사하게 생성하는 지가 관건이다.값이 작을수록 토픽 모델이 문서 집합을 잘 반영한 것이다.

 

 

토픽 응집도 (Coherence)

 

각 토픽에서 상위 비중을 차지하는 단어들이 의미적으로 얼마나 유사한 지를 평가한다.

높을 수록 토픽이 단일 주제를 잘 표현한다고 볼 수 있다.

 


20 Newsgroups Data 를 이용한 토픽 모델링 실습

 

 

20 뉴스그룹 데이터셋 (20 Newsgroups Dataset) 을 이용하여

LDA 토픽 모델링을 실습해보자.

20 뉴스그룹 데이터셋은 문서 분류의 성능 측정을 위해서 가장 많이 사용되는

데이터셋중 하나이다.

뉴스그룹은 초창기 인터넷에서 이메일과 함께 가장 많이 사용된 유즈넷 게시판에서 제공한

특정 주제들로 이루어진 데이터이다.

총 20개의 다른 주제의 뉴스그룹에서 수집한 문서들의 집합이다.

 

사이킷런 내부의 sklearn.datasets.fetch_20newsgroups 모듈로 제공한다.

subset 매개변수로 train set ,test set 을 지정할 수 있다/

categories 매개변수로 원하는 주제를 선정할 수 있다. (20개중 5개)

remove 로 필요없는 부분은 삭제 가능하다.

 

 

학습 데이터셋 20개의 카테고리 중에서 6개를 가져오고,

나중에  주제 별로 토픽 모델링이 잘 되는 지 확인해보자,

가져온 문서들은 모두 카운트 벡터로 변환한다.

 

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer

# 6개의 주제를 선정
categories = ['alt.atheism', 'talk.religion.misc','comp.graphics', 'sci.space',
             'comp.sys.ibm.pc.hardware', 'sci.crypt']
newsgroups_train = fetch_20newsgroups(subset = 'train', categories= categories)

# 토큰화에 사용할 정규표현식 지정 후 자체적인 불용어 처리 진행
cv = CountVectorizer(token_pattern = "[\w']{3,}", stop_words = 'english',
                    
                    # 최대 단어 수는 2000
                    max_features = 2000, min_df = 5, max_df = 0.5)
reviews_cv = cv.fit_transform(newsgroups_train.data)

주제는 각각 무신론, 종교, 컴퓨터그래픽, 우주, 시스템 하드웨어, 암호화 6개이다.20newsgroup 데이터를 train 데이터로 넣어주고, 위의 6개 카테고리로 카테고리화 한다.토큰화에 사용할 정규표현식 지정 후 자체적으로 불용어 처리를 진행한다. (stop_words = 'english')최대 단어 수는 2000개, 5개 이하 사용된 단어수와 전체의 50%를 차지하는 단어 수는 포함하지 않는다.fit_transform 을 이용하여 train data 를 학습시킨 후 reviews_cv 에 저장해준다.

 

먼저 sklearn 의 LDA 지원 패키지를 사용해보자.

 

 

sklearn.decomposition.LatentDirichletAllocation 

 

사이킷 런의 LDA 지원 패키지이다.

 

-  n_components : 토픽의 수이다. 가장 중요한 매개변수이므로 최적화가 필요하다.

 

- doc_topic_prior : 위에서 말한 α. 문서의 토픽 분포를 결정한다. 1을 많이 사용한다.

 

- topic_word_prior : 위에서 말한 β. 토픽의 단어 분포를 결정한다. 0.1 을 많이 사용한다.

 

- max_iter : 알고리즘의 최대 반복 횟수이다. 기본값은 10으로, 5 이하로는 잘 사용하지 않는다.

 

- learning_method : 'batch' 가 'online'  보다 성능이 좋은 대신에 느리다.

 

- n_jobs : 사용할 프로세서의 수이다. -1이면 모든 프로세서를 사용한다.

 

- random_state : 결과를 동일하게 만들기 위한 랜덤시드를 지정한다.

 

from sklearn.decomposition import LatentDirichletAllocation
import numpy as np
np.set_printoptions(precision = 3) # 표시할 유효숫자의 범위를 소수점 이하 3자리로 설정

# LDA 객체 생성
lda = LatentDirichletAllocation(n_components = 10, # 추출할 topic의 수
                               max_iter = 5,  # 시간 절약을 위한 최대 횟수 5
                               topic_word_prior = 0.1,   # 주요 예제에서 사용되는 값
                               doc_topic_prior = 1.0,    # 주요 예제에서 사용되는 값
                               learning_method = 'online',   # 빠른 속도를 선택
                               n_jobs = -1,     # 사용 processor 수를 최대로
                               random_state = 0)

# review_cv 에 토픽 모델링을 수행
reviews_topics = lda.fit_transform(reviews_cv)
# 변환된 결과를 각 문서의 토픽 별 비중, 10개 토픽에 대한 0~1 사이의 값이다.
print("Shape of reviews_topics : ", reviews_topics.shape)

# 첫 문서에대한 토픽 비중 - 첫째 토픽에 대한 비중이 가장 높다.
print("Sample of reviews_topics : ", reviews_topics[0])

#토픽의 비중을 전부 더함으로써 말뭉치 전체에 나타난 토픽의 비중을 볼 수 있다.
gross_topic_weights = np.mean(reviews_topics, axis = 0)
print("Sum of topic weights of documents :", gross_topic_weights)

# components_ 속성은 토픽별 단어의 분포를 보여준다.
print('Shpae of topic word distributuon : ', lda.components_.shape)
Shape of reviews_topics :  (3219, 10)
Sample of reviews_topics :  [0.903 0.007 0.027 0.008 0.007 0.008 0.007 0.007 0.007 0.018]
Sum of topic weights of documents : [0.087 0.083 0.085 0.115 0.115 0.126 0.098 0.072 0.07  0.148]
Shpae of topic word distributuon :  (10, 2000)

 

Shape of reviws_topics 는 (3219, 10) 이 나왔다. 문서의 수가 3219개, 10개의 토픽이라는 말이다.

 

Sample of reviews_topics 는 첫 번째 문서의 topic 분포이다. (0번째)

1번째가 90%를 차지하는 것을 확인할 수 있다. 0번째 문서는 1번째 토픽에 관한 문서임을 확인할 수 있다.

 

Sum of topic weights of documents 는 전체 문서의 토픽 비중이다. 마지막 토픽이 값이 커서 눈에 띈다.

전체 문서에서 마지막 토픽의 문서가 가장 많다는 것을 확인할 수 있다. 15% 가량 되는 것 같다.

 

topic word 분류에서 10개 토픽으로 2000개의 특성이 분류됐다는 것을 알 수 있다.

나머지는 걸렀나보다.

당연하다. 최대 단어 수를 2000개로 설정해서 LDA 모델을 만들었기 때문이다.

 

# 토픽 별로 비중이 높은 상위 n개의 단어를 출력하는 함수

def print_top_words(model, feature_names, n_top_words):
    for topic_idx, topic in enumerate(model.components_):
        print("Topic #%d: " % topic_idx, end='')
        print(
            ", ".join([feature_names[i] for i in topic.argsort()[:-n_top_words - 1:-1]])
        )
# 위 slicing에서 맨 뒤 -1은 역순을 의미, 역순으로 했을 때 처음부터
# n_top_words까지
    print()
print_top_words(lda,cv.get_feature_names_out(), 10)

토픽 별로 비중이 높은 상위 n개의단어를 출력하는 함수를 정의했다.

안의 값을 가지고 있는 인덱스를 반환하게 하고, -1 로 설정하여 역순. 즉 빈도가 높은 순으로정렬되도록 하는 함수이다.

 

함수를 사용해서 10개의 단어를 출력해보았다.

10개의 토픽으로 분류된다.

 

Topic #0: com, morality, keith, article, sgi, think, sandvik, objective, caltech, moral
Topic #1: image, file, graphics, files, ftp, available, software, use, data, mail
Topic #2: space, nasa, access, launch, earth, orbit, shuttle, digex, lunar, satellite
Topic #3: article, com, just, don't, like, i'm, nntp, university, host, posting
Topic #4: key, clipper, chip, encryption, com, government, law, keys, use, escrow
Topic #5: scsi, com, bit, ibm, bus, know, windows, thanks, card, university
Topic #6: host, gov, nntp, posting, university, distribution, nasa, ___, world, com
Topic #7: drive, com, disk, hard, controller, drives, dos, tape, floppy, problem
Topic #8: key, public, message, faq, mail, pgp, des, group, uni, ripem
Topic #9: god, people, don't, jesus, believe, just, does, say, think, know

 

토픽 1은 com, morality 등이 사용된 것을 보아 graphics 에 관한 내용인 것 같다.

토픽 2는 space, nasa ,launch 등이 사용된 것을 보아 space 에 관한 내용일 것이다.

토픽 3은 잘 모르겠다.

토픽 7은 drive, com, disk 를 보아 hardware 에 관한 내용일 것이다.

토픽 9는 god, jesus 를 보아 종교에 관한 내용일 것이다.

 

이렇게 각 단어를 보고 topic 을 유추할 수 있다.

 

 

최적 토픽 수 (K) 찾기

최적 토픽 수를 찾는 함수를 perplexity(X) 메서드를 이용하여 만들어보자.

# 최적 토픽 수를 찾는 함수 선언
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

def test_perplexity(cv, start=10, end=30, max_iter=5, topic_word_prior=0.1,
                    doc_topic_prior=1.0):
    iter_num = []     # 시도한 n_components 값을 저장할 리스트
    per_value = []    # n_components에 대한 혼란도 지정 리스트
    for i in range(start, end + 1):     # i 는 시도할 n_components 값이다.
        lda = LatentDirichletAllocation(n_components = i, max_iter=max_iter,
            topic_word_prior= topic_word_prior, doc_topic_prior=doc_topic_prior, 
            learning_method='batch', n_jobs= -1, random_state=7)
        lda.fit(cv)
        iter_num.append(i)
        per_value.append(lda.perplexity(cv))  # 계산된 혼란도 값 저장
    plt.plot(iter_num, per_value, 'g-')    # n_components 와 혼란도를 그래프로 시각화
    plt.show()
    return start + per_value.index(min(per_value))   # 혼란도가 가장 낮은 n_components

print("n_components with minimum perplexity:", 
      test_perplexity(reviews_cv, start=6, end=15))

6부터 15까지의 토픽 수를 X축,

혼란도를 Y축에 넣는다.

그리고 그래프를 시각화한다.

혼란도가 가장 낮은 n_components 를 따로 알려주도록 한다.

 

n_components with minimum perplexity: 9

 

9가 가장 낮은 n_components 로 결정되었다.

 

다시 넣어서 돌려보자.

 

lda = LatentDirichletAllocation(n_components=9, #추출할 topic의 수를 지정 max_iter=20,
                                topic_word_prior=0.1,
                                doc_topic_prior=1.0,
                                learning_method='batch',
                                n_jobs=-1,
                                random_state=7)
reviews_topics = lda.fit_transform(reviews_cv)
print_top_words(lda, cv.get_feature_names_out(), 10)

 

Topic #0: image, available, file, data, ftp, mail, information, files, graphics, internet
Topic #1: nasa, gov, space, ___, orbit, posting, center, earth, jpl, research
Topic #2: com, keith, morality, caltech, sgi, objective, moral, think, host, article
Topic #3: com, jesus, just, article, know, god, posting, host, nntp, john
Topic #4: people, god, don't, think, does, say, believe, just, like, evidence
Topic #5: scsi, drive, card, ide, controller, bus, disk, hard, bit, drives
Topic #6: space, access, launch, article, year, just, digex, henry, toronto, moon
Topic #7: key, encryption, clipper, chip, com, government, keys, security, use, public
Topic #8: com, university, posting, nntp, article, host, thanks, i'm, know, help

 

0부터 8까지 9개의 토픽 그룹이 생성되었다.

 

토픽 0은 graphics 에 관한 내용같다.

토픽 1은 space,

토픽 2는 아마 graphics,

토픽 3은 모호하다. 종교관련일 것 같다.

토픽 4도 종교 관련인 것 같다.

토픽 5는 아마 hardware 내용이 아닐까 라고 추측해본다.

토픽 6는 space,

토픽 7은 암호화에 관련된 내용

토픽 8도 뭔가 애매하다.

 

 

 

다음시간에는 sklearn 패키지가 아닌 다른 라이브러리로 토픽 모델링을 진행해보자.

 

 

 ※ 해당 자료는 모두 공공 빅데이터 청년 인턴 교육자료들을 참고합니다.

 

 

 

Comments