반응형

안녕하세요.

이번에는 한국어 데이터셋을 가지고 gensim을 사용한 LDA 토픽 모델링 실습을 해보도록 하겠습니다.

 

데이터 준비

테이터셋으로 '청와대 국민청원' 에 만료된 청원 데이터를 사용하도록 하겠습니다.

아래 사이트 접속 후 

https://github.com/akngs/petitions

 

GitHub - akngs/petitions: 청와대 국민청원 데이터

청와대 국민청원 데이터. Contribute to akngs/petitions development by creating an account on GitHub.

github.com

petition_sampled.csv 를 클릭하여 다운받아 주세요.

전체 데이터는 데이터 용량이 커서 샘플 데이터를 사용하겠습니다.

 

파일 불러오기

다운받은 파일중 5개만 예시로 출력해보겠습니다.

import pandas as pd
df = pd.read_csv('petition_sampled.csv')
df.head()

데이터는 위와 같이 구성되어 있는 것을 확인할 수 있습니다.

여기서 실습에서는 청원 내용을 가지고 토픽 모델링을 해보도록 하겠습니다.

때문에 content컬럼만 따로 뽑아줍니다.

contents = df['content']
contents

또한 원본 데이터에 카테고리 항목이 있는데 

데이터가 몇 개의 카테고리가 있는지도 한번 확인해 보겠습니다.

groupby를 사용하여 카테고리가 몇 개 있는지 확인해 보겠습니다.

len(df.groupby('category').votes.sum())

이렇게 17개의 카테고리가 있는 것을 확인할 수 있습니다.

참고로 가장 많은 votes 수의 카테고리는 '인권/성평등' 카테고리임을 확인할 수있습니다.

 

 토픽 모델링을 위한 텍스트 토큰화(형태소분석)하기

여기서는 Gensim을 사용하여 LDA 토픽 모델링을 해 볼 건데요,

gensim을 사용하면 혼란도와 토픽 응집도를 간단하게 구할 수 있어 많이 사용됩니다.

pip install gensim

을 터미널에 입력하여 설치할 수 있습니다.

gensim은 입력값으로 텍스트를 그대로 입력하는 것이 아닌 토큰화된 결과를 입력값으로 사용하므로

청원 내용을 형태소 분석을 통해 토큰화 해보도록 하겠습니다.

형태소 분석으로는 konlpy의 Okt를 사용하여 분석해 보겠습니다. 

단어 중 '명사' 및 2글자 이상의 단어만 추출해 보도록 하겠습니다.

from konlpy.tag import Okt
###형태소 분석###
okt = Okt()
def analysis_pos(text):
    morphs = okt.pos(text,stem=True)

    words = []
    #명사 추출, 2글자 이상 단어 추출
    for word, pos in morphs:
        if pos == 'Noun':
            if len(word) > 1:
                words.append(word)
    return words

texts = [analysis_pos(news) for news in contents]

이렇게 함수를 만들어 texts 변수 안에 토큰화한 텍스트를 저장하였습니다.

 

토큰화 된 텍스트로 gensim Dictionary 만들기

gensim LDA에서 id2word에 사용하기 위해 토큰화된 텍스트로 사전을 만들어 보겠습니다.

from gensim.corpora.dictionary import Dictionary
# 형태소 분석으로 토큰화 후 dictionary 생성
dictionary = Dictionary(texts)
print("#문서에 있는 단어 수: %d개"%len(dictionary))

문서에 있는 단어 수가 3만개가 넘는데요,

이를 filter_extremes를 사용해 단어를 2000개까지 설정하고 5개 미만으로 나온 단어는 제외, 전체 50% 이상으로 많이 나오는 단어들도 제외를 해보도록 하겠습니다.

keep_n 으로 단어 갯수를 설정하고, no_below로 일정 횟수 미만으로 나온 단어는 제외, no_above로 일정 비율 이상으로 나오는 단어들도 제외하도록 설정할 수 있습니다.

물론 다른 비율 또는 횟수, 갯수로 바꿔서 필터링 할 수 도 있습니다.

# 너무 많은 단어나 너무 적은 단어를 제외하고, 단어 빈도순으로 정리
dictionary.filter_extremes(keep_n=2000, no_below=5, no_above=0.5)
print("#너무 많은 단어 및 적은 단어를 제외하고 문서에 남아 있는 단어 수: %d개"%len(dictionary))

카운트벡터로 변환하기

gensim LDA 에 사용하기 위한 corpus를 생성하려면 doc2bow를 사용하여 카운트 벡터로 변환하여야 합니다.

gensim에서는 토큰화 된 결과를 texts라 하고 이것을 카운트 벡터로 변환한 것을 corpus라고 합니다.

# 카운트 벡터로 변환
corpus = [dictionary.doc2bow(text) for text in texts]
print("#최종적으로 문서에 있는 단어 수: %d개"%len(dictionary))
print("#카운트 벡터 수: %d개"%len(corpus))

Gensim LDA 모델 생성

gensim의 LdaModel을 사용해서 LDA 모델링을 수행할 수 있습니다.

num_topics은 토픽의 수를, passes는 말뭉치 전체를 학습하는 횟수, corpus는 토큰화한 값들을 카운트 벡터화 한것, id2word는 위에서 만든 dictionary를 입력하면 됩니다.

우선 원본 데이터가 총 17개의 카테고리를 가지고 있으므로,

num_topics를 17로 설정하여 모델링을 수행해 보도록 하겠습니다.

from gensim.models import LdaModel
num_topics = 17
passes = 5
model = LdaModel(corpus=corpus,id2word=dictionary,\
    passes=passes, num_topics=num_topics,\
        random_state=7)

그 후 모델링 한 결과를 각 토픽당 10개의 단어씩 뽑아서 보도록 하겠습니다.

model.print_topics(num_words=10)

pyLDAvis를 사용한 시각화

위의 모델링 결과를 한눈에 보기 쉽게 pyLDAvis를 사용하여 시각화할 수 있습니다.

설치는 pip install pyLDAvis 로 설치 가능합니다.

import pyLDAvis
import pyLDAvis.gensim_models as gensimvis
pyLDAvis.enable_notebook()

#LDA모형 시각화
lda_visual= gensimvis.prepare(model,corpus,dictionary)
pyLDAvis.display(lda_visual)

최적의 토픽 수 선정하기

원본이 17개 카테고리여서 num_topics를 17로 설정했지만 실제 분석시에는 카테고리조차 없는 데이터들도 있을 것입니다. 

또한 위의 결과도 최적의 토픽 개수를 설정한 것인지 알 수 없으므로 혼란도와 토픽응집도를 구해서 살펴보도록 하겠습니다.

gensim에서 .log_perplexity를 사용하면 혼란도를, CoherenceModel로 토픽응집도를 간편히 구할 수 있습니다.

여기서는 혼란도와 토픽 응집도를 구하는 함수를 만들고

for문을 사용하여 토픽 갯수당 각각의 혼란도, 응집도를 구해 그래프로 시각화 후 최적화를 진행해보겠습니다.

import matplotlib.pyplot as plt
from gensim.models import CoherenceModel
def coherences(corpus, dictionary, start=6,end=15):
    iter_num=[]
    per_value=[]
    coh_value=[]
    for i in range(start, end+1):
        model = LdaModel(corpus=corpus, id2word=dictionary,
                        chunksize=1000, num_topics=i,
                        random_state=7)
        iter_num.append(i)
        pv = model.log_perplexity(corpus)
        per_value.append(pv)
        CM = CoherenceModel(model=model, corpus=corpus, coherence='u_mass')
        coherence_value = CM.get_coherence()
        coh_value.append(coherence_value)
        print(f'토픽 수: {i}, 혼란도:{pv:0.3f}, 응집도: {coherence_value:0.3f}')
        
    plt.plot(iter_num,per_value,'g-')
    plt.xlabel("num_topics")
    plt.ylabel("perplexity")
    plt.show()
        
    plt.plot(iter_num, coh_value,'r--')
    plt.xlabel("num_topics")
    plt.ylabel("coherence")
    plt.show()

start는 토픽의 시작 갯수를, end는 마지막으로 구할 토픽 갯수입니다.

간단히 말해 for문의 시작과 끝을 설정한다고 볼 수 있습니다.

여기서는 토픽 5개~20개까지로 설정했을 때 각각의 혼란도, 토픽 응집도를 구해 보겠습니다.

coherences(corpus, dictionary, start=5,end=20)

혼란도는 낮을 수록 좋고 토픽 응집도는 0에 가까울수록 좋습니다.

위의 그래프를 보면 혼란도가 가장 낮게 나타난 곳은 17개이고, 토픽 응집도가 가장 0에 가까운 곳은 6개로

차이가 있음을 알 수 있습니다.

최적화가 쉽지 않아보이지만 둘 중 더 토픽을 잘 나타내는 것으로 최종 선택을 할 수 있겠습니다.

혼란도가 가장 낮은 토픽 17개로는 이미 전에 모델링을 실행해 보았으니,

이번에는 응집도가 가장 높은 토픽 6개로 모델링을 해보도록 하겠습니다.

num_topics = 6
passes = 5
model2 = LdaModel(corpus=corpus,id2word=dictionary,\
    passes=passes, num_topics=num_topics,\
        random_state=7)
        
model2.print_topics(num_words=10)

#LDA모형 시각화
lda_visual2= gensimvis.prepare(model2,corpus,dictionary)
pyLDAvis.display(lda_visual2)

이 둘 중에 또는 모델을 조정하여 최종적으로 토픽 수를 정할 수 있겠습니다.

 

코드 파일

kor_LDA.ipynb
0.40MB

참고자료

1. 책- 파이썬 텍스트 마이닝 완벽 가이드(위키북스)

2. https://radimrehurek.com/gensim/models/coherencemodel.html

 

Gensim: topic modelling for humans

Efficient topic modelling in Python

radimrehurek.com

 

반응형

+ Recent posts