티스토리 뷰

  • 이 글은 하단의 Reference에 있는 강의, 교재를 보고 정리한 것입니다.

8.0 Intro

이 글에서는

  • 자연어 처리 분야에서 컴퓨터가 자연어를 이해하게 만드는 방법
  • 파이썬으로 텍스트를 처리하는 방법

에 대해 다룬다.

8.1 What is Natural Language Processing?(NLP)

영어, 일본어, 한국어 등 일상에서 의사소통을 위해 사용하는 말을 자연어Natural language라 한다. 그러니, 자연어 처리란 '우리가 하는 말을 컴퓨터가 이해하도록 만드는 작업' 정도로 정의할 수 있다. 이것이 어려운 이유는 컴퓨터가 이해할 수 있는 것은 명확한 규칙에 따라 해석될 수 있는 코드들 뿐이기 때문이다. 그 의미가 모호하거나 변화하는 자연어는 이런 코드와는 상당히 다른 성격을 갖고 있으므로 어떻게 처리할지 고민해봐야 할 것이다.

8.1.1 Meaning of word

자연어는 문장으로 구성되고 문장은 단어로 구성된다. 따라서 '단어'의 의미를 컴퓨터에게 이해시키는 것이 자연어 처리의 시작이 될 것이다. 더 정확히는 단어의 의미를 컴퓨터가 이해할 수 있는 방법으로 표현할 수 있어야 한다. 이 글과 다음 글에서는 단어의 의미를 표현하는 세가지 방법에 대해 다룬다.

  • '시소러스'를 사용한 기법
  • 통계 기반 기법
  • 추론 기반 기법(word2vec)

8.2 Thesaurus

시소러스thesaurus란 유의어, 동의어끼리 한 그룹으로 분류되어 있는 유의어 사전을 말하는데, 사람이 직접 만든 것이다. 특히, 자연어 처리에 사용되는 시소러스는 다음 <그림1>과 같이 단어의 상/하위 관계도 표현하고 있다.

<그림1: 시소러스에서 단어의 상하관계 표현>

이런 만들어진 유의어 사전을 이용하면, '단어의 관계'를 이용해 컴퓨터에게 단어의 의미를 간접적으로 이해시킬 수 있다. 예를 들어 만약 'car'라는 단어가 'automobile'이라는 단어와 유사도가 크다고 하면 컴퓨터는 car라는 단어의 의미를 automobile과 유사하다고 이해할 수 있을 것이다. 실제로 WordNet과 같은 유명한 시소러스에는 수많은 단어에 대한 동의 관계/유의 관계/상하 관계등이 정의되어 있다. 그러나 이처럼 사람이 수작업으로 레이블링 하는 작업이 필요할 경우 다음과 같은 문제들이 생긴다.

  • 시간의 흐름에 따른 단어 의미 변화에 대응하기 어렵다
  • 사람을 쓰므로 구축 비용이 크다
  • 단어의 미묘한 차이를 표현하기 힘들다

이러한 문제점들 때문에 대량의 텍스트 데이터로부터 '단어의 의미'를 자동으로 추출하는 '통계 기반 기법''추론 기반 기법'을 도입하게 된다.

8.3 statistical based method

통계 기반 기법에서는 말뭉치corpus를 이용한다. 말뭉치란 일반적으로 NLP를 위해 수집된 대량의 텍스트 데이터를 얘기한다. 말뭉치는 사람이 쓴 글이므로 그 안에 문장을 쓰는 방법, 단어를 선택하는 방법, 단어의 의미 등 사람이 알고있는 자연어에 대한 지식이 포함되어 있다고 볼 수 있다. 통계 기반 기법의 목표는 이처럼 사람의 지식이 포함되어 있는 Corpus에서 자동으로 그 핵심을 추출하는 것이다. 그를 위해 우선 파이썬으로 text data를 어떻게 처리하는지 살펴보자.

8.3.1 Preprocessing Corpus using Python

텍스트 데이터는 그 형태가 다양하여 바로 처리하기 힘들기 때문에 전처리 과정이 필요하다. 이 챕터에서 말하는 전처리란 구체적으로는 다음과 같은 과정을 말한다.

  • 텍스트를 단어로 분할
  • 분할된 단어들을 단어 ID 목록으로 변환

우선, 다음과 같이 한 문장으로 이루어진 간단한 텍스트를 생각해보자.

>>> text  = "You say goodbye and I say hello."

-spliting text into words

문장에서 단어는 공백으로 구분되므로 공백을 기준으로 끊어서 단어로 처리하면 된다. 그 전에 문장의 끝에 붙는 .은 hello뒤에 붙어 hello.이 한 단어로 인식될 수 있기 때문에 '.'을' .'로 바꾸어주는 작업이 필요하다. 이는 파이썬의 텍스트 자료형 내장함수를 사용하면 간단하게 처리할 수 있다.

# spliting text into words
text  = "You say goodbye and I say hello."
text = text.lower()
text = text.replace('.',' .')
print(text) # >>> you say goodbye and i say hello .
words = text.split(' ')
print(words)

.lower()는 문자열의 대문자를 소문자로 바꾸어주는 역할이다. 또한 replace(a,b)는 string에 포함된 a를 전부 b로 바꾸어주는 역할을 한다. 이렇게 처리된 문자열 'you say goodbye and i say hello .'를 공백을 기준으로 나누면 되므로 .split(" ")을 이용한다. split은 패러미터를 기준으로 주어진 텍스트를 끊어서 리스트로 반환하는 메서드다. 최종적으로 words는 다음과 같은 list가 된다.

['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']

-converting words to [Word:Id] list

텍스트를 분할된 단어의 형태로 이용해도 되지만, 이는 조작이 불편하므로 한번 더 조작하여 단어에 ID를 붙인 WORD:ID list를 만들어 이용해보자.

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
print(id_to_word)
print(word_to_id)

word->id dictionary와 id->word dictionary를 만들었다. 출력 결과는 다음과 같다.

>>> {0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
>>> {'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}

그러면 이제 word_to_id가 있으므로, 단어의 배열인 words를 대응하는 id의 배열로 바꿀 수 있다. list comprehension을 사용했다.

# text==words array -> id array
import numpy as np
corpus = np.array([word_to_id[w] for w in words])
print(corpus)

이 결과는 다음과 같다. say에 해당하는 1은 두 번 나온 것을 확인할 수 있다.

[0 1 2 3 4 1 5 6]

이를 전부 모아 하나의 전처리 함수로 만들어보면 다음과 같다.

# Preprocess function
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
    corpus = np.array([word_to_id[w] for w in words])
    return corpus,word_to_id,id_to_word

이제 전처리 과정은 끝났다. 이제 본론으로 들어가서 어떻게 통계 기반 방법을 이용해 텍스트를 처리할 수 있는지 알아보자.

8.3.2 Distributional Representation of Words

색을 표현하는 데는 여러가지 방법이 있지만, 정량적이고 명확하게 이를 전달하는 방법에는 (R,G,B) 표현법이 있을 것이다. 이는 어떤 색을 3성분의 벡터로 표현하는 것으로 간결하고 컴퓨터가 처리하기 쉽다는 점이 있다. 벡터 표현이라는 아이디어에는 이런 강점이 있으므로 단어 또한 벡터로 표현하는데, 이를 자연어 처리 분야에서는 단어의 분산 표현Distributional Representation이라 한다.

8.3.3 Distributional Hypothesis

분포 가설(Distributional Hypothesis)이란, '단어의 의미는 주변 단어에 의해 형성된다'는 가설이다. 즉, 단어 자체에는 의미가 없고 그 단어가 사용된 맥락이 의미를 형성한다고 생각한다. 예를 들어, "I drink beer", "we drink wine"처럼 drink 주변에는 음료와 관련한 단어가 등장하기 쉬울 것이다. 또, "I guzzle beer", "We guzzle wine"이라는 다른 문장이 있다면 guzzle이라는 단어는 drink와 비슷한 맥락에서 비슷한 의미로 사용되는 말임을 알 수 있다.

또한, 자연어 처리 분야에서 어떤 단어의 맥락context이라는 용어는 그 단어의 주변에 해당하는 단어들을 뜻한다. 예를 들어 윈도우 크기가 2인 맥락은 다음 <그림2>와 같다. 윈도우 크기는 주변 단어를 얼마나 포함할 지를 뜻하는 용어다.

<그림2: goodbye의 맥락>

물론 맥락으로 앞에 있는 단어들만을, 뒤에 있는 단어들만을 이용하는 것도 가능하다.

8.3.4 Co-occurence Matrix

위에서 설명한 분산 표현, 분포 가설을 기반으로 단어를 벡터로 표현하는 방법을 생각해보자. 분포 가설특정 단어의 맥락에 등장하는 단어들이 중요했고, 이를 벡터로 표현해야 하므로 수치화해야할 것이다. 그러면 자연스럽게 그 주변에 어떤 단어가 몇 번 등장하는지를 세어 집계하는 방법을 생각해볼 수 있다. 이것을 통계 기반 기법이라 한다.

그러면 이제 구체적으로 통계 기반 기법에서 어떻게 집계를 처리하는지 살펴보자. 예를 들어 "You say goodbye and I say hello."라는 문장을 생각해보자. 맨 앞에 등장하는 You의 맥락은 윈도우를 1로 정했을 때 'say'라는 단어 하나 뿐이다. 따라서 주변 단어의 분포를 표로 정리해보면 다음 <그림3>과 같다.

<그림3: You의 벡터표현>

그러면, 이제 you라는 단어를 [0,1,0,0,0,0,0]라는 벡터로 표현하는 것이 가능해진다. 이제 다음 단어인 say에 대해서도 동일한 작업을 해보자. 그런데, say는 문장에서 두 번 등장하므로 다음 <그림4>와 같이 두 맥락을 모두 체크해야 한다.

<그림4: say의 벡터표현>

you와 같은 방법으로, say는 [1,0,1,0,1,1,0]라는 벡터로 표현 가능할 것이다. 이제 이런 작업을 등장하는 모든 단어에 대해서 진행해보면 다음 <그림5>와 같은 행렬이 나올 것이다.

<그림5: 동시발생 행렬>

이제, 이 표의 각 행은 해당 단어를 표현한 벡터가 되며, 이 표가 행렬의 형태이므로 동시발생 행렬co-occurence matrix이라 한다. 이제 이렇게 말뭉치로부터 동시발생 행렬을 만드는 작업을 해주는 함수를 짜보자.

import numpy as np
# making co-ocurrence matrix
def create_co_matrix(corpus,vocab_size,window_size=1): # vocab_size: corpus에 존재하는 서로 다른 단어의 수
  corpus_size = len(corpus)
  co_matrix = np.zeros((vocab_size,vocab_size),dtype = np.int32) # Vocab_size x Vocab_size 크기의 행렬 생성 기본 값은 0

  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

(enumerate함수에 대한 설명:https://rednooby.tistory.com/57)

8.3.5 Similarity between vectors

단어를 벡터로 표현했으므로, 단어 간의 유사도를 수치화할 때는 벡터 간 유사도를 측정하는 방법을 이용할 수 있을 것이다. 벡터 간 유사도를 측정하는 방법은 다양하지만 가장 대표적으로 사용하는 방법으로 코사인 유사도cosine similarity가 있다. 이 값은 두 벡터 x와 y사이의 cos값이며, 다음과 같이 정의된다.
$$
sim(x,y)=\frac{x\cdot y}{|x||y|} = \frac{x_1y_1+\cdot\cdot\cdot x_ny_n}{\sqrt{x_1^2+\cdot\cdot\cdot x_n^2}\sqrt{y_1^2+\cdot\cdot\cdot y_n^2}}
$$
이 값은 cos값으로 가장 유사할 때 1, 가장 유사하지 않을 때 -1이 된다. 이를 파이썬 함수로 구현하면 다음과 같다.

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

이 때 eps는 div by 0 에러를 예방하기 위해 추가한 값으로 nx와ny의 각 분모에 더해서 사용한다.

8.3.6 Print Ranking of Similar Words

코사인 유사도를 기반으로 corpus에 있는 단어들을 유사도 순서대로 출력하는 함수를 짜보자.

# 유사 단어 검색
def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
    '''유사 단어 검색

    :param query: 쿼리(단어)
    :param word_to_id: 단어에서 단어 ID로 변환하는 딕셔너리
    :param id_to_word: 단어 ID에서 단어로 변환하는 딕셔너리
    :param word_matrix: 단어 벡터를 정리한 행렬. 각 행에 해당 단어 벡터가 저장되어 있다고 가정한다.
    :param top: 상위 몇 개까지 출력할 지 지정
    '''
    if query not in word_to_id: # query가 word에 없으면
        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

참고로 (-1 * similarity).argsort()는 결과적으로 내림차순 정렬이 된다. 오름차순 정렬을 리스트의 반대 부호로 했기 때문. 이제 구현했던 모든 함수들을 한 번 사용해보자.

# example
import sys
sys.path.append('..')
from common.util import *

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

I는 상식적으로 납득가능한 결과지만 goodbye와 hello는 납득되지 않는 결과다. text의 양이 적으니까 당연히 어쩔 수 없는 것으로, 후에 이 방법을 개선하여 조금 더 큰 말뭉치를 넣어보자.

8.4 Improving statistical based method

위에서 알아본 동시발생행렬 기반의 통계적 방법을 개선해보고 더 큰 말뭉치에 대한 분석을 진행해보자.

8.4.1 Mutual Information

동시발생 행렬의 원소는 두 단어가 동시에 발생한 횟수를 나타낸다. 그러나 이 '발생'횟수라는 수치는 연관성을 분석할 때 좋은 특징이라 할 수 없다. 이는 고빈도 단어들을 생각해보면 알 수 있다. 예컨데, "the"라는 단어를 생각해보면 "the"는 명사 앞에 자주 나오기 때문에 'car'의 동시 발생 단어들 중 drive나 stop같이 실제로 연관성 있는 단어들보다 연관성이 높게 분석될 확률이 크다. 이는 분석의 의도와 맞지 않으므로 동시발생 행렬을 개선할 필요가 있다.

따라서 이 문제를 해결하기 위해 점별 상호정보량Pointwise Mutual Information(PMI)이라는 척도를 도입한다. PMI는 동시 발생 확률을 개별 발생 확률로 나눈 것으로 다음과 같이 정의된다.
$$
PMI(x,y)=\log_{2}{\frac{P(x,y)}{P(x)P(y)}}
$$
이 수치는 개별 발생 확률의 나눗셈을 이용하여 고빈도 단어와의 연관성을 낮추려는 시도를 보인다. 또한, 확률을 (발생횟수/모수)로 풀어내보면 식을 다음과 같이 정리할 수도 있다. (C: 발생 횟수, N: 말뭉치에서 단어의 수)
$$
PMI(x,y) =\log_{2}{\frac{\frac{C(x,y)}{N}}{\frac{C(x)}{N}\frac{C(y)}{N}}}=\log_{2}{\frac{N\cdot C(x,y)}{C(x)C(y)}}
$$
이 수치는 "a","the"같은 고빈도 단어의 연관성을 잘 낮추어 주는 것으로 알려진 수치다. 그러나 C(x)나 C(y)가 0이 될 때 -무한대 로 발산하는 문제가 있으므로(div by 0) 실제 구현시에는 다음과 같이 정의되는 양의 상호정보량Positive Pointwise Mutual Information(PPMI)을 사용한다.
$$
PPMI(x,y)=\max(0,PMI(x,y))
$$
이를 코드로 구현해보면 다음과 같다.

# PPMI 행렬 구현 함수
def ppmi(C, verbose=False, eps = 1e-8):
    '''PPMI(점별 상호정보량) 생성

    :param C: 동시발생 행렬
    :param verbose: 진행 상황을 출력할지 여부
    :return:
    '''
    M = np.zeros_like(C, dtype=np.float32) # C와 동일한 형상을 가진 zeros행렬
    N = np.sum(C) # C 원소의 총 합 = 말뭉치에 있는 총 단어의 수에 비례하는 값.
    S = np.sum(C, axis=0) # axis = 0이므로 그 단어가 총 몇번 나왔는지에 비례하는 값.
    total = C.shape[0] * C.shape[1] # 중간 결과 출력 위해.
    cnt = 0

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

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

이제 이 함수를 이용해서 C를 ppmi행렬로 변환해보자.

# C to ppmi
import sys
sys.path.append('..')
from common.util import *

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

이제 더 괜찮은 척도로 이루어진 행렬을 얻었으나 여전히 문제가 있는데, 말뭉치의 크기가 커질수록 각 단어 벡터의 차원수도 증가한다는 문제가 있다. 예를 들어 말뭉치의 어휘 수가 10만개라면 벡터 하나가 10만 차원이 되므로 계산량이 너무 많다. 또한 한 단어와 연관있는 단어는 10만 단어보다 훨씬 적을 것이기 때문에 대부분의 원소가 0인 벡터가 나와 메모리 낭비가 심한 측면이 있다.(위의 출력 결과만을 보아도 알 수 있다.) 따라서 이런 문제를 해결하기 위해 벡터의 차원 감소 기법을 이용한다.

8.4.2 Dimensionality Reduction of Vector

차원 감소dimensionality reduction는 벡터의 정보를 최대한 유지하면서 벡터의 차원을 줄이는 방법을 말한다. 직관적인 예로 다음 <그림6>과 같이 데이터의 분포를 고려해 제일 중요한 축을 찾는 것을 목표로 한다.

<그림6: 차원감소의 목적>

<그림6>을 보면 원래 2차원의 데이터를 1차원의 데이터로 표현하기 위해 새로운 축을 찾는 것을 보여준다. 이 때 새로운 축은 기존 데이터를 최대한 잘 구분할 수 있어야 한다. 이런 일은 2차원 데이터 뿐이 아닌 다차원 데이터에 대해서도 수행할 수 있다.

차원을 감소시키는 방법은 여러가지가 있으나, 여기서는 선형대수에서 배우는 특잇값 분해Singular Vector Decomposition(SVD)를 이용한다. SVD는 어떤 행렬 X를 다음과 같이 세 행렬의 곱으로 분해하는 방법이다.
$$
X=USV^T
$$
여기서 U와 V는 직교행렬orthogonal matrix,S는 대각행렬diagnoal matrix이다. 시각적으로는 다음과 <그림7>과 같이 표현가능하다.

<그림7: SVD>

이 때 U는 직교행렬이므로 단어 공간의 기저를 형성한다. 또한 S의 각 대각성분은 특잇값Singular Value이 큰 순서로 나열되어 있다. 이 특잇값은 해당 축의 중요도로 볼 수 있으므로, SVD에서의 차원 감소란 다음 <그림8>과 같이 중요하지 않은 S의 대각 성분을 깎아내는 작업으로 볼 수 있다.

<그림8: 차원감소 in SVD>

이 과정에서 S의 특잇값에 곱해지는 U의 열벡터, VT의 행벡터도 같이 지워진다. 이제 이 과정을 코드로 구현해보자.

8.4.3 Dimensionality Reduction using SVD

SVD는 numpy에서 제공하는 라이브러리를 사용한다. 계속 사용해왔던 예시 문장인 "You say goodbye and I say hello."를 다시 분석해보자. 우선 다음 코드와 같이 SVD를 수행한다.

# SVD
import sys
sys.path.append('..')
from common.util import *
import matplotlib.pyplot as plt
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,window_size=1)
W = ppmi(C)
U,S,V = np.linalg.svd(W)

그 후 U를 확인해보면 (7, 7) 행렬이므로 이를 2차원으로 줄여보려면 그냥 U의 처음 두 원소를 꺼내면 된다. 이렇게 두 원소로만 표현된 U는 2차원이므로 다음 코드를 통해 plot해볼 수 있다.

#plot
print(U.shape)
for word,word_id in word_to_id.items():
    plt.annotate(word,(U[word_id,0],U[word_id,1])) # plt에 좌표를 text와 함께 넘겨줌

plt.scatter(U[:,0],U[:,1],alpha=0.5) # (x,y)형태의 산포도 그림
plt.show()

그러면 이 결과는 다음 <그림9>와 같다.

<그림9: 차원 감소의 결과>

이런 작업을 조금 더 큰 데이터셋에 대해 진행해보자.

8.4.4 PTB Dataset

지금부터 조금 더 큰 말뭉치를 사용하기 위해 PTB(Pen TreeBank) 데이터를 이용한다. 지금 다루는 도서인 Deep Learning from scratch 2에서는 이 데이터셋을 전처리하여 이용하기 쉽게 load함수를 만들어놓았다. 다음과 같은 방법으로 load하여 사용할 수 있다.

# loading data
corpus,word_to_id,id_to_word = ptb.load_data('train')

ptb.load_data의 인수로는 train,test,valid중 하나를 선택할 수 있다. 이제 이 방법으로 Data를 load하여 co_matrix와 PPMI matrix를 만든뒤 차원감소까지 진행해보자. numpy의 SVD 계산은 오래 걸리므로 sklearn 라이브러리의 고속 SVD를 이용하기로 한다. 참고로 SVD의 n_components는 추출할 특잇값의 수를 의미한다.

import sys
sys.path.append('..')
import numpy as np
from common.util import *
from dataset import ptb
from sklearn.utils.extmath import randomized_svd
window_size = 2
wordvec_size = 100

#Loading Data
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 계산")
U,S,V = randomized_svd(W,n_components = wordvec_size,n_iter = 5, random_state = None)
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)

이 코드의 실행 결과는 다음과 같다.

[query] you
 i: 0.670712947845459
 we: 0.6143624186515808
 'd: 0.5446480512619019
 do: 0.5251259803771973
 someone: 0.5199155807495117

[query] year
 month: 0.6592502593994141
 quarter: 0.6317232847213745
 months: 0.5873333811759949
 last: 0.5852419137954712
 next: 0.5838537216186523

[query] car
 auto: 0.6387380957603455
 luxury: 0.6251621842384338
 corsica: 0.5281869173049927
 vehicle: 0.5250070095062256
 truck: 0.49140435457229614

[query] toyota
 motor: 0.7263075113296509
 motors: 0.6416112184524536
 nissan: 0.6241037845611572
 honda: 0.6156324148178101
 lexus: 0.5946853160858154

분서 결과를 보면 Top5에 연관 단어들이 꽤 있는 것을 보아 분석이 잘 된것을 확인할 수 있다.

Reference

  1. All pictures on: Deep Learning from scratch 2 by Saito Goki
  2. All codes on : https://github.com/WegraLee/deep-learning-from-scratch-2

'머신러닝' 카테고리의 다른 글

[ML/NLP] 10. Improving Word2Vec  (0) 2020.09.05
[ML/NLP] 9. Simple Word2vec  (0) 2020.08.27
[ML] 7. Convolutional Neural Network  (0) 2020.08.22
[ML] 6. Back Propagation  (2) 2020.08.08
[ML] 5. Training Neural Network  (0) 2020.08.06
댓글