循环神经网络:参数共享的意义何在?无论如何填充都不能解决问题吗?
Recurrent NNs: what's the point of parameter sharing? Doesn't padding do the trick anyway?
以下是我对 RNN 中参数共享要点的理解:
在常规的前馈神经网络中,每个输入单元都被分配了一个单独的参数,这意味着输入单元(特征)的数量对应于要学习的参数的数量。在加工过程中图像数据,输入单元的数量在所有训练示例中都是相同的(通常是常数像素大小 * 像素大小 * rgb 帧)。
但是,像句子这样的顺序输入数据的长度可能会有很大差异,这意味着参数的数量不会相同,具体取决于处理的是哪个例句。这就是为什么参数共享对于有效处理顺序数据是必要的:它确保模型始终具有相同的输入大小,而不管序列长度如何,因为它是根据从一种状态到另一种状态的转换指定的。因此可以在每个时间步使用具有相同权重(输入到隐藏权重、隐藏到输出权重、隐藏到隐藏权重)的相同转换函数。最大的优点是它允许泛化到训练集中没有出现的序列长度。
我的问题是:
- 如上所述,我对 RNN 的理解是否正确?
- 在我查看 LSTM 的 Keras 实际代码示例中,他们首先将句子填充为等长。这样一来,这不是抹杀了 RNN 中参数共享的全部目的吗?
参数共享
能够有效地处理不同长度的序列并不是参数共享的唯一优势。正如您所说,您可以通过填充来实现。参数共享的主要目的是减少模型必须学习的参数。这就是使用 RNN 的全部目的。
如果您要为每个时间步学习一个不同的网络并将第一个模型的输出提供给第二个等等,您最终会得到一个常规的前馈网络。对于 20 个时间步长,您将有 20 个模型要学习。在卷积网络中,参数由卷积过滤器共享,因为当我们可以假设图片的不同区域(例如简单边缘)中存在相似的有趣模式时。这大大减少了我们必须学习的参数数量。类似地,在序列学习中,我们通常可以假设在不同的时间步长上存在相似的模式。比较 'Yesterday I ate an apple'
和 'I ate an apple yesterday'
。这两句话的意思是一样的,只是 'I ate an apple'
部分出现在不同的时间步长上。通过共享参数,您只需了解一次该部分的含义。否则,您必须针对模型中可能出现的每个时间步学习它。
共享参数有一个缺点。因为我们的模型在每个时间步对输入应用相同的转换,所以它现在必须学习对所有时间步都有意义的转换。因此,它必须记住,哪个词出现在哪个时间步,即 'chocolate milk'
不应导致与 'milk chocolate'
相同的隐藏和记忆状态。但是这个缺点和使用大的前馈网络相比是小的。
填充
关于padding the sequences:主要目的不是直接让模型预测变长的序列。就像你说的,这可以通过使用参数共享来完成。填充用于高效训练 - 特别是在训练期间保持计算图较低。没有填充,我们有两种训练选择:
- 我们为每个训练样本展开模型。因此,当我们有一个长度为 7 的序列时,我们将模型展开为 7 个时间步长,输入序列,通过 7 个时间步长进行反向传播并更新参数。这在理论上似乎很直观。但实际上,这是低效的,因为 TensorFlow 的计算图不允许循环,它们是前馈的。
- 另一种选择是在开始训练之前创建计算图。我们让它们共享相同的权重,并为训练数据中的每个序列长度创建一个计算图。但是当我们的数据集有 30 个不同的序列长度时,这意味着在训练期间有 30 个不同的图,所以对于大型模型,这是不可行的。
这就是我们需要填充的原因。我们将所有序列填充到相同的长度,然后只需要在开始训练之前构建一个计算图。当你有很短和很长的序列长度(例如 5 和 100)时,你可以使用 bucketing and padding。这意味着,您将序列填充到不同的桶长度,例如 [5, 20, 50, 100]。然后,您为每个存储桶创建一个计算图。这样做的好处是,您不必填充长度为 5 到 100 的序列,因为您会在 "learning" 那里的 95 个填充标记上浪费大量时间。
以下是我对 RNN 中参数共享要点的理解:
在常规的前馈神经网络中,每个输入单元都被分配了一个单独的参数,这意味着输入单元(特征)的数量对应于要学习的参数的数量。在加工过程中图像数据,输入单元的数量在所有训练示例中都是相同的(通常是常数像素大小 * 像素大小 * rgb 帧)。
但是,像句子这样的顺序输入数据的长度可能会有很大差异,这意味着参数的数量不会相同,具体取决于处理的是哪个例句。这就是为什么参数共享对于有效处理顺序数据是必要的:它确保模型始终具有相同的输入大小,而不管序列长度如何,因为它是根据从一种状态到另一种状态的转换指定的。因此可以在每个时间步使用具有相同权重(输入到隐藏权重、隐藏到输出权重、隐藏到隐藏权重)的相同转换函数。最大的优点是它允许泛化到训练集中没有出现的序列长度。
我的问题是:
- 如上所述,我对 RNN 的理解是否正确?
- 在我查看 LSTM 的 Keras 实际代码示例中,他们首先将句子填充为等长。这样一来,这不是抹杀了 RNN 中参数共享的全部目的吗?
参数共享
能够有效地处理不同长度的序列并不是参数共享的唯一优势。正如您所说,您可以通过填充来实现。参数共享的主要目的是减少模型必须学习的参数。这就是使用 RNN 的全部目的。
如果您要为每个时间步学习一个不同的网络并将第一个模型的输出提供给第二个等等,您最终会得到一个常规的前馈网络。对于 20 个时间步长,您将有 20 个模型要学习。在卷积网络中,参数由卷积过滤器共享,因为当我们可以假设图片的不同区域(例如简单边缘)中存在相似的有趣模式时。这大大减少了我们必须学习的参数数量。类似地,在序列学习中,我们通常可以假设在不同的时间步长上存在相似的模式。比较 'Yesterday I ate an apple'
和 'I ate an apple yesterday'
。这两句话的意思是一样的,只是 'I ate an apple'
部分出现在不同的时间步长上。通过共享参数,您只需了解一次该部分的含义。否则,您必须针对模型中可能出现的每个时间步学习它。
共享参数有一个缺点。因为我们的模型在每个时间步对输入应用相同的转换,所以它现在必须学习对所有时间步都有意义的转换。因此,它必须记住,哪个词出现在哪个时间步,即 'chocolate milk'
不应导致与 'milk chocolate'
相同的隐藏和记忆状态。但是这个缺点和使用大的前馈网络相比是小的。
填充
关于padding the sequences:主要目的不是直接让模型预测变长的序列。就像你说的,这可以通过使用参数共享来完成。填充用于高效训练 - 特别是在训练期间保持计算图较低。没有填充,我们有两种训练选择:
- 我们为每个训练样本展开模型。因此,当我们有一个长度为 7 的序列时,我们将模型展开为 7 个时间步长,输入序列,通过 7 个时间步长进行反向传播并更新参数。这在理论上似乎很直观。但实际上,这是低效的,因为 TensorFlow 的计算图不允许循环,它们是前馈的。
- 另一种选择是在开始训练之前创建计算图。我们让它们共享相同的权重,并为训练数据中的每个序列长度创建一个计算图。但是当我们的数据集有 30 个不同的序列长度时,这意味着在训练期间有 30 个不同的图,所以对于大型模型,这是不可行的。
这就是我们需要填充的原因。我们将所有序列填充到相同的长度,然后只需要在开始训练之前构建一个计算图。当你有很短和很长的序列长度(例如 5 和 100)时,你可以使用 bucketing and padding。这意味着,您将序列填充到不同的桶长度,例如 [5, 20, 50, 100]。然后,您为每个存储桶创建一个计算图。这样做的好处是,您不必填充长度为 5 到 100 的序列,因为您会在 "learning" 那里的 95 个填充标记上浪费大量时间。