3. Ускоренный анализ настроений
В предыдущей статье мы представили обновленную версию анализа настроений на основе RNN, в этом подразделе мы изучим метод, который не использует RNN: мы реализуем статьюBag of Tricks for Efficient Text Classificationмодель в . Эта простая модель обеспечивает производительность, сравнимую с анализом тональности второй статьи, но обучается намного быстрее.
3.1 Предварительная обработка данных
Самая большая разница между моделью классификации FastText и другими моделями классификации текста заключается в том, что она вычисляет n-грамму входного предложения и использует n-грамму в качестве дополнительной функции для получения информации о локальном порядке слов и добавления ее в конец предложения. список токенизации. Основная идея n-граммы состоит в том, чтобы выполнять операцию скользящего окна размера n над содержимым текста по байтам, формируя последовательность байтовых фрагментов длины n, каждый из которых называется граммом. В частности, здесь мы используем биграммы.
Например, в предложении «как дела?» биграммы: «как дела», «ты» и «ты?».
Функция «generate_bigrams» берет уже помеченное предложение, подсчитывает биграммы и добавляет их в конец токенизированного списка.
def generate_bigrams(x):
n_grams = set(zip(*[x[i:] for i in range(2)]))
for n_gram in n_grams:
x.append(' '.join(n_gram))
return x
пример:
generate_bigrams(['This', 'film', 'is', 'terrible'])
['This', 'film', 'is', 'terrible', 'film is', 'This film', 'is terrible']
TorchText 'Поле' имеетpreprocessing
параметр. Переданная здесь функция будет применена к предложению после его токенизации (из строки в список токенов), но до его оцифровки (из списка токенов в список индексов). мы пройдем здесьgenerate_bigrams
функция.
Поскольку мы не используем RNN, нам не нужно использовать сжатую последовательность заполнения, поэтому нам не нужно устанавливать «include_length=True».
import torch
from torchtext.legacy import data
from torchtext.legacy import datasets
SEED = 1234
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = True
TEXT = data.Field(tokenize = 'spacy',
tokenizer_language = 'en_core_web_sm',
preprocessing = generate_bigrams)
LABEL = data.LabelField(dtype = torch.float)
Как и раньше, загрузите набор данных IMDb и создайте разбиения:
import random
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
train_data, valid_data = train_data.split(random_state = random.seed(SEED))
Создайте словарный запас и загрузите предварительно обученные вложения слов:
MAX_VOCAB_SIZE = 25_000
TEXT.build_vocab(train_data,
max_size = MAX_VOCAB_SIZE,
vectors = "glove.6B.100d",
unk_init = torch.Tensor.normal_)
LABEL.build_vocab(train_data)
Создайте итератор:
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,
device = device)
3.2 Построение модели
FastText — это типичный метод представления векторов слов для глубокого обучения.Слой встраивания сопоставляет слова с плотным пространством, а затем усредняет все слова в предложении в пространстве встраивания для завершения классификации. Поэтому количество параметров в этой модели будет намного меньше по сравнению с моделью из предыдущей главы.
В частности, он сначала вычисляет встраивание каждого слова, используя слой «Внедрение» (синий), затем вычисляет среднее значение всех вложений слов (розовый) и передает его через слой «Линейный» (серебристый).
Мы используем двумерную функцию объединения «avg_pool2d» для усреднения слов в пространстве встраивания. Мы можем думать о встраиваниях слов как о двумерной сетке, в которой слова расположены вдоль одной оси, а размеры вложений слов — вдоль другой оси. Изображение ниже представляет собой пример предложения, преобразованного в 5-мерное вложение слов, со словами по вертикальной оси и вложениями по горизонтальной оси. Каждый элемент в тензоре [4x5] представлен зеленым блоком.
«avg_pool2d» использует фильтр размера «embedded.shape[1]» (т.е. длина предложения), умноженный на 1. На изображении ниже показано розовым цветом.
Мы вычисляем среднее значение всех элементов, покрываемых фильтром, затем сдвигаем фильтр вправо и вычисляем среднее значение следующего столбца вложений для каждого слова в предложении.
Каждая позиция фильтра предоставляет значение, среднее значение всех покрытых элементов. После того, как фильтр охватывает все размеры встраивания, вы получаете тензор [1x5], который затем предсказывается линейным слоем.
import torch.nn as nn
import torch.nn.functional as F
class FastText(nn.Module):
def __init__(self, vocab_size, embedding_dim, output_dim, pad_idx):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)
self.fc = nn.Linear(embedding_dim, output_dim)
def forward(self, text):
#text = [sent len, batch size]
embedded = self.embedding(text)
#embedded = [sent len, batch size, emb dim]
embedded = embedded.permute(1, 0, 2)
#embedded = [batch size, sent len, emb dim]
pooled = F.avg_pool2d(embedded, (embedded.shape[1], 1)).squeeze(1)
#pooled = [batch size, embedding_dim]
return self.fc(pooled)
Как и раньше, создайте экземпляр класса FastText:
INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 100
OUTPUT_DIM = 1
PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token]
model = FastText(INPUT_DIM, EMBEDDING_DIM, OUTPUT_DIM, PAD_IDX)
Глядя на количество параметров в модели, мы видим, что параметр примерно такой же, как у стандартной RNN в разделе 1, и только половина предыдущей модели.
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f'The model has {count_parameters(model):,} trainable parameters')
The model has 2,500,301 trainable parameters
Предварительно обученный вектор копируется в слой внедрения:
pretrained_embeddings = TEXT.vocab.vectors
model.embedding.weight.data.copy_(pretrained_embeddings)
tensor([[-0.1117, -0.4966, 0.1631, ..., 1.2647, -0.2753, -0.1325],
[-0.8555, -0.7208, 1.3755, ..., 0.0825, -1.1314, 0.3997],
[-0.0382, -0.2449, 0.7281, ..., -0.1459, 0.8278, 0.2706],
...,
[-0.1606, -0.7357, 0.5809, ..., 0.8704, -1.5637, -1.5724],
[-1.3126, -1.6717, 0.4203, ..., 0.2348, -0.9110, 1.0914],
[-1.5268, 1.5639, -1.0541, ..., 1.0045, -0.6813, -0.8846]])
Обнулите начальные веса неизвестных токенов и заполненных токенов:
UNK_IDX = TEXT.vocab.stoi[TEXT.unk_token]
model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM)
model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM)
3.3 Обучение модели
Обучение модели точно такое же, как и в предыдущем разделе.
Инициализируйте оптимизатор:
import torch.optim as optim
optimizer = optim.Adam(model.parameters())
Определите критерии и поместите модель и критерии на GPU:
criterion = nn.BCEWithLogitsLoss()
model = model.to(device)
criterion = criterion.to(device)
Расчет функции точности:
def binary_accuracy(preds, y):
"""
Returns accuracy per batch, i.e. if you get 8/10 right, this returns 0.8, NOT 8
"""
#round predictions to the closest integer
rounded_preds = torch.round(torch.sigmoid(preds))
correct = (rounded_preds == y).float() #convert into float for division
acc = correct.sum() / len(correct)
return acc
Определена функция для обучения модели.
Примечание: Поскольку мы не будем использовать отсев, нам не нужно использоватьmodel.train()
, но для того, чтобы поддерживать хорошие привычки кодирования, эта строка кода сохранена здесь.
def train(model, iterator, optimizer, criterion):
epoch_loss = 0
epoch_acc = 0
model.train()
for batch in iterator:
optimizer.zero_grad()
predictions = model(batch.text).squeeze(1)
loss = criterion(predictions, batch.label)
acc = binary_accuracy(predictions, batch.label)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
epoch_acc += acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
Функция определена для проверки обученной модели.
Уведомление: Аналогично, мы также оставляем за собойmodel.eval()
def evaluate(model, iterator, criterion):
epoch_loss = 0
epoch_acc = 0
model.eval()
with torch.no_grad():
for batch in iterator:
predictions = model(batch.text).squeeze(1)
loss = criterion(predictions, batch.label)
acc = binary_accuracy(predictions, batch.label)
epoch_loss += loss.item()
epoch_acc += acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
Сколько времени требуется, чтобы получить эпоху через функцию:
import time
def epoch_time(start_time, end_time):
elapsed_time = end_time - start_time
elapsed_mins = int(elapsed_time / 60)
elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
return elapsed_mins, elapsed_secs
Наконец, обучите нашу модель:
N_EPOCHS = 5
best_valid_loss = float('inf')
for epoch in range(N_EPOCHS):
start_time = time.time()
train_loss, train_acc = train(model, train_iterator, optimizer, criterion)
valid_loss, valid_acc = evaluate(model, valid_iterator, criterion)
end_time = time.time()
epoch_mins, epoch_secs = epoch_time(start_time, end_time)
if valid_loss < best_valid_loss:
best_valid_loss = valid_loss
torch.save(model.state_dict(), 'tut3-model.pt')
print(f'Epoch: {epoch+1:02} | Epoch Time: {epoch_mins}m {epoch_secs}s')
print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%')
print(f'\t Val. Loss: {valid_loss:.3f} | Val. Acc: {valid_acc*100:.2f}%')
Получите точность теста (намного меньше, чем время обучения модели в предыдущем разделе):
model.load_state_dict(torch.load('tut3-model.pt'))
test_loss, test_acc = evaluate(model, test_iterator, criterion)
print(f'Test Loss: {test_loss:.3f} | Test Acc: {test_acc*100:.2f}%')
Test Loss: 0.381 | Test Acc: 85.42%
3.3 Проверка модели
import spacy
nlp = spacy.load('en_core_web_sm')
def predict_sentiment(model, sentence):
model.eval()
tokenized = generate_bigrams([tok.text for tok in nlp.tokenizer(sentence)])
indexed = [TEXT.vocab.stoi[t] for t in tokenized]
tensor = torch.LongTensor(indexed).to(device)
tensor = tensor.unsqueeze(1)
prediction = torch.sigmoid(model(tensor))
return prediction.item()
Примеры негативных отзывов:
predict_sentiment(model, "This film is terrible")
2.1313092350011553e-12
Примеры положительных отзывов:
predict_sentiment(model, "This film is great")
1.0
резюме
В следующем разделе мы будем использовать сверточные нейронные сети (CNN) для анализа настроений.