.. _sec_sentiment_rnn: Анализ тональности: использование рекуррентных нейронных сетей ============================================================== Как и в задачах на определение сходства слов и аналогий, мы можем применять предобученные векторные представления слов к анализу тональности. Поскольку набор данных с отзывами IMDb в :numref:`sec_sentiment` невелик, использование текстовых представлений, предобученных на крупномасштабных корпусах, может снизить переобучение модели. В качестве конкретного примера, проиллюстрированного в :numref:`fig_nlp-map-sa-rnn`, мы будем представлять каждый токен с помощью предобученной модели GloVe и подавать эти представления в многослойный двунаправленный RNN для получения представления текстовой последовательности, которое затем преобразуется в выходные данные анализа тональности :cite:`Maas.Daly.Pham.ea.2011`. Для той же задачи последующей обработки мы рассмотрим другой архитектурный подход позже. .. _fig_nlp-map-sa-rnn: .. figure:: ../img/nlp-map-sa-rnn.svg В этом разделе мы используем предварительно обученные векторы GloVe в архитектуре на основе рекуррентной нейронной сети для анализа тональности. .. raw:: latex \diilbookstyleinputcell .. 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) Представление одного текста с помощью RNN ----------------------------------------- В задачах классификации текстов, таких как анализ тональности, последовательность текста переменной длины преобразуется в категории фиксированной длины. В следующем примере ``BiRNN``, хотя каждый токен текстовой последовательности получает своё собственное предобученное представление GloVe через слой эмбеддинга (``self.embedding``), вся последовательность кодируется двунаправленным RNN (``self.encoder``). Более конкретно, скрытые состояния (на последнем слое) двунаправленного LSTM как на начальном, так и на конечном временных шагах объединяются для формирования представления текстовой последовательности. Это единое представление текста затем преобразуется в выходные категории с помощью полносвязного слоя (``self.decoder``) с двумя выходами («положительный» и «отрицательный»). .. raw:: latex \diilbookstyleinputcell .. 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 RNN 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 time steps). Because # LSTM requires its input's first dimension to be the temporal # dimension, the input is transposed before obtaining token # representations. The output shape is (no. of time steps, batch size, # word vector dimension) embeddings = self.embedding(inputs.T) self.encoder.flatten_parameters() # Returns hidden states of the last hidden layer at different time # steps. The shape of `outputs` is (no. of time steps, batch size, # 2 * no. of hidden units) outputs, _ = self.encoder(embeddings) # Concatenate the hidden states at the initial and final time steps 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 Давайте построим двунаправленную RNN с двумя скрытыми слоями для представления отдельного текста в задаче анализа тональности. .. raw:: latex \diilbookstyleinputcell .. 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) .. raw:: latex \diilbookstyleinputcell .. code:: python def init_weights(module): if type(module) == nn.Linear: nn.init.xavier_uniform_(module.weight) if type(module) == nn.LSTM: for param in module._flat_weights_names: if "weight" in param: nn.init.xavier_uniform_(module._parameters[param]) net.apply(init_weights); Загрузка предобученных векторов слов ------------------------------------ Ниже мы загружаем предварительно обученные 100-мерные (размерность должна совпадать с ``embed_size``) векторные представления GloVe для токенов из словаря. .. raw:: latex \diilbookstyleinputcell .. code:: python glove_embedding = d2l.TokenEmbedding('glove.6b.100d') Выведите форму векторов для всех токенов в словаре. .. raw:: latex \diilbookstyleinputcell .. code:: python embeds = glove_embedding[vocab.idx_to_token] embeds.shape Мы используем эти предварительно обученные векторы слов для представления токенов в отзывах и не будем обновлять их во время обучения. .. raw:: latex \diilbookstyleinputcell .. code:: python net.embedding.weight.data.copy_(embeds) net.embedding.weight.requires_grad = False Обучение и оценка модели ------------------------ Теперь мы можем обучить двунаправленную RNN для анализа тональности. .. raw:: latex \diilbookstyleinputcell .. 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) Определим следующую функцию для предсказания тональности текстовой последовательности с помощью обученной модели ``net``. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save def predict_sentiment(net, vocab, sequence): """Predict the sentiment of a text sequence.""" 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' Наконец, давайте используем обученную модель для предсказания тональности двух простых предложений. .. raw:: latex \diilbookstyleinputcell .. code:: python predict_sentiment(net, vocab, 'this movie is so great') .. raw:: latex \diilbookstyleinputcell .. code:: python predict_sentiment(net, vocab, 'this movie is so bad') Итоги ----- - Предобученные векторные представления слов могут представлять отдельные токены в последовательности текста. - Двусторонние рекуррентные нейронные сети могут представлять текстовую последовательность, например, путем объединения её скрытых состояний на начальном и конечном временных шагах. Это единое представление текста можно преобразовать в категории с помощью полносвязного слоя. Упражнения ---------- 1. Увеличьте количество эпох. Удается ли улучшить точность на обучающих и тестовых данных? А как насчет настройки других гиперпараметров? 2. Используйте более крупные предобученные векторные представления слов, например, 300-мерные эмбеддинги GloVe. Улучшает ли это точность классификации? 3. Можно ли повысить точность классификации, используя токенизацию spaCy? Для этого необходимо установить spaCy (``pip install spacy``) и английский пакет (``python -m spacy download en``). В коде сначала импортируйте spaCy (``import spacy``). Затем загрузите английский пакет spaCy (``spacy_en = spacy.load('en')``). Наконец, определите функцию ``def tokenizer(text): return [tok.text for tok in spacy_en.tokenizer(text)]`` и замените ею исходную функцию ``tokenizer``. Обратите внимание на различия в представлении фразовых токенов в GloVe и spaCy. Например, фразовый токен “new york” в GloVe имеет вид “new-york”, а после токенизации spaCy он сохраняет форму “new york”. `Обсуждения `__