How to Use the TimeDistributed Layer for Long Short-Term Memory Networks in Python 译文

时间:2022-05-06
本文章向大家介绍How to Use the TimeDistributed Layer for Long Short-Term Memory Networks in Python 译文,主要内容包括How to Use the TimeDistributed Layer for Long Short-Term Memory Networks in Python、如何在Python中将TimeDistributed层用于Long Short-Term Memory Networks、教程概述、TimeDistributed层、序列学习问题(Sequence Learning Problem)、用于序列预测的一对一LSTM、用于多对一的序列预测的LSTM(不含TimeDistributed)、用于序列预测的多对多LSTM(带TimeDistributed)、进一步阅读、概要、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

How to Use the TimeDistributed Layer for Long Short-Term Memory Networks in Python

如何在Python中将TimeDistributed层用于Long Short-Term Memory Networks

Long Short-Term Memory Networks或LSTM是一种流行的强大的循环神经网络(即RNN)。

对于任意的序列预测(sequence prediction )问题,配置和应用起来可能会相当困难,即使在Python中的Keras深度学习库中提供的定义良好且“易于使用”的接口上也是如此。

在Keras中遇到这种困难的其中一个原因是使用了TimeDistributed装饰器层,并且需要一些LSTM层来返回序列而不是单个值。

在本教程中,您将了解配置LSTM网络进行序列预测的不同方法、TimeDistributed层所扮演的角色以及如何使用它。

完成本教程后,您将知道:

  • 如何设计一个一对一的LSTM进行序列预测。
  • 如何在没有TimeDistributed层的情况下设计一个多对一的LSTM进行序列预测。
  • 如何利用TimeDistributed层设计一个多对多的LSTM进行序列预测。

让我们开始吧。

如何在Python中使用TimeDistributed层实现Long Short-Term Memory Networks

图片由jans canon提供,保留部分权利。

教程概述

本教程分为5个部分; 他们是:

  1. TimeDistributed层
  2. 序列学习问题
  3. 用于序列预测的一对一LSTM
  4. 用于序列预测的多对一LSTM(不含TimeDistributed)
  5. 用于序列预测的多对多LSTM(带TimeDistributed)

环境

本教程假设你已经安装了带SciPy的Python 2或Python 3开发环境,以及NumPy和Pandas。

该教程还假设已经安装了scikit-learn和keras v2.0 +,并且后端有Theano或TensorFlow其中之一。

有关如何配置及您的Python环境的帮助,请参阅以下文章:

TimeDistributed层

LSTM功能强大,但难以使用,难于配置,尤其是对于初学者来说。

TimeDistributed Layer(以前的_TimeDistributedDense_layer)被隐秘地地描述为一个layer装饰器,这是一个额外的复杂因素:

这个装饰器允许我们在输入的每个时间片上应用一个layer。

如何在LSTM上使用该装饰器,应该在何时使用?

当您在Keras GitHub issues和StackOverflow上搜索该包装饰器层的讨论时,您的困惑将会是多个层面的。

例如,在问题“ 何时以及如何使用TimeDistributedDense ”中,fchollet(Keras的作者)解释道:

TimeDistributedDense对3D张量的每个时间步应用相同的Dense(完全连接)操作。

如果您已经理解了TimeDistributed图层的用途以及何时使用它,这是非常有意义的,但这对初学者毫无帮助,。

本教程旨在消除您在LSTM上使用的TimeDistributed装饰器的疑惑,其中包含了您可以检查,运行和把玩的工作示例,以帮助您进行具体的理解。

序列学习问题(Sequence Learning Problem)

我们将使用一个简单的序列学习问题来演示TimeDistributed层。

在这个问题中,序列[0.0, 0.2, 0.4, 0.6, 0.8]将作为输入一次给出一项,并且必须依次作为输出返回,一次一项。

把它想象成学习一个简单回声的程序。我们给出0.0作为输入,我们期望看到0.0作为输出,对序列中的每个项目来说都是如此。

我们可以像下面这样直接生成这个序列:

from numpy import array
length = 5
seq = array([i/float(length) for i in range(length)])
print(seq)

运行这个例子打印生成的序列:

[ 0.   0.2  0.4  0.6  0.8] 

这个例子是可配置的,如果你喜欢,你可以稍后自己用更长/更短的序列来尝试一下。请在评论中告诉我们你的结果。

用于序列预测的一对一LSTM

在我们深入之前,最重要的是要证明这个序列学习问题可以分段地进行学习。

也就是说,我们可以将问题重构为一个(由序列中每个项目的输入-输出对组成的)数据集。给定0,网络应输出0,给定0.2,网络必须输出0.2,依此类推。

这是问题的最简单的表述形式,并且要求将序列分成输-输出对,并且需要一次一步地预测序列然后在网络之外聚集在一起。

输入输出对如下所示:

X,     y
0.0,    0.0
0.2,    0.2
0.4,    0.4
0.6,    0.6
0.8,    0.8

LSTM的输入必须是三维的。我们可以把2D序列重塑一个具有5个样本、1个时间步和1个特征的3D序列。我们将输出定义为具有1个特征的5个样本。

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

我们将网络模型定义为1个时间步有1个输出。第一个隐藏层将是一个5个单元的LSTM。输出层具有1个输出的完全连接层。

该模型将适配高效ADAM优化算法和均方误差损失函数。

批大小(或批尺寸,batch size)被设置为迭代次数(epoch)中的样本数量,以避免必须手动配置LSTM处于有状态(模式)和管理状态的重置,尽管(这些操作)在每个样本被显示给网络之后,为了更新权重可以很容易地完成。 (1个epoch等于使用训练集中的全部样本进行一次训练,译者注)

下面提供了完整的代码清单:

from numpy import array
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
# prepare sequence
length = 5
seq = array([i/float(length) for i in range(length)])
X = seq.reshape(len(seq), 1, 1)
y = seq.reshape(len(seq), 1)
# define LSTM configuration
n_neurons = length
n_batch = length
n_epoch = 1000
# create LSTM
model = Sequential()
model.add(LSTM(n_neurons, input_shape=(1, 1)))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())
# train LSTM
model.fit(X, y, epochs=n_epoch, batch_size=n_batch, verbose=2)
# evaluate
result = model.predict(X, batch_size=n_batch, verbose=0)
for value in result:
    print('%.1f' % value)

运行该示例首先会打印出配置网络的结构。

我们可以看到LSTM层有140个参数。这是根据输入数量(1)和输出数量(5是隐藏层中有5个单元)计算的,如下所示:

n = 4 * ((inputs + 1) * outputs + outputs^2)
n = 4 * ((1 + 1) * 5 + 5^2)
n = 4 * 35
n = 140

我们还可以看到,完全连接层只有6个输入参数(5代表来自前一层的5个输入),输出数量(1代表该层有1个神经元)以及乖离率(bias)。

n = inputs * outputs + outputs
n = 5 * 1 + 1
n = 6
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
lstm_1 (LSTM)                (None, 1, 5)              140
_________________________________________________________________
dense_1 (Dense)              (None, 1, 1)              6
=================================================================
Total params: 146.0
Trainable params: 146
Non-trainable params: 0.0
_________________________________________________________________

网络(将会)正确地学习预测问题。

0.0
0.2
0.4
0.6
0.8

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

在本小节中,我们开发了一个LSTM来一次性输出序列,尽管没有TimeDistributed装饰器层。

LSTM的输入必须是三维的。我们可以将2D序列重塑为具有1个样本、5个时间步长和1个特征的3D序列。我们将输出定义为具有5个特征的1个样本。

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

马上,您就可以看到问题定义必须稍微调整,以支持没有TimeDistributed装饰器的用于序列预测的网络。具体来说,输出一个矢量,而不是一次一步地构建输出序列。这种差异听起来很微妙,但了解TimeDistributed装饰器的作用还是很重要的。

我们将该模型定义为一个输入具有5个时间步。第一个隐藏层将是一个5个单位的LSTM。输出层是一个具有5个神经元的完全连接层。

# create LSTM
model = Sequential()
model.add(LSTM(5, input_shape=(5, 1)))
model.add(Dense(length))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())

接下来,我们将模型适配到500 epoches并且训练数据集中的单个样本的批大小(bach size)为1。

# train LSTM
model.fit(X, y, epochs=500, batch_size=1, verbose=2)

综上所述,下面提供了完整的代码清单。

from numpy import array
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
# prepare sequence
length = 5
seq = array([i/float(length) for i in range(length)])
X = seq.reshape(1, length, 1)
y = seq.reshape(1, length)
# define LSTM configuration
n_neurons = length
n_batch = 1
n_epoch = 500
# create LSTM
model = Sequential()
model.add(LSTM(n_neurons, input_shape=(length, 1)))
model.add(Dense(length))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())
# train LSTM
model.fit(X, y, epochs=n_epoch, batch_size=n_batch, verbose=2)
# evaluate
result = model.predict(X, batch_size=n_batch, verbose=0)
for value in result[0,:]:
    print('%.1f' % value)

运行该示例首先打印配置网络的摘要。

我们可以看到LSTM层有140个参数,如上一节所述。

LSTM单元已被瘫痪掉,并且将各自输出一个单值,向完全连接的层提供5个值的向量作为输入。时间维度或序列信息已被丢弃,并坍缩成5个值的向量。

我们可以看到,完全连接的输出层有5个输入,预期输出5个值。我们可以解释30个被学习的权重如下:

n = inputs * outputs + outputs
n = 5 * 5 + 5
n = 30 

网络摘要报告如下:

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
lstm_1 (LSTM)                (None, 5)                 140
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 30
=================================================================
Total params: 170.0
Trainable params: 170
Non-trainable params: 0.0
_________________________________________________________________

在完成和打印预测序列之前,该模型是适配的,会打印出损失信息,。

序列被正确地重现,但是作为一个整体,而不是像逐步地输入数据(那样)。我们可能已经使用一个密集层(Dense layer)作为第一隐藏层而不是LSTM,因为这种LSTM的使用(方式)没有充分利用它们的序列学习和处理的全部性能。

0.0
0.2
0.4
0.6
0.8

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

在本小节中,我们将使用TimeDistributed图层来处理来自LSTM隐藏层的输出。

使用TimeDistributed装饰器层时要记住两点:

  • 输入必须(至少)是3D。这通常意味着您需要在TimeDistributed 装饰的Dense层之前配置上一个LSTM图层以返回序列(例如,将“return_sequences”参数设置为“True”)。
  • 输出将是3D。这意味着如果TimeDistributed包装的Dense层是输出层,并且您正在预测一个序列,则需要将y阵列调整为3D矢量。

我们可以将输出的样式定义为具有1个样本,5个时间步和1个特征,就像输入序列一样,如下所示:

y = seq.reshape(1, length, 1)

通过设置“return_sequences”参数为true,我们可以定义LSTM隐藏层来返回序列而不是单个值。

model.add(LSTM(n_neurons, input_shape=(length, 1), return_sequences=True))

这样,每个LSTM单元都会返回一个有5个输出的序列,一个(输出)对应输入数据的一个时间步,而不是像前面的例子那样输出单个输出值。

我们也可以在输出层上使用TimeDistributed来装饰一个完全连接的Dense层,并且只带有一个输出。

model.add(TimeDistributed(Dense(1)))

输出层中的单个输出值是关键。它强调我们打算从输入序列中的每个时间步中输出一个时间步。恰好我们会一次性处理输入序列的5个时间步。

TimeDistributed通过一次一个时间步在LSTM输出上应用相同的Dense层(相同的权重)来实现这个技巧。通过这种方法,输出层只需要一个连接到每个LSTM单元(加上一个bias)的连接。

出于这个考虑,需要增加训练的epoch(迭代次数)以兼顾到较小的网络容量。我把它从500倍增加到1000倍,以匹配起初的一对一的例子。

综上所述,下面提供了完整的代码清单。

from numpy import array
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import TimeDistributed
from keras.layers import LSTM
# prepare sequence
length = 5
seq = array([i/float(length) for i in range(length)])
X = seq.reshape(1, length, 1)
y = seq.reshape(1, length, 1)
# define LSTM configuration
n_neurons = length
n_batch = 1
n_epoch = 1000
# create LSTM
model = Sequential()
model.add(LSTM(n_neurons, input_shape=(length, 1), return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())
# train LSTM
model.fit(X, y, epochs=n_epoch, batch_size=n_batch, verbose=2)
# evaluate
result = model.predict(X, batch_size=n_batch, verbose=0)
for value in result[0,:,0]:
    print('%.1f' % value)

运行这个例子,我们可以看到配置网络的结构。

我们可以看到,跟前面的例子一样,我们在LSTM隐藏层中有140个参数。

完全连接的输出则层是一个非常不同的结果。实际上,它完全符合一对一的例子。一个神经元对于前一层中的每个LSTM单元有一个权重,另外一个用于bias输入。

这做了两件重要的事情:

  • 允许将问题重构并像它被定义那样来学习,即一个输入对应一个输出,保持每个时间步的内部过程分离。
  • 通过要求少得多的权重来简化网络,使得一次只处理一个时间步。

一个更简单的完全连接层被应用到从前一层提供的序列中的每个时间步骤,以建立输出序列。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
lstm_1 (LSTM)                (None, 5, 5)              140
_________________________________________________________________
time_distributed_1 (TimeDist (None, 5, 1)              6
=================================================================
Total params: 146.0
Trainable params: 146
Non-trainable params: 0.0
_________________________________________________________________

再次,网络学习到了序列。

0.0
0.2
0.4
0.6
0.8 

在第一个例子中,我们可以考虑将问题用时间片重构并且将一个Time Distrubuted layer作为一个更为紧密的实现一对一网络的方法。它甚至可能在更大的规模(空间或时间)上更有效率。

进一步阅读

以下是您可能想要深入了解的TimeDistributed layer的一些资源和讨论。

概要

在本教程中,您了解了如何开发用于序列预测的LSTM网络以及TimeDistributed层的作用。

具体来说,你了解到:

  • 如何设计一对一的LSTM进行序列预测。
  • 如何在不使用TimeDistributed层的情况下设计多对一的LSTM进行序列预测。
  • 如何利用TimeDistributed层设计一个多对多的LSTM进行序列预测。a