【问题标题】:Implementing word dropout in pytorch在 pytorch 中实现 word dropout
【发布时间】:2018-10-14 21:36:21
【问题描述】:

我想在我的网络中添加单词 dropout,以便我可以有足够的训练示例来训练“unk”标记的嵌入。据我所知,这是标准做法。假设 unk 标记的索引为 0,填充的索引为 1(如果更方便,我们可以切换它们)。

这是一个简单的 CNN 网络,它以我预期的方式实现 word dropout:

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 需要浮点张量以便它可以正确缩放它们,而我的输入是不需要缩放的长张量。

在 pytorch 中是否有一种简单的方法可以做到这一点?我本质上想使用对 LongTensor 友好的 dropout(奖励:如果它能让我指定一个不为 0 的 dropout 常数更好,这样我就可以使用零填充)。

【问题讨论】:

    标签: pytorch dropout


    【解决方案1】:

    实际上,我会在您的模型之外执行此操作,然后将您的输入转换为 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)])
    

    编辑:

    所以我想到了两个实际上对 GPU 友好的选项。一般来说,这两种解决方案都应该更有效。

    选项一 - 直接在 forward()

    中进行计算

    如果您不使用 torch.utils 并且没有计划以后使用它,那么这可能是您要走的路。

    我们在 PyTorch 主类的 forward() 方法中进行计算,而不是先进行计算。但是我在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])
    

    现在我们在输入x 上应用退出概率probs

    >>> 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 的辍学概率。我现实你可能希望它更小。

    您可以为此选择任何您喜欢的令牌/ID,这是一个以42 作为未知令牌ID 的示例:

    >>> 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.0https://pytorch.org/docs/master/torch.html#torch.where

    我不知道您的网络的形状,但是您的 forward() 应该看起来像这样(使用小批量时,您需要在应用 dropout 之前展平输入):

    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() 而不用 dropout 进行评估/预测。但你也可以使用一些if conditionstrain()

    选项二:使用 torch.utils.data.Dataset

    如果您正在使用torch.utils 提供的Dataset,那么高效地进行这种预处理非常容易。 Dataset 默认使用强大的多处理加速,因此上面的代码示例只需在 Dataset 类的 __getitem__ 方法中执行。

    这可能看起来像这样:

    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
    

    这有点断章取义,看起来不太优雅,但我想你明白了。根据这个blog post of Shervine Amidi at Stanford,在这个函数中做更复杂的预处理步骤应该是没问题的:

    由于我们的代码 [Dataset 是指] 设计为对多核友好,请注意您 可以改为执行更复杂的操作(例如,从源计算 文件),而不必担心数据生成成为瓶颈 训练过程。

    链接的博客文章 - "A detailed example of how to generate your data in parallel with PyTorch" - 还为使用 DatasetDataLoader 实现数据生成提供了很好的指南。

    我猜你会更喜欢选项一 - 只有两行,它应该非常有效。 :)

    祝你好运!

    【讨论】:

    • 谢谢。不过,这个解决方案对我来说是不必要的......由于单词 dropout 需要为每个批次单独计算并且不能重复使用,因此在 GPU 加速的张量上执行 word dropout 更有意义,与这个解决方案效率低下。我相信 Tensorflow 可以做到这一点
    • 你还对这个感兴趣吗?如果是这样,我认为可以为您提供 GPU 友好的解决方案。顺便提一句。 '为每批单独计算并且不能重复使用' - 我不会只计算一次,你应该为每个时期重新计算它,这可以让你更好地利用你的数据。该模型可以在 epoch n+1 中使用 epoch n 中丢弃的单词,当丢弃的单词不同时。
    • 现在我已经实现了预处理解决方案,所以我接受了你的回答,如果你有 GPU 解决方案当然会更好:)
    • 好的 - 我更新了答案,我认为选项一应该是你要找的:)
    猜你喜欢
    • 2019-01-29
    • 2021-10-08
    • 2019-08-05
    • 2020-03-19
    • 2016-10-18
    • 2017-07-18
    • 2021-03-17
    • 1970-01-01
    • 2021-01-09
    相关资源
    最近更新 更多