在这里,我们将分解LSTM自动编码器网络以逐层理解它们。我们将介绍各层之间的输入和输出流,并将LSTM Autoencoder与常规LSTM网络进行比较
在我之前的文章LSTM Autoencoder for Extreme Rare Event Classification [ 1]中,我们学习了如何为多变量时间序列数据构建LSTM自动编码器。
但是,深度学习中的LSTM涉及更多。理解LSTM中间层及其设置并不简单。
例如,return_sequences
参数RepeatVector
和TimeDistributed
层的使用可能会令人困惑。
LSTM教程已很好地解释的结构和输入/ LSTM细胞的输出,例如[ 2,3 ]。但尽管有其特殊性,但很少有人能够解释LSTM层在网络中协同工作的机制。
在这里,我们将分解LSTM自动编码器网络以逐层理解它们。此外,常用的seq2seq网络类似于LSTM Autoencoders。因此,大多数这些解释也适用于seq2seq。
在本文中,我们将使用一个简单的玩具示例来学习,
- 含义的
return_sequences=True
,RepeatVector()
和TimeDistributed()
。 - 了解每个LSTM网络层的输入和输出。
- 常规LSTM网络和LSTM自动编码器之间的差异。
理解模型架构
首先进口我们的必需品。
# lstm autoencoder to recreate a timeseries
import numpy as np
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import RepeatVector
from keras.layers import TimeDistributed
'''
A UDF to convert input data into 3-D
array as required for LSTM network.
'''
def temporalize(X, y, lookback):
output_X = []
output_y = []
for i in range(len(X)-lookback-1):
t = []
for j in range(1,lookback+1):
# Gather past records upto the lookback period
t.append(X[[(i+j+1)], :])
output_X.append(t)
output_y.append(y[i+lookback+1])
return output_X, output_y
创建示例数据
我们将创建一个多变量时间序列数据的玩具示例。
# define input timeseries
timeseries = np.array([[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
[0.1**3, 0.2**3, 0.3**3, 0.4**3, 0.5**3, 0.6**3, 0.7**3, 0.8**3, 0.9**3]]).transpose()
timesteps = timeseries.shape[0]
n_features = timeseries.shape[1]
timeseries
根据LSTM网络的要求,我们需要将输入数据重新整形为n_samples x timesteps x n_features。在这个例子中,n_features
是2.我们将做timesteps = 3
。由此,结果n_samples
为5(因为输入数据具有9行)。
timesteps = 3
X, y = temporalize(X = timeseries, y = np.zeros(len(timeseries)), lookback = timesteps)
n_features = 2
X = np.array(X)
X = X.reshape(X.shape[0], timesteps, n_features)
X
了解LSTM自动编码器结构
在本节中,我们将构建一个LSTM Autoencoder网络,并可视化其架构和数据流。我们还将研究常规LSTM网络,以便与Autoencoder进行比较和对比。
定义LSTM自动编码器。
# define model
model = Sequential()
model.add(LSTM(128, activation='relu', input_shape=(timesteps,n_features), return_sequences=True))
model.add(LSTM(64, activation='relu', return_sequences=False))
model.add(RepeatVector(timesteps))
model.add(LSTM(64, activation='relu', return_sequences=True))
model.add(LSTM(128, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(n_features)))
model.compile(optimizer='adam', loss='mse')
model.summary()
# fit model
model.fit(X, X, epochs=300, batch_size=5, verbose=0)
# demonstrate reconstruction
yhat = model.predict(X, verbose=0)
print('---Predicted---')
print(np.round(yhat,3))
print('---Actual---')
print(np.round(X, 3))
该model.summary()
提供的模型架构的摘要。为了更好地理解,让我们在下面的图2.3中可视化它。
该图说明了通过LSTM自动编码器网络层的数据流,用于一个数据样本。数据样本是数据集中的一个实例。在我们的示例中,一个示例是图1.2中大小为3x2的子数组。
从这个图中,我们学习
- LSTM网络将2D阵列作为输入。
- 一层LSTM具有与时间步长一样多的单元。
- 设置
return_sequences=True
每个时间步长的每个单元格发出一个信号。 - 这在图2.4中变得更清楚,其显示了
return_sequences
asTrue
(图2.4a)与False
(图2.4b)之间的差异。
- 在图2.4a中,来自一层中的时间步进单元的信号由后续层中相同时间步长的单元接收。
- 在LSTM自动编码器中的编码器和解码器模块中,重要的是在连续LSTM层中的各个时间步长单元之间具有直接连接,如图2.4a所示。
- 在图2.4b中,只有最后一个时间步长单元发出信号。因此,输出是矢量。
- 如图2.4b所示,如果后续层是LSTM,我们使用复制此向量
RepeatVector(timesteps)
来获取下一层的2D数组。 - 如果后续层是
Dense
(因为Dense
图层需要向量作为输入),则不需要转换。
回到图2.3中的LSTM Autoencoder。
- 输入数据有3个时间步长和2个功能。
- 第1层,LSTM(128),读取输入数据并输出128个特征,每个特征为3个时间步长,因为
return_sequences=True
。 - 第2层,LSTM(64),从第1层获取3x128输入,并将特征尺寸减小到64.因为
return_sequences=False
,它输出大小为1x64的特征向量。 - 该层的输出是输入数据的编码特征向量。
- 这个编码的特征向量可以被提取并用作数据压缩,或用于任何其他有监督或无监督学习的特征(在下一篇文章中,我们将看到如何提取它)。
- 第3层,RepeatVector(3),复制特征向量3次。
- RepeatVector层充当编码器和解码器模块之间的桥梁。
- 它为解码器中的第一个LSTM层准备2D数组输入。
- 解码器层旨在展开编码。
- 因此,解码器层以与编码器相反的顺序堆叠。
- 第4层,LSTM(64)和第5层,LSTM(128)分别是第2层和第1层的镜像。
- 最后添加第6层TimeDistributed(Dense(2))以获得输出,其中“2”是输入数据中的要素数。
- TimeDistributed层创建一个长度等于从前一层输出的要素数的向量。在该网络中,第5层输出128个功能。因此,TimeDistributed层创建一个128长的向量并复制它2(= n_features)次。
- 第5层的输出是3x128阵列,我们将其表示为U,而第6层中的TimeDistributed的输出是128x2阵列,表示为V. U和V之间的矩阵乘法产生3x2输出。
- 拟合网络的目的是使该输出接近输入。请注意,此网络本身可确保输入和输出尺寸匹配。
将LSTM Autoencoder与常规LSTM网络进行比较
当我们将它与为重建输入而构建的常规LSTM网络进行比较时,上述理解会变得更加清晰。
# define model
model = Sequential()
model.add(LSTM(128, activation='relu', input_shape=(timesteps,n_features), return_sequences=True))
model.add(LSTM(64, activation='relu', return_sequences=True))
model.add(LSTM(64, activation='relu', return_sequences=True))
model.add(LSTM(128, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(n_features)))
model.compile(optimizer='adam', loss='mse')
model.summary()
# fit model
model.fit(X, X, epochs=300, batch_size=5, verbose=0)
# demonstrate reconstruction
yhat = model.predict(X, verbose=0)
print('---Predicted---')
print(np.round(yhat,3))
print('---Actual---')
print(np.round(X, 3))
常规LSTM网络和LSTM自动编码器之间的差异
- 我们
return_sequences=True
在所有LSTM层中使用。 - 这意味着,每个层都输出一个包含每个时间步长的2D数组。
- 因此,没有一维编码特征向量作为任何中间层的输出。因此,不会将样本编码为特征向量。
- 缺少该编码向量区分用于重建的常规LSTM网络与LSTM自动编码器。
- 但请注意,自动编码器(图2.1)和常规网络(图3.1)中的参数数量相同。
- 这是因为,
RepeatVector
Autoencoder中的额外层没有任何附加参数。 -
值得深思
使用LSTM Autoencoder中针对稀有事件分类 [ 1 ] 讨论的异常检测方法的稀有事件分类是训练LSTM自动编码器来检测罕见事件。[ 1 ]中自动编码器网络的目标是重建输入并将重建不良的样本分类为罕见事件。
既然如此,我们还可以建立一个常规的LSTM网络来重建时间序列数据,如图3.3所示,这会改善结果吗?
这背后的假设是, 由于缺少编码层,在某些情况下重建的准确性可能更好(因为尺寸时间维度没有减小)。除非编码矢量是任何其他分析所必需的,否则尝试常规LSTM网络值得尝试进行罕见事件分类。Github存储库
完整的代码可以在这里找到。
cran2367 / understanding-lstm-autoencoder
了解LSTMAutoencoder。通过创建…_github.com,为cran2367 / understanding-lstm-autoencoder开发_做出贡献结论
在这篇文章中,我们
与玩具示例合作,逐层理解LSTM网络。
- 理解每层的输入和输出流量。
- 理解的含义
return_sequences
,RepeatVector()
和TimeDistributed()
。 - 将LSTM Autoencoder与常规LSTM网络进行比较和对比。
在下一篇文章中,我们将了解如何优化网络:如何决定添加新图层及其大小?