LSA - 잠재 의미 분석

개요

BoW에 기반한 DTM이나 TF-IDF는 기본적으로 단어의 빈도 수를 이용한 수치화 방법이기 때문에 단어의 의미를 고려하지 못한다는 단점이 있다. 이를 위한 대안으로 DTM의 잠재된(Latent) 의미를 이끌어내는 방법으로 잠재 의미 분석(Latent Semantic Analysis, LSA)이라는 방법이 존재한다.

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

SVD란 A가 m × n 행렬일 때, 다음과 같이 3개의 행렬의 곱으로 분해(decomposition)하는 것을 말한다.

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

U = m×m의 직교행렬

V = n×n 직교행렬 Σ = m×n 직사각 대각행렬

직교행렬(orthogonal matrix)이란 자신과 자신의 전치 행렬(transposed matrix)의 곱 또는 이를 반대로 곱한 결과가 단위행렬(identity matrix)이 되는 행렬을 말한다.

2. TSVD(Truncated SVD)

LSA의 경우 위에서 생성된 Full SVD에서 나온 3개의 행렬에서 일부 벡터들을 삭제시켜 절단된 SVD(truncated SVD)를 사용한다.

절단된 SVD는 대각 행렬 Σ의 대각 원소의 값 중에서 상위 값 t개가 남게 된다. 절단된 SVD를 수행하면 값의 손실이 일어나므로 기존의 행렬 A를 복구할 수 없으며 U행렬과 V행렬의 t열까지만 남는다. 여기서 t는 토픽의 수를 반영한 하이퍼 파라미터 값으로 사용자가 직접 값을 선택하며 성능에 영향을 주는 매개변수를 뜻한다.

예제 실행 코드

아래와 같은 DTM 시나리오를 통해 코드 상에서 TSVD를 구현해보면 다음과 같다.

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]])

해당 행렬의 Shape은 다음과 같다.

>>> np.shape(A)
(4, 9)

생성된 행렬에 대하여 DTM 및 SVD를 생성한다.

>>> U, s, VT = np.linalg.svd(A, full_matrices = True)

>>> 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)

>>> np.shape(s)
[2.69 2.05 1.73 0.77]
(4,)

>>> S = np.zeros((4, 9)) # 대각 행렬의 크기인 4 x 9의 임의의 행렬 생성
>>> S[:4, :4] = np.diag(s) # 특이값을 대각행렬에 삽입
>>> 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)

>>> 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)

이제 t를 정하고, TSVD(Truncated SVD)를 수행해야 한다. 하이퍼 파라미터의 값은 t=2로 가정하고 각 성분에 대하여 절단 작업을 수행한다.

S=S[:2,:2]
U=U[:,:2]
VT=VT[:2,:]
A_prime=np.dot(np.dot(U,S), VT)

Full SVD(A)와 완성된 TSVD(A_prime)의 결과를 비교해보자.

# A
[[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]]
 
 # A_prime
[[ 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는 4 × 2의 크기를 가지며, 이는 문서의 개수 × 토픽의 수 t의 크기이다. 즉, U의 각 행은 잠재 의미를 표현하기 위한 수치화 된 각각의 문서 벡터이며, 2 × 9의 크기를 가지는 축소된 VT는 토픽의 수 t × 단어의 개수의 크기를 뜻한다.

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

기존의 DTM이나 DTM에 단어의 중요도에 따른 가중치를 주었던 TF-IDF 행렬은 단어의 의미를 전혀 고려하지 못한다는 단점을 갖고 있다. 그리하여 LSA는 기본적으로 DTM이나 TF-IDF 행렬에 TSVD(truncated SVD)를 적용 시 차원을 축소시키고, 단어들의 잠재적인 의미를 끌어내는 방식이다.

아래의 코드는 LSA를 기반으로 Sklearn에서 제공하는 테스트 데이터를 사용하여 주제 분류 모델을 학습하는 코드이다.

# LSA : DTM을 차원 축소 하여 축소 차원에서 근접 단어들을 토픽으로 묶는다.
import pandas as pd
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.corpus import stopwords
from sklearn.decomposition import TruncatedSVD
import numpy as np
dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove=('headers', 'footers', 'quotes'))
#####list, 테스트용 뉴스데이터
documents = dataset.data
# 실제 토픽(답안)
#print(len(dataset.target_names), dataset.target_names)
#### 전처리
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())
# NLTK로부터 불용어 사전 로드
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])
#### 역토큰화
detokenized_doc = []
for i in range(len(news_df)):
	t = " ".join(tokenized_doc[i])
	detokenized_doc.append(t)
news_df['clean_doc'] = detokenized_doc
#### TF-IDF 행렬 만들기
vectorizer = TfidfVectorizer(stop_words = "english",
							#max_features = 10000, # 최대 단어 제한
							max_df = 0.5,
							smooth_idf = True)
X = vectorizer.fit_transform(news_df['clean_doc'])
#### 토픽모델링
svd_model = TruncatedSVD(n_components = 20, 
						algorithm = 'randomized',
						n_iter = 100,
						random_state = 122)
svd_model.fit(X)
#(토픽의 수, 해당 토픽과 관련된 단어)
print(np.shape(svd_model.components_))
#결과출력
terms = vectorizer.get_feature_names()
def get_topics(components, feature_names, n = 5):
	for idx, topic in enumerate(components):
		print("Topic %d:" % (idx+1), 
			[(feature_names[i], topic[i]) for i in topic.argsort()[:-n - 1:-1]])
get_topics(svd_model.components_, terms)

아래는 생성된 모델에 기반형 분류된 토픽 및 해당 토픽에 기여한 중요 빈도 단어를 출력시켜 보았다.

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)]
.....
생략

LSA 모델 분석

LSA는 단어의 잠재적인 의미를 이끌어낼 수 있어 문서의 유사도 계산 등에서 좋은 성능을 보여준다는 장점을 갖고 있다. 그러나 SVD의 특성상 이미 계산된 LSA에 새로운 데이터가 추가될 경우, 보편적으로 처음부터 다시 계산해야 한다. 즉, 새로운 정보에 대해 업데이트가 거의 불가능하다. 접근 방법 자체는 본 프로젝트와 유사하지만 해당 결점으로 인해 모델로 선정하기에는 부족한다고 판단된다.

Last updated