时间反向传播简明介绍

时间反向传播(Backpropagation Through Time,简称 BPTT)是用于更新循环神经网络(如 LSTM)权重的训练算法。

为了有效地为循环神经网络构建序列预测问题,您必须对时间反向传播的工作原理以及可配置变体(如截断时间反向传播)将如何影响网络训练时的技能、稳定性和速度有一个深刻的理解。在这篇文章中,您将获得针对实践者的时间反向传播的温和介绍(无公式!)。

在这篇文章中,您将获得针对实践者的时间反向传播的温和介绍(无公式!)。

阅读本文后,你将了解:

  • 什么是时间反向传播,以及它与多层感知器网络使用的反向传播训练算法的关系。
  • 导致需要截断时间反向传播的动机,这是深度学习中用于训练 LSTM 最广泛使用的变体。
  • 一种用于思考如何配置截断时间反向传播的表示法,以及研究和深度学习库中使用的典型配置。

通过我的新书《使用 Python 的长短期记忆网络》启动您的项目,包括分步教程和所有示例的 Python 源代码文件。

让我们开始吧。

A Gentle Introduction to Backpropagation Through Time

时间反向传播简明介绍
摄影:Jocelyn Kinghorn,保留部分权利。

反向传播训练算法

反向传播指两件事

  • 用于计算导数的数学方法和导数链式法则的应用。
  • 用于更新网络权重以最小化误差的训练算法。

我们这里使用的是对反向传播的后一种理解。

反向传播训练算法的目标是修改神经网络的权重,以最小化网络输出与相应输入预期输出之间的误差。

它是一种监督学习算法,允许根据所犯的具体错误对网络进行纠正。

通用算法如下

  1. 呈现一个训练输入模式并将其传播通过网络以获得输出。
  2. 比较预测输出与预期输出并计算误差。
  3. 计算误差相对于网络权重的导数。
  4. 调整权重以最小化误差。
  5. 重复。

有关反向传播的更多信息,请参阅文章

反向传播训练算法适用于训练固定大小输入-输出对的前馈神经网络,但对于可能按时间顺序排列的序列数据呢?

需要 LSTM 帮助进行序列预测吗?

参加我的免费7天电子邮件课程,了解6种不同的LSTM架构(附代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

时间反向传播

时间反向传播(Backpropagation Through Time,简称 BPTT)是将反向传播训练算法应用于循环神经网络,以处理时间序列等序列数据。

循环神经网络在每个时间步显示一个输入并预测一个输出。

概念上,BPTT 通过展开所有输入时间步工作。每个时间步都有一个输入时间步、一个网络副本和一个输出。然后计算并累积每个时间步的误差。网络被重新折叠并更新权重。

在空间上,展开的循环神经网络的每个时间步都可以看作是一个附加层,考虑到问题的顺序依赖性以及前一个时间步的内部状态被作为后续时间步的输入。

我们可以将算法总结如下

  1. 向网络呈现一系列输入和输出对的时间步。
  2. 展开网络,然后计算并累积每个时间步的误差。
  3. 折叠网络并更新权重。
  4. 重复。

随着时间步数量的增加,BPTT 的计算成本可能很高。

如果输入序列包含数千个时间步,那么这将是单个更新权重更新所需的导数数量。这可能导致权重消失或爆炸(趋于零或溢出),并使学习缓慢和模型技能嘈杂。

截断时间反向传播

截断时间反向传播(Truncated Backpropagation Through Time,简称 TBPTT)是 BPTT 训练算法的修改版本,用于循环神经网络。在这种方法中,序列一次处理一个时间步,并定期(k1 个时间步)执行 BPTT 更新,并回溯固定的时间步数(k2 个时间步)。

Ilya Sutskever 在他的博士论文中对此作了明确阐述:

截断反向传播可以说是训练 RNN 最实用的方法。

BPTT 的主要问题之一是单个参数更新的成本很高,这使得无法使用大量的迭代。

可以通过一种简单的方法来降低成本,该方法将 1,000 个长度的序列分成 50 个序列(例如),每个序列长度为 20,并将每个长度为 20 的序列视为单独的训练案例。这是一种明智的方法,在实践中可以很好地工作,但它对跨越超过 20 个时间步的时间依赖性是盲目的。

截断 BPTT 是一种密切相关的方法。它一次处理一个时间步的序列,并且每 k1 个时间步,它都会运行 k2 个时间步的 BPTT,因此如果 k2 较小,参数更新可能会很便宜。因此,其隐藏状态已经暴露于许多时间步,因此可能包含有关遥远过去的有用信息,这些信息将被机会性地利用。

— Ilya Sutskever,《训练循环神经网络》,博士论文,2013 年

我们可以将算法总结如下

  1. 向网络呈现 k1 个时间步的输入和输出对序列。
  2. 展开网络,然后计算并累积 k2 个时间步的误差。
  3. 折叠网络并更新权重。
  4. 重复

TBPTT 算法需要考虑两个参数

  • k1:两次更新之间的前向传播时间步数。通常,这会影响训练的快慢,因为它决定了权重更新的频率。
  • k2:应用 BPTT 的时间步数。通常,它应该足够大以捕获问题中的时间结构,供网络学习。过大的值会导致梯度消失。

为了更清楚地说明这一点

……可以使用有限历史近似,其中相关信息保存固定数量 h 的时间步,任何比这更旧的信息都会被遗忘。通常,这应该被视为简化计算的启发式技术,尽管如下所述,它有时可以作为真实梯度的充分近似,并且在权重随着网络运行而调整的情况下也可能更合适。我们称此算法为截断时间反向传播。其中 h 表示保存的先前时间步数,此算法将表示为 BPTT(h)。

请注意,在 BPTT(h) 中,每次网络运行一个额外的时间步时,都会对最近的 h 个时间步执行新的反向传播。为了推广这一点,可以考虑让网络运行 h0 个额外的时间步,然后再执行下一个 BPTT 计算,其中 h0 <= h。

此算法的关键特征是直到时间步 t + h0 之后才执行下一次反向传播;在此期间,网络输入、网络状态和目标值的历史记录保存在历史缓冲区中,但不对这些数据进行处理。我们称此算法为 BPTT(h; h0)。显然,BPTT(h) 与 BPTT(h; 1) 相同,BPTT(h; h) 是分批次的 BPTT 算法。

— Ronald J. Williams 和 Jing Peng,《一种用于循环网络轨迹在线训练的高效基于梯度的算法》,1990 年

我们可以借鉴 Williams 和 Peng 的符号,将不同的 TBPTT 配置称为 TBPTT(k1, k2)。

使用此表示法,我们可以定义一些标准或常用方法

注意,这里的 n 指的是序列中的总时间步数

  • TBPTT(n,n):在序列结束时对序列中的所有时间步执行更新(例如经典 BPTT)。
  • TBPTT(1,n):时间步逐一处理,然后执行覆盖到目前为止所有时间步的更新(例如 Williams 和 Peng 的经典 TBPTT)。
  • TBPTT(k1,1):网络可能没有足够的时间上下文来学习,严重依赖内部状态和输入。
  • TBPTT(k1,k2),其中 k1<k2<n:每个序列执行多次更新,可以加速训练。
  • TBPTT(k1,k2),其中 k1=k2:一种常见配置,其中固定数量的时间步用于前向和后向传播(例如 10 到 100)。

我们可以看到,所有配置都是 TBPTT(n,n) 的变体,它们本质上试图以更快的训练速度和更稳定的结果来近似相同的效果。

论文中报告的典型 TBPTT 可以认为是 TBPTT(k1,k2),其中 k1=k2=h 且 h<=n,并且所选参数较小(几十到几百个时间步)。

在 TensorFlow 和 Keras 等库中,情况类似,h 定义了准备好的数据时间步的向量化固定长度。

进一步阅读

本节提供了一些进一步阅读的资源。

书籍

论文

文章

总结

在这篇文章中,您发现了用于训练循环神经网络的时间反向传播。

具体来说,你学到了:

  • 什么是时间反向传播,以及它与多层感知器网络使用的反向传播训练算法的关系。
  • 导致需要截断时间反向传播的动机,这是深度学习中用于训练 LSTM 最广泛使用的变体。
  • 一种用于思考如何配置截断时间反向传播的表示法,以及研究和深度学习库中使用的典型配置。

您对时间反向传播有任何疑问吗?
在下面的评论中提出你的问题,我会尽力回答。

立即开发用于序列预测的 LSTM!

Long Short-Term Memory Networks with Python

在几分钟内开发您自己的 LSTM 模型。

...只需几行python代码

在我的新电子书中探索如何实现
使用 Python 构建长短期记忆网络

它提供关于以下主题的自学教程
CNN LSTM、编码器-解码器 LSTM、生成模型、数据准备、进行预测等等...

最终将 LSTM 循环神经网络引入。
您的序列预测项目。

跳过学术理论。只看结果。

查看内容

对《时间反向传播温和介绍》的 37 条回复

  1. Sam Taha 2017 年 6 月 23 日 上午 5:28 #

    Keras/Tensorflow 支持 TBPTT 吗?

    • Jason Brownlee 2017 年 6 月 23 日 上午 6:44 #

      是的,但它不是很灵活。

      您必须将序列拆分为子序列才能实现“截断”BPTT,并且您受限于 BPTT(k1,k2),其中 k1=k2。

      • Pranav Goel 2017 年 6 月 27 日 上午 2:34 #

        你好 Jason,

        这篇文章写得非常好,我对 TBPTT 的概念感到非常清楚。但是,我在 Keras 中实现时有点困惑。我知道我们受限于 BPTT(k1,k1),但即便如此,有没有一个参数可以设置以固定展开的时间步数 (h)?

        在 Tensorflow 中——“num_steps”选项基本上是 k1=k2 的 BPTT(k1,k2) 吗?你知道它在 Keras 中的等效项吗?

        此外,对于 Theano,我们有 truncate_gradient 选项——Keras 中是否有等效选项?

        我们是否必须在任何情况下都拆分为子序列?
        谢谢!

        • Jason Brownlee 2017 年 6 月 27 日 上午 8:34 #

          据我所知,没有。

          您必须通过每个样本中指定的时间步数来实现这一点。

          我同意这很麻烦。

          • Pranav Goel 2017 年 6 月 27 日 晚上 10:58 #

            谢谢您的回复!我还看了您关于 Keras 中有状态 LSTM 的帖子。
            为了更清楚,假设我有一个非常长的序列(经过零填充后,假设所有序列长度为 1500)。我希望执行截断 BPTT,只使用最后 20 个时间步(因为通过整个 1500 长度的序列进行反向传播在计算上是不可行的)。
            这可以使用 Keras 中的有状态 LSTM 来完成吗?如果是,您能告诉我如何做吗?即使是一个大致的建议也将不胜感激。

            谢谢!

          • Jason Brownlee 2017 年 6 月 28 日 上午 6:26 #

            是的,将您的序列分成长度为 20 的子序列。

  2. Anh Huynh 2017 年 6 月 26 日 下午 1:15 #

    嗨,Jason,

    我有一个关于 Keras 中 LSTM 实现的问题。

    在 Keras 中,input_shape 是 (batch_size, timesteps, input_dim),output_shape 是 (batch_size, units)。所以我们只需要为每个时间步序列提供一个输出。

    Keras 的这种设计似乎与您的“向网络呈现一系列输入和输出对的时间步”的说法不太吻合。

    您能帮我澄清这个问题吗?谢谢。

    • Jason Brownlee 2017 年 6 月 27 日 上午 8:24 #

      对于序列分类,您将提供您的时间步,输出是序列末尾的单个预测。

      对于序列回归,例如预测序列中的下一个实值,您可以将该序列构建为一个监督学习问题,即给定输入序列并从您的一个序列中抽取多个“样本”来预测下一个值。

      这有帮助吗?

      • Anh Huynh 2017 年 6 月 27 日 下午 6:30 #

        谢谢你的回答。我理解你的例子,但我仍然无法将其与 BPTT(k1,k2) 的基本方案联系起来。

        在 tensorflow 中,我们有 k1=k2=num_steps,并且我们需要为网络的每个输入提供一个输出。错误在每 k1 个时间步之后进行反向传播,并且不会回溯超过 k2 个时间步。在 keras 中,我们为一系列输入提供一个输出。那么在 Keras 的情况下,k1 和 k2 是什么?

        • Jason Brownlee 2017 年 6 月 28 日 上午 6:21 #

          很好的问题。

          在 Keras 中,如果您为每个时间步进行预测,k1=k2=time_steps;如果您在序列末尾进行预测(例如序列分类),则 k1=n,k2=time_steps。

          这有帮助吗?

          • Anh Huynh 2017 年 6 月 30 日 下午 5:11 #

            太棒了。非常感谢。

          • Jason Brownlee 2017 年 7 月 1 日 上午 6:28 #

            不客气。

  3. De Handschutter Pierre 2018 年 7 月 23 日 晚上 9:39 #

    你好 Jason,

    您通过引文说:“这是一种明智的方法,在实践中可以很好地工作,但它对跨越超过 20 个时间步的时间依赖性是盲目的”。除非您使用有状态 LSTM,对吗?

    无论如何,感谢您的文章!

  4. Martin 2018 年 8 月 2 日 下午 6:00 #

    我很好奇 BPTT 是否可以用来训练动态系统 (dx/dt = f(x,y,t)),而不是 RNN。该模型是非线性等效电路 (EC) 模型,具有 1 个状态(1 个导数)。目的是发现具有物理意义的 EC 模型参数(与 RNN 的超参数相反)。

    您能否指出一些文献,或分享这方面的经验?非线性动态模型和 RNN 之间是否存在任何根本区别,会阻止 BPTT 收敛?

    附言:假设动态系统状态存在合理的初始猜测,以及参数的一些粗略估计。此外,假设系统是 SISO,时间范围为 50-100 个样本。

    • Jason Brownlee 2018 年 8 月 3 日 上午 5:59 #

      有趣的想法。

      也许可以在 scholar.google.com 上搜索一些早期论文?

  5. Simran Agarwal 2019 年 3 月 6 日 上午 6:50 #

    您好,我非常喜欢您的帖子。我的问题是,我有超过 9 个序列,每个序列有超过 10,000 个步骤。现在,我应该如何划分我的序列。我应该使用有状态 LSTM 吗?

      • Simran 2019 年 3 月 6 日 晚上 9:48 #

        您好,谢谢您的回复。我看了所有的教程和帖子。我看了“https://machinelearning.org.cn/truncated-backpropagation-through-time-in-keras/”
        这篇帖子,以及如何使用有状态 lstm。但我的问题是所有序列的长度都不同。那么我应该如何将我的序列分成子序列并进行训练呢?

        • Jason Brownlee 2019 年 3 月 7 日 上午 6:49 #

          没有最好的方法,您必须选择一种对您的项目有意义的方法——例如,您希望通过模型实现什么。

          • Simran Agarwal 2019 年 3 月 7 日 上午 7:16 #

            我想制作一个 LSTM 自动编码器。但是我的序列长度非常大,因此我正在使用有状态 LSTM。很抱歉打扰您,但我还有一个疑问。我不太明白当有状态 LSTM 中 batch_size>1 时会发生什么。那么,这意味着如果我的批次大小为 10,那么批次 1 中的样本 1 的状态会转移到批次 2 中的样本 11 吗?样本 2 的状态会发送到样本 12 吗?此外,状态会在批次之间传递吗?例如,如果我的批次大小为 10,那么批次内样本 1 的状态会传递给样本 2 吗?

          • Jason Brownlee 2019 年 3 月 7 日 下午 2:29 #

            当 LSTM 是有状态的时候,这意味着在批次结束时状态不会被重置。这意味着它会跨批次传递。

            是的,正如您所说,上一批次末尾的状态将用于下一批次的开头。这可能对您的建模问题有用也可能无用——请测试以找出答案。

  6. Simran 2019 年 3 月 7 日 上午 1:29 #

    您好,很抱歉打扰您,但我真的很困惑。根据您的教程,我使用有状态 LSTM 创建了 LSTM 自动编码器。

    我的数据形状是 (1,2000000,4),我将其分为子序列为
    (5000,400,4)。但是我的程序仍然崩溃,我不明白为什么。这是我代码的一部分

    data= data(5000,400,4)
    n_batch = data.shape[0]
    timesteps= data.shape[1]
    input_dim = data.shape[2]
    latent_dim = 64

    inputs = Input(batch_shape=(n_batch,timesteps, input_dim))
    encoded = LSTM(latent_dim, stateful=True)(inputs)
    decoded = RepeatVector(时间步长)(encoded)
    decoded = LSTM(latent_dim, return_sequences=True, stateful=True,
    batch_input_shape=(n_batch,timesteps, input_dim))(decoded)
    decoded = TimeDistributed(Dense(input_dim, activation=’sigmoid’))(decoded)
    autoencoder= Model(inputs, decoded)
    encoder = Model(inputs, encoded)

    autoencoder.compile(loss=’mse’, optimizer=’adam’, metrics=[‘accuracy’])

    但是当我使用 fit 时
    n_epochs = 10
    for i in range(n_epochs)
    autoencoder.fit(data, data, epochs=1, batch_size=n_batch, verbose=2, shuffle=False)
    autoencoder.reset_states()
    我的程序崩溃了。请帮我解决这个问题。

  7. Mark West 2019 年 7 月 11 日 上午 2:08 #

    您好,感谢这篇非常有用的博客文章!

    我对 BPTT 有几个问题

    (1) 损失函数何时应用?
    (2) 优化函数何时应用?

    这两个是在 RNN 展开后应用吗?或者是在 RNN 再次折叠后应用?

    • Jason Brownlee 2019 年 7 月 11 日 上午 9:51 #

      优化是一个过程,它使用损失来估计模型的性能,估计误差梯度并更新模型权重。

      损失在每个批次样本结束后计算,权重也在此刻更新。

      • Mark West 2019 年 7 月 11 日 晚上 11:08 #

        感谢快速回复。我想我有点被 Keras 的损失和优化函数搞糊涂了,这让我有点困惑 🙂

  8. Michael Przystupa 2019 年 7 月 22 日 上午 10:30 #

    好帖子,但第三种情况不应该是
    TBPTT(k1, k2) = k2 <= k1 k1 计算图

    希望这个问题有意义,谢谢!

  9. sandra demirki 2020 年 1 月 20 日 上午 3:43 #

    您好 Jason,感谢您的教程
    但我还不明白损失函数是如何使用的。在 Keras 中 return_sequences=true 的情况下(输入和输出形状为 (batch_size, timesteps, n_features)),损失函数的形状应该是 (batch_size, timesteps) 吗?还是每个序列都可以是标量?由于您提到权重更新是在折叠之后完成的,那么每个时间步的损失是否单独用于更新权重?或者损失函数是在时间步之间取平均值,并在每个序列结束时实现?
    我之所以问这个问题,是因为我希望在 Keras 中实现一个自定义损失函数,我希望做的是找到输入序列和输出序列之间的相关性,因此每个序列(T 个时间步)返回一个标量。

  10. Jenny 2020 年 3 月 20 日 下午 4:49 #

    亲爱的 Brownlee 博士

    感谢您的精彩帖子,但我不太明白权重更新。
    您说权重是在网络卷起后更新的。
    如果我正确理解了 RNN,我知道 3 个权重(用于输入、隐藏层和输出)在每个时间步都是相同的,并且它们在正向处理过程中在所有时间步共享这 3 个权重。那么对于反向传播,随着梯度在前一个时间步累加,更新后的权重值对于每个时间步都会不同吗?我说的对吗,还是我只是误解了整个概念?我指的是一般简单的 RNN 情况。

    • Jason Brownlee 2020 年 3 月 21 日 上午 8:18 #

      权重在每个时间步并非相同,它们会随着时间步依次改变。

  11. Andrew 2024 年 5 月 23 日 上午 6:57 #

    感谢您的工作,先生。您的文章用简单易懂的语言解释了难题。您是专家!

    • James Carmichael 2024 年 5 月 23 日 上午 7:51 #

      感谢您的反馈和支持,Andrew!我们很感激!

发表回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。