Keras 中的 TimeDistributed 层

2025年2月28日 | 阅读 12 分钟

RNN 变体中,长短期 记忆 (LSTM) 更受欢迎和实用;这就是 LSTMs 的情况。然而,尽管 Keras 深度学习 库为在 Python 中使用提供了接口,但设置和利用它们进行序列预测有时可能很复杂。

造成这种复杂性的原因之一是使用了 TimeDistributed 封装层,以及需要一些 LSTM 层返回序列而不是标量。在这里,您将发现几种设置 LSTM 网络进行序列预测的方法,并了解 TimeDistributed 层以及如何正确使用它。

TimeDistributed 层

TimeDistributed 层增加了使用 LSTMs 的难度,并使它们的配置比以前更具挑战性,特别是对于那些可能对该主题概念有初步了解的用户。

一个令人困惑的点是 TimeDistributed 层(以及以前的 TimeDistributedDense 层),它被更具体地描述为“一个层封装器,它接收一个层并将其应用于输入的每个时间切片。”

您应该如何以及何时将此封装器与 LSTM 一起使用?

当尝试在 Keras GitHub 上搜索或浏览与该封装器相关的 StackOverflow 帖子时,这种困惑只会加剧。

例如,在题为“何时以及如何使用 TimeDistributedDense”的问题中,François Chollet(Keras 的创建者)澄清道:在题为“何时以及如何使用 TimeDistributedDense”的问题中,François Chollet(Keras 的创建者)澄清道

TimeDistributedDense 使用 Dense(一个全连接操作)解释 3D 张量的每个时间步。

尽管如果一个人知道 TimeDistributed 层是用来做什么的,这个答案是显而易见的,但答案的字面意义却令人困惑。

这一次,本指南将尝试进一步解释 TimeDistributed 封装器如何与 LSTM 一起工作,我将尝试通过您可以运行和调整的代码示例来增强理解。

序列学习问题

让我们借助一个基本的序列学习任务来展示 TimeDistributed 层的用法。

在这个任务中,给定序列 [0.0, 0.2, 0.4, 0.6, 0.8] 作为输入序列,模型将返回相同的序列,但一次只返回一个元素。

将其视为一个基本的 ECHO 程序:当我们输入 0 时。因此,对应于 0.0 输入的值,我们将能够为序列中的每个项目获得 0.0 作为输出,并且该序列将对所有项重复。

以下是您可以生成序列的方法

代码

当您运行此代码时,它将输出以下序列

输出

 
[0.   0.2 0.4 0.6 0.8]   

此示例是完全可自定义的,如果您愿意,可以尝试使用不同长度的序列。

用于序列预测的一对一 LSTM

解决此序列学习问题有两种方法;第一种方法是单独处理序列中的每个项目。换句话说,模型应该能够找到关系,使其能够使用其输入对应项估计给定序列的每个元素。

在这个最简单的问题版本中,网络接收一个输入,例如 0,并应该输出 0。这必须发生在序列中的每个值上,这使得使用此序列比在算术级数中使用它更有效。

以下是输入-输出对的样子

X, y

0.0, 0.0

0.2, 0.2

0.4, 0.4

0.6, 0.6

0.8, 0.8

由于 LSTM 需要三维输入,我们需要将此 2D 序列重塑为 3D 格式:5 个样本、1 个时间步长和 1 个特征。相应的输出将包含 5 个样本,每个样本有 1 个特征。这使得 LSTM 能够一次接收序列的一个步骤,而不是像之前假设的那样接收整个序列。

在这里,输入序列被重塑为 5 个样本,每个样本有 1 个时间步长和 1 个特征

X = seq.reshape(5, 1, 1)

y = seq.reshape(5, 1)

网络模型将输入 1 个输入并进行 1 个时间步长。第一个隐藏层将是 5 个单元的 LSTM 层,最后一层将提供 1 个输出。

模型的训练将使用 ADAM 优化器来提高其有效性,并使用均方误差作为损失函数。

批处理大小被定义为每个 epoch 中的样本总数,以摆脱有状态 LSTM 并手动重置状态。然而,这也可以修改,以在将每个样本呈现给网络后增强权重更新。以下是用于设置和训练 LSTM 模型以完成序列学习任务的完整代码

代码

输出

 
0.0
0.2
0.4
0.6
0.8   

说明

此代码设计了一个 LSTM 模型,用于执行简单的序列预测问题。首先,它安排输入序列并将其标准化为 LSTM 特定的 3D 格式。广告 模型具有一个包含 5 个单元的 LSTM 层(即序列中的时间步数),然后是一个 Dense 层。网络使用 ADAM 优化器以及均方误差损失函数进行训练。然后模型训练总共 1000 个 epoch,批处理大小设置为等于序列长度。最后,它概述序列,然后生成结果。

在第一阶段,当运行示例时,它显示网络结构。

LSTM 层包含 140 个参数,这些参数是根据输入数量 (1) 和输出数量 (LSTM 层中 5 个单元对应 5 个) 使用以下公式计算的

n = 4 * ((输入 + 1) * 输出 + 输出^2)

n = 4 * ((1 + 1) * 5 + 5^2)

n = 4 * 35 = 140

全连接 (Dense) 层有 6 个参数,根据输入数量 (5) 和输出数量 (1) 计算,包含偏置项

n = 输入 * 输出 + 输出

n = 5 * 1 + 1 = 6

模型摘要显示

层 (类型)输出形状参数 #
lstm_1 (LSTM)(无, 1, 5)140
dense_1 (Dense)(无, 1, 1)6

总参数: 146

可训练参数: 146

不可训练参数: 0

用于序列预测的多对一 LSTM(不带 TimeDistributed)

在本节中,我们开发了一个 LSTM 模型,该模型一次运行即可预测整个序列,而不使用 Time-Distributed 封装器。

LSTMs 期望输入为三维。我们将 2D 序列重塑为 3D 数组,其中我们有 1 个样本、5 个时间步长和 1 个特征

这里,输出是一个具有五个特征的样本,这意味着输出是一个五维向量。与之前迭代构建输出的方法相反,模型现在将一次生成整个输出。此更改突出显示了 TimeDistributed 示例的工作方式的差异——它为单个时间步长而不是整个序列生成输出作为向量。

我们的模型输入层将包含五个时间步,因此我们将以此方式构建模型。第一层将是具有 5 个单元的普通 LSTM 层,最后一层(也称为输出层)将是具有 5 个神经元的 Dense 层,对应于输出序列中的五个时间步。在本节中,我们构建了一个 LSTM 模型来预测整个系列,而不使用 TimeDistributed 封装器。

LSTMs 要求输入信号具有三个维度。我们将把 2D 序列重塑为一个 3D 数组,其中包含 1 个样本、5 个时间步和 1 个特征:我们将把 2D 序列重塑为一个 3D 数组,其中包含 1 个样本、5 个时间步和 1 个特征

这里,输出将是一个具有 5 个特征的样本。至于输出的逐步构建,模型将一步输出序列,而不是逐步输出。此更改突出了 TimeDistributed 的工作方式的区别——它一次为一个时间步生成输出,而不是以向量形式给出完整序列的输出。

接下来,我们将创建一个具有特定架构的模型,其特殊之处在于它有一个输入层,其中包含 5 个时间步。第一个单一隐藏层将是 LSTM,并将包含 5 个单元。最终或输出层也将包含 5 个神经元,即输出的每个时间步长一个。

以下是用于构建和训练用于序列预测的多对一 LSTM 模型的完整代码,不使用 TimeDistributed 层

代码

输出

 
0.0
0.2
0.4
0.6
0.8   

说明

此代码中提出的 LSTM 模型旨在接受一个包含五个时间步的序列,然后一次性生成整个序列。在模型训练期间,使用 ADAM 优化器和均方误差损失函数超过 500 个 epoch。最后,在训练模型后,它会预测序列并显示下面输出部分中描述的每个估计值。

运行此示例将简要介绍配置的网络。

该论文也确实有一个 LSTM 层,它与之前的情况一样有 140 个参数,并且每个 LSTM 单元输出一个值。这使得它生成一个包含 5 个值的向量,然后由全连接层处理。在此向量中,序列的时间维度被最小化。

Dense 层有 5 个输入,并期望输出 5 个值,需要学习 30 个参数,计算如下

n = 输入 * 输出 + 输出

n = 5 * 5 + 5 = 30

网络摘要为

层 (类型)输出形状参数 #
lstm_1 (LSTM)(无, 5)140
dense_1 (Dense)(无, 5)30

总参数: 170

可训练参数: 170

不可训练参数: 0

模型成功地复制了序列,但它将作为一个整体提供,而不是分阶段提供。尽管模型很好,但它未能充分表达 LSTM 的序列处理能力,而且此处可以用 Dense 替代 LSTM。输出保持不变

用于序列预测的多对多 LSTM(带 TimeDistributed)

用于序列预测的多对多 LSTM(带 TimeDistributed)在时间步数相当大且我们处理序列预测时使用的 LSTM 网络,多对多 LSTM 用于序列预测采用 TimeDistributed。

在本教程的这一特定部分,为了处理 LSTM 隐藏层的输出

我们将使用名为 TimeDistributed 的层。应用 TimeDistributed 封装器时需要考虑两个重要点

输入必须再次至少是三维的。这通常涉及设置 TimeDistributed Dense 层之前的最后一个 LSTM 层以使用 return_sequences 返回序列

= True。输出也将是 3D 的。如果 TimeDistributed Dense 层是您在预测序列时使用的最后一层,则目标 y 应该是一个 3 维数组。

我们可以将输出重塑为 1 个样本、5 个时间步长和 1 个特征,类似于输入序列

LSTM 隐藏层可以使用以下方法设置为返回序列而不是单个值

这有助于确保每个 LSTM 单元生成 5 个输出,而不是像先前模型中那样生成 5 个输出,即输入中每个时间步长一个。

我们还可以将 TimeDistributed 层应用于输出,将全连接 Dense 层封装为一个单输出,如下所示:我们还可以将 TimeDistributed 层应用于输出,将全连接 Dense 层封装为一个单输出,如下所示

Dense 层中的“单输出”表示我们希望为输入序列中的相应时间步长提供一个输出。在这里,我们从提供的输入序列中一次获取 5 个时间步长。

这是通过 TimeDistributed 层完成的,它将相同的 Dense 层(具有相同的权重)分别映射到 LSTM 的每个时间步长输出上。因此,将 y(t) 值发送到外部世界的输出层,只需要与每个 LSTM 单元建立一个连接(加上一个偏差)。

本质上,由于网络容量的降低,有必要增加训练 epoch 的数量。在这种情况下,我将其从 500 增加到 1000,使其与上面完成的一对一示例相对应。

代码

输出

 
0.0
0.2
0.4
0.6
0.8   

说明

此脚本定义一个序列,构建一个使用 TimeDistributed 层的 LSTM 模型,训练该模型并检查模型所做预测的准确性。

执行此示例演示了网络架构。同样,LSTM 隐藏层有 140 个参数,就像上一个示例的 LSTM 隐藏层一样。

最终的全连接输出层也与一对一示例具有相同的模式。它采用一个神经元,其中包含每个 LSTM 单元的一个权重,以及一个偏置。

这完成了两件事

  • 这使其能够学习定义的输入-输出映射,同时保持每个 t 的独立性。
  • 这使得网络管理更容易,并显著减少了要处理的权重;它只处理一个时间步。

单个全连接层被连续应用于来自 LSTM 层的每个时间步,并构建输出序列。

层 (类型)输出形状参数 #
lstm_1 (LSTM)(无, 5, 5)140
时间_分布式_1 (TimeDist)(无, 5, 1)6

总参数: 146.0

可训练参数: 146

不可训练参数: 0.0

网络成功学习序列

这种用时间步和 TimeDistributed 层来构建问题的方法是实现一对一网络设计的更简洁方式,并且在更大规模上可能更具容量效率。

结论

Keras 中的 TimeDistributed 层在使用序列数据时是必需的,尤其是在 LSTM 网络中,因为它将给定的层(例如 Dense 层)应用于每个时间步。它使序列数据的处理是顺序的,而不是一次性全部处理的。然而,这使得模型的设置稍微困难一些,特别是对于 LSTM 新手。TimeDistributed 与 LSTM 是在输出需要匹配每个时间步的活动(例如序列到序列预测)中一个有用的组件,并且可以通过适当的配置加以利用。