데이터과학 유망주의 매일 글쓰기 — 58

배우는 자(Learner Of Life)
13 min readDec 16, 2020

--

Topic Modelling

# Topic Modelling, # Latent Dirichlet Allocation(LDA)

Topic Modelling을 그림으로 나타낸 모습 (1)

오늘 한일:

오늘은 문서를 머신러닝모델을 통해 토큰화 시켜 학습시키고, 새로운 문서를 통해 모델의 성능을 평가하는 과정을 배웠다. 오늘 배운 것 중에 핵심 개념은 Singular Value Decomposition(SVD)라는 기법인데, 행렬로 이루어진 문서를 행과 열로 분리하는 방법이다. 섹션 1에서 배웠던 PCA와 같은 차원 축소 기법이다.

오늘의 추가 과제는 Topic Modelling에 대해 공부해 보는 것이었다. 오늘은 추가과제를 수행할 겸, 이에 대해 공부하고 블로그를 써 보기로 했다.

Topic Modelling

추상적인 의미(Topics)를 찾을 수 있는 통계적 모델링 기법으로써, 문서(documents)에 적용할 수 있다. Latent Dirichlet Allocation(LDA)는 의미 모델의 예로써, 특정한 의미에 따라 문서의 텍스트를 구분하는데 사용된다. 문서 모델마다 의미를 만들며, 의미 모델마다 단어를 생성하고, Dirichlet 분산에 따라 모델링한다.

LDA를 문서의 셋(set)에 적용하고, 의미로 쪼개는 작업을 해보자.

데이터

데이터셋은 1백만 여개의 뉴스 헤드라인을 담고 있는 리스트로써, 15년 동안 Kaggle에서 수집된 데이터이다.

import pandas as pddata = pd.read_csv(‘abcnews-date-text.csv’, error_bad_lines=False);data_text = data[[‘headline_text’]]data_text[‘index’] = data_text.indexdocuments = data_text

데이터의 일부를 한번 보자.

print(len(documents))print(documents[:5])
데이터의 일부를 확인한 모습

데이터 전처리

아래 과정에 따라 데이터를 전처리 한다.

  1. 토큰화: 텍스트를 문장으로 나누고, 문장을 단어로 나눈다. 단어는 모조리 소문자화 시키고, 특수문자는 제거한다.
  2. 철자가 3개 이하인 단어들은 제거한다.
  3. 모든 불용어(stopwords)는 제거한다.
  4. 단어들의 표제어를 추출(lemmatization)한다. 3인칭 단어는 1인칭으로, 동사의 과거형과 미래형은 모두 현재형으로 대체한다.
  5. 단어들의 어간을 추출(stemming)한다. 모든 단어들이 공유하는 철자로 이루어진 단어를 추출한다. 이는 사전적으로 정당한 단어는 아니다.

Genius 및 nltk 라이브러리를 사용한다.

import gensimfrom gensim.utils import simple_preprocessfrom gensim.parsing.preprocessing import STOPWORDSfrom nltk.stem import WordNetLemmatizer, SnowballStemmerfrom nltk.stem.porter import *import numpy as npnp.random.seed(2018)import nltknltk.download('wordnet')
  1. 표제어와 어간 추출을 할 수 있는 함수를 작성한다.
def lemmatize_stemming(text):return stemmer.stem(WordNetLemmatizer().lemmatize(text, pos='v'))def preprocess(text):result = []for token in gensim.utils.simple_preprocess(text):if token not in gensim.parsing.preprocessing.STOPWORDS and len(token) > 3:result.append(lemmatize_stemming(token))return result

2. 전처리 이후 preview할 문서를 선택한다.

doc_sample = documents[documents['index'] == 4310].values[0][0]print('original document: ')words = []for word in doc_sample.split(' '):words.append(word)print(words)print('\n\n tokenized and lemmatized document: ')print(preprocess(doc_sample))
전처리의 결과

3. 헤드라인 텍스트를 전처리하고, 결과를 저장한다.

processed_docs = documents['headline_text'].map(preprocess)processed_docs[:10]
헤드라인 전처리 결과

4. Bag of Words(BOW)기법을 활용한다. 이전에 훈련 데이터의 단어 빈도수에 대한 정보를 담은 결과를 저장한 파일에서, dictionary를 생성한다.

dictionary = gensim.corpora.Dictionary(processed_docs)count = 0for k, v in dictionary.iteritems():print(k, v)count += 1if count > 10:break
  1. 토큰을 필터링한다.
  • 15개 이하의 문서들(절대값)
  • 0.5 이상의 문서들 (전체 corpus에서, 절대값 아님)
  • 위 두 과정 이후, 첫 십만 개의 가장 자주 나온 토큰만을 유지한다.
dictionary.filter_extremes(no_below=15, no_above=0.5, keep_n=100000)

6. Gensim doc2bow라는 라이브러리를 사용한다. 각 문서에 대해, 단어가 얼마나 들어있는지, 각 단어가 얼마나 많은 빈도로 나타났는지 등의 정보를 포함하는 dictionary를 생성한다. bow_corpus에 저장하고 이전에 선택한 문서를 체크한다.

bow_corpus = [dictionary.doc2bow(doc) for doc in processed_docs]bow_corpus[4310]
bow_corpus에 저장된 값

7. 전처리 문서 샘플에 대해 Bag of Words를 적용한다.

bow_doc_4310 = bow_corpus[4310]for i in range(len(bow_doc_4310)):print("Word {} (\"{}\") appears {} time.".format(bow_doc_4310[i][0],dictionary[bow_doc_4310[i][0]],bow_doc_4310[i][1]))

TF-IDF

  1. TF-IDF 모델을 생성한다. 이후 전체 corpus를 변환하여 저장한다. 마지막으로 첫 문서에 대해 TF-IDF를 확인한다.
from gensim import corpora, modelstfidf = models.TfidfModel(bow_corpus)corpus_tfidf = tfidf[bow_corpus]from pprint import pprintfor doc in corpus_tfidf:pprint(doc)break
TD-IDF 형태로 변환한 모습

2. Bag of Words를 사용하여 LDA 실행하기

gensim.models.LdaMulticore를 이용하여 LDA 모델을 학습시키고 저장한다.

lda_model = gensim.models.LdaMulticore(bow_corpus, num_topics=10, id2word=dictionary, passes=2, workers=2)

3. 각 의미에 등장하는 단어와 단어의 상대적 가중치를 찾는다.

for idx, topic in lda_model.print_topics(-1):print('Topic: {} \nWords: {}'.format(idx, topic))
Bag of Words(BOW)로 LDA를 한 결과

4. TF-IDF를 사용하여 LDA를 실행한다.

lda_model_tfidf = gensim.models.LdaMulticore(corpus_tfidf, num_topics=10, id2word=dictionary, passes=2, workers=4)for idx, topic in lda_model_tfidf.print_topics(-1):print('Topic: {} Word: {}'.format(idx, topic))

Figure 3

Can you distinguish different topics using the words in each topic and their corresponding weights?

Running LDA using TF-IDF

lda_model_tfidf = gensim.models.LdaMulticore(corpus_tfidf, num_topics=10, id2word=dictionary, passes=2, workers=4)for idx, topic in lda_model_tfidf.print_topics(-1):
print('Topic: {} Word: {}'.format(idx, topic))
TF-IDF로 LDA를 모델링한 모습

5. LDA Bag of Words(BOW)모델을 사용하여 샘플 문서를 구분하는 성능 평가를 진행한다. 데스트할 문서가 어디에 있는지 찾는다.

processed_docs[4310]
평가할 샘플 문서의 토큰
for index, score in sorted(lda_model[bow_corpus[4310]], key=lambda tup: -1*tup[1]):print("\nScore: {}\t \nTopic: {}".format(score, lda_model.print_topic(index, 10)))
Bag of Words(BOW) LDA 모델을 통해 예측한 결과

6. LDA TF-IDF 모델을 사용하여 샘플 문서의 성능을 평가하기

for index, score in sorted(lda_model_tfidf[bow_corpus[4310]], key=lambda tup: -1*tup[1]):print("\nScore: {}\t \nTopic: {}".format(score, lda_model_tfidf.print_topic(index, 10)))
TF-IDF LDA모델로 성능을 평가한 모습

7. 마지막으로 새로운 문서를 이용해 모델을 테스트한다.

unseen_document = 'How a Pentagon deal became an identity crisis for Google'bow_vector = dictionary.doc2bow(preprocess(unseen_document))for index, score in sorted(lda_model[bow_vector], key=lambda tup: -1*tup[1]):print("Score: {}\t Topic: {}".format(score, lda_model.print_topic(index, 5)))
새로운 문서로 모델을 테스트한 모습

소스 코드는 GitHub을 통해 확인할 수 있다.

앞으로 할일:

오늘은 Topic Modelling에 대해 알아보았다. 오늘과 어제 배운 TF-IDF와 BOW의 개념을 사용한 것이 인상적이었다. 자연어 처리에서 이 두 기법이 정말 중요하게 느껴졌다.

오늘 배운 것 중에 잠재의미분석(Latent Semantic Analysis, LSA)이라는 개념이 가장 중요했는데, 그 것을 제대로 리뷰하지 못한 것 같아 아쉽다. 내일은 오늘 배운 것을 리뷰해볼 수 있는 글을 작성할 생각이다. LSA와 SVD에 대해 좀 더 잘 이해하고 싶기 때문이다.

다시 코랩으로 돌아와 작업을 하니 한결 편해졌다. 이제는 SQL, HTML등도 같이 신경써야할 필요가 없어졌기 때문이다. 하지만, 현장에서 일하게 된다면 이들을 다시 맞이할테니, 웹 프로그래밍과 데이터베이스 공부도 게을리하지 말아야겠다는 생각이든다.

세상은 넓고 배울 것은 많다. 하지만, 그만큼 많이 배우면 기회도 많다. 아직 개척될 여지가 많은 자연어처리 분야를 좀 더 파고들어, 이 분야에서 좋은 기회를 얻을 수 있도록 노력해야겠다. 언어에 관심이 많은 나에게는, 이 분야가 매우 매력적으로 느껴지기 때문이다. 물론, 이 분야를 잘 하기 위해서는 인간의 언어와, 컴퓨터의 언어인 수학과 통계를 모두 잘 해야할 것이다. 어렵지만, 불가능하다고 생각하지는 않는다. 더 많은 것을 알수록, 더 많은 것을 모른다는 것을 깨닫게 되지만, 그만큼 더 많은 것들을 보게된다. 그 기회들을 잡을 수 있도록 나만의 낙싯대를 갈고 닦아야겠다.

참조:

(1) https://www.analyticsvidhya.com/wp-content/uploads/2016/08/Modeling1.png

(2) Udacity — NLP

(3) Topic Modeling and Latent Dirichlet Allocation (LDA) in Python

--

--

배우는 자(Learner Of Life)

배움은 죽을 때까지 끝이 없다. 어쩌면 그게 우리가 살아있다는 증거일지도 모른다. 배움을 멈추는 순간, 혹은 배움의 기회가 더 이상 존재하지 않는 순간, 우리의 삶은 어쩌면 거기서 끝나는 것은 아닐까? 나는 배운다 그러므로 나는 존재한다. 배울 수 있음에, 그래서 살아 있음에 감사한다.