토픽 모델링은 컴퓨터와 인간 언어의 관계를 탐구하는 컴퓨터 과학 분야인 자연 언어 처리의 한 부분으로, 텍스트 데이터 셋의 가용성이 증가하면서 인기를 끌고 있다.

NLP는 텍스트, 음성 및 이미지를 포함해 거의 모든 형태의 언어를 다룰 수 있다.

토픽 모델

토픽 모델은 거의 대부분 주제를 사전에 알지 못하기 때문에 비지도 학습 영역에 속한다. 비지도 학습 관점에서 토픽 모델은 클러스터링 알고리즘, 특히 k-평균 클러스터링과 가장 유사하고 시작할 때 주제의 수를 선택한 후 모델에서 해당 주제를 구성하는 단어를 추출한다. 

라이브러리 용도
langdetect 언어 종류 감지
matplotlib.pyplot 기본 도표 그리기
nltk 다양한 자연어 처리 작업
numpy 배열과 행렬 작업
pandas 데이터 프레임 작업
pyLDAvis 잠재 디리클레 할당 모델의 결과 시각화
pyLDAvis.sklearn pyLDAvis를 sklearn 모델과 함께 실행
regex 정규표현식 작성 및 실행
sklearn 머신러닝 구축

토픽 모델 개요

잠재적으로 관련이 있는 대량의 텍스트 데이터를 분석할 때 토픽 모델이 좋은 해법이 될 수 있다. 여기에서 연관된 데이터라는 것은 이상적으로 동일한 출처의 문서를 의미한다.

토픽 모델은 문서 모음으로부터 말뭉치(corpus)라는 부르는 단어들을 사용해 추상적인 주제를 식별한다. 즉 문장에 급여나 직원, 회의라는 단어가 포함돼 있다면 그 문장은 일에 관한 것이라고 가정한다고 생각해도 무방하다. 

토픽모델은 동일한 문서에 있는 단어가 연관돼 있다고 가정하고 가정하고 그 가정을 사용해 반복적으로 유사하게 나타나는 단어 그룹을 찾아 추상적인 주제를 정의한다. 이와 같이 이러한 모델은 감지되는 패턴이 단어로 구성돼 있는 고전적인 패턴 인식 알고리즘이다.

토픽 모델링 알고리즘에 4가지 단계

1. 토픽의 수를 정의한다.(모델을 피팅하기 전에 주제의 수를 선택)

2. 문서를 스캔하면서 동시에 등장하는 단어나 구절을 인식한다.

3. 문서의 특성을 나타내는 단어의 클러스터를 스스로 학습한다.

4. 말뭉치를 단어 클러스터로 특성화하는 추상 토픽을 출력한다.

- 일반적인 토픽 모델링 작업 흐름

일반적인 토픽 모델링 작업 흐름

> 가능한 최적의 주제 수를 선택해야 모델이 미리 정의된 주제 수의 제약하에 말뭉치에 가장 적합한 단어 그룹을 찾기 때문이다. 주제 수가 너무 많으면 주제가 부적절하게 좁아지면서 지나치게 세분화된 주제는 오버쿡드 라고 말한다. 마찬가지로 주제의 수가 너무 적으면 주제가 일반적으로 모호해지고, 위 상황을 언더쿡드 라고 말한다.

토픽 모델의 주요 특징

- 특정한 단어 또는 한 구절의 주제를 생성하지 않고 각각 추상 주제를 나타내는 단어 모음을 생성한다

- 직접 내용을 확인하지 않은 문서에 대해서도 주제를 예측해낼 수 있다. (단 문서에 학습 데이터에 없는 단어가 있는 경우 모델은 학습 데이터에서 식별된 주제 중 하나에 연결된 경우에도 해당 단어를 처리할 수 없다.) 그래서 예측보다는 탐색 분석 및 추론에 더 많이 사용되는 경향이 있다.

- 토픽 모델링을 논의할때 주제를 나타내는 단어 그룹이 개념적으로 연관된 것이 아니고 근접성으로만 연관된 것이라는 사실을 지속적으로 강화하는 것이 중요하다. 

* 근접성의 판단

- 동일한 문서에 등장하는 모든 단어는 관련이 있다는 가정에 따르면 자주 근접하는 단어는 주제를 정의하기에 충분하다. 하지만 위 방법의 가정이 일관된 주제를 형성하기에 너무 일반적일 수도 있다. 

* 추상적인 주제를 해석 할려면

- 텍스트 데이터의 고유 특성과 생성된 단어 그룹을 균형 있게 조정해야한다. 

* 추가로 고려할점

- 텍스트 데이터가 갖는 기본적인 노이즈 특성으로 인해 토픽 모델이 주제 중 하나와 관련이 없는 단어를 해당 주제에 지정할 수도 있다.

 

토픽 모델링의 알고리즘 

1. 잠재 디리클레 할당(LDA)

2. 음수 미포함 행렬 분해(NMF)

 

비지니스 활용

- 제약 사항이 있음에도 토픽 모델링은 적당한 곳에 제대로 사용하면 사업적인 가치를 이끌어낼 수 있는 실행 가능한 통찰을 제공하며 예를 들면 개인이 이전에 읽은 글을 토대로 구성된 모음을 사용해 토픽 모델은 독자가 읽기 원하는 글의 유형을 알려준다. 이는 사용자에게 단순성과 사용 편의성을 높여주는 맞춤형 큐레이션이다.

 

연습 1

1. 데이터 로드

//필요한 모듈 임포트

import langdetect
import matplotlib.pyplot
import nltk
import numpy
import pandas
import pyLDAvis
import pyLDAvis.sklearn
import regex
import sklearn

// 전처리 과정에서 사용될 2개의 사전을 로드

nltk.download('wordnet')
nltk.download('stopwords')

// 데이터 로드

path = "News_Final.csv"
df = pandas.read_csv(path, header=0)

// 데이터 모양과 열들을 출력한다.

def dataframe_quick_look(df, nrows):
    print("SHAPE:\n{shape}\n".format(shape=df.shape))
    print("COLUMN NAMES:\n{names}\n".format(names=df.columns))
    print("HEAD:\n{head}\n".format(head=df.head(nrows)))

dataframe_quick_look(df, nrows=2)

// Topic열에는 실제로 토픽 모델이 확인하려는 정보가 포함돼 있다. 그래서 자체적으로 주제를 생성한 후 이를 제공된 주제 데이터와 서로 결과를 비교할수 있게 미리 고유한 주제와 발생 횟수를 출력

print("TOPICS:\n{topics}\n".format(topics=df["Topic"].value_counts()))

 

// 헤드라인 데이터를 추출하고 추출된 데이터를 리스트(list) 오브젝트로 변환한다. 그 후 출력을 해본다.

raw = df["Headline"].tolist()
print("HEADLINES:\n{lines}\n".format(lines=raw[:5]))
print("LENGTH:\n{length}\n".format(length=len(raw)))

2. 텍스트 데이터 정리

// 텍스트가 영어인지 아닌지 식별하는 함수

def do_language_identifying(txt):
try:
 the_language = langdetect.detect(txt)
except: 
the_language = 'none'
return 
the_language

// 표제어를 추출하는 함수

def do_lemmatizing(wrd):
out = nltk.corpus.wordnet.morphy(wrd)
 return 
(wrd if out is None else out)

// 메인 전처리 함수 코드

def do_headline_cleaning(txt):
# 언어를 식별한다.
# 영어가 아니면 return을 null 로 한다. 
    lg = do_language_identifying(txt)
    if lg != 'en': 
        return None
# 공백을 사용해 문자열을 토큰이라고하는 조각으로 나눈다.
    out = txt.split(" ")
# URL(http:s)을 식별해 "URL"이라는 문자열로 교체
    out = ['URL' if bool(regex.search("http[s]?://", i)) else i for i in out]
# 정규 표현식을 사용해 모든 문장 부호와 줄 바꾸기 기호를 빈문자열로 교체
    out = [regex.sub("[^\\w\\s]|\n", "", i) for i in out]
# 정규 표현식을 이용해 숫자를 모두 빈문자열로 교체
    out = [regex.sub("^[0-9]*$", "", i) for i in out]
# 모든 대문자를 소문자로 변경(복잡도를 줄이기 위해서)
    out = [i.lower() if i not in "URL" else i for i in out]
# URL이라는 문자열을 제거하자(왜냐 문자열과 주제 사이에 연결이 발생할 가능성이 있어서)
    out = [i for i in out if i not in "URL"]
# ntlk에서 stopwords을 불러와 stopwords를 제거
    list_stop_words = nltk.corpus.stopwords.words("English")
    list_stop_words = [regex.sub("[^\\w\\s]", "", i) for i in list_stop_words]
    out = [i for i in out if i not in list_stop_words]
# 각 헤드라인에 적용할 수 있는 함수를 정의해 표제어 추출 이때 wordnet사전을 불러서 사용
    out = [do_lemmatizing(i) for i in out]
# 토큰 목록에서 길이가 4 미만인 단어를 제거한다.(이는 대체로 짧은 단어는 일반적인 단어이고 주제와 관련이 적다는 가정을 전제로 한다.) 
    out = [i for i in out if len(i) >= 5]
    return out

clean = list(map(do_headline_cleaning, raw))

clean = list(filter(None.__ne__, clean))
print("HEADLINES:\n{lines}\n".format(lines=clean[:5]))
print("LENGTH:\n{length}\n".format(length=len(clean)))

// 각 헤더라인에 대해 공백을 사용해 토큰들을 연결한다.

clean_sentences = [" ".join(i) for in clean]
print(clean_sentences[0:10])

 

1) 잠재 디리클레 할당(LDA)

- 주어진 문서에 대하여 각 문서에 어떤 주제들이 존재하는지를 서술하는 대한 확률적 토픽 모델 기법 중 하나이며, 미리 알고 있는 주제별 단어수 분포를 바탕으로, 주어진 문서에서 발견된 단어수 분포를 분석함으로써 해당 문서가 어떤 주제들을 함께 다루고 있을지를 예측할 수 있다.

- LDA는 생식 확률 모델이며, 확률로 표현된 데이터를 생성한 프로세스를 알고있다고 가정한 다음 데이터로 부터 이를 생성한 매개변수를 역으로 찾는 작업이다.

LDA에서 중요한 가정

 - 단어의 교환성(exchangeability)이다. 이는 '단어 주머니(bag of words)'라고 표현하며, 교환성은 단어들의 순서는 상관하지 않고 오로지 단어들의 유무만이 중요하다는 가정이다. 예를 들어, 'Apple is red'와 'Red is apple' 간에 차이가 없다고 생각하는 것이다. 단어의 순서를 무시할 경우 문헌은 단순히 그 안에 포함되는 단어들의 빈도수만을 가지고 표현이 가능하게 된다. 이 가정을 기반으로 단어와 문서들의 교환성을 포함하는 혼합 모형을 제시한 것이 바로 LDA이다. 하지만 단순히 단어 하나를 단위로 생각하는 것이 아니라 특정 단어들의 묶음을 한 단위로 생각하는 방식(n-gram)으로 LDA의 교환성 가정을 확장시킬 수도 있다.

LDA 모델의 그래픽 표현

- M개의 문서가 주어져 있고, 모든 문서는 각각 k개의 주제중 하나에 속할때, 문서는 N개의 단어의 연속이며, 전집은 M개의 문서의 집합이며, 생성 프로세스는 말뭉치의 모든 문서에 대해 실행됐으므로 M으로 표시된 가장 바깥쪽 플레이트는 각문서에 대한 반복을 나태낸다. 마찬가지로 단어에 대한 반복은 N으로 표기된 다이어그램의 가장 안쪽 프레이트로 표시된다. 원은 매개변수와 분포, 결과를 나타내고 w로 표시된 음영 처리된 원은 선택된 단어로, 유일하게 알고 있는 데이터 조각이므로 생성 과정을 역으로 엔지니어링 하는 데 사용된다.

- 4가지 변수

* α : 주제 문서 디리클레 배포용 하이퍼파라미터

* β : 각 주제에 대한 단어의 분포

* Z : 주제에 대한 잠재 변수

* θ : 각 문서의 주제 배포를 위한 잠재 변수

>> αβ는 문서 내 주제와 주제 내 단어의 빈도를 제어한다. 만일 α가 증가하면 각 문서의 주제 수가 증가함에 따라 문서가 점점 유사해진다. 반면에 α가 줄어들면 각 문서의 주제 수가 감소함에 따라 문서의 유사성이 낮아진다. β 또한 비슷하게 동작한다.

변분 추론

LDA가 가진 큰 문제는 조건부 확률, 분포의 평가를 관리할 수 없으므로 직접 계산하는 대신 확률을 추정한다는 점이다. 

변수 추론은 더 간단한 근사 알고리즘 중 하나지만 확률에 관한 상당한 지식을 필요로 하는 광범위한 파생이 존재한다.

변수추론은 각 문서의 각 단어를 주제 중 하나에 무작위로 할당해 시작하고 각 문서와 각 문서의 각 단어에 대한 두 비율을 계산하며 이 두 비율 중 하나는 문서에 대한 주제의 비율 P(Topic | Document)이며, 나머지 하나는 주제에 대한 단어의 비율 P(Word | Topic)이다. 이 두 비율을 곱하고 결과 비율을 사용해 단어를 새 주제에 할당하고 주제 할당이 크게 변하지 않는 정상 상태에 도달할 때까지 이 과정을 반복한다. 이후에 문서 내 주제 혼합 및 주제 내 단어 혼합을 추정하는데 사용된다.

변수 추론의 목적

- 실제 분포가 다루기 어려운 형태일 때, 초기 분포와 매우 유사하면서도 다루기 쉬운 간단한 변형 분포를 사용하려는  데 있다.

 

Bag of words

- 텍스트는 머신 러닝으로 전달 할수 없어서 먼저 숫자 형태로 인코딩을 해야한다. 머신러닝에서 텍스트를 사용하는 간단한 방법은 Bag of words 모델을 사용하는 것이다.

- 이 모델은 단어 순서에 관한 모든 정보를 제거하고 각 단어의 존재하는 정도(횟수 또는 빈도)에 중점을 둔다.

Bag of words 데이터 구조로 변환하기 위해 CountVectorizer를 실행 예제

number_words, number_docs, number_features를 정의하며 첫 두개의 변수는 LAD 결과의 시각화를 제어하며 마지막 인자는 특징 공간에 유지할 단어의 수를 제어한다.

number_words = 10
number_docs = 10
number_features = 1000

카운트 벡터라이저를 실행하기 전에 입력값을 세가지 max_df, min_df, max_features이다. 이들 매개변수는 말뭉치의 단어 수를 모델에 영향을 줄 수 있는 수준까지 추가 필터링을 한다. 적은 수의 문서에만 등장하는 단어는 주제에 속하기 너무 드물기 때문에 이를 제거하기 위해 min_df는 지정된 수보다 적은 수의 문서에 등자하는 단어를 버리는데 사용된다. 너무 많은 문서에 나타나는 단어는 특정 주제와 연결될 만큼 구체적이지 않으므로 max_df는 지정된 백분율보다 더 많은 문서에 등장하는 단어를 버리기 위해 사용된다. 마지막으로 너무 과한 모델 피팅을 원치는 않으므로 모델에 맞는 단어의 수는 가장 자주 발생하는 지정된 수(max_features)로 제한한다.

vectorizer1 = sklearn.feature_extraction.text.CountVectorizer(
    analyzer="word",
    max_df=0.5
    min_df=20
    max_features=number_features
)
clean_vec1 = vectorizer1.fit_transform(clean_sentences)
print(clean_vec1[0])

벡터라이저에서 항상 이름과 단어를 추출한다. 모델은 단어를 숫자로 인코딩된 형래로만 제공하므로 형상 이름 벡터를 결과와 병합하면 해석이 쉬워진다.

feature_names_vec1 = vectorizer1.get_feature_names()

퍼블렉서티

- 모델에는 일반적으로 성능을 평가하는 데 활용할 수 있는 지표가 있다. 성능에 대한 정의가 살짝 다르다. 회귀 및 분류에서 예측값은 성과에 대한 명확한 측정값을 계산할 수 있는 실제값과 비교할 수 있다. 토픽 모델을 사용하면 모델이 학습한 단어만 알기 때문에 동일한 주제를 사용하더라도 새 문서에 해당 단어가 포함되지 않을 수 있으므로 예측의 신뢰성이 떨어진다. 이러한 차이로 인해 토픽 모델은 퍼플렉서티라는 언어 모델에 특화된 지표를 사용해 평가한다.

-퍼플렉서티는 PP라고 줄여 부르고, 주어진 단어를 평균적으로 따를 수 있는 가장 가능성이 높은 다른 단어들의 수를 측정한다.

평균적으로 가장 가능성이 높은 소수의 단어들이 뒤따를 수 있는 단어가 더 구체적이고 주제와 더 밀접하게 연관될 수 있다는 생각을 할수 있다. 이와 같이 낮은 퍼플렉서티 점수는 더 나은 언어 모델임을 의미하며, 더 나은 최적의 주제 수를 선택하는데 사용할 수 있다.

n은 단어의 나열에 속한 단어의 수를 의미한다.

연습 : 주제 선택

- LDA는 2개의 입력을 가진다. 첫 번째는 문서 자체이며, 두 번째는 주제의 수이다. 

최적의 수를 찾는 방법

- 여러 개의 주제를 검색하고 가장 퍼플렉서티 값이 작은 주제 수를 선택하는 것이다. 위 방식을 격자 검색이라고 한다.

// 다양한 주제에 LDA 모델을 피팅하는 함수를 정의하고 퍼플렉서티 점수를 계산하고 주제와 퍼플렉서티 점수의 데이터 프레임이고 또다른 하나는 최소 퍼플렉서티 점수를 갖는 주제의 수의 데이터프레임을 반환한다.

def perplexity_by_ntopic(data, ntopics):
    output_dict = {
        "Number Of Topics": [], 
        "Perplexity Score": []
    }
    
    for in ntopics:
        lda = sklearn.decomposition.LatentDirichletAllocation(
            n_components=t,
            learning_method="online",
            random_state=0
        )
        lda.fit(data)
        
        output_dict["Number Of Topics"].append(t)
        output_dict["Perplexity Score"].append(lda.perplexity(data))
        
    output_df = pandas.DataFrame(output_dict)
    
    index_min_perplexity = output_df["Perplexity Score"].idxmin()
    output_num_topics = output_df.loc[
        index_min_perplexity,  # 인덱스
        "Number Of Topics"  # 컬럼 
    ]
        
    return (output_df, output_num_topics)

//퍼플렉서티 함수 실행

df_perplexity, optimal_num_topics = perplexity_by_ntopic(
    clean_vec1, 
    ntopics=[1, 2, 3, 4, 6, 8, 10]
)
print(df_perplexity)

// 시각화

df_perplexity.plot.line("Number Of Topics""Perplexity Score")

그림으로 보았을때, 퍼플렉서티를 통한 최적의 주제 수는 3개이다. 하지만 위에서 Topic에 포함된 주제 수는 4개였다. 그래서 3개가 반환된 이유가 있다 그 이유는 잠시뒤에 다루겠다.

+ Recent posts