일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 30 |
- 피보나치수열
- 스티커
- 딥러닝
- 퍼셉트론
- 인공지는
- 밑시딥
- 아직
- 역전파
- 메모이제이션
- 힙구조
- 자료구조
- 병합정렬
- ssac
- 알고리즘
- 인공지능
- 트리
- dp
- memoization
- 힙
- tree
- 분할정복
- AIFFEL
- 어렵다
- 머신러닝
- 피보나치
- 우선순위큐
- Image Classification
- 오차역전파법
- 다이나믹프로그래밍
- Today
- Total
딥러닝 for Deep Learning
밑.시.딥. 2 - 4장 word2vec 속도개선 part2 본문
지금까지 긍정적인 예(정답)에 관해서만 이진 분류를 수행했다.
그러나 이진분류를 제대로 다루기 위해서는
부정적인 예(정답 이외의 단어)의 관한 학습도 함께 필요하다.
따라서,
긍정적인 예를 입력했을 때의 출력이 1에 가깝다면
부정적인 예을 입력했을 때의 출력은 0에 가까워져야 한다.
그렇기 때문에 아래와 같은 결과를 내어주는 가중치를 찾는 것이 중요하다.
Q. 부정적 예를 이진 분류하려면 모든 부정적 예를 대상으로 이진 분류를 학습시키면 되는가?
-Nope! 모든 부정적 예을 대상으로 삼으면 어휘 수가 늘어났을 때에는 감당할 수 없다.
Q. 그러면 어떻게 하면 되는가?
-부정적 예를 샘플링(예를 들어, 5개 혹은 10개) 한다. 이를 네거티브 샘플링 기법이라 한다.
4.2.5 네거티브 샘플링
- 긍정적 예와 함께 부정적 예를 입력한다.
- 긍정적 예는 정답 레이블을 '1'로, 부정적 예는 정답 레이블을 '0'으로 해서 Sigmoid with Loss계층에 입력한다.
- 각 데이터의 손실을 모두 합해 최종 손실을 출력한다.
4.2.6. 네거티브 샘플링의 샘플링 기법
말뭉치의 통계 데이터를 기초로 샘플링을 하는데,
말뭉치에서의 단어 빈도를 기준으로 해서 자주 등장하는 단어를 많이 추출하고 드물게 등장하는 단어는 적게 추출한다.
그렇게 하기 위해선 우선 각 단어의 출연 횟수를 구해 확률분포로 나타낸다.
import numpy as np
# 0에서 9까지의 숫자 중 하나를 무작위로 샘플링
np. random.choice(10)
np. random.choice(10)
# word에서 하나만 무작위로 샘플링
words = ['you', 'say', 'goodbye', 'I', 'hello', '.']
np.random.choice(words)
# 5개만 무작위로 샘플링 (중복 있음)
np.random.choice(words, size=5)
# 5개만 무작위로 샘플링 (중복 없음)
np.random.choice(words, size=5, replace=False)
# 확률분포에 따라 샘플링
p = [0.5, 0.1, 0.05, 0.2, 0.05, 0.1]
np.random.choice(words, p=p)
다만, 여기에서 한 가지 수정해야 할 것이 있는데 아래의 식과 같다.
위 식에서는 각 단어의 확률분포에 0.75 제곱을 해준다.
이유는 0.75제곱을 함으로써 원래 확률이 낮은 단어의 확률을 조금 높여줄 수 있다.
그렇게 하면 확률이 낮은 단어가 버려지지 않고 데이터가 골고루 샘플링될 수 있다.
p = [0.7, 0.29, 0.01]
new_p = np.power(p, 0.75)
new_p /= np.sum(new_p)
print(new_p)
# [0.64196878, 0.33150408, 0.02652714]
위와 같은 처리를 담당하는 클래스를 UnigramSampler라 한다.
4.2.7 네거티브 샘플링 구현
class NegativeSamplingLoss:
def __init__(self, W, corpus, power=0.75, sample_size=5):
self.sample_size = sample_size
self.sampler = UnigramSampler(corpus, power, sample_size)
self.loss_layers = [SigmoidWithLoss() for _ in range(sample_size + 1)]
self.embed_dot_layers = [EmbeddingDot(W) for _ in range(sample_size + 1)]
self.params, self.grads = [], []
for layer in self.embed_dot_layers:
self.params += layer.params
self.grads += layer.grads
[초기화 메서드의 인수]
- W : 출력 층 가중치
- corpus : 말뭉치(단어 ID의 리스트)
- power : 확률분포에 제곱할 값
- sample_size : 부정적 예의 샘플링 횟수
인스턴스 변수인 loss_layers와 embed_dot_layers에는 원하는 계층을 리스트로 보관하는데
두 리스트 모두에 긍정적 예를 다루는 계층이 더 필요하기 때문에 sample_size + 1개의 계층을 생성한다.
**순전파 구현**
def forward(self, h, target):
batch_size = target.shape[0]
negative_sample = self.sampler.get_negative_sample(target)
# 긍정적 예 순전파
score = self.embed_dot_layers[0].forward(h, target)
correct_label = np.ones(batch_size, dtype=np.int32)
loss = self.loss_layers[0].forward(score, correct_label)
# 부정적 예 순전파
negative_label = np.zeros(batch_size, dtype=np.int32)
for i in range(self.sample_size):
negative_target = negative_sample[:, i]
score = self.embed_dot_layers[1 + i].forward(h, negative_target)
loss += self.loss_layers[1 + i].forward(score, negative_label)
return loss
**역전파 구현**
def backward(self, dout=1):
dh = 0
for l0, l1 in zip(self.loss_layers, self.embed_dot_layers):
dscore = l0.backward(dout)
dh += l1.backward(dscore)
return dh
4.3 개선판 word2vec 학습
이번 절에서는 PTB 데이터 셋을 사용한다.
4.3.1 CBOW 모델 구현
기존의 simpleCBOW 클래스에 Embedding 계층과 Negative Sampling Loss 계층을 적용한다.
또한, 맥락의 윈도우 크기를 조정할 수 있도록 한다.
import sys
sys.path.append('..')
from common.np import * # import numpy as np
from common.layers import Embedding
from ch04.negative_sampling_layer import NegativeSamplingLoss
class CBOW:
def __init__(self, vocab_size, hidden_size, window_size, corpus):
V, H = vocab_size, hidden_size
# 가중치 초기화
W_in = 0.01 * np.random.randn(V, H).astype('f')
W_out = 0.01 * np.random.randn(V, H).astype('f')
# 계층 생성
self.in_layers = []
for i in range(2 * window_size):
layer = Embedding(W_in) # Embedding 계층 사용
self.in_layers.append(layer)
self.ns_loss = NegativeSamplingLoss(W_out, corpus, power=0.75, sample_size=5)
# 모든 가중치와 기울기를 배열에 모은다.
layers = self.in_layers + [self.ns_loss]
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 : 은닉층의 뉴런 수
- corpus : 단어 ID 목록
- window_size : 맥락의 크기(타깃 단어 좌우로 맥락을 몇 개로 삼을지 정함.)
[계층 생성]
- Embedding 계층을 2*window_size개 작성하여 in layers에 배열로 보관
- Negative Sampling Loss 계층 생성
그런 다음 모든 가중치와 배열을 params와 grads에 모으고 인스턴스 변수에 단어의 분산 표현을 저장한다.
**순전파 구현**
def forward(self, contexts, target):
h = 0
for i, layer in enumerate(self.in_layers):
h += layer.forward(contexts[:, i])
h *= 1 / len(self.in_layers)
loss = self.ns_loss.forward(h, target)
return loss
**역전파 구현**
def backward(self, dout=1):
dout = self.ns_loss.backward(dout)
dout *= 1 / len(self.in_layers)
for layer in self.in_layers:
layer.backward(dout)
return None
순전파 메서드가 인수로 받는 맥락과 타깃은 '단어 ID'로 나타난다.
맥락은 2차원 배열, 타깃은 1차원 배열이다.
4.3.2 CBOW 모델 학습 코드
# coding: utf-8
import sys
sys.path.append('..')
import numpy as np
from common import config
# GPU에서 실행하려면 아래 주석을 해제하세요(CuPy 필요).
# ===============================================
# config.GPU = True
# ===============================================
import pickle
from common.trainer import Trainer
from common.optimizer import Adam
from cbow import CBOW
from skip_gram import SkipGram
from common.util import create_contexts_target, to_cpu, to_gpu
from dataset import ptb
# 하이퍼파라미터 설정
window_size = 5
hidden_size = 100
batch_size = 100
max_epoch = 10
# 데이터 읽기
corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
contexts, target = create_contexts_target(corpus, window_size)
if config.GPU:
contexts, target = to_gpu(contexts), to_gpu(target)
# 모델 등 생성
model = CBOW(vocab_size, hidden_size, window_size, corpus)
# model = SkipGram(vocab_size, hidden_size, window_size, corpus)
optimizer = Adam()
trainer = Trainer(model, optimizer)
# 학습 시작
trainer.fit(contexts, target, max_epoch, batch_size)
trainer.plot()
# 나중에 사용할 수 있도록 필요한 데이터 저장
word_vecs = model.word_vecs
if config.GPU:
word_vecs = to_cpu(word_vecs)
params = {}
params['word_vecs'] = word_vecs.astype(np.float16)
params['word_to_id'] = word_to_id
params['id_to_word'] = id_to_word
pkl_file = 'cbow_params.pkl' # or 'skipgram_params.pkl'
with open(pkl_file, 'wb') as f:
pickle.dump(params, f, -1)
[하이퍼 파라미터]
- window_size : 2~10개가 좋은 결과를 출력
- hidden_size : 50~500개가 좋은 결과를 출력
- batch_size
- max_epoch
PTB 데이터 셋을 학습시키려면 오랜 시간이 걸리기 때문에 GPU를 사용할 수 있는 모드도 추가한다.
다만, GPU를 사용하려면 엔비디아 GPU가 장착되어야 하며, 쿠파이도 설치되어 있어야 한다.
4.3.3 CBOW 모델 평가
import sys
sys.path.append('..')
from common.util import most_similar, analogy
import pickle
pkl_file = 'cbow_params.pkl'
# pkl_file = 'skipgram_params.pkl'
with open(pkl_file, 'rb') as f:
params = pickle.load(f)
word_vecs = params['word_vecs']
word_to_id = params['word_to_id']
id_to_word = params['id_to_word']
# 가장 비슷한(most similar) 단어 뽑기
querys = ['you', 'year', 'car', 'toyota']
for query in querys:
most_similar(query, word_to_id, id_to_word, word_vecs, top=5)
# 유추(analogy) 작업
print('-'*50)
analogy('king', 'man', 'queen', word_to_id, id_to_word, word_vecs)
analogy('take', 'took', 'go', word_to_id, id_to_word, word_vecs)
analogy('car', 'cars', 'child', word_to_id, id_to_word, word_vecs)
analogy('good', 'better', 'bad', word_to_id, id_to_word, word_vecs)
위 코드를 실행하면 아래와 같은 결과를 얻을 수 있다.
analogy('king', 'man', 'queen', word_to_id, id_to_word, word_vecs)
analogy('take', 'took', 'go', word_to_id, id_to_word, word_vecs)
analogy('car', 'cars', 'child', word_to_id, id_to_word, word_vecs)
analogy('good', 'better', 'bad', word_to_id, id_to_word, word_vecs)
[word2vec으로 얻은 단어의 분산 표현을 사용 시 장점]
- 벡터의 덧셈과 뺄셈으로 유추 문제를 풀 수 있다.
- 단어의 의미와 더불어 문법적인 패턴을 파악할 수 있다.
- 단어의 관계성을 파악해 정답을 유추해 낼 수 있다.
- 데이터 셋이 크면 유추 문제의 정답률도 크게 향상된다.
4.4 word2vec 남은 주제
단어의 분산 표현은 비슷한 단어를 찾는 용도로 이용할 수도 있지만,
전이 학습에 아주 중요한 역할을 한다.
[전이 학습(transfer learning)이란?]
전이 학습은 한 분야에서 배운 지식을 다른 분야에도 적용하는 기법이다.
특정 환경에서 만들어진 AI 알고리즘을 다른 비슷한 분야에 적용하는 것을 말한다.
전이 학습의 예로,
사과 깎는 방법을 익힌 AI에게 배를 깎게 시킨다거나 체스를 익힌 AI에게 장기를 두게 시킬 수 있다.
이러한 전이 학습의 장점은 알고리즘을 적용시킬 문제의 데이터가 부족한 상황에서 빛을 발한다.
자연어 처리 관점에서 살펴보면,
위키백과나 구글 뉴스의 텍스트 데이터로 학습을 마친 뒤,
그 분산 표현을 텍스트 분류, 문서 클러스터링, 품사 태그 달기 등의 작업에 이용할 수 있다.
또한, 단어의 분산 표현은 단어나 문장을 bag-of-words 혹은 순환 신경망(RNN)을 사용해 고정 길이 벡터로 변환해 줄 수 있다.
단어나 문장을 고정 길이 벡터로 변환하면
이를 일반적인 머신러닝 기법에 적용시켜 머신러닝 시스템의 틀에서 원하는 답을 출력해 낼 수 있다.
[메일 자동 분류 시스템]
- 메일(데이터) 수집
- 긍정, 중립, 부정 3가지의 감정을 나타내는 레이블을 붙임.
- word2vec을 이용해 메일을 벡터로 변환
- 벡터화된 메일과 감정 레이블을 분류 시스템(SVM 혹은 신경망)에 입력시켜 학습 수행
4.4.2 단어 벡터 평가 방법
단어 분산 표현을 만드는 시스템과 분류하는 시스템의 학습은 따로 수행할 수도 있지만,
그럴 경우, 각 시스템을 따로 학습시킨 다음 평가해야 하고 이에 더해 최적의 하이퍼 파라미터를 찾기 위한 튜닝도 필요해서 오랜 시간이 걸린다.
그래서 단어의 분산 표현의 우수성을 실제 어플리케이션과 분리해 평가한다.
여기에서 평가의 척도가 되는 것은 '유사성'이나 '유추 문제'로 볼 수 있다.
4.5 정리
- 말뭉치의 어휘수 증가에 비례해 증가하는 계산량 문제를 해결하기 위해 Embedding 계층과 Negative Sampling Loss 계층을 도입했다. 핵심은 전체 데이터 대신 꼭 필요한 일부 데이터를 처리하는 것.
- 네거티브 샘플링은 부정적 예를 몇 개 샘플링하는 기법으로, 이를 이용하면 다중 분류를 이진 분류처럼 취급할 수 있다.
- word2vec의 단어의 분산 표현을 이용하면 유추 문제를 벡터의 덧셈과 뺄셈으로 풀 수 있게 된다.
- word2vec을 통해 얻은 단어의 분산 표현은 다양한 자연어 처리 문제뿐만이 아니라 음성, 이미지, 영상과 같은 분야에서도 응용될 수 있다.
'지하에서부터 시작하는 딥러닝' 카테고리의 다른 글
밑.시.딥. Chapter8. 딥러닝 (0) | 2021.09.01 |
---|---|
밑.시.딥. Chapter5. 오차역전파법(Backpropagation) (0) | 2021.08.11 |
밑.시.딥. Chapter2. 퍼셉트론(Perceptron) (0) | 2021.07.13 |
5장 오차역전파법 part2 (0) | 2021.01.26 |