개선판 word2vec 학습
CBOW 모델 구현
import sys
sys.path.append('..')
import numpy as np
from layers import Embedding
from negative_sampling_layer import NegativeSamplingLoss
class CBOW:
def __init__(self, vocab_size, hidden_size, window_size, corpus):
#어휘수, 은닉층 뉴런수, 맥락크기, 단어 ID 목록
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계층 2*window_size 개 작성하여 in_layers에 배열로 보관
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 #가중치 할당
순전파, 역전파 처리
앞에서는 단어 id를 one-hot vector로 변환해서 사용했지만 forward 메서드가 인수로 받는 맥락과 타깃이 단어 ID다.
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
CBOW 모델 학습 코드
import sys
sys.path.append('..')
from common import config
config.GPU = True #gpu 설정
import pickle
from trainer import Trainer
from optimizer import Adam
from cbow import CBOW
from skip_gram import SkipGram
from util import create_contexts_target, to_cpu, to_gpu
from dataset import ptb
import numpy as np
# 하이퍼파라미터 설정
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)
학습이 끝나면 가중치(입력측 가중치) 를 나중에 이용할 수 있도록 pickle 파일로 저장한다.학습 과정에서 무작위 값이 쓰이기 때문에 최종적으로 얻는 가중치가 각자의 환경에 따라 달라진다.
CBOW 모델 평가
from ssearch_while import most_similar, analogy
import sys
sys.path.append('..')
import pickle
pkl_file = 'C:/Users/admin/Downloads/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 task
querys = ['you', 'year', 'car', 'toyota']
for query in querys:
most_similar(query, word_to_id, id_to_word, word_vecs, top=5)
[query] you
we: 0.6103515625
someone: 0.59130859375
i: 0.55419921875
something: 0.48974609375
anyone: 0.47314453125
[query] year
month: 0.71875
week: 0.65234375
spring: 0.62744140625
summer: 0.6259765625
decade: 0.603515625
[query] car
luxury: 0.497314453125
arabia: 0.47802734375
auto: 0.47119140625
disk-drive: 0.450927734375
travel: 0.4091796875
[query] toyota
ford: 0.55078125
instrumentation: 0.509765625
mazda: 0.49365234375
bethlehem: 0.47509765625
nissan: 0.474853515625
----------------------------------
you로 물었을때 인칭대명서 i와we 등이 나왔고, year에 대해서는 month나 week 같은 기간을 뜻하는 같은 성격의 단어들이 도출되었다.toyota는 ford, nissan,mazda 같은 자동차 브랜드가 도출되었다.
word2vec 으로 얻은 단어의 분산표현은 비슷한 단어들을 가까이 모으고, 복잡한 패턴까지 파악할 수 있다.
정확히 말하면 유추문제를 벡터의 덧셈과 뺄셈으로 풀 수 있다는 것이다.
man:woman = king : ?
이 문제를 수식으로 나타내면 vec('woman) - vec('man') = vec(?)-vec('king')이 된다.
그러므로 vec('king')+ vec('woman')- vec('man') = vec(?)라는 벡터에 가장 가까운 단어 벡터를구한다.
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:?
woman: 5.16015625
veto: 4.9296875
ounce: 4.69140625
earthquake: 4.6328125
successor: 4.609375
[analogy] take:took = go:?
went: 4.55078125
points: 4.25
began: 4.09375
comes: 3.98046875
oct.: 3.90625
[analogy] car:cars = child:?
children: 5.21875
average: 4.7265625
yield: 4.20703125
cattle: 4.1875
priced: 4.1796875
[analogy] good:better = bad:?
more: 6.6484375
less: 6.0625
rather: 5.21875
slower: 4.734375
greater: 4.671875
-------------------------
마지막 문제를 제외하고 윗 문제 유추가 잘 된 것을 알 수 있다. 단어의 단순한 의미 뿐 아니라 문법적인 패턴도 파악 할 수 있는 것이다. 더 큰 말뭉치를 이용하면 정확도를 올릴 수 있을것임.
'Deep Learning > from scratch II' 카테고리의 다른 글
[밑바닥부터 시작하는 딥러닝 2] - 5. 순환신경망(RNN) II (0) | 2024.02.17 |
---|---|
[밑바닥부터 시작하는 딥러닝 2] - 5. 순환 신경망(RNN) I (0) | 2024.02.16 |
[밑바닥부터 시작하는 딥러닝 2]- 4. word2vec 속도 개선 I (1) | 2024.02.15 |
[밑바닥부터 시작하는 딥러닝 2] - 3장 word2vec II (1) | 2024.02.15 |
[밑바닥부터 시작하는 딥러닝 2] - 3장 word2vec I (0) | 2024.02.14 |