14.4. word2vec Ön Eğitimi¶ Open the notebook in SageMaker Studio Lab
Section 14.1 içinde tanımlanan skip-gram modelini uygulamaya
devam ediyoruz. Ardından, PTB veri kümesindeki negatif örnekleme
kullanarak word2vec ön eğiteceğiz. Her şeyden önce,
Section 14.3 içinde açıklanan d2l.load_data_ptb
işlevini çağırarak veri yineleyicisini ve bu veri kümesi için sözcük
dağarcığını elde edelim.
import math
from d2l import mxnet as d2l
from mxnet import autograd, gluon, np, npx
from mxnet.gluon import nn
npx.set_np()
batch_size, max_window_size, num_noise_words = 512, 5, 5
data_iter, vocab = d2l.load_data_ptb(batch_size, max_window_size,
num_noise_words)
import math
import torch
from torch import nn
from d2l import torch as d2l
batch_size, max_window_size, num_noise_words = 512, 5, 5
data_iter, vocab = d2l.load_data_ptb(batch_size, max_window_size,
num_noise_words)
14.4.1. Skip-Gram Modeli¶
Katmanları ve toplu matris çarpımlarını kullanarak skip-gram modelini uyguluyoruz. İlk olarak, gömme katmanların nasıl çalıştığını inceleyelim.
14.4.1.1. Gömme Katmanı¶
Section 9.7 içinde açıklandığı gibi, bir katman, bir
belirteç dizinini öznitelik vektörüyle eşler. Bu katmanın ağırlığı,
satır sayısı sözlük boyutuna (input_dim
) ve sütun sayısı her
belirteç için vektör boyutuna (output_dim
) eşit olan bir matristir.
Bir sözcük gömme modeli eğitildikten sonra, bu ağırlık ihtiyacımız olan
şeydir.
embed = nn.Embedding(input_dim=20, output_dim=4)
embed.initialize()
embed.weight
Parameter embedding0_weight (shape=(20, 4), dtype=float32)
embed = nn.Embedding(num_embeddings=20, embedding_dim=4)
print(f'Parameter embedding_weight ({embed.weight.shape}, '
f'dtype={embed.weight.dtype})')
Parameter embedding_weight (torch.Size([20, 4]), dtype=torch.float32)
Gömülü katmanın girdisi, bir belirteç (sözcük) dizinidir. Herhangi bir
belirteç dizini \(i\) için vektör gösterimi, gömme katmanındaki
ağırlık matrisinin \(i.\) satırından elde edilebilir. Vektör boyutu
(output_dim
) 4 olarak ayarlandığından, gömme katmanı (2, 3) şekle
sahip bir minigrup işlemi için (2, 3, 4) şekilli vektörler döndürür.
x = np.array([[1, 2, 3], [4, 5, 6]])
embed(x)
array([[[ 0.01438687, 0.05011239, 0.00628365, 0.04861524],
[-0.01068833, 0.01729892, 0.02042518, -0.01618656],
[-0.00873779, -0.02834515, 0.05484822, -0.06206018]],
[[ 0.06491279, -0.03182812, -0.01631819, -0.00312688],
[ 0.0408415 , 0.04370362, 0.00404529, -0.0028032 ],
[ 0.00952624, -0.01501013, 0.05958354, 0.04705103]]])
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
embed(x)
tensor([[[-1.7168, 1.5441, -0.8471, 0.6239],
[-0.8008, 0.1647, -0.0691, -0.4562],
[-0.4796, 1.1483, 1.5281, 0.7885]],
[[ 1.3627, -0.1596, -0.4472, -1.0897],
[-0.2692, 0.7414, 0.3256, -0.1186],
[ 0.2164, -0.8939, 0.3935, 2.7814]]], grad_fn=<EmbeddingBackward0>)
14.4.1.2. İleri Yaymayı Tanımlama¶
İleri yayılımda, skip-gram modelinin girdisi, (toplu iş boyutu, 1)
şekilli center
merkez sözcük indekslerini ve max_len
’in
Section 14.3.5 içinde tanımlandığı (toplu
iş boyutu, max_len
) şeklindeki contexts_and_negatives
bitiştirilmiş bağlam ve gürültü sözcük indekslerini içerir. Bu iki
değişken önce belirteç dizinlerinden gömme katmanı aracılığıyla
vektörlere dönüştürülür, daha sonra toplu matris çarpımı
(Section 10.2.4.1 içinde açıklanmıştır) (toplu iş boyutu, 1,
max_len
) şekilli bir çıktı döndürür. Çıktıdaki her eleman, bir
merkez sözcük vektörü ile bir bağlam veya gürültü sözcük vektörünün
nokta çarpımıdır .
def skip_gram(center, contexts_and_negatives, embed_v, embed_u):
v = embed_v(center)
u = embed_u(contexts_and_negatives)
pred = npx.batch_dot(v, u.swapaxes(1, 2))
return pred
def skip_gram(center, contexts_and_negatives, embed_v, embed_u):
v = embed_v(center)
u = embed_u(contexts_and_negatives)
pred = torch.bmm(v, u.permute(0, 2, 1))
return pred
Bazı örnek girdiler için bu skip_gram
işlevinin çıktı şeklini
yazdıralım.
skip_gram(np.ones((2, 1)), np.ones((2, 4)), embed, embed).shape
(2, 1, 4)
skip_gram(torch.ones((2, 1), dtype=torch.long),
torch.ones((2, 4), dtype=torch.long), embed, embed).shape
torch.Size([2, 1, 4])
14.4.2. Eğitim¶
Skip-gram modelini negatif örnekleme ile eğitmeden önce, öncelikle kayıp işlevini tanımlayalım.
14.4.2.1. İkili Çapraz Entropi Kaybı¶
Section 14.2.1 içinde negatif örnekleme için kayıp fonksiyonunun tanımına göre ikili çapraz entropi kaybını kullanacağız.
loss = gluon.loss.SigmoidBCELoss()
class SigmoidBCELoss(nn.Module):
# Maskeleme ile ikili çapraz entropi kaybı
def __init__(self):
super().__init__()
def forward(self, inputs, target, mask=None):
out = nn.functional.binary_cross_entropy_with_logits(
inputs, target, weight=mask, reduction="none")
return out.mean(dim=1)
loss = SigmoidBCELoss()
Section 14.3.5 içindeki maske değişkeninin ve etiket değişkeninin tanımlarını hatırlayın. Aşağıdaki ifade, verilen değişkenler için ikili çapraz entropi kaybını hesaplar.
pred = np.array([[1.1, -2.2, 3.3, -4.4]] * 2)
label = np.array([[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]])
mask = np.array([[1, 1, 1, 1], [1, 1, 0, 0]])
loss(pred, label, mask) * mask.shape[1] / mask.sum(axis=1)
array([0.93521017, 1.8462094 ])
pred = torch.tensor([[1.1, -2.2, 3.3, -4.4]] * 2)
label = torch.tensor([[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]])
mask = torch.tensor([[1, 1, 1, 1], [1, 1, 0, 0]])
loss(pred, label, mask) * mask.shape[1] / mask.sum(axis=1)
tensor([0.9352, 1.8462])
Aşağısı, ikili çapraz entropi kaybında sigmoid etkinleştirme fonksiyonu kullanılarak yukarıdaki sonuçların nasıl hesaplandığını (daha az verimli bir şekilde) göstermektedir. İki çıktıyı, maskelenmemiş tahminlere göre ortalaması alınan iki normalleştirilmiş kayıp olarak düşünebiliriz.
def sigmd(x):
return -math.log(1 / (1 + math.exp(-x)))
print(f'{(sigmd(1.1) + sigmd(2.2) + sigmd(-3.3) + sigmd(4.4)) / 4:.4f}')
print(f'{(sigmd(-1.1) + sigmd(-2.2)) / 2:.4f}')
0.9352
1.8462
def sigmd(x):
return -math.log(1 / (1 + math.exp(-x)))
print(f'{(sigmd(1.1) + sigmd(2.2) + sigmd(-3.3) + sigmd(4.4)) / 4:.4f}')
print(f'{(sigmd(-1.1) + sigmd(-2.2)) / 2:.4f}')
0.9352
1.8462
14.4.2.2. Model Parametrelerini İlkleme¶
Sırasıyla merkez sözcükler ve bağlam sözcükleri olarak
kullanıldıklarından sözcük dağarcığındaki tüm sözcükler için iki gömme
katmanı tanımlarız. embed_size
sözcük vektör boyutu 100 olarak
ayarlanır.
embed_size = 100
net = nn.Sequential()
net.add(nn.Embedding(input_dim=len(vocab), output_dim=embed_size),
nn.Embedding(input_dim=len(vocab), output_dim=embed_size))
embed_size = 100
net = nn.Sequential(nn.Embedding(num_embeddings=len(vocab),
embedding_dim=embed_size),
nn.Embedding(num_embeddings=len(vocab),
embedding_dim=embed_size))
14.4.2.3. Eğitim Döngüsünün Tanımlanması¶
Eğitim döngüsü aşağıda tanımlanmıştır. Dolgunun varlığı nedeniyle, kayıp fonksiyonunun hesaplanması önceki eğitim fonksiyonlarına kıyasla biraz farklıdır.
def train(net, data_iter, lr, num_epochs, device=d2l.try_gpu()):
net.initialize(ctx=device, force_reinit=True)
trainer = gluon.Trainer(net.collect_params(), 'adam',
{'learning_rate': lr})
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[1, num_epochs])
# Normalleştirilmiş kayıpların toplamı, normalleştirilmiş kayıpların sayısı
metric = d2l.Accumulator(2)
for epoch in range(num_epochs):
timer, num_batches = d2l.Timer(), len(data_iter)
for i, batch in enumerate(data_iter):
center, context_negative, mask, label = [
data.as_in_ctx(device) for data in batch]
with autograd.record():
pred = skip_gram(center, context_negative, net[0], net[1])
l = (loss(pred.reshape(label.shape), label, mask) *
mask.shape[1] / mask.sum(axis=1))
l.backward()
trainer.step(batch_size)
metric.add(l.sum(), l.size)
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(metric[0] / metric[1],))
print(f'loss {metric[0] / metric[1]:.3f}, '
f'{metric[1] / timer.stop():.1f} tokens/sec on {str(device)}')
def train(net, data_iter, lr, num_epochs, device=d2l.try_gpu()):
def init_weights(m):
if type(m) == nn.Embedding:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
net = net.to(device)
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[1, num_epochs])
# Normalleştirilmiş kayıpların toplamı, normalleştirilmiş kayıpların sayısı
metric = d2l.Accumulator(2)
for epoch in range(num_epochs):
timer, num_batches = d2l.Timer(), len(data_iter)
for i, batch in enumerate(data_iter):
optimizer.zero_grad()
center, context_negative, mask, label = [
data.to(device) for data in batch]
pred = skip_gram(center, context_negative, net[0], net[1])
l = (loss(pred.reshape(label.shape).float(), label.float(), mask)
/ mask.sum(axis=1) * mask.shape[1])
l.sum().backward()
optimizer.step()
metric.add(l.sum(), l.numel())
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(metric[0] / metric[1],))
print(f'loss {metric[0] / metric[1]:.3f}, '
f'{metric[1] / timer.stop():.1f} tokens/sec on {str(device)}')
Artık negatif örnekleme kullanarak bir skip-gram modelini eğitebiliriz.
lr, num_epochs = 0.002, 5
train(net, data_iter, lr, num_epochs)
loss 0.407, 98407.8 tokens/sec on gpu(0)
lr, num_epochs = 0.002, 5
train(net, data_iter, lr, num_epochs)
loss 0.410, 361758.5 tokens/sec on cuda:0
14.4.3. Sözcük Gömmelerini Uygulama¶
Word2vec modelini eğittikten sonra, eğitimli modeldeki sözcük vektörlerinin kosinüs benzerliğini kullanarak sözlükten bir girdi sözcüğüne en çok benzeyen sözcükleri bulabiliriz.
def get_similar_tokens(query_token, k, embed):
W = embed.weight.data()
x = W[vocab[query_token]]
# Kosinüs benzerliğini hesaplayın. Sayısal kararlılık için 1e-9 ekleyin
cos = np.dot(W, x) / np.sqrt(np.sum(W * W, axis=1) * np.sum(x * x) + 1e-9)
topk = npx.topk(cos, k=k+1, ret_typ='indices').asnumpy().astype('int32')
for i in topk[1:]: # Remove the input words
print(f'cosine sim={float(cos[i]):.3f}: {vocab.to_tokens(i)}')
get_similar_tokens('chip', 3, net[0])
cosine sim=0.668: intel
cosine sim=0.638: microprocessor
cosine sim=0.616: workstations
def get_similar_tokens(query_token, k, embed):
W = embed.weight.data
x = W[vocab[query_token]]
# Kosinüs benzerliğini hesaplayın. Sayısal kararlılık için 1e-9 ekleyin
cos = torch.mv(W, x) / torch.sqrt(torch.sum(W * W, dim=1) *
torch.sum(x * x) + 1e-9)
topk = torch.topk(cos, k=k+1)[1].cpu().numpy().astype('int32')
for i in topk[1:]: # Remove the input words
print(f'cosine sim={float(cos[i]):.3f}: {vocab.to_tokens(i)}')
get_similar_tokens('chip', 3, net[0])
cosine sim=0.679: microprocessor
cosine sim=0.665: intel
cosine sim=0.626: processes
14.4.4. Özet¶
Gömülü katmanlar ve ikili çapraz entropi kaybını kullanarak negatif örnekleme ile bir skip-gram modelini eğitebiliriz.
Sözcük gömme uygulamaları, sözcük vektörlerinin kosinüs benzerliğine dayanan belirli bir sözcük için anlamsal olarak benzer sözcükleri bulmayı içerir.
14.4.5. Alıştırmalar¶
Eğitilmiş modeli kullanarak, diğer girdi sözcükleri için anlamsal olarak benzer sözcükleri bulun. Hiper parametreleri ayarlayarak sonuçları iyileştirebilir misiniz?
Bir eğitim külliyatı çok büyük olduğunda, model parametrelerini güncellerken mevcut minigrup içindeki merkez sözcükler için bağlam sözcükleri ve gürültü sözcüklerini sık sık örnekleriz. Başka bir deyişle, aynı merkez sözcük farklı eğitim dönemlerinde farklı bağlam sözcüklerine veya gürültü sözcüklerine sahip olabilir. Bu yöntemin faydaları nelerdir? Bu eğitim yöntemini uygulamaya çalışın.