[NLP] 한국어 토픽 모델링 - LDA 활용방법(gensim)
안녕하세요.
이번에는 한국어 데이터셋을 가지고 gensim을 사용한 LDA 토픽 모델링 실습을 해보도록 하겠습니다.
데이터 준비
테이터셋으로 '청와대 국민청원' 에 만료된 청원 데이터를 사용하도록 하겠습니다.
아래 사이트 접속 후
https://github.com/akngs/petitions
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)
이 둘 중에 또는 모델을 조정하여 최종적으로 토픽 수를 정할 수 있겠습니다.
코드 파일
참고자료
1. 책- 파이썬 텍스트 마이닝 완벽 가이드(위키북스)
2. https://radimrehurek.com/gensim/models/coherencemodel.html