参数共享
能够有效地处理不同长度的序列并不是参数共享的唯一优势。正如你所说,你可以通过填充实现。参数共享的主要目的是减少模型必须学习的参数。这是使用RNN的全部目的。
如果您要为每个时间步学习不同的网络,并将第一个模型的输出提供给第二个模型等,您最终将得到一个常规的前馈网络。对于20个时间步骤,您需要学习20个模型。在卷积网络中,参数由卷积滤波器共享,因为当我们可以假设在图片的不同区域存在相似的有趣图案时(例如简单的边缘)。这大大减少了我们必须学习的参数数量。类似地,在序列学习中,我们经常可以假设在不同的时间步骤有相似的模式。比较'Yesterday I ate an apple'
和'I ate an apple yesterday'
。这两句话意思相同,但部分发生在不同的时间步骤。通过共享参数,您只需要了解该部分的含义。否则,您必须在每个时间步骤学习它,它可能发生在您的模型中。
共享参数有一个缺点。因为我们的模型在每个时间步骤对输入应用相同的转换,所以现在必须学习一个对所有时间步骤都有意义的转换。所以,必须记住,哪个单词出现在哪个时间步,即'chocolate milk'
不应该导致与'milk chocolate'
相同的隐藏和存储状态。但与使用大型前馈网络相比,这个缺点很小。
填充
作为填充序列:主要目的是不直接让模型预测不同长度的序列。就像你说的那样,这可以通过使用参数共享来完成。填充用于有效的训练 - 特别是在训练时保持低的计算图。没有填充,我们有两种训练选项:
- 我们展开每个训练样本的模型。因此,当我们有一个长度为7的序列时,我们将模型展开为7个时间步长,馈送序列,通过7个时间步长进行反向传播并更新参数。理论上这看起来很直观。但实际上,这是非常低效的。当使用TensorFlow时,您会在每个训练步骤创建一个新的计算图,因为TensorFlows计算图不允许重复性,它们是前馈的。
- 另一种选择是在开始训练前创建计算图。我们让他们分享相同的权重,并为我们的训练数据中的每个序列长度创建一个计算图。但是当我们的数据集有30个不同的序列长度时,这意味着在训练期间有30个不同的图形,所以对于大型模型,这是不可行的。
这就是为什么我们需要填充。我们将所有序列填充到相同长度,然后只需在开始训练之前构造一个计算图。如果序列长度非常短且很长(例如5和100),则可以使用bucketing and padding。这意味着,您将序列填充到不同的桶长度,例如[5,20,50,100]。然后,为每个存储桶创建一个计算图。这样做的好处是,你不必填充长度为5到100的序列,因为你会浪费大量时间在那里“学习”95个填充令牌。
参见https://stats.stackexchange.com/q/221513/130598 – Maxim
谢谢,很好的提示! –