带有用于可变长度输入的掩蔽层的 Keras lstm

Posted

技术标签:

【中文标题】带有用于可变长度输入的掩蔽层的 Keras lstm【英文标题】:Keras lstm with masking layer for variable-length inputs 【发布时间】:2018-09-15 04:27:31 【问题描述】:

我知道这是一个有很多问题的主题,但我找不到任何解决问题的方法。

我正在使用掩码层在可变长度输入上训练 LSTM 网络,但它似乎没有任何效果。

输入形状 (100, 362, 24),其中 362 是最大序列长度,24 是特征数,100 是样本数(除以 75 训练 / 25 有效)。

输出形状 (100, 362, 1) 稍后转换为 (100, 362 - N, 1)。

这是我的网络的代码:

from keras import Sequential
from keras.layers import Embedding, Masking, LSTM, Lambda
import keras.backend as K


#                          O O O
#   example for N:3        | | |
#                    O O O O O O
#                    | | | | | | 
#                    O O O O O O

N = 5
y= y[:,N:,:]

x_train = x[:75]
x_test = x[75:]
y_train = y[:75]
y_test = y[75:]

model = Sequential()
model.add(Masking(mask_value=0., input_shape=(timesteps, features)))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(64, return_sequences=True))
model.add(LSTM(1, return_sequences=True))
model.add(Lambda(lambda x: x[:, N:, :]))

model.compile('adam', 'mae')

print(model.summary())
history = model.fit(x_train, y_train, 
                    epochs=3, 
                    batch_size=15, 
                    validation_data=[x_test, y_test])

我的数据在最后被填充。示例:

>> x_test[10,350]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
   0., 0., 0., 0., 0., 0., 0.], dtype=float32)

问题是遮罩层似乎没有效果。我可以看到它在训练期间打印的损失值等于我计算后没有掩码的损失值:

Layer (type)                 Output Shape              Param #   
=================================================================
masking_1 (Masking)          (None, 362, 24)           0         
_________________________________________________________________
lstm_1 (LSTM)                (None, 362, 128)          78336     
_________________________________________________________________
lstm_2 (LSTM)                (None, 362, 64)           49408     
_________________________________________________________________
lstm_3 (LSTM)                (None, 362, 1)            264       
_________________________________________________________________
lambda_1 (Lambda)            (None, 357, 1)            0         
=================================================================
Total params: 128,008
Trainable params: 128,008
Non-trainable params: 0
_________________________________________________________________
None
Train on 75 samples, validate on 25 samples
Epoch 1/3
75/75 [==============================] - 8s 113ms/step - loss: 0.1711 - val_loss: 0.1814
Epoch 2/3
75/75 [==============================] - 5s 64ms/step - loss: 0.1591 - val_loss: 0.1307
Epoch 3/3
75/75 [==============================] - 5s 63ms/step - loss: 0.1057 - val_loss: 0.1034

>> from sklearn.metrics import mean_absolute_error
>> out = model.predict(x_test, batch_size=1)
>> print('wo mask', mean_absolute_error(y_test.ravel(), out.ravel()))
>> print('w mask', mean_absolute_error(y_test[~(x_test[:,N:] == 0).all(axis=2)].ravel(), out[~(x_test[:,N:] == 0).all(axis=2)].ravel()))
wo mask 0.10343371
w mask 0.16236152

此外,如果我使用 nan 值作为掩码输出值,我可以看到 nan 在训练期间传播(损失等于 nan)。

要使遮罩层按预期工作,我缺少什么?

【问题讨论】:

遮罩在 keras 中有一些非常严重的问题。您的数据看起来 post 填充在这里。由于您的 LSTM 正朝着前进的方向前进,您可以尝试使用预填充进行上述操作吗? 这里不是根本原因,但 'w mask' 行是错误的。您正在选择带有 y_test[(x_test[:,N:] == 0).all(axis=2)] 的屏蔽条目。 感谢您注意到 Yu-Yang 的错字,编辑并测试您的答案:) 这是否是 CMAPSSData? 【参考方案1】:

Lambda 层默认不传播掩码。也就是说Masking层计算的mask张量被Lambda层扔掉了,因此Masking层对输出损失没有影响。

如果您希望Lambda 层的compute_mask 方法传播先前的掩码,则必须在创建层时提供mask 参数。从Lambda层的源码可以看出,

def __init__(self, function, output_shape=None,
             mask=None, arguments=None, **kwargs):
    # ...
    if mask is not None:
        self.supports_masking = True
    self.mask = mask

# ...

def compute_mask(self, inputs, mask=None):
    if callable(self.mask):
        return self.mask(inputs, mask)
    return self.mask

因为mask的默认值是None,所以compute_mask返回None,完全没有屏蔽loss。

为了解决这个问题,由于您的Lambda 层本身并没有引入任何额外的遮罩,compute_mask 方法应该只返回前一层的遮罩(使用适当的切片以匹配该层的输出形状)。

masking_func = lambda inputs, previous_mask: previous_mask[:, N:]
model = Sequential()
model.add(Masking(mask_value=0., input_shape=(timesteps, features)))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(64, return_sequences=True))
model.add(LSTM(1, return_sequences=True))
model.add(Lambda(lambda x: x[:, N:, :], mask=masking_func))

现在您应该可以看到正确的损失值了。

>> model.evaluate(x_test, y_test, verbose=0)
0.2660679519176483
>> out = model.predict(x_test)
>> print('wo mask', mean_absolute_error(y_test.ravel(), out.ravel()))
wo mask 0.26519736809498456
>> print('w mask', mean_absolute_error(y_test[~(x_test[:,N:] == 0).all(axis=2)].ravel(), out[~(x_test[:,N:] == 0).all(axis=2)].ravel()))
w mask 0.2660679670482195

使用 NaN 值进行填充不起作用,因为掩码是通过将损失张量与二进制掩码相乘来完成的(0 * nan 仍然是nan,因此平均值将为nan)。

【讨论】:

像魅力一样工作,非常感谢您的完整回答。从掩蔽层文档link 中可以看出,如果 lambda 层有罪,我期待一个例外 @Yu-Yang 请问输入在 masking_func 函数中有什么作用。同时,如果最终的 Lambda 层不进行切片操作怎么办?例如,如果我使用 Lambda(lambda x: x + 1),那么 masking_func 应该是 masking_func = lambda inputs, previous_mask: previous_mask,对吧? @BsHe inputs 变量在这种情况下无效。它是必需的,因为在Lambda 层的compute_mask() 方法中,使用参数self.mask(inputs, mask) 调用掩码函数。是的,如果图层没有进行切片操作,那么您可以直接返回 previous_mask 而无需更改它。 重塑层是否传播遮罩?

以上是关于带有用于可变长度输入的掩蔽层的 Keras lstm的主要内容,如果未能解决你的问题,请参考以下文章

用于可变长度序列的 LSTM 变分自动编码器

连接 Keras 中的屏蔽输入

Keras 中 TimeDistributed 层的作用是啥?

可变列表项的长度

如何在 keras 中提供可变大小的图像作为输入

用于 keras 中可变大小图像的全卷积自动编码器