LDA로 할수 있는것

LDA로 토픽모델링을 구현하면 무엇을 할 수 있나요?

Posted by coredot on September 25, 2018

본 문서는 다음과 같은 사이트를 참고하였습니다.
Gensim
ratsgo - 토픽모델링

LDA로 그럼 이제 무엇을 해야하나요?

Gensim LDA 객체를 한번 다시 살펴봅시다. LDA 객체는 3가지 관점에서 살펴 볼수 있습니다. 첫번째는 Topic의 관점, 두번째는 documents의 관점, 그리고 마지막은 term의 관점입니다.

Data를 넣어봅시다

우선 다시 라이브러리를 입력하고

1
2
3
4
5
6
7
8
9
10
11
12
import os
import json, requests
from pymongo import MongoClient
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import re
import coredottext.nlp as nlp
from gensim import corpora
from gensim.corpora import Dictionary
from gensim.models.ldamodel import LdaModel
%pylab inline

지난번에 전처리하고 파라미터를 튜닝했었던 문서를 다시 호출해 봅시다.

1
2
3
4
5
6
7
if (os.path.exists('stop&bi(min=2,threshold=50,no_above=0.5).dict')):
    dictionary = corpora.Dictionary.load('stop&bi(min=2,threshold=50,no_above=0.5).dict')
    corpus = corpora.MmCorpus('stop&bi(min=2,threshold=50,no_above=0.5).mm')
    LDA = LdaModel.load('coredotLDA.model')
    print("LDA, Dictionary와 corpus가 준비되었습니다!")
else:
    print("데이터가 없어요!")
LDA, Dictionary와 corpus가 준비되었습니다!

좋습니다 이제 시작할 수 있어요!

1. Topic view : 토픽의 관점

토픽의 관점에서 다른 요소를 본다면, 토픽별 관련이 높은 documents 그리고 관련이 높은 term을 반환할수 있어야합니다. 외부 라이브러리인 pyLDAvis와 gensim LDA객체에 내장되어있는 몇가지 함수들을 소개하려고 합니다.

1.1. pyLDAvis

pyLDAvis는 파이썬의 토픽모델링을 구현시켜주는 좋은 툴입니다.

모델링에서 최적화 시킨 토픽별 토픽을 대표하는 단어들을 반환시킨 후 PCA를 통해 2차원에 mapping시킵니다.

왼쪽 2차원 버블 차트는 PCA에 의해 변환된 토픽들이며, 오른쪽 bar 차트는 해당 토픽을 대표하는 단어들로 구성되어 있습니다. 해당 단어들은 relevance라는 measure에 의해 대표되고 있는데요 과연 무엇을 의미하는 것일까요?

1
2
3
import pyLDAvis.gensim
pyLDAvis.enable_notebook()
pyLDAvis.gensim.prepare(LDA, corpus, dictionary)

Lambda Change 구현

  • Goal : topic 에 대해서 relevent 한 term이 무엇인가?.
Relevance 란?

Topic 안에 있는 Terms 을 평하는 기준이 됩니다.
는 특정한 용어 w가 topic 에 등장할 확률입니다.
은 특정 코퍼스의 marginal probability 입니다.

Relevance (원문)

Definition

  • Let denote the probability of term $w$ in for topic in , where denote the number of terms in the vocabulary(전체 코퍼스).
  • Let denote the marginal probability of term in corpus.

Expaination

  • in LDA using Variational Bayes methods or Collapsed Gibbs sampling
  • emprical distribution of the corpus

Lambda

  • λ determines the weight given to the probability of term under topic relative to its lift.

1.2. LDA function

  • Topic-term
  • Get_topics_terms
  • Get_topics
  • show_topics

Get_topic_terms(topicid, topn=10)

하나의 토픽에 관하여, 토픽을 대표하는 term들에 대해서 ID값을 리턴합니다.

Parameters:

topicid (int) – 지정한 topic number중 원하는 id값을 선언합니다. topn (int, optional) – 토픽을 대표하는 단어들 중, 상위 n개의 중요성을 가지는 단어를 반환합니다.

Return:

Word ID - term의 ID와 해당 확률값을 반환합니다

1
2
topic_term1 = LDA.get_topic_terms(0,10)
topic_term1
[(33, 0.10774062),
 (53, 0.10437591),
 (386, 0.088203155),
 (47, 0.06783413),
 (457, 0.05815126),
 (550, 0.04847299),
 (72, 0.040648986),
 (120, 0.03677915),
 (52, 0.035780977),
 (1076, 0.020439167)]

0번째 topic을 대표하는 10가지 term의 ID는 33번, 53번, 386번, 47번… 등등 이였습니다.

Get_topics()

Get_topics함수는 Term-topic Metrics를 반환 받을수 있습니다.

Returns

shape (num_topics, vocabulary_size) 으로 구성되어 있는, 토픽별 단어의 확률 분포를 받을 수 있습니다.

1
LDA.get_topics()
array([[1.97014360e-05, 1.33696623e-04, 2.89882792e-05, ...,
        1.15434468e-05, 1.31974211e-05, 1.15434468e-05],
       [3.47235109e-05, 1.58586330e-03, 5.10913997e-05, ...,
        2.03451655e-05, 2.32602724e-05, 2.03451655e-05],
       [6.87927386e-05, 5.27945766e-03, 1.01220096e-04, ...,
        4.03069716e-05, 4.60822594e-05, 4.03069716e-05],
       ...,
       [3.37744605e-05, 2.22615796e-04, 4.99950911e-05, ...,
        1.97891004e-05, 2.26245320e-05, 1.97891004e-05],
       [1.03068400e-04, 6.52172836e-04, 1.51652537e-04, ...,
        6.03897352e-05, 6.90425295e-05, 6.03897352e-05],
       [1.80663948e-04, 1.14951900e-03, 4.16062307e-03, ...,
        1.05854437e-04, 1.21021534e-04, 1.05854437e-04]], dtype=float32)

본 함수는 나중에 위에서 나온 relevance의 lambda값을 조절하고 싶을때 사용하게 됩니다.

1
2
# 그냥 토픽들별 대표 단어를 보여준다
LDA.print_topics()
[(17,
  '0.011*"제조" + 0.006*"번호" + 0.005*"변경" + 0.005*"사항" + 0.005*"고시" + 0.004*"국토교통부장관" + 0.004*"면적" + 0.004*"지정" + 0.004*"계획" + 0.004*"기준"'),
 (5,
  '0.011*"제조" + 0.006*"번호" + 0.005*"변경" + 0.005*"고시" + 0.005*"사항" + 0.004*"국토교통부장관" + 0.004*"지정" + 0.004*"면적" + 0.004*"계획" + 0.004*"기준"'),
 (16,
  '0.011*"제조" + 0.006*"변경" + 0.006*"번호" + 0.005*"사항" + 0.005*"고시" + 0.004*"면적" + 0.004*"국토교통부장관" + 0.004*"지정" + 0.004*"계획" + 0.004*"기준"'),
 (26,
  '0.045*"취소" + 0.044*"등급" + 0.035*"분야" + 0.033*"인증" + 0.023*"번호" + 0.021*"건축" + 0.016*"제조" + 0.016*"주소" + 0.015*"비고" + 0.015*"구분"'),
 (29,
  '0.095*"건축" + 0.051*"건축법" + 0.037*"제조" + 0.031*"건축물" + 0.026*"완화" + 0.024*"면적" + 0.024*"관계" + 0.023*"미터" + 0.017*"유도" + 0.017*"심의"'),
 (22,
  '0.038*"시스템" + 0.038*"보호" + 0.037*"규정안" + 0.036*"번호" + 0.032*"기술" + 0.025*"보조" + 0.021*"과태료_부과" + 0.020*"고시" + 0.018*"지정" + 0.018*"제조"'),
 (20,
  '0.059*"개발제한구역" + 0.033*"개소" + 0.028*"면적" + 0.023*"별표" + 0.018*"분할" + 0.014*"제조" + 0.014*"명칭" + 0.013*"허용" + 0.013*"범위" + 0.012*"용도"'),
 (9,
  '0.094*"변경" + 0.057*"기정" + 0.054*"일원" + 0.029*"면적" + 0.026*"계획" + 0.024*"도로" + 0.022*"용지" + 0.022*"번호" + 0.019*"이하" + 0.018*"건축_한계선"'),
 (6,
  ....

2. Documents view : 문서의 관점

문서의 관점에서는 topic과 term을 볼 수도 있습니다. 이는 가장 적합한 토픽이 무엇인지, 그리고 문서를 대표하는 단어가 무엇인지 알수 있습니다. 지난 튜토리얼에서 사용했던 corpus[1]번 문서를 가져와 봅시다.

' 도로법 시행령 을 개정하는 데에 있어, 그 개정이유와 주요내용을 국민에게 미리 알려 이에 대한 의견을 듣기 위하여 행정절차법 제41조에 따라 다음과 같이 공고합니다. 2017년 3월 16일 국토교통부장관 도로법 시행령 일부개정령(안) 입법예고 1. 개정이유 도로법 제68조 도로점용료 감면조항에「영유아보육법」제2조제3호에 따른 어린이집 또는「유아교 육법」제2조제2호에 따른 유치원(민간어린이집과 민간유치원)에 출입하기 위하여 통행로로 사용하는경우가 신설되었기에 이에 대한 하위법령을 마련하고자 함2. 주요내용 가. 도로점용료 감면 대상 추가(안 제73조제3항제1호가목) ㅇ 도로법 제68조(점용료 징수의 제한) 제9호 “「영유아보육법」제2조제3호에 따른 어린이집 또는「유아교육법」제2조제2호에 따른 유치원에 출입하기 위하여 통행로로 사용하는 경우” 신설에 따른 하위법령 마련 나. 도로점용료에 대한 경과규정 명확화(안 부칙 제6조) ㅇ 영 제27751호 도로법 시행령 일부개정령안 부칙 제6조에서 규정한 도로점용료에 대한 경과규정을 명확하게 하여 행정업무상 혼란 예방 3. 의견제출 이 개정안에 대해 의견이 있는 기관․단체 또는 개인은 2017년 3월 20일까지 통합입법예고센터 (http://opinion.lawmaking.go.kr)를 통하여 온라인으로 의견을 제출하시거나, 다음 사항을 기재한 의견서를 국토교통부장관에게 제출하여 주시기 바랍니다. 가. 예고 사항에 대한 찬성 또는 반대 의견(반대 시 이유 명시) 나. 성명(기관ㆍ단체의 경우 기관ㆍ단체명과 대표자명), 주소 및 전화번호다. 그 밖의 참고 사항 등※ 제출의견 보내실 곳 - 일반우편 : (30103) 세종특별자치시 도움6로 11 국토교통부 도로운영과- 전자우편 : jwsuh@korea.kr- 전화 : 044-201-3920 / 팩스 : 044-201-5591'

위 문서가전처리가 끝났을때 bag of word 포맷을 보고 싶다면 다음과 같이 선언합니다.

1
corpus[1]
[(60, 3.0),
 (71, 1.0),
 (122, 3.0),
 (150, 1.0),
 (190, 1.0),
 (191, 1.0),
 (192, 1.0),
 (193, 1.0),
 (194, 1.0),
 (195, 5.0),
 (196, 1.0),
 (197, 1.0),
 (198, 1.0),
 (199, 2.0),
 (200, 1.0),
 ...

LDA모형의 파라미터로 corpus[1]를 넣게 된다면, topic probability distribution 을 얻을 수 있습니다.

1
LDA[corpus[1]]
[(1, 0.4902441), (4, 0.49875113)]

이제 1번과 4번의 토픽만이 존재하는군요, 나머지 토픽을 보고싶다면, get_document_topics()라는 함수를 불러와 줍시다.

get_document_topics(bow, minimum_probability=None, minimum_phi_value=None, per_word_topics=False)

같은 함수로는 get_document_topics가 있다.

Parameters:

  • bow (corpus : list of (int, float)) – 문서의 Bag of Word 포맷으로 넣어줘야한다.
  • minimum_probability (float) – 확률의 threshold를 의미한다.
  • minimum_phi_value (float) – 확률의 lower boundary로 일반적으로 0이 되지않기 위해 1e-8을 넣는다
  • per_word_topics (bool) – 만약 참이라면, 해당 documents의 단어당 단어에 지배적인 topic과 그 반환값을 알려줍니다.

Returns:

  • 할당되는 토픽과 그 확률값을 리턴합니다.
  • per_word_topics가 True일시 각 term들이 어떤 topic으로 할당되는지도 알수 있습니다.
1
LDA.get_document_topics(corpus[1],minimum_probability=0, per_word_topics=True)
([(0, 0.00037254576),
  (1, 0.49019727),
  (2, 0.00039519096),
  (3, 0.00038756858),
  (4, 0.49879798),
  (5, 0.0002814814),
  (6, 0.00034191413),
  (7, 0.00034965939),
  (8, 0.0003961847),
  (9, 0.0003346577),
  (10, 0.0005500844),
  (11, 0.00038646077),
  (12, 0.0005443118),
  (13, 0.0004353034),
  (14, 0.00060919026),
  (15, 0.00064918963),
  (16, 0.00028155153),
  (17, 0.00028032894),
  (18, 0.00044023903),
  (19, 0.00038665938),
  (20, 0.00033284377),
  (21, 0.0004991137),
  (22, 0.0003204023),
  (23, 0.00035180285),
  (24, 0.00036439745),
  (25, 0.00037174425),
  (26, 0.00030278723),
  (27, 0.00034906858),
  (28, 0.0003821023),
  (29, 0.000308007)],
 [(60, [1, 4]),
  (71, [1]),
  (122, [4]),
  (150, [1, 4]),
  (190, [4]),
  (191, [1]),
  (192, [1]),
  (193, [1]),
  (194, [1]),
  (195, [4, 1]),
  (196, [1]),
  (197, [1, 4]),
  (198, [1, 4]),
  (199, [4]),
  (200, [1]),
  (201, [1, 4]),
  (202, [4]),
  (203, [1]),
  (204, [1]),
  (205, [1, 4]),
  (206, [1]),
  (207, [1]),
  (208, [4]),
  (209, [4, 1]),
  (210, [1]),
  (211, [1]),
  (212, [1, 4]),
  (213, [1]),
  (214, [1]),
  (215, [1]),
  (216, [1, 4]),
  (217, [4]),
  (218, [1, 4]),
  (219, [1]),
  (220, [1]),
  (221, [4]),
  (222, [4]),
  (1271, [1, 4]),
  (1278, [4, 1])],
 [(60, [(1, 2.0730827), (4, 0.9269175)]),
  (71, [(1, 0.9907328)]),
  (122, [(4, 2.9999998)]),
  (150, [(1, 0.97792697), (4, 0.022073045)]),
  (190, [(4, 0.9999764)]),
  (191, [(1, 0.9999992)]),
  (192, [(1, 0.9999998)]),
  (193, [(1, 0.99999976)]),
  (194, [(1, 0.99999946)]),
  (195, [(1, 0.023917388), (4, 4.976083)]),
  (196, [(1, 0.99999857)]),
  (197, [(1, 0.90002096), (4, 0.09997896)]),
  (198, [(1, 0.805789), (4, 0.19421098)]),
  (199, [(4, 1.9999999)]),
  (200, [(1, 0.9999999)]),
  (201, [(1, 1.8413019), (4, 0.1586982)]),
  (202, [(4, 2.0)]),
  (203, [(1, 0.9999935)]),
  (204, [(1, 0.99999887)]),
  (205, [(1, 0.910265), (4, 0.08973501)]),
  (206, [(1, 0.9999629)]),
  (207, [(1, 0.99999917)]),
  (208, [(4, 0.9999999)]),
  (209, [(1, 0.48777202), (4, 0.51222795)]),
  (210, [(1, 0.9999984)]),
  (211, [(1, 0.99999976)]),
  (212, [(1, 0.8218902), (4, 0.17810993)]),
  (213, [(1, 0.99999535)]),
  (214, [(1, 0.9999952)]),
  (215, [(1, 0.99999976)]),
  (216, [(1, 0.6156353), (4, 0.38436478)]),
  (217, [(4, 1.0)]),
  (218, [(1, 0.924231), (4, 0.075769044)]),
  (219, [(1, 0.9999975)]),
  (220, [(1, 0.99998087)]),
  (221, [(4, 0.9998273)]),
  (222, [(4, 1.9999998)]),
  (1271, [(1, 0.51270425), (4, 0.48729575)]),
  (1278, [(1, 0.8525369), (4, 9.147463)])])

per_word_topics를 빼고, 해당 문서가 어느 토픽순으로 관련이 있는지 sorting 을 해볼까요.

1
2
3
doc_topic_dist = LDA.get_document_topics(corpus[1], minimum_probability=0)
sorted_doc_topic = sorted(doc_topic_dist, key=lambda x:x[1], reverse=True)
sorted_doc_topic
[(4, 0.49878946),
 (1, 0.49020573),
 (15, 0.0006491896),
 (14, 0.00060919026),
 (10, 0.00055008434),
 (12, 0.00054431177),
 (21, 0.0004991137),
 (18, 0.000440239),
 (13, 0.00043530337),
 (8, 0.00039618468),
 (2, 0.00039519093),
 (3, 0.00038756855),
 (19, 0.00038665935),
 (11, 0.00038646074),
 (28, 0.00038210227),
 (0, 0.00037254574),
 (25, 0.00037174422),
 (24, 0.00036439742),
 (23, 0.00035180282),
 (7, 0.00034965936),
 (27, 0.00034906855),
 (6, 0.0003419141),
 (9, 0.00033465767),
 (20, 0.00033284375),
 (22, 0.00032040227),
 (29, 0.00030800697),
 (26, 0.0003027872),
 (16, 0.0002815515),
 (5, 0.0002814814),
 (17, 0.0002803289)]

3. Term view : 단어의 관점

위 문서가 사실상 어떤 term으로 대표되고 있는지 한번 확인해봅시다.

1
2
words = [dictionary[word_id] for word_id, count in corpus[1]]
print(words)
['도로', '번호', '유치원', '주소', '경과', '국민_행정절차법', '기관단체_단체명', '기관단체_통합', '대표_자명', '도로법', '도움', '마련', '명시', '민간', '반대_이유', '부개_정령', '부칙', '사항_기재', '사항_우편', '성명', '세종_특별자치시', '예고_사항', '예방', '을 ', '의견서_국토교통부장관', '의견제출_개정안', '이유_주요', '입법예고', '입법예고_이유', '전자우편_팩스', '제한', '조항', '주요', '찬성_반대', '참고', '추가', '출입', '국토교통부장관', '제조']

get_term_topics(word_id, minimum_probability=None)

주어진 문서의 term의 관점에서 topic distribution을 보여줍니다.

Parameters:

word_id (int) – bow형태에세 단어의 index를 받습니다. minimum_probability (float, optional) – 최소 확률을 규정합니다.

Return type:

확률값이 리턴됩니다.

1
2
3
4
# 도로에 관련된 토픽을 찾아봅시다. id:60 -> '도로'
term60_topic_dist = LDA.get_term_topics(60, minimum_probability=0)
sorted_term_topic = sorted(term60_topic_dist, key=lambda x:x[1], reverse=True)
sorted_term_topic
[(7, 0.047465038),
 (8, 0.044691533),
 (27, 0.03814212),
 (3, 0.024955835),
 (9, 0.02439684),
 (14, 0.022255316),
 (23, 0.015337902),
 (28, 0.007400141),
 (20, 0.0062398217),
 (1, 0.004050396),
 (25, 0.002654567),
 (10, 0.0021217426),
 (18, 0.00182698),
 (4, 0.0017792667),
 (2, 0.001449916),
 (21, 0.00079256285),
 (12, 0.000614244),
 (16, 0.000428165),
 (17, 0.00029511366),
 (5, 0.0002944533),
 (26, 0.00019368336),
 (15, 0.00017721627),
 (22, 0.00013696458),
 (29, 0.000115174604),
 (11, 8.151882e-05),
 (24, 6.048537e-05),
 (6, 4.6618254e-05),
 (13, 3.339472e-05),
 (0, 1.9319625e-05),
 (19, 1.0128587e-05)]

도로 와 관련이 가장 높은 토픽은 7번쨰 토픽이군요! 이렇듯, LDA를 통해서 우리는 문서 전체집합에 부여되어 있는 topic, 그리고 문서의 관점에서 Topic과 documents 그리고 단어의 관점에서 topic과 documents를 알수 있습니다.