자연어처리(NLP) 7일차 (토픽 모델링)

정민수
29 min readJun 9, 2019

--

2019.06.09

본 글은 https://wikidocs.net/book/2155을 참고하여 작성되었음을 밝힙니다.

핵심키워드

  • 토픽 모델링(Topic Modeling)
  • SVD(Singualr Value Decomposition)
  • 잠재 의미 분석(Latent Semantic Analysis, LSA)

토픽 모델링(Topic Modeling)

토픽 모델링(Topic Modeling)이란 기계 학습 및 자연어 처리 분야에서 토픽 모델(Topic model)이라는 문서 집합의 추상적인 주제를 발견하기 위한 통계적 모델 중 하나로, 텍스트 본문의 숨겨진 의미구조를 발견하기 위해 사용되는 텍스트 마이닝 기법이다.

잠재 의미 분석(Latent Semantic Analysis, LSA)

LSA는 정확히는 토픽 모델링을 위해 최적화된 알고리즘은 아니지만, 토픽 모델링이라는 분야에 아이디어를 제공한 알고리즘이라고 볼 수 있다. 이에 토픽 모델링 알고리즘인 LDA에 앞서 배워보도록 하자. 뒤에서 배우게 되는 LDA는 LSA의 단점을 개선해나가며 탄생한 알고리즘으로 토픽 모델링에 보다 적합한 알고리즘이다.

BoW에 기반한 단어 문서 행렬이나 TF-IDF는 기본적으로 단어의 빈도 수를 이용한 수치화 방법이기 때문에 단어의 의미를 고려하지 못한다는 단점이 있다. (이를 토픽 모델링 관점에서는 단어의 토픽을 고려하지 못한다고도 한다.) 이를 위한 대안으로 단어 문서 행렬의 잠재된(Latent) 의미를 이끌어내는 방법으로 잠재 의미 분석(Latent Semantic Analysis, LSA)이라는 방법이 있다. 잠재 의미 분석(Latent Semantic Indexing, LSI)이라고 부르기도 한다.

이 방법을 이해하기 위해서는 선형대수학의 특이값 분해(Singular Value Decomposition, SVD)를 이해할 필요가 있다. 이 챕터에서는 SVD를 수행하는 구체적인 선형대수학에 대해서는 설명하지 않고, SVD가 갖고 있는 의미를 이해하는 것에 초점을 맞춘다.

1. 특이값 분해(Singular Value Decomposition, SVD)

시작하기 앞서, 여기서의 특이값 분해(Singular Value Decomposition, SVD)는 실수 벡터 공간에 한정하여 내용을 설명함을 명시한다. SVD란 A가 m x n 행렬일 때, 다음과 같이 3개의 행렬의 곱으로 분해(Decomposition)하는 것을 말한다.

여기서 각 3개의 행렬은 다음과 같은 조건을 만족한다.

여기서 직교행렬(Orthogonal matrix)이란 자신과 자신의 전치 행렬(Transposed matrix)의 곱 또는 이를 반대로 곱한 결과가 단위행렬(Indentity matrix)이 되는 행렬을 말한다. 또한 대각행렬(Diagonal matrix)이란 주대각선을 제외한 곳의 원소가 모두 0인 행렬을 의미한다.

이때 SVD로 나온 대각 행렬의 대각 원소의 값을 행렬 A의 특이값(Singulary Value)라고 한다.

1) 전치 행렬(Transposed Matrix)

전치 행렬(Transposed Matrix)은 원래의 행렬에서 행과 열을 바꾼 행렬이다. 즉, 주대각선을 축으로 반사대칭을 하여 얻는 행렬이다. 기호는 기존 행렬 표현의 우측에 T를 붙인다.

2) 단위 행렬(Identity Matrix)

단위 행렬(Identity Matrix)은 주대각선의 원소가 모두 1이며 나머지 원소는 모두 0인 정사각 행렬을 말한다. 보통 줄여서 대문자 I로 표현하기도 하는데, 2 x 2 단위 행렬과 3 x 3 단위 행렬을 표현하면 다음과 같다.

3) 역행렬(Inverse Matrix)

4) 직교 행렬(Orthogonal Matrix)

직교 행렬(Orthogonal Matrix)는 실수 n x n 행렬 A에 대해서 AxA^T = I를 만족하면서 A^TxA=I를 만족하는 행렬 A를 직교행렬이라고 한다. 그런데 역행렬의 정의를 다시 생각해보면, 결국 직교 행렬은

을 만족한다.

5) 대각 행렬(Diagonal Matrix)

대각행렬(Diagonal Matrix)은 주대각선을 제외한 곳의 원소가 모두 0인 행렬을 말한다. 아래의 그림에서는 주대각선의 원소를 a라고 표현하고 있다. 만약 대각 행렬 Σ가 3 X 3 행렬이라면, 다음과 같은 모양을 가진다.

여기까진 정사각 행렬이기 때문에 직관적으로 이해하기 쉽다. 그런데 정사각행렬이 아니라 직사각 행렬이 될 경우를 잘봐야 헷갈리지 않는다. 만약 행의 크기가 열의 크기가보다 크다면 다음과 같은 모양을 가진다. 즉, m x n 행렬일 때, m > n인 경우다.

반면 n > m인 경우에는 다음과 같은 모양을 가진다.

여기까지는 일반적인 대각 행렬에 대한 정의다. SVD를 통해 나온 대각 행렬 Σ는 추가적인 성질을 가지는데, 대각 행렬 Σ의 주대각원소를 행렬 A의 특이값(Singular Value)라고 하며, 이를

라고 표현한다고 하였을 때, 특이값

은 내림차순으로 정렬되어 있다는 특징을 가진다.

아래의 그림은 특이값 12.4, 9.5, 1.3이 내림차순으로 정렬되어져 있는 모습을 보여준다.

2. 절단된 SVD(Truncated SVD)

위에서 설명한 SVD를 풀 SVD(full SVD)라고 한다. 하지만 LSA의 경우 풀 SVD에서 나온 3개의 행렬에서 일부 벡터들을 삭제시킨 절단된 SVD(Truncated SVD)를 사용하게 된다. 그림을 통해 이해해보자.

절단된 SVD는 대각 행렬 Σ의 대각 원소의 값 중에서 상위값 t개만 남게 된다. 절단된 SVD를 수행하면 값의 손실이 일어나므로 기존의 행렬 A를 복구할 수 없다. 또한, U행렬과 V행렬의 t열까지만 남게 된다. 여기서 t는 우리가 찾고자하는 토픽의 수를 반영한 하이퍼파라미터값이 되는 것이다. 하이퍼파라미터란 사용자가 직접 값을 선택하며 성능에 영향을 주는 매개변수를 말한다. t를 선택하는 것은 쉽지 않은 일이다. t를 크게 잡으면 기존의 행렬 A로부터 다양한 의미를 가져갈 수 있지만, t를 작제 잡아야만 노이즈를 제거할 수 있기 때문이다.

이렇게 일부 벡터들을 삭제하는 것을 데이터의 차원을 줄인다고도 말하는데, 데이터의 차원을 줄이게 되면 당연히 풀 SVD를 하였을 때보다 직관적으로 계산비용이 낮아지는 효과를 볼 수 있다.

하지만 계산 비용이 낮아지는 것 외에도 상대적으로 중요하지 않은 정보를 삭제하는 효과를 갖고 있는데, 이는 영상 처리 분야에서는 노이즈를 제거한다는 의미를 갖고 자연어 처리 분야에서는 설명력이 낮은 정보를 삭제하고 설명력이 높은 정보를 남긴다는 의미를 갖고 있다. 즉, 다시 말하면 기존의 행렬에서는 드러나지 않았던 심층적인 의미를 확인할 수 있게 해준다.

3. 잠재 의미 분석(Latent Semantic Analysis, LSA)

기존의 단어 문서 행렬이나 단어 문서 행렬에 단어의 중요도에 따른 가중치를 주었던 TF-IDF 행렬은 단어의 의미를 전혀 고려하지 못한다는 단점을 갖고 있다. LSA는 기본적으로 단어 문서 행렬이나 TF-IDF 행렬에 절단된 SVD(Truncated SVD)를 사용하여 차원을 축소시키고, 단어들의 잠재적인 의미를 끌어낸다는 아이디어를 갖고 있다.

실습을 통해서 이해해보자.

위와 같은 TDM을 실제로 파이썬을 통해서 만들면 다음과 같다.

import numpy as np
A=np.array([[0,0,0,1,0,1,1,0,0],[0,0,0,1,1,0,1,0,0],[0,1,1,0,2,0,0,0,0],[1,0,0,0,0,0,0,1,1]])
np.shape(A)
(4, 9)

4 x 9 의 크기를 가지는 TDM이 생성되었다. 이에 대해서 풀 SVD(Full SVD)를 수행해보자.

U, s, VT = np.linalg.svd(A, full_matrices = True)print(U.round(2))
np.shape(U)
[[-0.24 0.75 0. -0.62]
[-0.51 0.44 -0. 0.74]
[-0.83 -0.49 -0. -0.27]
[-0. -0. 1. 0. ]]
(4, 4)

4 x 4의 크기를 가지는 직교 행렬 U가 생성되었다. 이제 대각 행렬 S를 확인해보자.

print(s.round(2))
np.shape(s)
[2.69 2.05 1.73 0.77]
(4,)

Numpy의 linalg.svd()는 특이값 분해의 결과로 대각행렬이 아니라 특이값의 리스트를 반환한다. 그러므로 앞서 본 수식의 형식으로 보려면 이를 다시 대각행렬로 바꾸어 주어야 한다. 우선 특이값을 s에 저장하고 대각 행렬 크기의 행렬을 생성한 후에 그 행렬에 특이값을 삽입해본다.

S = np.zeros((4,9)) # 대각 행렬의 크기인 4x9의 제로 행렬 생성
S[:4, :4] = np.diag(s) # 특이값을 대각행렬에 삽입
print(S.round(2))
np.shape(S)
[[2.69 0. 0. 0. 0. 0. 0. 0. 0. ]
[0. 2.05 0. 0. 0. 0. 0. 0. 0. ]
[0. 0. 1.73 0. 0. 0. 0. 0. 0. ]
[0. 0. 0. 0.77 0. 0. 0. 0. 0. ]]
(4, 9)

4 x 9의 크기를 가지는 대각 행렬 S가 생성되었다. 2.69 > 2.05 > 1.73 > 0.77 순으로 값이 내림차순을 보이는 것을 확인할 수 있다.

print(VT.round(2))
np.shape(VT)
[[-0. -0.31 -0.31 -0.28 -0.8 -0.09 -0.28 -0. -0. ]
[ 0. -0.24 -0.24 0.58 -0.26 0.37 0.58 -0. -0. ]
[ 0.58 -0. 0. 0. -0. 0. -0. 0.58 0.58]
[ 0. -0.35 -0.35 0.16 0.25 -0.8 0.16 -0. -0. ]
[-0. -0.78 -0.01 -0.2 0.4 0.4 -0.2 0. 0. ]
[-0.29 0.31 -0.78 -0.24 0.23 0.23 0.01 0.14 0.14]
[-0.29 -0.1 0.26 -0.59 -0.08 -0.08 0.66 0.14 0.14]
[-0.5 -0.06 0.15 0.24 -0.05 -0.05 -0.19 0.75 -0.25]
[-0.5 -0.06 0.15 0.24 -0.05 -0.05 -0.19 -0.25 0.75]]
(9, 9)

9 x 9의 크기를 가지는 직교 행렬 VT(V의 전치행렬)가 생성되었다. 즉, U x S x VT를 하면 기존의 행렬 A가 나와야 한다. Numpy의 allclose()는 2개의 행렬이 동일하면 True를 리턴한다. 이를 사용하여 정말로 기존의 행렬 A와 동일한지 확인해보자.

np.allclose(A, np.dot(np.dot(U,S), VT).round(2))
True

지금까지 수행한 것은 풀 SVD(Full SVD)이다. 이제 t를 정하고, 절단된 SVD(Truncated SVD)를 수행해보도록 하자. 여기서는 t = 2로 한다. 우선 대각 행렬 S 내의 특이값 중에서 상위 2개만 남기고 제거해보도록 한다.

S = S[:2, :2]
print(S.round(2))
[[2.69 0. ]
[0. 2.05]]

상위 2개의 값만 남기고 나머지는 모두 제거된 것을 볼 수 있다. 이제 직교 행렬 U에 대해서도 2개의 열만 남기고 제거한다.

U = U[:, :2]
print(U.round(2))
[[-0.24 0.75]
[-0.51 0.44]
[-0.83 -0.49]
[-0. -0. ]]

2개의 열만 남기고 모두 제거가 된 것을 볼 수 있다. 이제 행렬 V의 전치 행렬인 VT에 대해서 2개의 행만 남기고 제거한다. 이는 V관점에서는 2개의 열만 남기고 제거한 것이 된다.

VT = VT[:2, :]
print(VT.round(2))
[[-0. -0.31 -0.31 -0.28 -0.8 -0.09 -0.28 -0. -0. ]
[ 0. -0.24 -0.24 0.58 -0.26 0.37 0.58 -0. -0. ]]

이제 축소된 행렬 U, S, VT에 대해서 다시 U x S x VT연산을 하면 기존의 A와는 다른 결과가 나오게 된다. 값이 손실되었기 때문에 이 세 개의 행렬로는 이제 기존의 A행렬을 복구할 수 없다. U x S x VT 연산을 해서 나오는 값을 A_prime이라 하고 기존의 행렬 A와 값을 비교해보도록 하자.

A_prime = np.dot(np.dot(U, S), VT)print(A)
print(A_prime.round(2))
[[0 0 0 1 0 1 1 0 0]
[0 0 0 1 1 0 1 0 0]
[0 1 1 0 2 0 0 0 0]
[1 0 0 0 0 0 0 1 1]]
[[ 0. -0.17 -0.17 1.08 0.12 0.62 1.08 -0. -0. ]
[ 0. 0.2 0.2 0.91 0.86 0.45 0.91 0. 0. ]
[ 0. 0.93 0.93 0.03 2.05 -0.17 0.03 0. 0. ]
[ 0. 0. 0. 0. 0. -0. 0. 0. 0. ]]

대체적으로 기존에 0인 값들은 0에 가까운 값이 나오고, 1인 값들은 1에 가까운 값이 나오는 것을 볼 수 있다. 또한 값이 제대로 복구되지 않은 구간도 존재해보인다. 이제 이렇게 차원이 축소된 U, S, VT의 크기가 어떤 의미를 가지고 있는지 알아보자.

축소된 U는 4 x 2 의 크기를 가지는데, 이는 잘 생각해보면 문서의 개수 x 토픽의 수 t의 크기이다. 단어의 개수인 9는 유지되지 않는데 문서의 개수인 4의 크기가 유지되었으니 4개의 문서 각각을 2개의 값으로 표현하고 있다. 즉, U의 각 행은 잠재 의미를 표현하기 위한 수치화된 각각의 문서 벡터라고 볼 수 있다. 축소된 VT는 2 x 9의 크기를 가지는데, 이는 잘 생각해보면 토픽의 수 t x 단어의 개수의 크기이다. VT의 각 열은 잠재 의미를 표현하기 위해 수치화된 각각의 단어 벡터라고 볼 수 있다.

이 문서 벡터들과 단어 벡터들을 통해 다른 문서의 유사도, 다른 단어의 유사도, 단어(쿼리)로부터 문서의 유사도를 구하는 것들이 가능해진다.

4. 실습을 통한 이해

사이킷 런에서는 Twenty Newgroups 라고 불리는 20개의 다른 주제를 가진 뉴스 데이터를 제공한다. 앞서 언급했듯이 LSA가 토픽 모델리에 최적화된 알고리즘은 아니지만, 토픽 모델링이라는 분야의 시초가 되는 알고리즘이다. 여기서는 LSA를 사용해서 문서의 수를 원하는 토픽의 수로 압축한 뒤에 각 토픽당 가장 중요한 단어 5개를 출력하는 실습으로 토픽 모델링을 수행해보도록 한다.

1) 뉴스 데이터에 대한 이해

import pandas as pd
from sklearn.datasets import fetch_20newsgroups
dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove=('headers', 'footers', 'quotes'))
documents= dataset.data
len(documents)
11314

해당 데이터는 총 11,314개의 데이터를 갖고 있음을 확인할 수 있다. 이 중 첫번째 데이터만 출력해보도록 하자.

documents[0]"Well i'm not sure about the story nad it did seem biased. What\nI disagree with is your statement that the U.S. Media is out to\nruin Israels reputation. That is rediculous. The U.S. media is\nthe most pro-israeli media in the world. Having lived in Europe\nI realize that incidences such as the one described in the\nletter have occured. The U.S. media as a whole seem to try to\nignore them. The U.S. is subsidizing Israels existance and the\nEuropeans are not (at least not to the same degree). So I think\nthat might be a reason they report more clearly on the\natrocities.\n\tWhat is a shame is that in Austria, daily reports of\nthe inhuman acts commited by Israeli soldiers and the blessing\nreceived from the Government makes some of the Holocaust guilt\ngo away. After all, look how the Jews are treating other races\nwhen they got power. It is unfortunate.\n"

해당 데이터에는 많은 특수문자가 포함된 다수의 영어문장으로 구성되어져 있다. 즉, 이러한 데이터가 11,314개가 존재한다는 것이다.

사이킷 런이 제공하는 뉴스 데이터에서 target_name에는 본래 이 뉴스 데이터가 어떤 20개의 카테고리를 갖고 있었는지가 저장되어져 있다. 이를 출력해보도록 하자

print(dataset.target_names)
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']

2) 텍스트 전처리

시작하기에 앞서, 텍스트 데이터에 대해서 가능한한 정제 과정을 거쳐야만 한다. 기본적인 아이디어는 알파벳을 제외한 구두점, 숫자, 특수 문자를 제거하는 것이다. 이는 텍스트 전처리 챕터에서 정제 기법으로 배웠던 정규 표현식을 통해서 해결할 수 있다. replace(“[^a-zA-Z#]”, “”)는 알파벳을 제외한 모든 문자를 공백(space)로 처리하는 작업을 수행한다. 또한 짧은 단어는 유용한 정보를 담고 있지 않다고 가정하고, 길이가 짧은 단어도 제거한다. 그리고 마지막으로 모든 알파벳을 소문자로 바꿔서 단어의 개수를 줄이는 작업을 한다.

news_df = pd.DataFrame({'document': documents})# 알파벳을 제외하고 모두 제거
news_df['clean_doc'] = news_df['document'].str.replace("[^a-zA-Z#]"," ")
# 길이가 3이하인 단어는 제거
news_df['clean_doc'] = news_df['clean_doc'].apply(lambda x: ' '.join([w for w in x.split() if len(w)>3]))
# 전체 단어에 대한 소문자 변환
news_df['clean_doc'] = news_df['clean_doc'].apply(lambda x: x.lower())
news_df['clean_doc'][0]
'well sure about story seem biased what disagree with your statement that media ruin israels reputation that rediculous media most israeli media world having lived europe realize that incidences such described letter have occured media whole seem ignore them subsidizing israels existance europeans least same degree think that might reason they report more clearly atrocities what shame that austria daily reports inhuman acts commited israeli soldiers blessing received from government makes some holocaust guilt away after look jews treating other races when they power unfortunate'

우선 특수문자가 제거되었고, if나 you와 같은 길이가 3이하인 단어가 제거된 것을 확인할 수 있다. 뿐만 아니라 대문자가 소문자로 바뀌었다.

아직 정제 작업은 끝나지 않았다. 텍스트 데이터로부터 자주 사용되지 않는 불용어를 제거하도록 한다. 텍스트 데이터로부터 불용어를 제거하기 위해서는, 우선 텍스트 데이터에 대해서 토큰화를 수행해야 한다. 토큰화와 불용어 제거를 순차적으로 진행한다.

from nltk.corpus import stopwords
stop_words = stopwords.words('english')
tokenized_doc = news_df['clean_doc'].apply(lambda x : x.split())
tokenized_doc = tokenized_doc.apply(lambda x: [item for item in x if item not in stop_words])
print(tokenized_doc[0])
['well', 'sure', 'story', 'seem', 'biased', 'disagree', 'statement', 'media', 'ruin', 'israels', 'reputation', 'rediculous', 'media', 'israeli', 'media', 'world', 'lived', 'europe', 'realize', 'incidences', 'described', 'letter', 'occured', 'media', 'whole', 'seem', 'ignore', 'subsidizing', 'israels', 'existance', 'europeans', 'least', 'degree', 'think', 'might', 'reason', 'report', 'clearly', 'atrocities', 'shame', 'austria', 'daily', 'reports', 'inhuman', 'acts', 'commited', 'israeli', 'soldiers', 'blessing', 'received', 'government', 'makes', 'holocaust', 'guilt', 'away', 'look', 'jews', 'treating', 'races', 'power', 'unfortunate']

기존에 있었던 불용어에 속하던 단어들이 사라졌을 뿐만 아니라 토큰화가 수행되었음을 알 수 있다.

3) TF-IDF 행렬 만들기

불용어 제거를 위해 토큰화 작업을 수행했지만, TfidfVectorizer는 기본적으로 토큰화가 되어있지 않은 텍스트 데이터를 입력으로 사용한다. 그렇기 때문에 TfidfVectorizer를 사용해서 TF-IDF 행렬을 만들기 위해서 다시 토큰화 작업을 역으로 취소하는 작업을 수행해보도록 한다. 이를 역토큰화(Detokenization)이라고 한다.

# 역토큰화 (토큰화 작업을 되돌림)
detokenized_doc = []
for i in range(len(news_df)):
t = ' '.join(tokenized_doc[i])
detokenized_doc.append(t)
news_df['clean_doc'] = detokenized_docnews_df['clean_doc'][0]
'well sure story seem biased disagree statement media ruin israels reputation rediculous media israeli media world lived europe realize incidences described letter occured media whole seem ignore subsidizing israels existance europeans least degree think might reason report clearly atrocities shame austria daily reports inhuman acts commited israeli soldiers blessing received government makes holocaust guilt away look jews treating races power unfortunate'

정상적으로 불용어가 제거된 상태에서 역토큰화가 진행된 것을 확인할 수 있다.

이제 사이킷런의 TfidfVectorizer를 통해 단어 1,000개에 대한 TF-IDF 행렬을 만들 것이다. 물론 텍스트 데이터에 있는 모든 단어를 가지고 행렬을 만들 수는 있겠지만, 이는 일반적으로 너무 많은 계산 시간을 필요로 한다. 그렇기 때문에 여기서는 1,000개의 단어로 제한하도록 한다.

from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(stop_words='english',
max_features=1000,
max_df = 0.5,
smooth_idf = True)
X = vectorizer.fit_transform(news_df['clean_doc'])
X.shape
(11314, 1000)

11,314 x 1,000의 크기를 가진 TF-IDF 행렬이 생성되었음을 확인할 수 있다.

4) 토픽 모델링(Topic Modeling)

이제 TF-IDF 행렬을 다수의 행렬로 분해해보도록 한다. 여기서는 사이킷 런의 절단된 SVD(Truncated SVD)를 사용한다. 절단된 SVD를 사용하면 차원을 축소할 수 있다. 원래 기존 뉴스 데이터 자체에 20개의 다른 뉴스 카테고리를 갖고 있었기 때문에, 텍스트 데이터에 20개의 토픽 모델링을 시도해본다. 토픽의 숫자는 n_components의 파라미터로 지정이 가능하다.

from sklearn.decomposition import TruncatedSVD
svd_model = TruncatedSVD(n_components=20,
algorithm='randomized',
n_iter=100,
random_state=122)
svd_model.fit(X)
len(svd_model.components_)
20

여기서 svd_components_는 앞서 배운 LSA에서 VT에 해당된다.

np.shape(svd_model.components_)
(20, 1000)

정확하게 토픽의 수 t x 단어의 수의 크기를 가지는 것을 볼 수 있다.

terms = vectorizer.get_feature_names() 
# 단어 집합. 1,000개의 단어가 저장됨
def get_topics(components, feature_names, n=5):
for idx, topic in enumerate(components):
print("Topic %d:" % (idx+1), [(feature_names[i], topic[i].round(5)) for i in topic.argsort()[:-n -1:-1]])
get_topics(svd_model.components_, terms)

각 20개의 행의 각 1,000개의 열 중 가장 값이 큰 5개의 값을 찾아서 단어로 출력한다.

Topic 1: [('like', 0.2138), ('know', 0.20031), ('people', 0.19334), ('think', 0.17802), ('good', 0.15105)]
Topic 2: [('thanks', 0.32918), ('windows', 0.29093), ('card', 0.18016), ('drive', 0.1739), ('mail', 0.15131)]
Topic 3: [('game', 0.37159), ('team', 0.32533), ('year', 0.28205), ('games', 0.25416), ('season', 0.18464)]
Topic 4: [('drive', 0.52823), ('scsi', 0.20043), ('disk', 0.15518), ('hard', 0.15511), ('card', 0.14049)]
Topic 5: [('windows', 0.40544), ('file', 0.25619), ('window', 0.1806), ('files', 0.16196), ('program', 0.14009)]
Topic 6: [('government', 0.16085), ('chip', 0.16071), ('mail', 0.15626), ('space', 0.15047), ('information', 0.13582)]
Topic 7: [('like', 0.67121), ('bike', 0.14274), ('know', 0.11189), ('chip', 0.11043), ('sounds', 0.10389)]
Topic 8: [('card', 0.44948), ('sale', 0.21639), ('video', 0.21318), ('offer', 0.14896), ('monitor', 0.1487)]
Topic 9: [('know', 0.44869), ('card', 0.35699), ('chip', 0.17169), ('video', 0.15289), ('government', 0.15069)]
Topic 10: [('good', 0.41575), ('know', 0.23137), ('time', 0.18933), ('bike', 0.11317), ('jesus', 0.09421)]
Topic 11: [('think', 0.7832), ('chip', 0.10776), ('good', 0.10613), ('thanks', 0.08985), ('clipper', 0.07882)]
Topic 12: [('thanks', 0.37279), ('right', 0.21787), ('problem', 0.2172), ('good', 0.21405), ('bike', 0.2116)]
Topic 13: [('good', 0.36691), ('people', 0.33814), ('windows', 0.28286), ('know', 0.25238), ('file', 0.18193)]
Topic 14: [('space', 0.39894), ('think', 0.23279), ('know', 0.17956), ('nasa', 0.15218), ('problem', 0.12924)]
Topic 15: [('space', 0.3092), ('good', 0.30207), ('card', 0.21615), ('people', 0.20208), ('time', 0.15716)]
Topic 16: [('people', 0.46951), ('problem', 0.20879), ('window', 0.16), ('time', 0.13873), ('game', 0.13616)]
Topic 17: [('time', 0.3419), ('bike', 0.26896), ('right', 0.26208), ('windows', 0.19632), ('file', 0.19145)]
Topic 18: [('time', 0.60079), ('problem', 0.15209), ('file', 0.13856), ('think', 0.13025), ('israel', 0.10728)]
Topic 19: [('file', 0.4489), ('need', 0.25951), ('card', 0.1876), ('files', 0.17632), ('problem', 0.1491)]
Topic 20: [('problem', 0.32797), ('file', 0.26268), ('thanks', 0.23414), ('used', 0.19339), ('space', 0.13861)]

5. LSA의 장단점(Pros and Cons of LSA)

정리해보면 LSA는 쉽고 빠르게 구현이 가능할 뿐만 아니라 단어의 잠재적인 의미를 이끌어낼 수 있어 문서의 유사도 계산 등에서 좋은 성능을 보여준다는 장점을 갖고 있다. 하지만 SVD의 특성상 이미 계산된 LSA에 새로운 데이터를 추가하여 계산하려고 하면 보통 처음부터 다시 계산해야 한다. 즉, 새로운 정보에 대해 업데이터가 어렵다. 이는 최근 LSA 대신 Word2Vec 등 단어의 의미를 벡터화할 수 있는 또 다른 방법론인 인공 신경망 기반의 방법론이 각광받는 이유이기도 하다.

--

--

정민수
정민수

No responses yet