.. _sec_sentiment_rnn: Análise de Sentimento: Usando Redes Neurais Recorrentes ======================================================= Semelhante a sinônimos e analogias de pesquisa, a classificação de texto também é uma aplicação posterior de incorporação de palavras. Nesta seção, vamos aplicar vetores de palavras pré-treinados (GloVe) e redes neurais recorrentes bidirecionais com múltiplas camadas ocultas :cite:`Maas.Daly.Pham.ea.2011`, como mostrado em :numref:`fig_nlp-map-sa-rnn`. Usaremos o modelo para determinar se uma sequência de texto de comprimento indefinido contém emoção positiva ou negativa. .. _fig_nlp-map-sa-rnn: .. figure:: ../img/nlp-map-sa-rnn.svg Esta seção alimenta o GloVe pré-treinado para uma arquitetura baseada em RNN para análise de sentimento. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python from mxnet import gluon, init, np, npx from mxnet.gluon import nn, rnn from d2l import mxnet as d2l npx.set_np() batch_size = 64 train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size) .. raw:: html
.. raw:: html
.. code:: python 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) .. parsed-literal:: :class: output Downloading ../data/aclImdb_v1.tar.gz from http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz... .. raw:: html
.. raw:: html
Usando um Modelo de Rede Neural Recorrente ------------------------------------------ Neste modelo, cada palavra obtém primeiro um vetor de recurso da camada de incorporação. Em seguida, codificamos ainda mais a sequência de recursos usando uma rede neural recorrente bidirecional para obter as informações da sequência. Por fim, transformamos as informações da sequência codificada para a saída por meio da camada totalmente conectada. Especificamente, podemos concatenar estados ocultos de memória de longo-curto prazo bidirecionais na etapa de tempo inicial e etapa de tempo final e passá-la para a classificação da camada de saída como informação de sequência de recurso codificada. Na classe ``BiRNN`` implementada abaixo, a instância\ ``Embedding`` é a camada de incorporação, a instância ``LSTM`` é a camada oculta para codificação de sequência e a instância\ ``Dense`` é a camada de saída para os resultados de classificação gerados. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python 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) # Set `bidirectional` to True to get a bidirectional recurrent neural # network 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): # The shape of `inputs` is (batch size, no. of words). Because LSTM # needs to use sequence as the first dimension, the input is # transformed and the word feature is then extracted. The output shape # is (no. of words, batch size, word vector dimension). embeddings = self.embedding(inputs.T) # Since the input (embeddings) is the only argument passed into # rnn.LSTM, it only returns the hidden states of the last hidden layer # at different time step (outputs). The shape of `outputs` is # (no. of words, batch size, 2 * no. of hidden units). outputs = self.encoder(embeddings) # Concatenate the hidden states of the initial time step and final # time step to use as the input of the fully connected layer. Its # shape is (batch size, 4 * no. of hidden units) encoding = np.concatenate((outputs[0], outputs[-1]), axis=1) outs = self.decoder(encoding) return outs .. raw:: html
.. raw:: html
.. code:: python 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) # Set `bidirectional` to True to get a bidirectional recurrent neural # network 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): # The shape of `inputs` is (batch size, no. of words). Because LSTM # needs to use sequence as the first dimension, the input is # transformed and the word feature is then extracted. The output shape # is (no. of words, batch size, word vector dimension). embeddings = self.embedding(inputs.T) # Since the input (embeddings) is the only argument passed into # nn.LSTM, both h_0 and c_0 default to zero. # we only use the hidden states of the last hidden layer # at different time step (outputs). The shape of `outputs` is # (no. of words, batch size, 2 * no. of hidden units). self.encoder.flatten_parameters() outputs, _ = self.encoder(embeddings) # Concatenate the hidden states of the initial time step and final # time step to use as the input of the fully connected layer. Its # shape is (batch size, 4 * no. of hidden units) encoding = torch.cat((outputs[0], outputs[-1]), dim=1) outs = self.decoder(encoding) return outs .. raw:: html
.. raw:: html
Criando uma rede neural recorrente bidirecional com duas camadas ocultas. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python 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) .. raw:: html
.. raw:: html
.. code:: python 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); .. raw:: html
.. raw:: html
Carregando Vetores de Palavras Pré-treinados ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Como o conjunto de dados de treinamento para classificação de sentimento não é muito grande, para lidar com o *overfitting*, usaremos diretamente vetores de palavras pré-treinados em um corpus maior como vetores de características de todas as palavras. Aqui, carregamos um vetor de palavras GloVe de 100 dimensões para cada palavra do ``vocabulário`` do dicionário. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python glove_embedding = d2l.TokenEmbedding('glove.6b.100d') .. raw:: html
.. raw:: html
.. code:: python glove_embedding = d2l.TokenEmbedding('glove.6b.100d') .. parsed-literal:: :class: output Downloading ../data/glove.6B.100d.zip from http://d2l-data.s3-accelerate.amazonaws.com/glove.6B.100d.zip... .. raw:: html
.. raw:: html
Consultando os vetores de palavras que estão em nosso vocabulário. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python embeds = glove_embedding[vocab.idx_to_token] embeds.shape .. parsed-literal:: :class: output (49346, 100) .. raw:: html
.. raw:: html
.. code:: python embeds = glove_embedding[vocab.idx_to_token] embeds.shape .. parsed-literal:: :class: output torch.Size([49346, 100]) .. raw:: html
.. raw:: html
Em seguida, usaremos esses vetores de palavras como vetores de características para cada palavra nas revisões. Observe que as dimensões dos vetores de palavras pré-treinados precisam ser consistentes com o tamanho de saída da camada de incorporação ``embed_size`` no modelo criado. Além disso, não atualizamos mais esses vetores de palavras durante o treinamento. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python net.embedding.weight.set_data(embeds) net.embedding.collect_params().setattr('grad_req', 'null') .. raw:: html
.. raw:: html
.. code:: python net.embedding.weight.data.copy_(embeds) net.embedding.weight.requires_grad = False .. raw:: html
.. raw:: html
Treinamento e Avaliação do Modelo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Agora podemos começar a treinar. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python 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) .. parsed-literal:: :class: output loss 0.323, train acc 0.865, test acc 0.842 444.5 examples/sec on [gpu(0), gpu(1)] .. figure:: output_sentiment-analysis-rnn_6199ad_57_1.svg .. raw:: html
.. raw:: html
.. code:: python 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) .. parsed-literal:: :class: output loss 0.333, train acc 0.860, test acc 0.829 749.0 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)] .. figure:: output_sentiment-analysis-rnn_6199ad_60_1.svg .. raw:: html
.. raw:: html
Finalmente, definir a função de previsão. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@save def predict_sentiment(net, vocab, sentence): sentence = np.array(vocab[sentence.split()], ctx=d2l.try_gpu()) label = np.argmax(net(sentence.reshape(1, -1)), axis=1) return 'positive' if label == 1 else 'negative' .. raw:: html
.. raw:: html
.. code:: python #@save def predict_sentiment(net, vocab, sentence): sentence = torch.tensor(vocab[sentence.split()], device=d2l.try_gpu()) label = torch.argmax(net(sentence.reshape(1, -1)), dim=1) return 'positive' if label == 1 else 'negative' .. raw:: html
.. raw:: html
Em seguida, usamos o modelo treinado para classificar os sentimentos de duas frases simples. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python predict_sentiment(net, vocab, 'this movie is so great') .. parsed-literal:: :class: output 'positive' .. code:: python predict_sentiment(net, vocab, 'this movie is so bad') .. parsed-literal:: :class: output 'negative' .. raw:: html
.. raw:: html
.. code:: python predict_sentiment(net, vocab, 'this movie is so great') .. parsed-literal:: :class: output 'positive' .. code:: python predict_sentiment(net, vocab, 'this movie is so bad') .. parsed-literal:: :class: output 'negative' .. raw:: html
.. raw:: html
Resumo ------ - A classificação de texto transforma uma sequência de texto de comprimento indefinido em uma categoria de texto. Esta é uma aplicação *downstream* de incorporação de palavras. - Podemos aplicar vetores de palavras pré-treinados e redes neurais recorrentes para classificar as emoções em um texto. Exercícios ---------- 1. Aumente o número de épocas. Que taxa de precisão você pode alcançar nos conjuntos de dados de treinamento e teste? Que tal tentar reajustar outros hiperparâmetros? 2. O uso de vetores de palavras pré-treinados maiores, como vetores de palavras GloVe 300-dimensionais, melhorará a acurácia da classificação? 3. Podemos melhorar a acurácia da classificação usando a ferramenta de tokenização de palavras spaCy? Você precisa instalar spaCy: ``pip install spacy`` e instalar o pacote em inglês: ``python -m spacy download en``. No código, primeiro importe spacy: ``import spacy``. Em seguida, carregue o pacote spacy em inglês: ``spacy_en = spacy.load ('en')``. Finalmente, defina a função ``def tokenizer(text): return [tok.text for tok in spacy_en.tokenizer(text)]`` e substitua a função original de ``tokenizer``. Deve-se notar que o vetor de palavras do GloVe usa “-” para conectar cada palavra ao armazenar frases nominais. Por exemplo, a frase “new york” é representada como “new-york” no GloVe. Depois de usar a tokenização spaCy, “new york” pode ser armazenado como “new york”. .. raw:: html
mxnetpytorch
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
.. raw:: html