LSTM 自动编码器

Posted

技术标签:

【中文标题】LSTM 自动编码器【英文标题】:LSTM Autoencoder 【发布时间】:2017-11-22 15:43:44 【问题描述】:

我正在尝试构建一个 ,目标是从序列中获取固定大小的向量,该向量尽可能好地表示序列。这个自动编码器由两部分组成:

LSTM 编码器:获取一个序列并返回一个输出向量 (return_sequences = False) LSTM解码器:获取一个输出向量并返回一个序列(return_sequences = True

所以,最后,编码器是多对一 LSTM,解码器是一对多 LSTM。

图片来源:Andrej Karpathy

在高层次上,编码看起来像这样(类似于here 的描述):

encoder = Model(...)
decoder = Model(...)

autoencoder = Model(encoder.inputs, decoder(encoder(encoder.inputs)))

autoencoder.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

autoencoder.fit(data, data,
          batch_size=100,
          epochs=1500)

data 数组的形状(训练示例数、序列长度、输入维度)为(1200, 10, 5),如下所示:

array([[[1, 0, 0, 0, 0],
        [0, 1, 0, 0, 0],
        [0, 0, 1, 0, 0],
        ..., 
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0]],
        ... ]

问题:我不知道如何进行,尤其是如何将LSTM 集成到Model 以及如何让解码器从向量生成序列。

我正在使用kerastensorflow 后端。

编辑:如果有人想尝试,这是我生成带有移动序列的随机序列(包括填充)的过程:

import random
import math

def getNotSoRandomList(x):
    rlen = 8
    rlist = [0 for x in range(rlen)]
    if x <= 7:
        rlist[x] = 1
    return rlist


sequence = [[getNotSoRandomList(x) for x in range(round(random.uniform(0, 10)))] for y in range(5000)]

### Padding afterwards

from keras.preprocessing import sequence as seq

data = seq.pad_sequences(
    sequences = sequence,
    padding='post',
    maxlen=None,
    truncating='post',
    value=0.
)

【问题讨论】:

你为什么要生成随机序列? @lelloman:仅用于测试目的。我希望测试这个就足够了。我认为它无论如何都应该起作用,因为这项任务是关于重建而不是真正寻找模式。 我只是出于好奇而问,我真的不是专家。但是自动编码器不应该需要模式才能工作吗? @lelloman:我认为你最终是对的。但我也猜想,如果问题不是太大并且您的输出向量具有足够高的维度,您可以编码随机序列而不会丢失大量信息。但这可能是我错了。希望我们能看到情况如何。而我的真实数据当然不是随机的。 您可以从这里使用one-to-many 方法:***.com/questions/43034960/… 【参考方案1】:

模型可以是您想要的任何方式。如果我没听错的话,你只是想知道如何用 LSTM 创建模型?

使用 LSTM

首先,您必须定义编码向量的外观。假设您希望它是一个包含 20 个元素的数组,一个一维向量。所以,形状(无,20)。它的大小取决于你,没有明确的规则可以知道理想的大小。

并且您的输入必须是三维的,例如您的 (1200,10,5)。在 keras 摘要和错误消息中,它将显示为 (None,10,5),因为“None”表示批量大小,每次训练/预测时都会有所不同。

有很多方法可以做到这一点,但是,假设您只需要一个 LSTM 层:

from keras.layers import *
from keras.models import Model

inpE = Input((10,5)) #here, you don't define the batch size   
outE = LSTM(units = 20, return_sequences=False, ...optional parameters...)(inpE)

这对于一个非常简单的编码器来说已经足够了,它会产生一个包含 20 个元素的数组(但如果需要,您可以堆叠更多层)。让我们创建模型:

encoder = Model(inpE,outE)   

现在,对于解码器,它变得晦涩难懂。你不再有一个实际的序列,而是一个静态的有意义的向量。您可能仍然想使用 LTSM,他们会假设向量是一个序列。

但在这里,由于输入具有形状 (None,20),因此您必须先将其重新整形为某个 3 维数组,以便接下来附加一个 LSTM 层。

重塑它的方式完全取决于您。 1 个元素的 20 个步骤? 20 个元素的 1 步? 2个元素的10个步骤?谁知道?

inpD = Input((20,))   
outD = Reshape((10,2))(inpD) #supposing 10 steps of 2 elements    

请务必注意,如果您不再有 10 个步骤,您将无法仅启用“return_sequences”并获得所需的输出。你将不得不工作一点。实际上,没有必要使用“return_sequences”甚至使用 LSTM,但您可以这样做。

由于在我的重塑中我有 10 个时间步(有意),因此可以使用“return_sequences”,因为结果将有 10 个时间步(作为初始输入)

outD1 = LSTM(5,return_sequences=True,...optional parameters...)(outD)    
#5 cells because we want a (None,10,5) vector.   

您可以通过许多其他方式工作,例如简单地创建一个 50 单元的 LSTM,而不返回序列,然后重新调整结果:

alternativeOut = LSTM(50,return_sequences=False,...)(outD)    
alternativeOut = Reshape((10,5))(alternativeOut)

我们的模型是这样的:

decoder = Model(inpD,outD1)  
alternativeDecoder = Model(inpD,alternativeOut)   

之后,您将模型与代码结合起来并训练自动编码器。 所有三个模型将具有相同的权重,因此您只需使用其predict 方法即可使编码器带来结果。

encoderPredictions = encoder.predict(data)

我经常看到用于生成序列的 LSTM 类似于预测下一个元素。

您只取序列中的几个元素并尝试找到下一个元素。然后你又向前迈了一步,依此类推。这可能有助于生成序列。

【讨论】:

谢谢 :) 这很有帮助。您定义了变量inpout 两次,这令人困惑(如果我复制并粘贴您的代码会导致错误)。但我想通了。似乎Reshape((10,2)) 期望out 作为参数。无论如何,我在一个不太随机的序列上测试了你的想法(一个移动的 1,比如 1 0 0 -> 0 1 0 -> 0 0 1)。生成的序列看起来像这样[ 7.61515856e-01, 0.00000000e+00, 0.00000000e+00, -7.51162320e-02, -8.43070745e-02, ...],损失约为 0.08 到 0.13。如果您有任何想法如何进一步改进这一点,我很想知道:) 对这个小错误感到抱歉。正如我更正的那样,Reshape((10,2)) 需要 inpD。 -- 如果你训练了很多个 epoch,但你的结果没有达到你的预期,也许你需要更多的层。这个模型可能对这项任务不够“智能”。请记住,LSTM 的标准“激活函数”是 tanh,这意味着结果将介于 -1 和 1 之间。如果您需要介于 0 和 1 之间的输出,则应将激活更改为 sigmoid,例如.【参考方案2】:

你可以在这里找到一个简单的序列到序列自动编码器:https://blog.keras.io/building-autoencoders-in-keras.html

【讨论】:

我意识到真正的 seq2seq 自动编码器不使用RepeatVectorReshape,而是将输出从一个单元重定向到另一个单元。看看这张图片:suriyadeepan.github.io/img/seq2seq/seq2seq1.png【参考方案3】:

这是一个例子

让我们创建一个由几个序列组成的合成数据。这个想法是通过自动编码器的镜头来研究这些序列。换句话说,就是降低维度或者将它们汇总成一个固定的长度。

# define input sequence
sequence = np.array([[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 
                     [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
                     [0.2, 0.4, 0.6, 0.8],
                     [0.3, 0.6, 0.9, 1.2]])

# prepare to normalize
x = pd.DataFrame(sequence.tolist()).T.values
scaler = preprocessing.StandardScaler()
x_scaled = scaler.fit_transform(x)
sequence_normalized = [col[~np.isnan(col)] for col in  x_scaled.T]

# make sure to use dtype='float32' in padding otherwise with floating points
sequence = pad_sequences(sequence, padding='post', dtype='float32')

# reshape input into [samples, timesteps, features]
n_obs = len(sequence)
n_in = 9
sequence = sequence.reshape((n_obs, n_in, 1))

让我们设备一个简单的 LSTM

#define encoder
visible = Input(shape=(n_in, 1))
encoder = LSTM(2, activation='relu')(visible)

# define reconstruct decoder
decoder1 = RepeatVector(n_in)(encoder)
decoder1 = LSTM(100, activation='relu', return_sequences=True)(decoder1)
decoder1 = TimeDistributed(Dense(1))(decoder1)

# tie it together
myModel = Model(inputs=visible, outputs=decoder1)

# summarize layers
print(myModel.summary())


#sequence = tmp
myModel.compile(optimizer='adam', loss='mse')

history = myModel.fit(sequence, sequence, 
                      epochs=400, 
                      verbose=0, 
                      validation_split=0.1, 
                      shuffle=True)

plot_model(myModel, show_shapes=True, to_file='reconstruct_lstm_autoencoder.png')
# demonstrate recreation
yhat = myModel.predict(sequence, verbose=0)
# yhat

import matplotlib.pyplot as plt

#plot our loss 
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model train vs validation loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper right')
plt.show()

                                               

让我们构建自动编码器

# use our encoded layer to encode the training input
decoder_layer = myModel.layers[1]

encoded_input = Input(shape=(9, 1))
decoder = Model(encoded_input, decoder_layer(encoded_input))

# we are interested in seeing how the encoded sequences with lenght 2 (same as the dimension of the encoder looks like)
out = decoder.predict(sequence)

f = plt.figure()
myx = out[:,0]
myy = out[:,1]
s = plt.scatter(myx, myy)

for i, txt in enumerate(out[:,0]):
    plt.annotate(i+1, (myx[i], myy[i]))

这是序列的表示

                                               

【讨论】:

您的解决方案不会尝试掩盖您所做的填充正确吗?填充不会干扰 AE 的训练吗?我问这个是因为我有一个时间序列数据,其中一些序列的长度为 20,其他序列的长度为 1,所以我填充它们,使它们的长度都为 20,并将它们通过掩码层以忽略填充。在 AE 的情况下,我不知道如何继续屏蔽填充。 @LFisher - 你是对的!这有帮助吗***.com/questions/49670832/… 我需要查看并更新我的答案 perhpas 您的链接似乎很好地解决了这个问题!我通过手动裁剪填充区域中的输出来解决它,但显然有更简单的方法,我一定会看看,谢谢! @Isfischer - 我还发现答案 ***.com/questions/44131718/… 有见地。似乎作者声称不需要填充。请随时在此处使用您自己的数据更新我的答案和您的体验。

以上是关于LSTM 自动编码器的主要内容,如果未能解决你的问题,请参考以下文章

从 LSTM 自动编码器馈送分类器数据

LSTM 自动编码器总是返回输入序列的平均值

Keras LSTM 自动编码器时间序列重建

损失函数的爆炸式增长,LSTM 自动编码器

带有嵌入层的 Keras LSTM 自动编码器

在 LSTM 自动编码器中需要帮助 - 异常检测