Posted:      Updated:

자연어와 단어의 분산 표현

자연어 처리란

자연어(natural language)란 한국어, 영어 등 인간이 평소에 사용하는 말이다.
같은 의미의 문장도 여러 형태로 표현할 수 있고, 문장의 뜻이 애매할 수 있으며, 의미나 형태가 유연하게 바뀐다.

자연어 처리(Natural Language Processing)는 자연어를 처리하는 분야로, 우리의 말을 컴퓨터에게 이해시키기 위한 기술(분야)이라고 할 수 있다.

컴퓨터에게 자연어를 이해시키려는 노력의 예로 검색 엔진, 기계 번역, 질의응답 시스템, IME(입력기 전환), 문장 자동 요약과 감정분석 등이 있다.

시소러스

시소러스는 유의어 사전으로, 뜻이 같은 단어(동의어)나 뜻이 비슷한 단어(유의어)가 한 그룹으로 분류되어 있다.
자연어처리에 이용되는 시소러스는 단어 사이의 상위와 하위, 전체와 부분 등 더 세세한 관계까지 정의해둔 경우도 있다.

map

유의어 집합을 만들어 단어들의 관계를 그래프로 표현해서 단어 사이의 연결을 정의하고, 이 단어 네트워크를 이용해 컴퓨터에게 단어 사이의 관계를 가르칠 수 있다.

WordNet

자연어 처리 분야에서 가장 유명한 시소러스이다.
프린스턴 대학교에서 1985년부터 구축하기 시작한 전통 있는 시소러스이다.
이를 통해 유의어를 얻거나 단어 네트워크를 이용할 수 있다.

시소러스의 문제점

사람이 수작업으로 레이블링하는 방식에는 결정이 존재한다.

  • 시대 변화에 대응하기 어렵다.
    우리가 사용하는 말은 때때로 새로운 단어가 생겨나고 옛말은 잊혀진다.
    시대에 따라 언어의 의미가 변하기도 한다.
    이러한 변화에 대응하기 위해서 시소러스를 사람이 수작업으로 끊임없이 갱신해야 한다.

  • 사람을 쓰는 비용이 크다.
    방대한 단어들 모두에 대해 단어 사이의 관계를 정의해야 하므로 엄청난 인적 비용을 발생시킨다.

  • 단어의 미묘한 차이를 표현할 수 없다.
    빈티지와 레트로같이 의미가 같지만 용법이 다른 단어들의 미묘한 차이를 표현할 수 없다.

오랫동안 특징을 수동적으로 설계하였으나, 딥러닝을 통해 사람의 개입을 줄일 수 있게 되었다.
사람의 개입을 최소로 줄이고 텍스트 데이터만으로 원하는 결과를 얻어내는 방향으로 패러다임이 바뀌고 있다.

통계 기반 기법

대량의 텍스트 데이터인 말뭉치(corpus)를 사용한다.
자연어 처리에 사용되는 코퍼스에는 텍스트 데이터에 대한 추가 정보가 포함되는 경우가 있다.
예를 들어 텍스트 데이터의 단어 각각에 품사를 레이블링 할 수 있다.
보통은 컴퓨터가 다루기 쉬운 트리 구조 등의 형태로 가공되어 주어진다.

파이썬으로 코퍼스 전처리하기

text = 'You say goodbye and I say hello.'
text = text.lower()
text = text.replace('.', ' .')
text
'you say goodbye i say hello .'
words = text.split(' ')
words
['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']

문장 첫머리의 대문자로 시작하는 단어를 처리하기 위해 lower() 메서드를 사용해 모든 문자를 소문자로 변환한다.
split('') 메서드를 통해 공백 기준으로 분할한다.
마침표 구분을 위해 마침표 앞에 공백을 추가했다.

정규표현식을 통해 간단하게 단어 단위로 분할할 수 있다.

word_to_id = {}
id_to_word = {}

for word in words:
    if word not in word_to_id:
        new_id = len(word_to_id)
        word_to_id[word] = new_id
        id_to_word[new_id] = word

단어에 ID를 부여하고 ID의 리스트로 이용할 수 있도록 한 번 더 손질하는 과정이다.
id_to_word 를 통해 단어 ID에서 단어로 변환하고, word_to_id 를 통해 단어에서 단어 ID로 변환한다.
words 의 단어를 하나씩 살펴보면서 word_to_id 에 들어있지 않으면 각 배열에 새로운 ID와 단어를 추가한다.

딕셔너리를 사용해 단어를 가지고 단어 ID를 검색하거나 단어 ID로 단어를 검색할 수 있게 된다.

id_to_word[1]
word_to_id['hello']
'say'
5

words 를 단어 ID 목록으로 변경한다.

import numpy as np
corpus = [word_to_id[w] for w in words]
corpus = np.array(corpus)
corpus
array([0, 1, 2, 3, 4, 1, 5, 6])

전처리를 위한 preprocess() 함수를 구현해보자.

def preprocess(text):
    text = text.lower()
    text = text.replace('.', ' .')
    words = text.split(' ')

    word_to_id = {}
    id_to_word = {}

    for word in words:
        if word not in word_to_id:
            new_id = len(word_to_id)
            word_to_id[word] = new_id
            id_to_word[new_id] = word

    return corpus, word_to_id, id_to_word
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)

단어의 분산 표현

단어의 의미를 정확하게 파악할 수 있도록 벡터로 표현하는 것을 단어의 분산 표현(distributional representation)이라고 한다.
단어를 고정 길이의 밀집 벡터(dense vector)로 표현하고, 밀집 벡터는 대부분의 원소가 0이 아닌 실수인 벡터를 말한다.
예를 들어 3차원의 분산 표현은 [0.21, -0.45, 0.83]과 같은 모습이다.

분포 가설

단어의 의미는 주변 단어에 의해 형성된다는 것을 분포 가설(distributional hypothesis)이라고 한다.
단어 자체에는 의미가 없고, 그 단어가 사용된 맥락(context)이 의미를 형성한다는 것이다.
단어를 벡터로 표현하는 최근 연구도 대부분 이 가설에 기초한다.

예를 들어 “I dring beer”와 “We drink wine”처럼 “drink”의 주변에는 음료가 등장하기 쉬울 것이다.
“I guzzle beer”와 “We guzzle wine”이라는 문장이 있다면, “guzzle”은 “drink”와 같은 맥락에서 사용된다는 것을 알 수 있다.
그리고 “guzzle”과 “drink”가 가까운 의미의 단어라는 것을 알 수 있다.

컨텍스트는 특정 단어를 중심에 둔 그 주변 단어를 말한다.
컨텍스트의 크기(주변 단어를 몇 개나 포함할지)를 윈도우 크기(window size)라고 한다.
윈도우 크기가 1이면 좌우 한 단어씩, 2이면 좌우 두 단어씩 맥락에 포함된다.
상황에 따라 왼쪽 단어만, 오른쪽 단어만 사용할 수도 있고, 문장의 시작과 끝을 고려할 수도 있다.

동시발생 행렬 (co-occurrence matrix)

어떤 단어에 주목했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지를 세어 집계하는 방법을 통계 기반 기법이라고 한다.

import sys
sys.path.append('..')
import numpy as np
from common.util import preprocess

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)

print(corpus)
# [0 1 2 3 4 1 5 6]

print(id_to_word)
# [0: 'you', 1: 'say', 2:'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.']

각 단어의 맥락에 포함되는 단어의 빈도를 표로 정리한다.

  you say goodbye and i hello .    
you 0 1 0 0 0 0 0    
say 1 0 1 0 1 1 0    
goodbye 0 1 0 1 0 0 0    
and 0 0 1 0 1 0 0    
i 0 1 0 1 0 0 0    
hello 0 1 0 0 0 0 1    
. 0 0 0 0 0 1 0    

이 표가 행렬의 형태를 띤다는 뜻에서 동시발생 행렬이라고 한다.
표의 각 행은 해당 단어를 표현한 벡터가 된다.

위의 동시발생 행렬을 파이썬으로 구현하면 아래와 같다.

C = np.array([
    [0, 1, 0, 0, 0, 0, 0],
    [1, 0, 1, 0, 1, 1, 0],
    [0, 1, 0, 1, 0, 0, 0],
    [0, 0, 1, 0, 1, 0, 0],
    [0, 1, 0, 1, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 1],
    [0, 0, 0, 0, 0, 1, 0],
], dtype=np.int32)
print(C[0])
# [0 1 0 0 0 0 0]

이와 같이 동시발생 행렬을 활용하면 단어를 벡터로 나타낼 수 있다.

코퍼스로부터 동시발생 행렬을 만들어주는 함수를 구현하여 동시발생 행렬의 구현을 자동화할 수 있다.

def create_co_matrix(corpus, vocab_size, window_size=1):
    corpus_size = len(corpus)
    co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)

    for idx, word_id in enumerate(corpus):
        for i in range(1, window_size + 1):
            left_idx = idx - i
            right_idx = idx + i

            if left_idx >= 0:
                left_word_id = corpus[left_idx]
                co_matrix[word_id, left_word_id] += 1

            if right_idx < corpus_size:
                right_word_id = corpus[right_idx]
                co_matrix[word_id, right_word_id] += 1

    return co_matrix

인수는 차례로 단어 ID 리스트, 어휘 수, 윈도우 크기를 나타낸다.
0으로 채워진 2차원 배열을 생성하고, 코퍼스의 모든 단어 각각에 대해 윈도우에 포함된 주변 단어를 센다.
코퍼스의 끝 경계를 벗어나지 않는지도 확인한다.

벡터 간 유사도

대표적으로 벡터의 내적이나 유클리드 거리 등을 꼽을 수 있다.
단어 벡터 사이의 유사도를 나타낼 때는 코사인 유사도를 자주 이용한다. 코사인 유사도는 두 벡터가 가리키는 방향이 얼마나 비슷한가를 의미한다.
두 벡터의 방향이 완전히 같다면 코사인 유사도가 1이 되고, 완전히 반대라면 -1이 된다.

두 벡터 $\mathbf{x}=(x_1, x_2, x_3, \cdots, x_n)$ 과 $\mathbf{y}=(y_1, y_2, y_3, \cdots, y_n)$ 이 있다면 코사인 유사도는 다음 식으로 정의된다.

\[\mathrm{similiarity}(\mathbf{x}, \mathbf{y}) = \frac{\mathbf{x} \cdot \mathbf{y}}{\begin{Vmatrix} \mathbf{x} \end{Vmatrix} {\begin{Vmatrix} \mathbf{y} \end{Vmatrix}}} = \frac{x_1y_1 + \cdots + x_ny_n} {\sqrt{x_1^2 + \cdots + x^2_n} \sqrt{y^2_1 + \cdots + y^2_n}}\]

분자에는 벡터의 내적, 분모에는 각 벡터의 노름(norm)이 등장한다.
노름은 벡터의 크기를 나타낸 것으로, 여기서는 벡터의 각 원소를 제곱해 더한 후 다시 제곱근을 구해 계산하는 L2 노름을 계산한다.

def cos_similarity(x, y, dps=1e-8):
    nx = x / np.sqrt(np.sum(x**2))
    ny = y / np.sqrt(np.sum(y**2))
    return np.dot(nx, ny)

제로 벡터가 들어왔을 때 발생하는 divide by zero 오류가 발생하지 않도록 eps(epsilon)을 인수로 받는다.
일반적으로 부동소수점 계산 시 반올림되어 다른 값에 흡수된다.

import sys
from common.util import preprocess, create_co_matrix, cos_similarity

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)

c0 = C[word_to_id['you']]
c1 = C[word_to_id['i']]

print(cos_similarity(c0, c1))

실행 결과 “you” 와 “i”의 코사인 유사도는 0.7…으로 나온다.

유사 단어의 랭킹 표시

어떤 단어가 검색어로 주어지면 비슷한 단어를 유사도 순으로 출력하는 프로그램이다.

def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
    if query not in word_to_id:
        print('%s(을)를 찾을 수 없습니다.' % query)
        return

    print('\n[query] ' + query)
    query_id = word_to_id[query]
    query_vec = word_matrix[query_id]

    vocab_size = len(id_to_word)
    similarity = np.zeros(vocab_size)
    for i in range(vocab_size):
        similarity[i] = cos_similarity(word_matrix[i], query_vec)

    count = 0
    for i in (-1 * similarity).argsort():
        if id_to_word[i] = query:
            continue
        print('%s: %s' % (id_to_word[i], similarity[i]))

    count += 1
    if count >= top:
        return

검색어에서 단어 벡터를 꺼내고, 단어 벡터와 모든 다른 단어 벡터와의 코사인 유사도를 각각 구한 뒤 계산한 코사인 유사도 결과를 기준으로 값이 가장 높은 순서대로 출력한다.

argsort() 메서드를 통해 넘파이 배열의 원소를 오름차순으로 정렬할 수 있다.
반환값은 배열의 인덱스이다.

import sys
from common.util import preprocess, create_co_matrix, most_similar

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)

most_similar('you', word_to_id, id_to_word, C, top=5)
[query] you
goodbye: 0.7071067691154799
i: 0.7071067691154799
hello: 0.7071067691154799
say: 0.0
and: 0.0

유사한 상위 5개만 출력한 결과이다.

통계 기반 기법 개선하기

상호정보량

단어의 등장 횟수만 고려하는 것은 좋지 않으므로, 이를 해결하기 위해 점별 상호정보량(PMI)이라는 척도를 사용할 수 있다.

\[\mathrm{PMI}(x, y) = \log{2} \frac{P(x,y)}{P(x)P(y)}\]

PMI 값이 높을 수록 관련성이 높다는 의미이다.

동시발생 행렬을 사용하여 위 식을 다시 써보면 아래와 같다.

\[\mathrm{PMI}(x, y) = \log{2} \frac{P(x,y)}{P(x)P(y)} = \log{2} \frac {\frac {C(x,y)} {N}} {\frac {C(x)} {N} \frac {C(y)} {N}} = \log{2} \frac {C(x, y) \cdot N} {C(x)C(y)}\]

$P(x)$는 $x$가 일어날 확률, $P(y)$는 $y$가 일어날 확률, $P(x,y)$는 $x$와 $y$가 동시에 일어날 확률이다.
동시발생 횟수가 0이면 $\log_{2}0=-\infty$ 가 되므로, 실제 구현 시에는 양의 상호정보량(PPMI)를 사용한다.

\[\mathrm{PPMI} (x, y) = max(0, \mathrm{PMI} (x,y))\]

이 식에 따라 PMI가 음수일 때는 0으로 취급한다.

def ppmi(C, verbose=False, eps=1e-8):
    M = np.zeros_like(C, dtype=np.float32)
    N = np.sum(C)
    S = np.sum(C, axis=0)
    total = C.shape[0] * C.shape[1]
    ent = 0

    for i in range(C.shape[0]):
        for j in range(C.shape[l]):
        pmi = np.log2(C[i, j]  N / (S[j]*S[i]) + eps)
        M[i, j] = max(0, pmi)

        if verbose:
            cnt += 1
            if ent % (total//100 + 1) == 0:
                print('%.1f%% 완료' % (100*cnt/total))
    return M

C 는 동시발생 행렬, verbose 는 진행상황 출력 여부를 결정하는 플래그이다.
단어 $x$와 $y$가 동시에 발생하는 횟수를 $C(x,y)$라 했을 때, $C(x) = \underset{i} \sum C(i,x), C(y) = \underset{i} \sum C(i,y), N = \underset{i} \sum \underset{j} \sum C(i,j)$ 가 되도록 근사값을 통해 구현했다.
np.log2(0)이 음의 무한대 (-inf)가 되는 사태를 피하기 위해 eps라는 작은 값을 사용한다.

동시발생 행렬을 PPMI 행렬로 변환하면 아래와 같다.

import sys
sys.path.append('..')
import numpy as np
from common.util import preprocess, create_co_matrix, cos_similarity, ppmi

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
W = ppmi(C)

np.set_printoptions(precision=3)
print('동시발생 행렬')
print(C)
print('-'*50)
print('PPMI')
print(W)
동시발생 행렬
[[0 1 0 0 0 0 0]
 [1 0 1 0 1 1 0]
 [0 1 0 1 0 0 0]
 [0 0 1 0 1 0 0]
 [0 1 0 1 0 0 0]
 [0 1 0 0 0 0 1]
 [0 0 0 0 0 1 0]]
---------------------------------
PPMI
[[0.    1.807 0.    0.    0.    0.    0.    ]
 [1.807 0.    0.807 0.    0.807 0.807 0.    ]
 [0.    0.807 0.    1.807 0.    0.    0.    ]
 [0.    0.807 0     1.807 0.    0.    0.    ]
 [0.    0.    1.807 0.    1.807 0.    0.    ]
 [0.    0.807 0.    1.807 0.    0.    0.    ]
 [0.    0.807 0.    0.    0.    0.    2.807 ]
 [0.    0.    0.    0.    0.    2.807 0.    ]]

PPMI 행렬에는 코퍼스의 어휘 수가 증가함에 따라 각 단어 벡터의 차원 수도 증가한다는 문제가 있다.
말뭉치의 어휘 수가 10만개라면 그 벡터 차원 수도 똑같이 10만이 된다.
행렬의 내용을 보면 원소 대부분이 0이으로, 각 원소의 중요도가 낮다.

차원 감소

차원 감소(dimensionality reduction)는 벡터의 차원을 줄이는 방법이다.
단순히 줄이는 게 아니라 중요한 정보는 최대한 유지하면서 줄이는 것이 핵심이다.

원소 대부분이 0인 행렬 또는 벡터를 희소행렬 또는 희소벡터라고 한다.
차원 감소는 희소벡터에서 중요한 축을 찾아내어 더 적은 차원으로 다시 표현하는 것이다.

특잇값분해(Singular Value Decomposition)를 이용하여 차원을 감소시킬 수 있다.

\[\mathrm{X = USV^T}\]

SVD는 행렬 $\mathrm{X}$를 $\mathrm{U}$, $\mathrm{S}$, $\mathrm{V}$라는 세 행렬의 곱으로 분해한다.
$\mathrm{U}$와 $\mathrm{V}$는 직교행렬(orthogonal matrix)이고, 열벡터는 서로 직교한다.
$\mathrm{S}$는 대각행렬(diagonal matrix)이다.

svd

$\mathrm{U}$는 직교행렬이고, 어떠한 공간의 축(기저)를 형성한다.
$\mathrm{U}$ 행렬을 단어 공간으로 취급할 수 있다.
$\mathrm{S}$는 대각 행렬로, 대각 성분에는 특잇값이 큰 순서로 나열되어 있다.
그래서 아래 그림과 같이 중요도가 낮은 원소(특잇값이 작은 원소)를 깎아내는 방법을 생각할 수 있다.

svd2

행렬 $\mathrm{S}$에서 특잇값이 작다면 중요도가 낮다는 뜻이므로, 행렬 $\mathrm{U}$에서 여분의 열벡터를 깎아내어 원래의 행렬을 근사할 수 있다.

SVD 에 의한 차원 감소

SVD는 linalg 모듈이 제공하는 svd 메서드로 실행할 수 있다.
linalg는 선형대수(linear algebra)의 약어이다.

import sys
sys.path.append('..')
import numpy as np
import matplotlib.pyplot as pit
from common.util import preprocess, create_co_matrix, ppmi

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess (text)
vocab_size = len(id_to_word)
C = create_co_matrix(corpus, vocab_size, window_size=1)
W = ppmi(C)

U, S, V = np.linalg.svd(W)  # SVD

SVD에 의해 변환된 밀집벡터 표현은 변수 $\mathrm{U}$에 저장된다.

print(C[0]) # 동시발생 행렬
# [0 1 0 0 0 0 0]

print(W[0]) # PPMI 행렬
# [0.    1.807 0.    0.    0.    0.    0.    ]

print(U[0]) # SVD
# [ 3.409e-01 -1.110e-16 -1.205e-01 -4.441e-16 0.000e+00 -9.323e-01
# 2.226e-16]

희소벡터인 W[0] 가 SVD에 의해 밀집벡터 U[0] 로 변했다.

밀집벡터의 차원을 2차원 벡터로 감소시키려면 처음의 두 원소를 꺼내면 된다.

print(U[0, :2]) # SVD
# [ 3.409e-01 -1.110e-16 ]

각 단어를 2차원 벡터로 표현한 후 그래프로 그리는 코드이다.

for word, word_id in word_to_id.items():
    plt.annotate(word, (U[word_id, 0], U[word_id, 1]))

plt.scatter(U[:,0], U[:,1], alpha=0.5)
plt.show

plt.anotate(word, x, y) 메서드는 2차원 그래프상에서 좌표 $\mathrm{(x, y)}$ 지점에 담긴 텍스트를 그린다.

plt

“goodbye”와 “hello”, “you”, “i”가 가까이 있는 것을 확인할 수 있다.

행렬의 크기가 $N$일 때 SVD 계산은 $O(N^3)$이 걸린다.
따라서 Truncated SVD와 같은 더 빠른 기법을 이용할 수 있다.
Truncated SVD는 특잇값이 작은 것은 버리는(truncated) 방식으로 성능을 향상시킨다.

PTB 데이터셋

적당히 큰 사이즈의 텍스트 데이터인 펜 트리뱅크(PTB) 코퍼스를 활용한다.
PTB는 주어진 기법의 품질을 측정하는 벤치마크로 자주 이용한다.

텍스트 파일로 제공되며, 희소한 단어를 <unk> 라는 특수 문자로 치환한다거나 구체적인 숫자를 “N”으로 대체하는 등의 작업이 적용되어 있다.
또한, 한 문장이 하나의 줄로 저장되어 있다.

아래는 ptb 를 활용하는 예시이다.

import sys
sys.path.append('..')
from dataset import ptb
corpus, word_to_id, id_to_word = ptb.load_data('train')

print('말뭉치 크기: ', len(corpus))
print('corpus[:30]: ', corpus[:30])
print()
print('id_to_word[0]: ', id_to_word[0])
print('id _to_word[1]: ', id_to_word[1])
print('id_to_word[2]: ', id_to_word[2])
print()
print("word_to_id['car']: ", word_to_id[ 'car'])
print("word_to_id['happy']: ", word_to_id[ 'happy'])
print("word_to_id['lexus']: ", word_to_id[ 'lexus'])
corpus size: 929589
corpus[:30]: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25 26 27 28 29]

id_to_word[0]: aer
id_to_word[1]: banknote
id_to_word[2]: berlitz

word_to_id['car']: 3856
word_to_id['happy']: 4428
word_to_id['lexus']: 7426

ptb.load_data() 는 데이터를 읽어들이고, 인수로는 train , test , valid 중 하나를 지정할 수 있다.
각각 훈련용, 테스트용, 검증용 데이터를 가리킨다.

PTB 데이터셋 평가

PTB 데이터셋에 통계 기반 기법을 적용하기 위해 고속 SVD를 이용한다.
고속 SVD를 사용하기 위해서는 sklearn 모듈을 설치해야 한다.

import sys
sys.path.append('..')
import numpy as np
from common.util import most_similar, create_co_matrix, ppmi
from dataset import ptb

window_size = 2
wordvec_size = 100

corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
print('동시발생 수 계산 ...')
C = create_co_matrix(corpus, vocab_size, window_size)
print('PPMI 계산 ...')
W = ppmi(C, verbose=True)

print('SVD 계산 ...')
try:
    from sklearn.utils.extmath import randomized_svd
    U, S, V = randomized_svd(W, n_components=wordvec_size n_iter=5, random_state=None)
except ImportError:
    U, S, V = np.linalg.svd(W)

word_vecs = U[:, :wordvec_size]

querys = ['you', 'year', 'car', 'toyota']
for query in querys: 
    most_similar(query, word_to_id, id_to_word, word_vecs, top=5)

sklearn의 randomized_svd() 메서드를 이용했다.
무작위 수를 활용한 Truncated SVD로, 특잇값이 큰 것들만 계산하여 기본적인 SVD보다 훨씬 빠르다.

[query] you
i: 0.702039909619
we: 0.699448543998
've: 0.554828709147
do: 0.534370693098
else: 0.512044146526

[query] year
month: 0.731561990308
quarter: 0.658233992457
last: 0.622425716735
earlier: 0.607752074689
next: 0.601592506413

[query] car
luxury: 0.620933665528
auto: 0.615559874277
cars: 0.569818364381
vehicle: 0.498166879744
corsica: 0.472616831915

[query] toyota
motor: 0.738666107068
nissan: 0.677577542584
motors: 0.647163210589
honda: 0.628862370943
lexus: 0.604740429865

단어의 의미 혹은 문법적인 관점에서 비슷한 단어들이 가까운 벡터로 나타난다.

Categories:

댓글남기기