15.2. Duygu Analizi: Yinemeli Sinir Ağlarının Kullanımı
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in SageMaker Studio Lab

Kelime benzerliği ve benzetme görevleri gibi, biz de duygu analizine önceden eğitilmiş kelime vektörleri de uygulayabiliriz. Section 15.1 içindeki IMDb inceleme veri kümesi çok büyük olmadığından, büyük ölçekli külliyat üzerinde önceden eğitilmiş metin temsillerinin kullanılması modelin aşırı öğrenmesini azaltabilir. Fig. 15.2.1 içinde gösterilen özel bir örnek olarak, önceden eğitilmiş GloVe modelini kullanarak her belirteci temsil edeceğiz ve bu belirteç temsillerini metin dizisi gösterimini, ki duygu analizi çıktılarına dönüştürülecektir, elde etmek için çok katmanlı çift yönlü bir RNN’ye besleyeceğiz (Maas et al., 2011). Aynı aşağı akım uygulaması için daha sonra farklı bir mimari seçimi ele alacağız.

../_images/nlp-map-sa-rnn.svg

Fig. 15.2.1 Bu bölüm, duygu analizi için önceden eğitilmiş GloVe’yi RNN tabanlı bir mimariye besler.

from d2l import mxnet as d2l
from mxnet import gluon, init, np, npx
from mxnet.gluon import nn, rnn

npx.set_np()

batch_size = 64
train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size)
import torch
from torch import nn
from d2l import torch as d2l

batch_size = 64
train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size)

15.2.1. RNN’lerle Tek Metni Temsil Etme

Duygu analizi gibi metin sınıflandırmalarında, değişen uzunluktaki bir metin dizisi sabit uzunlukta kategorilere dönüştürülür. Aşağıdaki BiRNN sınıfında, bir metin dizisinin her belirteci, gömme katman (self.embedding) aracılığıyla bireysel önceden eğitilmiş GloVe temsilini alırken, tüm dizi çift yönlü RNN (self.encoder) ile kodlanır. Daha somut olarak, iki yönlü LSTM’nin hem ilk hem de son zaman adımlarındaki gizli durumları (son katmanda), metin dizisinin temsili olarak bitiştirilir. Bu tek metin gösterimi daha sonra iki çıktıya (“pozitif” ve “negatif”) sahip tam bağlı bir katman (self.encoder) tarafından çıktı kategorilerine dönüştürülür.

class BiRNN(nn.Block):
    def __init__(self, vocab_size, embed_size, num_hiddens,
                 num_layers, **kwargs):
        super(BiRNN, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        # Çift yönlü bir RNN elde etmek için `bidirectional` (çift yönlü)
        # öğesini True olarak ayarlayın
        self.encoder = rnn.LSTM(num_hiddens, num_layers=num_layers,
                                bidirectional=True, input_size=embed_size)
        self.decoder = nn.Dense(2)

    def forward(self, inputs):
        # `inputs` şekli (parti boyutu, zaman adımı sayısı)'dır. LSTM,
        # girdisinin ilk boyutunun zamansal boyut olmasını gerektirdiğinden,
        # girdi, belirteç temsilleri elde edilmeden önce değiştirilir. Çıktı
        # şekli (zaman adımı sayısı, iş boyutu, kelime vektör boyutu)
        embeddings = self.embedding(inputs.T)
        # Farklı zaman adımlarında son gizli katmanın gizli durumlarını
        # döndürür. `outputs` şekli
        # (zaman adımı sayısı, iş boyutu, 2 * gizli birim sayısı)'dır.
        outputs = self.encoder(embeddings)
        # Tam bağlı katmanın girdisi olarak ilk ve son zaman adımlarında gizli
        # durumları bitiştirin.
        # Şekli (parti boyutu, 4 * gizli birim sayısı)'dır
        encoding = np.concatenate((outputs[0], outputs[-1]), axis=1)
        outs = self.decoder(encoding)
        return outs
class BiRNN(nn.Module):
    def __init__(self, vocab_size, embed_size, num_hiddens,
                 num_layers, **kwargs):
        super(BiRNN, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        # Çift yönlü bir RNN elde etmek için `bidirectional` (çift yönlü)
        # öğesini True olarak ayarlayın
        self.encoder = nn.LSTM(embed_size, num_hiddens, num_layers=num_layers,
                                bidirectional=True)
        self.decoder = nn.Linear(4 * num_hiddens, 2)

    def forward(self, inputs):
        # `inputs` şekli (parti boyutu, zaman adımı sayısı)'dır. LSTM,
        # girdisinin ilk boyutunun zamansal boyut olmasını gerektirdiğinden,
        # girdi, belirteç temsilleri elde edilmeden önce değiştirilir. Çıktı
        # şekli (zaman adımı sayısı, parti boyutu, kelime vektör boyutu)
        embeddings = self.embedding(inputs.T)
        self.encoder.flatten_parameters()
        # Farklı zaman adımlarında son gizli katmanın gizli durumlarını
        # döndürür. `outputs` şekli
        # (zaman adımı sayısı, iş boyutu, 2 * gizli birim sayısı)'dır.
        outputs, _ = self.encoder(embeddings)
        # Tam bağlı katmanın girdisi olarak ilk ve son zaman adımlarında gizli
        # durumları bitiştirin.
        # Şekli (parti boyutu, 4 * gizli birim sayısı)'dır
        encoding = torch.cat((outputs[0], outputs[-1]), dim=1)
        outs = self.decoder(encoding)
        return outs

Duygu analizi için tek bir metni temsil etmek üzere iki gizli katman içeren çift yönlü bir RNN oluşturalım.

embed_size, num_hiddens, num_layers, devices = 100, 100, 2, d2l.try_all_gpus()
net = BiRNN(len(vocab), embed_size, num_hiddens, num_layers)

net.initialize(init.Xavier(), ctx=devices)
embed_size, num_hiddens, num_layers, devices = 100, 100, 2, d2l.try_all_gpus()
net = BiRNN(len(vocab), embed_size, num_hiddens, num_layers)

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
    if type(m) == nn.LSTM:
        for param in m._flat_weights_names:
            if "weight" in param:
                nn.init.xavier_uniform_(m._parameters[param])
net.apply(init_weights);

15.2.2. Önceden Eğitilmiş Sözcük Vektörlerini Yükleme

Aşağıda önceden eğitilmiş 100 boyutlu (embed_size ile tutarlı olması gerekir) kelime dağarcığındaki belirteçler için GloVe gömmelerini yüklüyoruz.

glove_embedding = d2l.TokenEmbedding('glove.6b.100d')
glove_embedding = d2l.TokenEmbedding('glove.6b.100d')

Kelime dağarcığındaki tüm belirteçler için vektörlerin şeklini yazdırın.

embeds = glove_embedding[vocab.idx_to_token]
embeds.shape
(49346, 100)
embeds = glove_embedding[vocab.idx_to_token]
embeds.shape
torch.Size([49346, 100])

Bu önceden eğitilmiş kelime vektörlerini incelemelerde belirteçleri temsil etmek için kullanıyoruz ve eğitim sırasında bu vektörleri güncellemeyeceğiz.

net.embedding.weight.set_data(embeds)
net.embedding.collect_params().setattr('grad_req', 'null')
net.embedding.weight.data.copy_(embeds)
net.embedding.weight.requires_grad = False

15.2.3. Model Eğitimi ve Değerlendirilmesi

Şimdi çift yönlü RNN’leri duygu analizi için eğitebiliriz.

lr, num_epochs = 0.01, 5
trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr})
loss = gluon.loss.SoftmaxCrossEntropyLoss()
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
loss 0.276, train acc 0.885, test acc 0.859
732.3 examples/sec on [gpu(0), gpu(1)]
../_images/output_sentiment-analysis-rnn_6199ad_57_1.svg
lr, num_epochs = 0.01, 5
trainer = torch.optim.Adam(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss(reduction="none")
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
loss 0.293, train acc 0.881, test acc 0.854
693.3 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
../_images/output_sentiment-analysis-rnn_6199ad_60_1.svg

Eğitimli modeli, net, kullanarak bir metin dizisinin duygusunu tahmin etmek için aşağıdaki işlevi tanımlıyoruz.

#@save
def predict_sentiment(net, vocab, sequence):
    """Bir metin dizisinin duygusunu tahmin edin."""
    sequence = np.array(vocab[sequence.split()], ctx=d2l.try_gpu())
    label = np.argmax(net(sequence.reshape(1, -1)), axis=1)
    return 'positive' if label == 1 else 'negative'
#@save
def predict_sentiment(net, vocab, sequence):
    """Bir metin dizisinin duygusunu tahmin edin."""
    sequence = torch.tensor(vocab[sequence.split()], device=d2l.try_gpu())
    label = torch.argmax(net(sequence.reshape(1, -1)), dim=1)
    return 'positive' if label == 1 else 'negative'

Son olarak, iki basit cümlenin duygusunu tahmin etmek için eğitimli modeli kullanalım.

predict_sentiment(net, vocab, 'this movie is so great')
'positive'
predict_sentiment(net, vocab, 'this movie is so bad')
'negative'
predict_sentiment(net, vocab, 'this movie is so great')
'positive'
predict_sentiment(net, vocab, 'this movie is so bad')
'negative'

15.2.4. Özet

  • Önceden eğitilmiş sözcük vektörleri, bir metin dizisinde tek tek belirteçleri temsil edebilir.

  • Çift yönlü RNN’ler, örneğin ilk ve son zaman adımlarında gizli durumlarının bitiştirilmesi yoluyla olduğu gibi bir metin dizisini temsil edebilir. Bu tek metin gösterimi, tam bağlı bir katman kullanılarak kategorilere dönüştürülebilir.

15.2.5. Alıştırmalar

  1. Dönem sayısını artırın. Eğitim ve test doğruluklarını iyileştirebilir misiniz? Diğer hiper parametreleri ayarlamaya ne dersiniz?

  2. 300 boyutlu GloVe gömme gibi daha büyük önceden eğitilmiş sözcük vektörlerini kullanın. Sınıflandırma doğruluğunu arttırıyor mu?

  3. SpaCy andıçlamasını kullanarak sınıflandırma doğruluğunu artırabilir miyiz? SpaCy (pip install spacy) ve İngilizce paketini (python -m spacy download en) yüklemeniz gerekir. Kodda, önce spaCy’i (import spacy) içe aktarın. Ardından, spaCy İngilizce paketini yükleyin (spacy_en = spacy.load('en')). Son olarak, def tokenizer(text): return [tok.text for tok in spacy_en.tokenizer(text)] işlevini tanımlayın ve orijinal tokenizer işlevini değiştirin. GloVe ve spaCy içinde ifade belirteçlerinin farklı biçimlerine dikkat edin. Örneğin, “new york” ifade belirteci GloVe’de “new-york” biçimini ve spaCy andıçlamasından sonra “new york” biçimini alır.