NIRVANA

[밑바닥부터 시작하는 딥러닝2] 3장 word2vec 본문

AI

[밑바닥부터 시작하는 딥러닝2] 3장 word2vec

녜잉 2024. 3. 17. 17:50

추론 기반 기법과 신경망

단어를 벡터로 표현하는 방법

1. 통계 기반 기법

2. 추론 기반 기법 

 

통계 기반 기법의 문제점 

  • 통계 기반 기법에서는 주변 단어의 빈도를 기초로 단어를 표현함 
    • 단어의 동시 발생 행렬을 만들고, 그 행렬에 SVD를 적용하여 밀집 벡터를 얻음 
  • 통계 기반 기법은 학습 데이터를 한꺼번에 처리(배치 학습)
    • 대규모 말뭉치를 다룰 때 문제가 발생함 → 거대한 행렬에 SVD 적용에 어려움이 존재
      • SVD를 nxn행렬에 적용하는 비용은 O(n^3)

 

추론 기반 기법 개요 

  • 학습 데이터의 일부를 사용하여 순차적으로 학습을 진행함 
    • 데이터 양이 적으므로 말뭉치의 어휘 수가 많아 SVD 등 계산량이 큰 작업을 처리하기 어려운 경우에도 신경망을 학습 시킬 수 있음
    • 여러 머신과 여러 GPU를 이용한 병렬 계산도 가능하므로 학습 속도를 높일 수 있음
  • 추론은 주변 단어(맥락)가 주어졌을 때, 비어진 공간에 무슨 단어가 들어가는 지를 추측하는 작업 
    • 모델이 맥락 정보를 입력 받아 (출현할 수 있는) 각 단어의 출현 확률을 출력함 
  • 말뭉치를 사용하여 모델이 올바른 추측을 내놓도록 학습하고, 학습의 결과로 단어의 분산 표현을 얻는 것이 추론 기반 기법의 전체 그림 

 

신경망에서의 단어 처리 

  • 신경망에서 단어를 처리할 때, "you"와 "say"같은 단어를 있는 그대로 처리할 수 없으므로 단어를 '고정 길이의 벡터'로 변환해야 함 → "원-핫 표현(원핫 벡터)

ex) "You say goodbye and I say hello"의 두 단어를 원 핫 표현으로 나타내기 

단어(텍스트) 단어 ID 원핫 표현 
You 0 [1, 0, 0, 0, 0, 0, 0]
goodbye 2 [0, 0, 1, 0, 0, 0, 0] 
  • 총 어휘 수 만큼의 원소를 갖는 벡터를 준비하고, 인덱스가 단어 ID와 같은 원소를 1로, 나머지는 모두 0으로 설정
  • 단어를 고정 길이의 벡터로 변환하여 신경망 입력층 뉴런 수를 고정하게 됨 
    • 단어를 신경망으로 처리할 수 있게 됨 
import numpy as np

c = np.array([[1, 0, 0, 0, 0, 0, 0]]) #입력
W = np.random.randn(7, 3) #가중치
h = np.matmul(c, W) #행렬곱 수행 
print(h)

 

  • c와 W의 행렬 곱은 가중치의 행벡터 하나를 뽑아낸 것과 같은 의미 

행렬곱 연산을 MatMul 계층으로 수행하기 

#common 디렉터리에 있는 MatMul 계층 사용하기 
import sys
import numpy as np
from common.layers import MatMul 

c = np.array([[1, 0, 0, 0, 0, 0, 0]]) #입력
W = np.random.randn(7, 3) #가중치
layer = MatMul(W)
h = layer.forward(c)
print(h)

 

단순한 word2vec

CBOW 모델의 추론 처리 

  • CBOW 모델은 맥락으로부터 타깃을 추측하는 용도의 신경망
    • 타깃: 중앙 단어
    • 맥락: 타깃의 주변 단어 
  • CBOW 모델의 입력은 맥락이 됨 
  • 은닉층의 뉴런은 입력층의 완전연결계층에 의해 변환되는 값이 되고, 입력층의 여러 개일 경우 전체를 평균하여 사용함 
  • 출력층 뉴런은 각 단어의 '점수'를 뜻하며, 값이 높을 수록 대응 단어의 출현 확률이 높아짐 
    • 점수는 확률로 해석되기 전의 값, 이 값을 소프트맥스 함수에 적용하면 확률을 얻게 됨 
  • 입력층에서 은닉층으로의 변환은 완전연결계층(가중치 W_in)에 의해 이루어지고, 이때 W_in은 7x3행렬이 됨 
    • 가중치 W_in은 단어 분산 표현이 됨 
      • W_in의 각 행에는 해당 단어의 분산 표현이 담겨져 있음 → 학습이 진행될 수록 맥락에서 출현하는 단어를 잘 추출하는 방향으로 분산 표현들이 갱신 
import sys
sys.path.append('..')
import numpy as np
from common.layers import MathMul 

#샘플 맥락 데이터
c0 = np.array([[1, 0, 0, 0, 0, 0, 0]])
c1 = np.array([[0, 0, 1, 0, 0, 0, 0]])

W_in = np.random.randn(7, 3)
W_out = np.random.randn(3, 7)

#계층 생성
in_layer0 = MatMul(W_in)
in_layer1 = MatMul(W_in)
in_layer2 = MatMul(W_out)

# 순전파
h0 = in_layer0.forward(c0)
h1 = in_layer1.forward(c1)
h = 0.5 * (h0 + h1)
s =out_layer.forward(h)

print(h)

 

CBOW 모델의 학습 

  • CBOW 모델의 학습으로 얻어지는 확률은 맥락이 주어졌을 때, 중앙에 어떤 단어가 출현하는 지를 의미

ex) 맥락이 You와 Goodbye이 일 때, 정답 레이블은 "Say"가 되므로 가중치가 적절히 설정된 신경망이라면 확률을 나타내는 뉴런들 중, 정답에 해당하는 뉴런의 값이 클 것 

  • CBOW 모델의 학습에서는 올바른 예측을 할 수 있도록 가중치를 조정하는 일을 진행 
    • 가중치 W_in에는 단어의 출현 패턴을 파악한 벡터가 학습됨 
  • 사용하는 모델은 다중 클래스 분류를 수행하는 신경망이므로 신경망 학습을 위해 소프트맥스와 교차 엔트로피 오차만 이용하면 됨 
    • 소프트맥스 함수를 이용해 접수를 확률로 변환
    • 확률과 정답 레이블로부터 교차 엔트로피 오차를 구함
    • 값을 손실로 사용하여 학습 진행 

 

word2vec의 가중치와 분산 표현 

  • word2vec에서 사용되는 신경망에는 두 가지 가중치가 존재
    • 입력 측 완전연결계층의 가중치 W_in
      • 각 단어의 분산 표현에 해당
    • 출력 측 완전연결계층의 가중치 W_out
      • 단어의 의미가 인코딩된 벡터가 저장됨 
      • 각 단어의 분산표현이 열 방향(수직 방향)으로 저장됨 
  • word2vec에서는 입력 측의 가중치를 단어의 분산표현으로 이용하는 것이 대중적인 선택임 

 

학습 데이터 준비

맥락과 타깃 

  • word2vec에서 이용하는 신경망의 입력은 '맥락'이고 정답 레이블은 맥락에 둘러싸인 중앙의 단어 '타깃'이 됨

말 뭉치에서 타깃과 맥락을 만드는 작업

말뭉치 맥락(contexts) 타깃
you say goodbye and I say hello.  you, goodbye say
you say goodbye and I say hello.  say, and goodbye
you say goodbye and I say hello.  goodbye, I and
you say goodbye and I say hello.  and, say I
you say goodbye and I say hello.  I, hello say
you say goodbye and I say hello say, .  hello 

 

1. 말뭉치 텍스트를 단어 ID로 변경

import sys
sys.path.append('..')
from common.until import preprocess

text = 'You say goodbye and I say hello.'

corpus, word_to_id, id_to_word = preprocess(text)
print(corpus)

print(id_to_word)

 

2. 단어 ID의 배열인 corpus로부터 맥락과 타깃을 생성 

- 구체적으로는 corpus를 주면 맥락과 타깃으르 반환하는 함수를 작성 

def create_contexts_target(corpus, window_size=1):
  target = corpus[window_size: -window_size]
  contexts = []

  for idx in range(window_size, len(corpus)-window_size):
    cs = []

    for t in range(-window_size, window_size+1):
      if t == 0:
        continue
      cs.append(corpus[idx+ t])
    
    contexts.append(cs)


    return np.array(contexts), np.array(target)

 

원핫 표현으로 변환 

  • 단어 ID를 이용했을 때의 맥락의 형상은 (6, 2)인데 이를 원핫 표현으로 변환하면 (6, 2, 7)이 됨을 주의 
  • 원팟 표현으로 covert_one_hot()함수를 사용 
import sys
sys.path.append('..')
from common.until import preprocess, create_contexts_target, convert_one_hot

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

contexts , target = create_contexts_target(corpus, window_size=1)

vocab_size = len(word_to_id)
target = convert_one_hot(target, vocab_size)
contexts = convert_one_hot(contexts, vocab_size)

 

 

CBOW 모델 구현 

1. 초기화 메서드 구현 

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.in_layer2 = MatMul(W_in)
    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

 

2. 신경망 순전파 메서드 구현

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

 

3. 신경망 역전파 메서드 구현 

  • 각 매개변수의 기울기를 인스턴스 변수 grads에 모아두었으므로 forward() 메서드를 호출한 후, backward() 메서드를 실행하는 것만으로 grads 리스트의 기울기가 갱신됨 
def backward(self, dout=1):
  ds = self.loss_layer.backward(out)
  da = self.out_layer.backward(ds)

  da *= 0.5
  self.in_layer1.backward(da)
  self.in_layer.backward(da)
  return None

 

학습 코드 구현 

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

window_size = 1
hidden_size = 1
batch_size = 1
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, targte = 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()

 

  • 밀집벡터는 단어의 분산 표현이고, (학습이 잘 이루어졌으니) 밀집 벡터, 즉 분산 표현이 단어의 의미를 잘 파악한 벡터 표현으로 되어 있을 것이라 기대 가능 

 

word2vec 보충 

CBOW 모델과 확률 

CBOW모델을 확률 표기법으로 기술하기 

  •  CBOW 모델: 맥락을 주면 타깃 단어가 출현할 확률을 출력
  • 말뭉치를 w_1, w_2, ... w_t 처럼 단어 시퀀스로 표기하고, t번째 단어에 대해 윈도우 크기가 1인 맥락을 고려
  • 맥락으로 W_t-1과 W_t+1이 주어졌을 때, 타깃이 W_t가 될 확률 수식 작성 방법 
    • CBOW가 아래의 식을 모델링하고 있음을 확인 가능 

P(w_t | w_t-1, w_t+1)

 

 

  • CBOW 모델의 손실함수

L = -logP(w_t | w_t-1, w_t+1)

L = - 1/T ∑ log P (w_t | w_t-1, w_t=1)

 

 

Skip-gram 모델 

  • CBOW에서 다루는 맥락과 타깃을 역전시킨 모델 
    • CBOW 모델은 맥락이 여러 개 있고, 그 여러 맥락으로부터 중앙의 단어를 추측
    • Skip-gram 모델은 중앙의 단어로부터 주변의 여러 단어를 추측
      • 입력층이 한 개
  • skip-gram 모델은 맥락의 수만큼 추측하기 때문에 그 손실 함수는 각 맥락에서 구한 손실의 총합이어야 함
  • CBOW 모델은 타깃 하나의 손실을 구함 
  • Skip-gram 모델의 결과가 좋은 경우가 많음 단, CBOW 모델은 학습 속도가 빠름 

 

통계 기반 vs 추론 기반 

통계 기반 기법 추론 기반 기법 
- 말뭉치의 전체 통계로부터 1회 학습하여 단어의 분산 표현을 얻음

-어휘에 추가해야 할 새 단어 발생 시, 계산을 처음부터 다시 진행 

-단어의 유사성을 인코딩 

- 말뭉치의 일부분씩 여러 번 보면서 학습을 진행 

- 어휘에 추가해야 할 새 단어 발생 시, 매개변수를 다시 학습 → 지금까지 학습한 가중치를 초깃값으로 사용, 다시 학습 

- (특히 Skip-gram에서) 단어의 유사성과 더불어 단어 사이의 패턴도 파악 되어 인코딩 
  • 의외로 추론 기반 기법과 통계 기반 기법은 우열을 가리기 힘듦
  • skip-gram(추론 기반 기법)과 네거티프 샘플링을 이용한 모델(통계 기반 기법)은 모두 말뭉치의 전체 ㅈ동시 발생 행렬에 특수한 행렬 분해를 적용한 것과 같음 
  • word2vec 이후, GloVe기법이 등장
    • 말뭉치 전체의 통계 정보를 손실 함수에 도입하여 미니 배치 학습을 진행