Реализация выпадения слов в pytorch - PullRequest
0 голосов
/ 04 мая 2018

Я хочу добавить слово dropout в мою сеть, чтобы у меня было достаточно обучающих примеров для обучения встраиванию токена "unk". Насколько я знаю, это стандартная практика. Давайте предположим, что индекс токена unk равен 0, а индекс для заполнения равен 1 (мы можем переключать их, если это более удобно).

Это простая сеть CNN, в которой реализовано выпадение слов так, как я ожидал:

class Classifier(nn.Module):
    def __init__(self, params):
        super(Classifier, self).__init__()
        self.params = params
        self.word_dropout = nn.Dropout(params["word_dropout"])
        self.pad = torch.nn.ConstantPad1d(max(params["window_sizes"])-1, 1)
        self.embedding = nn.Embedding(params["vocab_size"], params["word_dim"], padding_idx=1)
        self.convs = nn.ModuleList([nn.Conv1d(1, params["feature_num"], params["word_dim"] * window_size, stride=params["word_dim"], bias=False) for window_size in params["window_sizes"]])
        self.dropout = nn.Dropout(params["dropout"])
        self.fc = nn.Linear(params["feature_num"] * len(params["window_sizes"]), params["num_classes"])

    def forward(self, x, l):
        x = self.word_dropout(x)
        x = self.pad(x)
        embedded_x = self.embedding(x)
        embedded_x = embedded_x.view(-1, 1, x.size()[1] * self.params["word_dim"]) # [batch_size, 1, seq_len * word_dim]
        features = [F.relu(conv(embedded_x)) for conv in self.convs]
        pooled = [F.max_pool1d(feat, feat.size()[2]).view(-1, params["feature_num"]) for feat in features]
        pooled = torch.cat(pooled, 1)
        pooled = self.dropout(pooled)
        logit = self.fc(pooled)
        return logit

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

Это не работает, потому что для dropout нужны Float Tensors, чтобы он мог их правильно масштабировать, в то время как мой ввод - Long Tensors, которые не нужно масштабировать.

Есть ли простой способ сделать это в pytorch? По сути, я хочу использовать LongTensor-дружественный отсев (бонус: лучше, если он позволит мне указать константу отсева, которая не равна 0, чтобы я мог использовать заполнение нулями).

1 Ответ

0 голосов
/ 04 мая 2018

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

Это будет выглядеть так:

import random

def add_unk(input_token_id, p):
    #random.random() gives you a value between 0 and 1
    #to avoid switching your padding to 0 we add 'input_token_id > 1'
    if random.random() < p and input_token_id > 1:
        return 0
    else:
        return input_token_id

#than you have your input token_id
#for this example I take just a random number, lets say 127
input_token_id = 127

#let p be your probability for UNK
p = 0.01

your_input_tensor = torch.LongTensor([add_unk(input_token_id, p)])

Edit:

Так что мне на ум приходят два варианта, которые на самом деле дружественны к GPU. В целом оба решения должны быть намного эффективнее.

Вариант 1 - Выполнение вычислений непосредственно в forward():

Если вы не используете torch.utils и не планируете использовать его позже, возможно, это и есть путь.

Вместо того, чтобы делать вычисления, прежде чем мы просто сделаем это в методе forward() основного класса PyTorch. Однако я не вижу (простого) способа сделать это в torch 0.3.1., поэтому вам нужно будет обновить его до версии 0.4.0:

Итак, представьте, x - ваш входной вектор:

>>> x = torch.tensor(range(10))
>>> x
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9])

probs - это вектор, содержащий равномерные вероятности отсева, поэтому мы можем позже проверить нашу вероятность отсева:

>>> probs = torch.empty(10).uniform_(0, 1)
>>> probs
tensor([ 0.9793,  0.1742,  0.0904,  0.8735,  0.4774,  0.2329,  0.0074,
         0.5398,  0.4681,  0.5314])

Теперь мы применяем вероятности отсева probs на нашем входе x:

>>> torch.where(probs > 0.2, x, torch.zeros(10, dtype=torch.int64))
tensor([ 0,  0,  0,  3,  4,  5,  0,  7,  8,  9])

Примечание: чтобы увидеть какой-то эффект, я выбрал здесь вероятность выпадения 0,2. На самом деле вы, вероятно, хотите, чтобы он был меньше.

Вы можете выбрать для этого любой токен / идентификатор, который вам нравится, вот пример с 42 в качестве неизвестного идентификатора токена:

>>> unk_token = 42
>>> torch.where(probs > 0.2, x, torch.empty(10, dtype=torch.int64).fill_(unk_token))
tensor([  0,  42,  42,   3,   4,   5,  42,   7,   8,   9])

torch.where поставляется с PyTorch 0.4.0: https://pytorch.org/docs/master/torch.html#torch.where

Я не знаю о формах вашей сети, но ваш forward() должен выглядеть примерно так (при использовании мини-пакетирования вам нужно сгладить ввод перед применением исключения):

def forward_train(self, x, l):
    # probabilities
    probs = torch.empty(x.size(0)).uniform_(0, 1)
    # applying word dropout
    x = torch.where(probs > 0.02, x, torch.zeros(x.size(0), dtype=torch.int64))

    # continue like before ...
    x = self.pad(x)
    embedded_x = self.embedding(x)
    embedded_x = embedded_x.view(-1, 1, x.size()[1] * self.params["word_dim"]) # [batch_size, 1, seq_len * word_dim]
    features = [F.relu(conv(embedded_x)) for conv in self.convs]
    pooled = [F.max_pool1d(feat, feat.size()[2]).view(-1, params["feature_num"]) for feat in features]
    pooled = torch.cat(pooled, 1)
    pooled = self.dropout(pooled)
    logit = self.fc(pooled)
    return logit

Примечание: я назвал функцию forward_train(), поэтому вы должны использовать другой forward() без выпадения для оценки / прогнозирования. Но вы также можете использовать if conditions с train().

Вариант два: использование torch.utils.data.Dataset:

Если вы используете Dataset, предоставленный torch.utils, очень легко эффективно выполнить такую ​​предварительную обработку. Dataset по умолчанию использует сильное ускорение мультиобработки, поэтому приведенный выше пример кода должен быть выполнен в методе __getitem__ вашего класса Dataset.

Это может выглядеть так:

def __getitem__(self, index):
    'Generates one sample of data'
    # Select sample
    ID = self.input_tokens[index]

    # Load data and get label
    # using add ink_unk function from code above
    X = torch.LongTensor(add_unk(ID, p=0.01))
    y = self.targets[index]

    return X, y

Это немного вне контекста и выглядит не очень элегантно, но я думаю, вы поняли идею. Согласно этому сообщению в блоге Шервайн Амиди в Стэнфорде не должно быть проблем с выполнением более сложных этапов предварительной обработки в этой функции:

Поскольку наш код [Dataset предназначен] предназначен для многоядерности, обратите внимание, что вы вместо этого можно выполнять более сложные операции (например, вычисления из источника файлы), не беспокоясь о том, что генерация данных становится узким местом в учебный процесс.

Связанное сообщение в блоге - "Подробный пример того, как генерировать ваши данные параллельно с PyTorch" - также предоставляет хорошее руководство по реализации генерации данных с Dataset и DataLoader.

Полагаю, вы предпочтете первый вариант - только две строки, и он должен быть очень эффективным. :)

Удачи!

...