如何使用双向RNN和pytorch填补空白?

时间:2019-01-23 09:00:42

标签: python nlp pytorch

我正在尝试使用双向RNN和pytorch填补空白。

输入将类似于:The dog is _____, but we are happy he is okay.

输出将类似于:

1. hyper (Perplexity score here) 
2. sad (Perplexity score here) 
3. scared (Perplexity score here)

我在这里发现了这个主意:https://medium.com/@plusepsilon/the-bidirectional-language-model-1f3961d1fb27

import torch, torch.nn as nn
from torch.autograd import Variable

text = ['BOS', 'How', 'are', 'you', 'EOS']
seq_len = len(text)
batch_size = 1
embedding_size = 1
hidden_size = 1
output_size = 1

random_input = Variable(
    torch.FloatTensor(seq_len, batch_size, embedding_size).normal_(), requires_grad=False)

bi_rnn = torch.nn.RNN(
    input_size=embedding_size, hidden_size=hidden_size, num_layers=1, batch_first=False, bidirectional=True)

bi_output, bi_hidden = bi_rnn(random_input)

# stagger
forward_output, backward_output = bi_output[:-2, :, :hidden_size], bi_output[2:, :, hidden_size:]
staggered_output = torch.cat((forward_output, backward_output), dim=-1)

linear = nn.Linear(hidden_size * 2, output_size)

# only predict on words
labels = random_input[1:-1]

# for language models, use cross-entropy :)
loss = nn.MSELoss()
output = loss(linear(staggered_output), labels)

我正在尝试重新实现博客文章底部的上述代码。我是pytorch和nlp的新手,无法理解代码的输入和输出是什么。

有关输入的问题:我猜输入就是给出的几个单词。在这种情况下,为什么需要一个句子的开头和结尾?为什么我看不到输入像其他经典的NLP问题一样是训练模型的语料库?我想使用Enron电子邮件语料库来训练RNN。

有关输出的问题:我看到输出是张量。我的理解是张量是一个向量,因此在这种情况下可能是一个词向量。您如何使用张量输出单词本身?

1 个答案:

答案 0 :(得分:4)

由于这个问题是开放式的,因此我将从最后一部分开始,转向标题中提出的主要问题的更一般的答案。

快速注释::正如@Qusai Alothman的评论中所指出的那样,您应该找到关于该主题的更好的资源,当涉及必要的信息时,这一资源很少。

附加说明:上一节中描述的过程的完整代码将花费太多空间来提供确切的答案,这将更多地是博客发布。我将着重介绍我们应该采取的可能步骤,以建立一个具有有用链接的网络。

最后的提示:如果下面有什么蠢话(或者您想以任何方式或形式扩展答案,请通过在下面发表评论来纠正我/添加信息)。

有关输入的问题

此处的输入是根据随机正态分布生成的,与实际单词没有任何关联。应该代表word embeddings,例如将单词表示为带有语义(这很重要!)含义的数字(有时还取决于上下文(请参见当前的最新技术方法之一,例如BERT))。

输入的形状

在您的示例中,它提供为:

seq_len, batch_size, embedding_size

其中

  • seq_len-表示单个句子的长度(因您 数据集),我们稍后再讨论。
  • batch_size-多少个句子 应该在forward通过的一步中处理(如果 PyTorch是从继承的类的前进方法 torch.nn.Module
  • embedding_size-代表一个单词的向量(它 范围从通常使用100/300的{​​{1}}到word2vec或 因此,使用提及的 BERT 之类的最新方法 以上)

在这种情况下,所有代码都是硬编码的,对于新手并没有太大用,只是以这种方式概述了这个想法。

在这种情况下,为什么需要一个句子的开头和结尾?

如果我错了,请更正我,但如果您将输入分为句子,则不需要。如果您向模型提供多个句子,并希望明确指出每个句子的开头和结尾(用于依赖于前一/下一句的模型,似乎并非如此)。这些是由特殊标记(整个语料库中不存在的特殊标记)编码的,因此神经网络“可以学习”它们表示句子的结尾和开头(此方法只需一个特殊标记即可)。

如果您要使用严肃的数据集,我建议您使用spaCynltk之类的库来分割文本(第一个是使用IMO的荣幸),它们确实做得很好为此任务。

您的数据集可能已经被拆分为句子,在这种情况下,您已经准备好了。

为什么我看不到输入是像其他经典的NLP问题一样训练模型的语料库?

我不记得正在像 那样在语料库上训练过的模型,例如使用字符串。通常,这些浮点数使用以下方式表示:

  • 简单的方法,例如Bag Of WordsTF-IDF
  • 更复杂的语言,提供有关单词的一些信息 关系(例如4096在语义上与king相关 而不是queen)。这些已经在上面链接了,有些 其他值得注意的可能是 GloVeELMo和大量其他广告素材 方法。

关于输出的问题

应该将索引输出到embeddings 中,这些索引又对应于由矢量表示的单词(上述更为复杂的方法)。

此类嵌入中的每一行代表一个唯一的词,并且其相应的列都是它们的唯一表示(在PyTorch中,第一个索引可能会保留未知表示的词[如果使用预训练的嵌入],您也可以删除那些单词,或将它们表示为句子/文档的平均值,还有其他一些可行的方法。

示例中提供的损失

banana

此任务没有任何意义,因为Mean Squared Error是一种回归指标,而不是分类指标。

我们要使用一个进行分类,因此softmax应该用于多类情况(我们应该输出跨越# for language models, use cross-entropy :) loss = nn.MSELoss() 的数字,其中[0, N]是我们中唯一词的数量语料库。

PyTorch的{​​{3}}已经进行了logit(最后一层的输出,没有激活,如softmax),并为每个示例返回损失值。我会建议这种方法,因为它在数值上是稳定的(我喜欢它是最小的方法)。

我正在尝试使用双向RNN和pytorch填充空白

这是一个漫长的过程,我将只重点介绍我将要执行的步骤,以创建一个模型,该模型的想法代表了本文中概述的想法。

数据集的基本准备

您可以使用上面提到的内容,也可以从 scikit-learn 中的CrossEntropyLoss开始。

第一步应该大致是这样:

  • 从数据集中删除元数据(如果有的话)(可能是HTML标记,某些标头等)
  • 使用预制库(如上所述)将您的文本分成句子

接下来,您想在每个句子中创建目标(例如要填充的单词)。 每个单词都应替换为特殊标记(例如N)并移至目标位置。

示例:

  • 句子:<target-token>

将为我们提供以下句子及其各自的目标:

  • 句子:Neural networks can do some stuff.目标:<target-token> networks can do some stuff.
  • 句子:Neural目标:Neural <target-token> can do some stuff.
  • 句子:networks目标:Neural networks <target-token> do some stuff.
  • 句子:can目标:Neural networks can <target-token> some stuff.
  • 句子:do目标:Neural networks can do <target-token> stuff.
  • 句子:some目标:Neural networks can do some <target-token>.
  • 句子:some目标:Neural networks can do some stuff <target-token>

您应该通过纠正错别字来调整这种方法以解决手头的问题,如果有错别字,请分词,去词迹化以及其他,请尝试!

嵌入

每个句子中的每个单词都应替换为一个整数,该整数又指向其嵌入。

我建议您使用经过预先培训的产品。 spaCy提供了单词向量,但是我强烈推荐的另一种有趣的方法是在开源库20 newsgroups中。

您可以自己训练,但无监督训练将花费大量时间+大量数据,我认为这超出了此问题的范围。

数据批处理

应该使用PyTorch的{​​{3}}和flair

在我的情况下,一个好主意是为.提供自定义collate_fn,它负责创建填充的数据批次(或已经表示为torch.utils.data.Dataset)。

重要提示::当前,您必须按长度(按字排列)对批次进行排序,并保持能够将批次“取消排序”为原始形式的索引,您应该记住在实现过程中。您可以将DataLoader用于该任务。在将来的PyTorch版本中,有机会可能不必这样做,请参见torch.utils.data.DataLoader

哦,记得在我们使用torch.sort时对数据集进行混洗。

型号

您应该通过继承DataLoader来创建合适的模型。我建议您创建一个更通用的模型,在其中可以提供PyTorch的单元(如GRU,LSTM或RNN),多层和双向(如后所述)。

关于模型构建的一些事情:

torch.nn.Module

如您所见,有关形状的信息可以以常规方式获得。这种方法将使您可以创建一个想要多少层的模型,是否需要双向(import torch class Filler(torch.nn.Module): def __init__(self, cell, embedding_words_count: int): self.cell = cell # We want to output vector of N self.linear = torch.nn.Linear(self.cell.hidden_size, embedding_words_count) def forward(self, batch): # Assuming batch was properly prepared before passing into the network output, _ = self.cell(batch) # Batch shape[0] is the length of longest already padded sequence # Batch shape[1] is the length of batch, e.g. 32 # Here we create a view, which allows us to concatenate bidirectional layers in general manner output = output.view( batch.shape[0], batch.shape[1], 2 if self.cell.bidirectional else 1, self.cell.hidden_size, ) # Here outputs of bidirectional RNNs are summed, you may concatenate it # It makes up for an easier implementation, and is another often used approach summed_bidirectional_output = output.sum(dim=2) # Linear layer needs batch first, we have to permute it. # You may also try with batch_first=True in self.cell and prepare your batch that way # In such case no need to permute dimensions linear_input = summed_bidirectional_output.permute(1, 0, 2) return self.linear(embedding_words_count) 参数是有问题的,但是您也可以以一般的方式解决它,为了更好的清晰度而将其省略了),请参见下方:

batch_first

您可以将model = Filler( torch.nn.GRU( # Size of your embeddings, for BERT it could be 4096, for spaCy's word2vec 300 input_size=300, hidden_size=100, num_layers=3, batch_first=False, dropout=0.4, bidirectional=True, ), # How many unique words are there in your dataset embedding_words_count=10000, ) 传递到模型中(如果已经过训练并且已经填充),可以从numpy矩阵或大量其他方法中创建模型,高度依赖于代码的精确结构方式。不过,请使您的代码更通用,不要硬编码形状,除非完全必要(通常​​不是)。

请记住,这只是一个展示柜,您必须自己进行调整和修复。 此实现返回logits,并且不使用任何torch.nn.Embedding层。如果要计算困惑度,则可能必须添加它,以便在所有可能的向量上获得正确的概率分布。

顺便说一句:torch.nn.utils.rnn.PackedSequence是有关RNN双向输出串联的一些信息。

模型训练

我会强烈推荐 this issue,因为它非常可定制,您可以使用它记录很多信息,执行验证并抽象混乱的部分,例如训练中的循环。

哦,将您的模型,训练和其他分解为单独的模块,不要将所有内容都放入一个不可读的文件中。

最后的笔记

这是我如何解决此问题的概述,尽管您不应该从此开始,但使用注意力网络而不是仅使用本示例中的最后一个输出层可能会带来更多乐趣。

并且请检查PyTorch的1.0文档,并且不要盲目跟随您在网上看到的教程或博客文章,因为它们可能真的过时了,并且代码质量差异很大。例如,Here已被弃用,如链接所示。