NIRVANA
[밑바닥부터 시작하는 딥러닝2] 3장 word2vec 본문
추론 기반 기법과 신경망
단어를 벡터로 표현하는 방법
1. 통계 기반 기법
2. 추론 기반 기법
통계 기반 기법의 문제점
- 통계 기반 기법에서는 주변 단어의 빈도를 기초로 단어를 표현함
- 단어의 동시 발생 행렬을 만들고, 그 행렬에 SVD를 적용하여 밀집 벡터를 얻음
- 통계 기반 기법은 학습 데이터를 한꺼번에 처리(배치 학습)
- 대규모 말뭉치를 다룰 때 문제가 발생함 → 거대한 행렬에 SVD 적용에 어려움이 존재
- SVD를 nxn행렬에 적용하는 비용은 O(n^3)
- 대규모 말뭉치를 다룰 때 문제가 발생함 → 거대한 행렬에 SVD 적용에 어려움이 존재
추론 기반 기법 개요
- 학습 데이터의 일부를 사용하여 순차적으로 학습을 진행함
- 데이터 양이 적으므로 말뭉치의 어휘 수가 많아 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의 각 행에는 해당 단어의 분산 표현이 담겨져 있음 → 학습이 진행될 수록 맥락에서 출현하는 단어를 잘 추출하는 방향으로 분산 표현들이 갱신
- 가중치 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
- 단어의 의미가 인코딩된 벡터가 저장됨
- 각 단어의 분산표현이 열 방향(수직 방향)으로 저장됨
- 입력 측 완전연결계층의 가중치 W_in
- 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기법이 등장
- 말뭉치 전체의 통계 정보를 손실 함수에 도입하여 미니 배치 학습을 진행
'AI' 카테고리의 다른 글
[밑바닥부터 시작하는 딥러닝2] 6장. 게이트가 추가된 RNN (0) | 2024.03.20 |
---|---|
[밑바닥부터 시작하는 딥러닝2] 5장. 순환 신경망(RNN) (0) | 2024.03.18 |
[밑바닥부터 시작하는 딥러닝2] 4장. word2vec 속도 개선 (5) | 2024.03.18 |
[밑바닥부터 시작하는 딥러닝2] 2장 자연어와 단어의 분산 표현 (0) | 2024.03.16 |
[pandas] 판다스 melt() (0) | 2023.08.09 |