【问题标题】:How to mask the inputs in an LSTM autoencoder having a RepeatVector() layer?如何屏蔽具有 RepeatVector() 层的 LSTM 自动编码器中的输入?
【发布时间】:2019-09-28 07:40:12
【问题描述】:

我一直在尝试使用 LSTM 自动编码器获得向量序列的向量表示,以便我可以使用 SVM 或其他此类监督算法对序列进行分类。数据量让我无法使用全连接密集层进行分类。

我输入的最短大小是 7 个时间步,最长的序列是 356 个时间步。因此,我用零填充较短的序列以获得形状为 (1326, 356, 8) 的最终 x_train,其中 1326 是训练样本的数量,8 是一个时间步的维度。我正在尝试使用给定的 LSTM 自动编码器将这些序列编码为单个向量。

model.add(Masking(mask_value=0.0, input_shape=(max_len, 8)))
model.add(LSTM(100, activation='relu'))
model.add(RepeatVector(max_len))
model.add(LSTM(8, activation='relu', return_sequences=True))
model.compile(optimizer='adam', loss='mse')
model.fit(x_train, x_train, batch_size=32, callbacks=[chk], epochs=1000, validation_split=0.05, shuffle=True)

我试图掩盖零填充结果,但 RepeatVector() 层可能会阻碍该过程。因此,一段时间后,均方误差损失变为nan。任何人都可以帮助我了解如何在计算损失函数时只包含相关的时间步而忽略其他时间步?

【问题讨论】:

    标签: keras lstm masking


    【解决方案1】:

    Keras 中的每一层都有一个input_maskoutput_mask,在您的示例中,在第一个LSTM 层(当return_sequence = False 时)之后,掩码已经丢失。让我在下面的例子中解释这一点,并展示在 LSTM-autoencoder 中实现掩码的 2 个解决方案。

    time_steps = 3
    n_features = 2
    input_layer = tfkl.Input(shape=(time_steps, n_features))
    # I want to mask the timestep where all the feature values are 1 (usually we pad by 0)
    x = tfk.layers.Masking(mask_value=1)(input_layer)
    x = tfkl.LSTM(2, return_sequences=True)(x)
    x = tfkl.LSTM(2, return_sequences=False)(x)
    x = tfkl.RepeatVector(time_steps)(x)
    x = tfkl.LSTM(2, return_sequences=True)(x)
    x = tfkl.LSTM(2, return_sequences=True)(x)
    x = tfk.layers.Dense(n_features)(x)
    lstm_ae = tfk.models.Model(inputs=input_layer, outputs=x)
    lstm_ae.compile(optimizer='adam', loss='mse')
    print(lstm_ae.summary())
    
    Model: "model_2"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    input_3 (InputLayer)         [(None, 3, 2)]            0         
    _________________________________________________________________
    masking_2 (Masking)          (None, 3, 2)              0         
    _________________________________________________________________
    lstm_8 (LSTM)                (None, 3, 2)              40        
    _________________________________________________________________
    lstm_9 (LSTM)                (None, 2)                 40        
    _________________________________________________________________
    repeat_vector_2 (RepeatVecto (None, 3, 2)              0         
    _________________________________________________________________
    lstm_10 (LSTM)               (None, 3, 2)              40        
    _________________________________________________________________
    lstm_11 (LSTM)               (None, 3, 2)              40        
    _________________________________________________________________
    dense_2 (Dense)              (None, 3, 2)              6         
    =================================================================
    Total params: 166
    Trainable params: 166
    Non-trainable params: 0
    _________________________________________________________________
    
    
    for i, l in enumerate(lstm_ae.layers):
        print(f'layer {i}: {l}')
        print(f'has input mask: {l.input_mask}')
        print(f'has output mask: {l.output_mask}')
    
    layer 0: <tensorflow.python.keras.engine.input_layer.InputLayer object at 0x645b49cf8>
    has input mask: None
    has output mask: None
    layer 1: <tensorflow.python.keras.layers.core.Masking object at 0x645b49c88>
    has input mask: None
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    layer 2: <tensorflow.python.keras.layers.recurrent_v2.LSTM object at 0x645b4d0b8>
    has input mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    layer 3: <tensorflow.python.keras.layers.recurrent_v2.LSTM object at 0x645b4dba8>
    has input mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    has output mask: None
    layer 4: <tensorflow.python.keras.layers.core.RepeatVector object at 0x645db0390>
    has input mask: None
    has output mask: None
    layer 5: <tensorflow.python.keras.layers.recurrent_v2.LSTM object at 0x6470b5da0>
    has input mask: None
    has output mask: None
    layer 6: <tensorflow.python.keras.layers.recurrent_v2.LSTM object at 0x6471410f0>
    has input mask: None
    has output mask: None
    layer 7: <tensorflow.python.keras.layers.core.Dense object at 0x647dfdf60>
    has input mask: None
    has output mask: None
    

    正如您在上面看到的,第二个LSTM 层(return_sequence=False)返回一个None,这是有道理的,因为时间步丢失了(形状改变了)并且该层不知道如何通过掩码,你也可以查看源代码,你会看到如果return_sequence=True返回input_mask,否则返回None。另一个问题当然是RepeatVector 层,该层根本不支持显式遮罩,这也是因为形状发生了变化。除了这个bottleneck部分(第二个LSTM + RepeatVector),模型的其他部分都可以通过mask,所以我们只需要处理bottleneck部分。

    这里有两种可能的解决方案,我也会根据计算损失来验证。

    第一个解决方案:通过传递 sample_weight 显式忽略时间步

    # last timestep should be masked because all feature values are 1
    x = np.array([1, 2, 1, 2, 1, 1], dtype='float32').reshape(1, 3, 2)
    print(x)
    array([[[1., 2.],
            [1., 2.],
            [1., 1.]]], dtype=float32)
    
    y = lstm_ae.predict(x)
    print(y)
    array([[[0.00020542, 0.00011909],
            [0.0007361 , 0.00047323],
            [0.00158514, 0.00107504]]], dtype=float32)
    
    # the expected loss should be the sum of square error between the first 2 timesteps
    # (2 features each timestep) divided by 6. you might expect that this should be 
    # divided by 4, but in the source code this is actually divided by 6, which doesn't 
    # matter a lot because only the gradient of loss matter, but not the loss itself.
    
    expected_loss = np.square(x[:, :2, :] - y[:, :2, :]).sum()/6
    print(expected_loss)
    1.665958086649577
    
    actual_loss_with_masking = lstm_ae.evaluate(x=x, y=x)
    print(actual_loss_with_masking)
    1.9984053373336792
    
    # the actual loss still includes the last timestep, which means the masking is not # effectively passed to the output layer for calculating the loss
    print(np.square(x-y).sum()/6)
    1.9984052975972493
    
    
    # if we provide the sample_weight 0 for each timestep that we want to mask, the
    # loss will be ignored correctly
    lstm_ae.compile(optimizer='adam', loss='mse', sample_weight_mode='temporal')
    sample_weight_array = np.array([1, 1, 0]).reshape(1, 3)  # it means to ignore the last timestep
    actual_loss_with_sample_weight = lstm_ae.evaluate(x=x, y=x, sample_weight=sample_weight_array)
    # the actual loss now is correct
    print(actual_loss_with_sample_weight)
    1.665958046913147
    
    

    方案二:自定义bottleneck layer,手动通过mask

    class lstm_bottleneck(tf.keras.layers.Layer):
        def __init__(self, lstm_units, time_steps, **kwargs):
            self.lstm_units = lstm_units
            self.time_steps = time_steps
            self.lstm_layer = tfkl.LSTM(lstm_units, return_sequences=False)
            self.repeat_layer = tfkl.RepeatVector(time_steps)
            super(lstm_bottleneck, self).__init__(**kwargs)
        
        def call(self, inputs):
            # just call the two initialized layers
            return self.repeat_layer(self.lstm_layer(inputs))
        
        def compute_mask(self, inputs, mask=None):
            # return the input_mask directly
            return mask
    
    time_steps = 3
    n_features = 2
    input_layer = tfkl.Input(shape=(time_steps, n_features))
    # I want to mask the timestep where all the feature values are 1 (usually we pad by 0)
    x = tfk.layers.Masking(mask_value=1)(input_layer)
    x = tfkl.LSTM(2, return_sequences=True)(x)
    x = lstm_bottleneck(lstm_units=2, time_steps=3)(x)
    # x = tfkl.LSTM(2, return_sequences=False)(x)
    # x = tfkl.RepeatVector(time_steps)(x)
    x = tfkl.LSTM(2, return_sequences=True)(x)
    x = tfkl.LSTM(2, return_sequences=True)(x)
    x = tfk.layers.Dense(n_features)(x)
    lstm_ae = tfk.models.Model(inputs=input_layer, outputs=x)
    lstm_ae.compile(optimizer='adam', loss='mse')
    print(lstm_ae.summary())
    
    Model: "model_2"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    input_3 (InputLayer)         [(None, 3, 2)]            0         
    _________________________________________________________________
    masking_2 (Masking)          (None, 3, 2)              0         
    _________________________________________________________________
    lstm_10 (LSTM)               (None, 3, 2)              40        
    _________________________________________________________________
    lstm_bottleneck_3 (lstm_bott (None, 3, 2)              40        
    _________________________________________________________________
    lstm_12 (LSTM)               (None, 3, 2)              40        
    _________________________________________________________________
    lstm_13 (LSTM)               (None, 3, 2)              40        
    _________________________________________________________________
    dense_2 (Dense)              (None, 3, 2)              6         
    =================================================================
    Total params: 166
    Trainable params: 166
    Non-trainable params: 0
    _________________________________________________________________
    
    
    for i, l in enumerate(lstm_ae.layers):
        print(f'layer {i}: {l}')
        print(f'has input mask: {l.input_mask}')
        print(f'has output mask: {l.output_mask}')
    
    layer 0: <tensorflow.python.keras.engine.input_layer.InputLayer object at 0x64dbf98d0>
    has input mask: None
    has output mask: None
    layer 1: <tensorflow.python.keras.layers.core.Masking object at 0x64dbf9f60>
    has input mask: None
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    layer 2: <tensorflow.python.keras.layers.recurrent_v2.LSTM object at 0x64dbf9550>
    has input mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    layer 3: <__main__.lstm_bottleneck object at 0x64dbf91d0>
    has input mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    layer 4: <tensorflow.python.keras.layers.recurrent_v2.LSTM object at 0x64e04ca20>
    has input mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    layer 5: <tensorflow.python.keras.layers.recurrent_v2.LSTM object at 0x64eeb8b00>
    has input mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    layer 6: <tensorflow.python.keras.layers.core.Dense object at 0x64ef43208>
    has input mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    has output mask: Tensor("masking_2/Identity_1:0", shape=(None, 3), dtype=bool)
    
    

    正如我们已经看到的,掩码现在已成功传递到输出层。我们还将验证损失不包括掩码时间步。

    # last timestep should be masked because all feature values are 1
    x = np.array([1, 2, 1, 2, 1, 1], dtype='float32').reshape(1, 3, 2)
    print(x)
    array([[[1., 2.],
            [1., 2.],
            [1., 1.]]], dtype=float32)
    
    y = lstm_ae.predict(x)
    print(y)
    array([[[ 0.00065455, -0.00294413],
            [ 0.00166675, -0.00742249],
            [ 0.00166675, -0.00742249]]], dtype=float32)
    
    # the expected loss should be the square error between the first 2 timesteps divided by 6
    expected_loss = np.square(x[:, :2, :] - y[:, :2, :]).sum()/6
    print(expected_loss)
    1.672815163930257
    
    # now the loss is correct with a custom layer
    actual_loss_with_masking = lstm_ae.evaluate(x=x, y=x)
    print(actual_loss_with_masking)
    1.672815203666687
    
    
    

    【讨论】:

    • 谢谢。我将尝试这些解决方案并让您知道结果。
    • 如果我的特征值在 0 到 1 之间,你能告诉我最好的激活函数是什么吗?
    猜你喜欢
    • 2018-03-21
    • 2021-04-23
    • 2017-11-27
    • 1970-01-01
    • 2019-01-15
    • 1970-01-01
    • 1970-01-01
    • 2019-10-13
    • 2017-11-22
    相关资源
    最近更新 更多