토픽 모델링(Topic Modeling) 이란 문서집합에서 추상적인 주제를 발견하기 위한 통계적 모델로, 잠재 의미 분석(Latent Semantic Analysis, LSA) / 잠재 디리클레 할당(Latent Dirichlet Allocation, LDA) 등의 알고리즘이 있다. LSA 는 기본적으로 DTM 이나 TF-IDF 행렬에 절단된 특이값 분해(truncated SVD) 를 사용하여 차원을 축소시키고, 단어들의 잠재적인 의미를 끌어내지만, SVD 의 특성상 새로운 데이터를 업데이트 하려면 처음부터 다시 계산해야 하는 단점이 있다.


특이값 분해(SVD) 는 A 가 m × n 행렬일 때,

3개의 행렬(U:m×m 직교행렬, VT:n×n 직교행렬, S:m×n 직사각 대각행렬) 의 곱으로 분해(decomposition) 하는 것이다.


(9 x 4) 행렬의 DTM 으로 절단된 특이값 분해(truncated SVD) 를 구하기.


import numpy as np
 
# 아래와 같은 DTM 이 있다고 할 때,
= [
    [000101100],
    [000110100],
    [011020000],
    [100000011]
]
 
# (4 x 9) 행렬에서
# 일단 특이값 분해 full SVD 구하기 : U x s x VT
# U : m×m 직교행렬,
# s : m×n 직사각 대각행렬,
# VT : n×n 직교행렬 이라 할 때,
U, s, VT = np.linalg.svd(A, full_matrices=True)
 
# 4 x 4 직교행렬 확인
print(U.round(2))
# [[ 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.  ]]
 
# 특이값 s 를 대각행렬로 바꾸고 직교행렬 구하기
print(s.round(2))
# [2.69 2.05 1.73 0.77]
= np.zeros((49))
S[:4, :4= np.diag(s)  # 특이값 s 를 대각행렬에 삽입
print(S.round(2))
# [[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.  ]]
 
# 9 x 9 직교행렬 확인
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.  ]
#  [ 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]]
cs


여기까지 구해본 full SVD 를 역으로 계산해 보면 U x S x VT = A 와 같음을 알 수 있다.

이제 3개 행렬을 축소시킨 truncated SVD 를 구하여 다른 문서나 단어의 유사도를 구할 수 있다.


# Truncated SVD 구하기
# 특이값 상위 2개만 남기기 (t = 2)
= U[:, :2]
= S[:2, :2]
VT = VT[:2, :]
print(np.dot(np.dot(U,S), VT).round(2))
# [[ 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.  ]]
cs


위와 같이 전체 코퍼스에서 절단된 특이값 분해를 구해야 하므로, 데이터를 추가하게 되면 전과정을 처음부터 다시 실행해야 하는 단점이 있다.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,