14.4. word2vec Ön Eğitimi
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in Colab
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)
../_images/output_word2vec-pretraining_d81279_93_1.svg
lr, num_epochs = 0.002, 5
train(net, data_iter, lr, num_epochs)
loss 0.410, 361758.5 tokens/sec on cuda:0
../_images/output_word2vec-pretraining_d81279_96_1.svg

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

  1. 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?

  2. 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.