LSTM в PyTorch классифицирующих имен - PullRequest
2 голосов
/ 28 марта 2020

Я пытаюсь использовать пример, представленный в https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html, но вместо RNN я использую модель LSTM. Набор данных состоит из разных имен (разных размеров) и соответствующего им языка (общее количество языков - 18), и цель состоит в том, чтобы обучить модель, которая дает определенное имя, выводит язык, которому она принадлежит.

Мои проблемы сейчас:

  • Как обращаться с именами переменных размеров, например, Гектором и Кимом, в LSTM
  • Целое имя (последовательность символов) обрабатывается каждый раз в LSTM выходные данные функции softmax имеют форму (#characters of name, #target classes), но я хотел бы просто получить (1,#target of classes), чтобы определить каждое имя, которому соответствует класс. Я пытался просто получить последнюю строку, но результаты очень плохие.
class LSTM(nn.Module):

    def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size):
        super(LSTM, self).__init__()
        self.hidden_dim = hidden_dim

        self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)

        # The LSTM takes word embeddings as inputs, and outputs hidden states
        # with dimensionality hidden_dim.
        self.lstm = nn.LSTM(embedding_dim, hidden_dim)

        # The linear layer that maps from hidden state space to tag space
        self.hidden2tag = nn.Linear(hidden_dim, tagset_size)
        self.softmax = nn.LogSoftmax(dim = 1)


    def forward(self, word):
        embeds = self.word_embeddings(word)
        lstm_out, _ = self.lstm(embeds.view(len(word), 1, -1))
        tag_space = self.hidden2tag(lstm_out.view(len(word), -1))
        tag_scores = self.softmax(tag_space)
        return tag_scores

    def initHidden(self):
        return Variable(torch.zeros(1, self.hidden_dim))
    lstm = LSTM(n_embedding_dim,n_hidden,n_characters,n_categories)
    optimizer = torch.optim.SGD(lstm.parameters(), lr=learning_rate)
    criterion = nn.NLLLoss()
    def train(category_tensor, line_tensor):
        # i.e. line_tensor = tensor([37,  4, 14, 13, 19,  0, 17,  0, 10,  8, 18]) and category_tensor = tensor([11])
        optimizer.zero_grad()
        output = lstm(line_tensor)

        loss = criterion(output[-1:], category_tensor) # VERY BAD
        loss.backward()

        optimizer.step()

        return output, loss.data.item()

Где line_tensor имеет переменный размер (в зависимости от размера каждого имени) и отображение между символом и его индексом в словаре

Ответы [ 2 ]

2 голосов
/ 28 марта 2020

Давайте рассмотрим решение шаг за шагом

Обрамление проблемы

Учитывая вашу формулировку проблемы, вам придется использовать LSTM для классификации, а не обычное использование тегов. LSTM развернут для определенного временного шага, и это является причиной того, что входные и выходные значения периодических моделей равны

  • Вход: batch size X time steps X input size
  • Выход: batch size X time steps X hidden size

Теперь, так как вы хотите использовать его для классификации, у вас есть два варианта:

  1. Поместите плотный слой поверх результатов всех временных шагов / развертываний [Мой пример ниже использует это]
  2. Игнорировать все выходные данные временного шага, кроме последнего, и поместить последний слой поверх последнего временного шага

Таким образом, входными данными для нашей модели LSTM являются имена, поданные как один символ на временной шаг LSTM и вывод будет классом, соответствующим его языку.

Как обрабатывать ввод / имена переменной длины

Здесь у нас снова два варианта.

  1. Пакетные имена одинаковой длины вместе. Это называется группированием
  2. Фиксировать максимальную длину на основе среднего размера имен, которые у вас есть. Дополните меньшие имена и отрежьте более длинные имена [В моем примере ниже используется максимальная длина 10]

Нужен ли нам слой Embedding?

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

Код модели LSTM игрушечного персонажа

import numpy as np
import torch
import torch.nn as nn

# Model architecture 
class Recurrent_Model(nn.Module):
    def __init__(self, output_size, time_steps=10):
        super(Recurrent_Model, self).__init__()
        self.time_steps = time_steps
        self.lstm = nn.LSTM(1,32, bidirectional=True, num_layers=2)
        self.linear = nn.Linear(32*2*time_steps, output_size)

    def forward(self, x):        
        lstm_out, _ = self.lstm(x)
        return self.linear(lstm_out.view(-1,32*2*self.time_steps))

# Sample input and output
names = ['apple', 'dog', 'donkey', "elephant", "hippopotamus"]
lang = [0,1,2,1,0]

def pad_sequence(name, max_len=10):
    x = np.zeros((len(name), max_len))
    for i, name in enumerate(names):
        for j, c in enumerate(name):
            if j >= max_len:
                break
            x[i,j] = ord(c)
    return torch.FloatTensor(x)

x = pad_sequence(names)
x = torch.unsqueeze(x, dim=2)
y = torch.LongTensor(lang)

model = Recurrent_Model(3)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), 0.01)

for epoch in range(500):
    model.train()
    output = model(x)
    loss = criterion(output, y)
    print (f"Train Loss: {loss.item()}")
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

Примечание

  1. Все тензоры загружаются в память, поэтому, если у вас огромный набор данных, вам придется использовать набор данных и загрузчик данных, чтобы избежать ошибки OOM.
  2. Вам нужно будет разбить данные на тесты по поездам и проверить их на наборе тестовых данных (стандартный материал построения модели)
  3. Вам придется нормализовать входные тензоры перед передачей его модели (опять же стандартная модель построения)

Наконец

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

0 голосов
/ 28 марта 2020

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

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

Встраивание очень важно для моделей НЛП, и, как вы видите, слой LSTM ожидает, что вы дадите ему полученные размеры вложения.

    self.embedding = nn.Embedding(vocab_size, embedding_dim)

vocab_size = количество уникальных имен в наборе данных.

embedding_dim = соответствующий номер. Экспериментируйте с разными размерами, что дает лучшие результаты? 5? 512?

    self.lstm = nn.LSTM(embedding_dim, 
                       hidden_dim)
...