TensorFlow 2 Keras 中意外的掩码形状

Unexpected mask shape in TensorFlow 2 Keras

我有形状为 (batch_size, n_time_steps, n_features, n_channels) 的批量张量。它们来自形状为 (n_time_steps, n_features, n_channels) 的张量,其中 n_time_steps 不恒定的 。在构建批次时,张量被填充到 n_time_steps.

的最大值

应将这些张量输入具有以下架构的神经网络:

  1. 由于填充,输入被屏蔽了。
  2. 每个时间步的张量被馈送到时间分布的 CNN 块。传播掩码。
  3. 提取的特征被馈送到 RNN。

在最后一层,我 运行 犯了一个错误,因为掩码的形状是 (batch_size, n_time_steps, n_features),但 RNN 期望它的形状是 (batch_size, n_time_steps)

有谁知道如何得到合适形状的面具吗?

这是一个最小的例子:

import tensorflow as tf

class TimeDistributedMaskPropagating(tf.keras.layers.TimeDistributed):
    """TimeDistributed layer that propagates mask."""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.supports_masking = True
        
    def compute_mask(self, inputs, mask=None):
        return mask

n_features = 3
n_channels = 1

cnn_block = tf.keras.layers.Flatten()
estimator = tf.keras.Sequential([
    tf.keras.layers.Input(shape=(None, n_features, n_channels)),
    tf.keras.layers.Masking(),
    TimeDistributedMaskPropagating(cnn_block),
    # tf.keras.layers.LSTM(10)
    # yields ValueError: Dimensions must be equal, but are 3 and 10
])

x1 = tf.random.uniform((4, 3, 1))       # shape: 4, 3, 1
x2 = tf.random.uniform((3, 3, 1))       # shape: 3, 3, 1

paddings = tf.constant([[0, 1], [0, 0], [0, 0]])
padded_x2 = tf.pad(x2, paddings)        # shape: 4, 3, 1
mini_batch = tf.stack((x1, padded_x2))  # shape: 2, 4, 3, 1

logits = estimator(mini_batch)          # shape: 2, 4, 3
print(logits._keras_mask)               # shape: 2, 4, 3
# mask has shape 2, 4, 3 with values
# [[[ True  True  True]
#   [ True  True  True]
#   [ True  True  True]
#   [ True  True  True]]
# 
#  [[ True  True  True]
#   [ True  True  True]
#   [ True  True  True]
#   [False False False]]]

# mask should have shape 2, 4 with values
# [[ True  True  True  True]
#  [ True  True  True False]]

tensorflow.keras.layers.Masking 的实现中,只要求最后一个轴的所有值都等于 mask_value,以便在掩码中生成条目 False。因此,mask 的张量秩变为输入张量的张量秩减 1(而不是 2,如预期的那样,batch_size 为 1,time_steps 为 1。

这个问题可以通过定义一个自定义遮罩层来解决,其中compute_maskcall方法中原始代码中的axis=-1axis=[2, 3]替换(在我的例子中)或者更一般地说,通过 axis=list(range(2, len(inputs.shape))).

完整代码如下:

class CustomMasking(Layer):

    def __init__(self, mask_value=0., **kwargs):
        super(CustomMasking, self).__init__(**kwargs)
        self.supports_masking = True
        self.mask_value = mask_value
        self._compute_output_and_mask_jointly = True

    def compute_mask(self, inputs, mask=None):
        return K.any(math_ops.not_equal(inputs, self.mask_value),
                     axis=list(range(2, len(inputs.shape))))

    def call(self, inputs):
        axes = list(range(2, len(inputs.shape)))
        boolean_mask = K.any(math_ops.not_equal(inputs, self.mask_value),
                             axis=axes, keepdims=True)
        outputs = inputs * math_ops.cast(boolean_mask, inputs.dtype)
        # Compute the mask and outputs simultaneously.
        outputs._keras_mask = array_ops.squeeze(boolean_mask, axis=axes)  # pylint: disable=protected-access
        return outputs

    def compute_output_shape(self, input_shape):
        return input_shape

    def get_config(self):
        config = {'mask_value': self.mask_value}
        base_config = super(Masking, self).get_config()
        return dict(list(base_config.items()) + list(config.items()))