혜온의 이것저것

[Chapter 3 word2vec] 4 CBOW 모델 구현 본문

Deep Learning/밑바닥부터 시작하는 딥러닝2

[Chapter 3 word2vec] 4 CBOW 모델 구현

혜온 :) 2022. 4. 26. 16:52

우리가 구현할 신경망은 다음과 같다.

우리는 이 신경망을 SimpleCBOW라는 이름으로 구현할 것이다. 클래스의 초기화 메서드부터 시작해보자.

import sys
sys.path.append('..')
import numpy as np
from common.layers import MatMul, SoftmaxWithLoss

class SimpleCBOW:
    def __init__(self, vocab_size, hidden_size):
    V,H=vocab_size, hidden_size
    
    # 가중치 초기화
    W_in = 0.01*np.random.randn(V,H).astype('f')
    W_out= 0.01*np.random.randn(H,V).astype('f')
    
    # 계층 생성
    self.in_layer0=MatMul(W_in)
    self.in_layer1=MatMul(W_in)
    self.out_layer=MatMul(W_out)
    self.loss_layer=SoftmaxWithLoss()
    
    # 모든 가중치와 기울기를 리스트에 모은다.
    layers=[self.in_layer0, self.in_layer1, self.out_layer]
    self.params, self.grads=[]
    for layer in layers:
        self.params+=layer.params
        self.grads+=layer.grads
        
    # 인스턴스 변수에 단어의 분산 표현을 저장한다.
    self.word_vecs=W_in

이 초기화 메서드는 인수로 어휘 수(vocab_size)와 은닉층의 뉴런 수(hidden_size)를 받는다.

가중치 초기화 부분에서는 가중치를 2개 생성한다.(W_in, W_out) 이 두 가중치는 랜덤으로 초기화된다.

그리고 이때 넘파이 배열의 데이터 타입을 astype('f')로 저장하여 32비트 부동소수점 수로 초기화한다.

 

이어서 필요한 계층을 생성한다.

입력 측의 MatMul 계층을 2개, 출력 측의 MatMul 계층을 하나, Softmax with Loss 계층을 하나 생성한다.

입력측의 맥락을 처리하는 MatMul 계층은 맥락에서 사용하는 단어의 수(윈도우 크기)만큼 만들어야 한다.

그리고 입력 측 MatMul 계층들은 모두 같은 가중치를 이용하도록 초기화한다.

 

마지막으로 이 신경망에서 사용되는 매개변수와 기울기를 인스턴스 변수의 params와 grads리스트에 각각 모아둔다.

 

이어서 신경망의 순전파 forward() 메서드를 구현한다. 이 메서드는 인수로 맥락과 타깃을 받아 손실을 반환한다.

def forward(self, contexts, target):
    h0=self.in_layer0.forward(contexts[:,0]
    h1=self.in_layer1.forward(contexts[:,1])
    h=(h0+h1)*0.5
    score=self.out_layer.forward(h)
    loss=self.loss_layer.forward(score,target)
    return loss

여기서 인수 contexts는 3차원 넘파이 배열이라고 가정한다.

[그림 3-18]의 예에서라면 이 배열의 형상은 (6,2,7)이 된다. 그 0번째 차원의 원소 수는 미니배치의 수만큼이고, 1번째 차원의 원소 수는 맥락의 윈도우 크기, 2번째 차원은 원핫 벡터이다.

또한 target의 형상은 2차원으로, 예컨대 (6,7)과 같은 형상이 된다.

 

마지막으로 역전파 backward()를 구현한다. 이 역전파의 계산 그래프는 다음과 같다.

신경망의 역전파는 기울기를 순전파 때와는 반대 방향으로 전파한다.

이 역전파는 1에서 시작하여 곧바로 Softmax with Loss 계층에 입력된다. 그리고 Softmax with Loss 계층의 역잔파 출력이 ds이며, 이 ds를 출력 측 MatMul 계층으로 입력한다.

그런 다음 x dhk + 연산으로 역전파된다.

x의 역전파는 순전파 시의 입력을 서로 바꿔 기울기에 곱한다.

+의 역전파는 기울기를 그대로 통과시킨다.

def backward(self, dout=1):
    ds=self.loss_layer.backward(dout)
    da=self.out_layer.backward(ds)
    da*=0.5
    self.in_layer1.backward(da)
    self.in_layer2.backward(da)
    return None

 

우리는 이미 각 매개변수의 기울기를 인스턴스 변수 grads에 모아뒀다.

따라서 forward() 메서드를 호출한 다음 backward() 메서드를 실행하는 것만으로 grads 리스트의 기울기가 갱신된다.

 

3.4.1 학습 코드 구현

CBOW 모델의 학습은 일반적인 신경망의 학습과 완전히 같다.

학습 데이터를 준비해 신경망에 입력한 다음, 기울기를 구하고 가중치 매개변수를 순서대로 갱신할 것이다.

import sys
sys.path.append('..')
from common.trainer import Trainer
from common.optimizer import Adam
from simple_cbow import SimpmleCBOW
from common.util import preprocess, create_contexts_target, convert_one_hot

window_size=1
hidden_size=5
batch_size=3
max_epoch=1000

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

vocab_size=len(word_to_id)
contexts,target=create_contexts_target(corpus, window_size)
target=convert_one_hot(target,vocab_size)
contexts=convert_one_hot(contexts, vocab_size)

model=SimpleCBOW(vocab_size, hidden_size)
optimizer=Adam()
trainer=Trainer(model,optimizer)

trainer.fit(contexts,target,max_epoch,batch_size)
trainer.plot()

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

학습을 거듭할수록 손실이 줄어드는 것을 확인할 수 있다.

학습이 끝난 후의 가중치 매개변수를 살펴보자. 입력 측 MatMul계층의 가중치를 꺼내 실제 내용을 확인할 것이다. 

입력측 MatMul 계층의 가중치는 인스턴스 변수 word_vecs에 저장돼 있으니, 앞의 코드 바로 뒤에 다음 코드르 추가한다.

word_vecs=model.word_vecs
for word_id, word in id_to_word.items():
    print(word, word_vecs[word_id])

이 코드는 word_vecs라는 이름으로 가중치를 꺼내는데, word_vecs의 각 행에는 대응하는 단어 ID의 분산 표현이 저장되어 있다. 실제로 이 코드를 실행하면 다음결과를 얻을 수 있다.

you [-0.9031807 -1.0.374491 -1.4682057 -1.3216232 0.93127245]
say [1.2172916 1.2620505 -0.07845993 -1.2389531]
goodbye [-1.0834033 -0.8826921 -0.33428606 -0.5720131 1.0488235]
and [1.0244362 1.0160093 -1.6284224 -1.6400533 -1.0564581]
i [-1.0642933 -0.9162385 -0.31357735 -0.5730831 1.041875]
hello [-0.9018145 -1.035476 -1.4629668 -1.3058501 0.9280102]
. [1.0985303 1.1642815 1.4365371 1.3974973 -1.0714306]

마침내 단어를 밀집벡터로 나타낼 수 있게 되었다.

이 밀집벡터가 바로 단어의 분산표현이다.

학습이 잘 이루어졌으니 이 분산 표현은 단어의 의미를 잘 파악한 벡터 표현으로 되어 있을 것이라 기대할 수 있다.

 

하지만 아쉽게도 여기서 다룬 작은 말뭉치로는 좋은 결과를 얻을 수 없다. (말뭉치가 워낙 작기 때문에)

실용적이고 충분히 큰 말뭉치로 바꾸면 결과도 그만큼 좋아지겠지만, 처리 속도 면에서 문제가 생긴다.

현시점의 CBOW 모델 구현은 처리 효율 면에서 몇 가지 문제가 있다.

그래서 4장에서는 현재의 단순한 CBOW 모델을 개선하여 진짜 CBOW 모델을 구현할 계획이다.

Comments