Поскольку мы изучали преобразование слов в вычислимые тензоры, давайте углубимся в развертывание того, что мы узнали при анализе реальных данных.

Ранее мы проверили, как преобразовывать слова в тензоры. (Предыдущий пост [EasyPeasyPyTorch] 01. Встраивание Word)



Описанный выше метод следует вскоре применить к набору данных IMDB, проведя анализ тональности с помощью PyTorch. В частности, в этой публикации мы будем использовать однократное кодирование, поэтому, если это не звонит в колокольчик, было бы неплохо потратить несколько минут, чтобы щелкнуть ссылку выше и проверить идею этого.

Готовый? Затем давайте кратко проверим, что такое рекуррентная нейронная сеть, ее прогресс или краткую историю.

Согласно словарю Google, слово «рекуррентный» означает «появляющееся часто или неоднократно».

В отличие от нейронной сети с прямой связью (FFNN), RNN многократно выполняет одну и ту же задачу для каждого из данных последовательности.

В результате RNN лучше анализирует наборы данных сигнала последовательности, такие как текст или речь, потому что повторение одной и той же задачи помогает алгоритму зафиксировать временную зависимость в наборе данных. Временная зависимость, другими словами, изменяющиеся во времени свойства данных, была упущена при вычислении значений в обычной нейронной сети с прямой связью.

Прежде чем подробно изучить захват временной зависимости, давайте кратко рассмотрим историю RNN. Это не займет много времени и будет освежать!

Когда RNN еще не существовало, люди, должно быть, пытались включить в свои модели изменяющиеся во времени функции. Самый простой способ сделать это — напрямую поместить значения с временной задержкой в ​​набор функций. Эта модель называется нейронными сетями с временной задержкой, сокращенно TDNN.

Допустим, в обычных нейронных сетях с прямой связью x(t) используется для генерации y(t) в качестве результата. Однако в нейронных сетях с временной задержкой не только x(t), но и x(t-1), x(t-2) и т. д., в зависимости от предварительно определенного размера окна, несколько значений функции задержки работать вместе, чтобы получить более подходящий результат y(t). Этот алгоритм был опубликован в 1989 году.

Эта модель имеет явные преимущества и недостатки.

  • Преимущества ) Удалось включить функции предыдущего времени для захвата частей изменяющихся во времени свойств данных.
  • Недостатки ) Возможность зафиксировать только ограниченное количество временных зависимостей в зависимости от размера окна временной задержки

А в 1990-х появился новый интересующий нас алгоритм RNN. Этот алгоритм успешно справился с недостатками TDNN. Однако появилось еще одно препятствие — «исчезающий градиент», указывающий на затухающий вклад градиентов по мере увеличения последовательности. Из-за этого производительность RNN упала меньше, чем мы ожидали, поскольку она не могла правильно обрабатывать длинную последовательность данных.

Потом появился новый алгоритм. Долгосрочная кратковременная память, сокращенно LSTM, является расширением RNN, поскольку она оснащена дополнительными воротами в каждой из ячеек для дальнейшей обработки более длинной последовательности данных.

(Вы можете поискать «LSTM» в Google, и там будет множество фотографий об этих уникальных вычислительных ячейках.)

Благодаря вентилям некоторые сигналы можно сохранить внутри в виде вектора состояния и снова ввести в более позднее время вычислений, чтобы повысить производительность алгоритма.

К настоящему времени мы проверили краткую историю от нейронных сетей с прямой связью до долговременной кратковременной памяти, и подробное объяснение каждого алгоритма будет представлено позже в отдельной публикации.

Итак, хватит закусок, а теперь пора заняться основным блюдом, развернув RNN для анализа текстовых данных и IMDB для анализа настроений.

Здесь мы сосредоточимся на построении самой RNN. В результате производительность в этой части будет несколько посредственной. Тем не менее, более высокая производительность будет достигнута в следующей публикации с небольшими изменениями. Итак, приступим!

  1. Загрузка библиотек, набор данных
import torch
import torch.nn as nn
import torch.optim as optim
from torchtext import data
from torchtext import dataset 
import random
SEED = 1 #any number you favor can be assigned here
torch.manual_seed(SEED)
TEXT = data.Field(tokenize = 'spacy') 
LABEL = data.LabelField(dtype = torch.float)
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)

Импортированные зависимые библиотеки; torch в качестве основного инструмента и torchtext для загрузки набора текстовых данных. Кроме того, мы устанавливаем значение «SEED», чтобы иметь возможность воспроизвести результат и настроить модель для повышения производительности с изменением параметров. И «ТЕКСТ» и «МЕТКА» подготовлены для хранения текста и метки набора данных.

print(len(train_data), len(test_data))

И мы обнаруживаем, что каждый набор данных поезда и теста содержит 25 000 единиц примеров.

Чтобы предотвратить переоснащение наших обучающих данных, всегда полезно разделить проверочный набор, чтобы время от времени проверять производительность нашей модели. Из всего набора обучающих данных мы выберем 20% в качестве проверочного набора и будем использовать только остальные 80% для обучения нашей модели.

train_data, valid_data = train_data.split(random_state = random.seed(SEED), split_ratio = 0.8)

Итак, мы подготовили три разных набора данных: обучающий, проверочный и тестовый. Однако, как вы уже знаете, наши умные машины не могут работать с текстовыми данными. Поэтому мы создадим таблицу, в которую машины смогут обращаться, когда сталкиваются со словом. В таблице каждому слову будут присвоены уникальные значения.

MAX_VOCAB_SIZE = 25000
TEXT.build_vocab(train_data, max_size = MAX_VOCAB_SIZE)
LABEL.build_vocab(train_data)

Мы будем использовать только наиболее часто используемые 25 000 слов. Можно предположить, что, поскольку мы ограничили количество уникальных слов, с которыми имеем дело, размерность вектора one-hot также будет равна 25 000.

BATCH_SIZE = 64
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(train_data, valid_data, test_data), batch_size = BATCH_SIZE, sort_within_batch = True, device = device)

Преимущество использования BuckeIterator заключается в том, что этот итератор пытается сгруппировать текстовые данные на основе их длины. Поэтому, используя это, мы можем ожидать, что мы минимизируем отступы нулями.

Теперь давайте построим нашу модель!

class RNN(nn.Module):
    def __init__(self, input_dim, embedding_dim, hidden_dim, output_dim):
    super().__init__() 
    self.embedding = nn.Embedding(input_dim, embedding_dim)
    self.rnn = nn.RNN(embedding_dim, hidden_dim)
    self.fc = nn.Linear(hidden_dim, output_dim) 
    
    def forward(self, tex):
    embedded = self.embedding(text)
    output, hidden = self.rnn(embedded)
return self.fc(hidden.squeeze(0))

А теперь давайте уточним параметры.

INPUT_DIM = len(TEXT.vocab) 
EMBEDDING_DIM = 128
HIDDEN_DIM = 256 
OUTPUT_DIM = 1

Мы включим каждый из параметров в нашу модель.

model = RNN(INPUT_DIM, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM)

Время выбрать оптимизатор!

optimizer = optim.Adam(model.parameters(), lr = 1e-3)

В качестве функции потерь (критерия) будет использоваться Binary Cross Entropy With Logits. Логиты сжимают результат нашей модели до границы [0,1].

criterion = nn.BCEWithLogitsLoss()

Для обучения модели мы будем использовать следующую функцию. Это будет использоваться для запуска обучающих данных.

def train(model, iterator, optimizer, criterion):
    epoch_loss = 0
    model.train() 
    for batch in iterator:
        optimizer.zero_grad() 
        predictions = model(batch.text).squeeze(1) 
        loss = criterion(predictions, batch.label) 
        loss.backward() 
        optimizer.step() 
        epoch_loss += loss.item()
    return epoch_loss/len(iterator)

Для оценки модели воспользуемся следующей функцией. Однако мы не хотим обновлять наши параметры при оценке производительности, будут некоторые изменения по сравнению с прежней функцией обучения. Эта функция будет использоваться для запуска данных проверки и тестовых данных.

def evaluate(model, iterator, criterion):
    epoch_loss = 0 
    model.eval() 
    with torch.no_grad():
        for batch in iterator:
            predictions = model(batch.text).squeeze(1) 
            loss = criterion(predictions, batch.label) 
            epoch_loss += loss.item() 
        return epoch_loss/len(iterator)

Запустим модель!

N_EPOCHS = 5 
best_valid_loss = float('inf')
for epoch in range(N_EPOCHS):
    train_loss = train(model, train_iterator, optimizer, criterion)
    valid_loss = evaluate(model, valid_iterator, criterion) 
    
    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss 
        torch.save(model.state_dict(), 'best_param.pt')
print("Epoch: ", epoch+1)
    print("Train loss: ", train_loss)
    print("Validatoin loss: ", valid_loss)

Мы будем постоянно проверять результат оценки в наборе данных проверки и сохранять наиболее эффективную комбинацию параметров под именем «best_param.pt». Эта сохраненная модель будет использоваться для окончательного прогнозирования тестового набора данных.

Теперь, последний шаг, делайте прогнозы!

model.load_state_dict(torch.load('best_param.pt'))
test_loss = evaluate(model, test_iterator, criterion)
print('Test loss: ', test_loss)

Мой тестовый проигрыш составил «0,6939689666413895», около 70%. Возможно, не так много, как вы ожидали. Поэтому постараемся обновить эту модель в следующей публикации. До скорой встречи!!