美文网首页
TF2 Keras (10) : Masking and pa

TF2 Keras (10) : Masking and pa

作者: 数科每日 | 来源:发表于2021-01-11 09:34 被阅读0次

    本文是对官方文档 的学习笔记。


    介绍

    • Masking : 在序列处理时, 告诉相关层在某些 timestamp 上 输入值是 missing , 这时候相关层需要跳过这些缺失值。
    • Padding: 为了保持所有样本长度一致, 在序列开头添加的空值。

    Padding 一个序列

    假设有如下字符串

    [
      ["Hello", "world", "!"],
      ["How", "are", "you", "doing", "today"],
      ["The", "weather", "will", "be", "nice", "tomorrow"],
    ]
    

    用词汇表转成数值后

    [
      [71, 1331, 4231]
      [73, 8, 3215, 55, 927],
      [83, 91, 1, 645, 1253, 927],
    ]
    

    数据是一个嵌套列表,其中各个样本的长度分别为3、5和6。由于深度学习模型的输入数据必须是单个 Tensor (在这种情况下,形状为例如(batch_size,6,vocab_size)的形状),因此,比最长的项目短的样本需要填充一些占位符值(或者,一个占位符)。可能还会在填充短样本之前截断长样本)。
    Keras 有专门的函数用来 padding 或者 truncate 一个序列 [tf.keras.preprocessing.sequence.pad_sequences](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/sequence/pad_sequences).

    raw_inputs = [
        [711, 632, 71],
        [73, 8, 3215, 55, 927],
        [83, 91, 1, 645, 1253, 927],
    ]
    
    # By default, this will pad using 0s; it is configurable via the
    # "value" parameter.
    # Note that you could "pre" padding (at the beginning) or
    # "post" padding (at the end).
    # We recommend using "post" padding when working with RNN layers
    # (in order to be able to use the
    # CuDNN implementation of the layers).
    padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(
        raw_inputs, padding="post"
    )
    print(padded_inputs)
    

    结果

    [[ 711  632   71    0    0    0]
     [  73    8 3215   55  927    0]
     [  83   91    1  645 1253  927]]
    

    Masking

    既然sequence 被padding 了, 那么就要通知相关层, 有些部分是padding 值, 这部分值就应该被 masking 掉。 在 Keras 中有三种机制做 masking:

    Mask 生成层 : Embedding and Masking

    这些层将创建一个 mask Tensor(形状为(batch,sequence_length)的2D张量),并将其附加到由Masking或Embedding层返回的 Tensor 输出。

    embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
    masked_output = embedding(padded_inputs)
    
    print(masked_output._keras_mask)
    
    masking_layer = layers.Masking()
    # Simulate the embedding lookup by expanding the 2D input to 3D,
    # with embedding dimension of 10.
    unmasked_embedding = tf.cast(
        tf.tile(tf.expand_dims(padded_inputs, axis=-1), [1, 1, 10]), tf.float32
    )
    
    masked_embedding = masking_layer(unmasked_embedding)
    print(masked_embedding._keras_mask)
    
    tf.Tensor(
    [[ True  True  True False False False]
     [ True  True  True  True  True False]
     [ True  True  True  True  True  True]], shape=(3, 6), dtype=bool)
    tf.Tensor(
    [[ True  True  True False False False]
     [ True  True  True  True  True False]
     [ True  True  True  True  True  True]], shape=(3, 6), dtype=bool)
    

    从打印结果中可以看到,遮罩是具有形状(batch_size,sequence_length)的2D 布尔Tensor,其中每个False条目表示在处理期间应忽略相应的 Timestamp。

    Functional API 和 Sequential API 中的 Mask 传播

    使用 Functional API或 Sequential API时,对于可以使用它们的任何层(例如RNN层),由 Embedding 或 M ask 层生成的 masking将通过网络传播。 Keras将自动获取与输入相对应的 masking,并将其传递给知道如何使用该masking 的任何层。

    例如,在以下顺序模型中,LSTM层将自动接收 masking,这意味着它将忽略填充值:

    model = keras.Sequential(
        [layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True), layers.LSTM(32),]
    )
    
    inputs = keras.Input(shape=(None,), dtype="int32")
    x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)
    outputs = layers.LSTM(32)(x)
    
    model = keras.Model(inputs, outputs)
    

    将Tensor 直接传递给相关层

    可以处理 mask 的层(例如 LSTM)一般在 call 函数中, 有mask 参数

    同样的,会产生 maks 的层(例如 Embedding) 会暴露 compute_mask(input, previous_mask) 接口以便调用。

    因此, 可以利用这两个函数将相关层连接起来。

    class MyLayer(layers.Layer):
        def __init__(self, **kwargs):
            super(MyLayer, self).__init__(**kwargs)
            self.embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
            self.lstm = layers.LSTM(32)
    
        def call(self, inputs):
            x = self.embedding(inputs)
            # Note that you could also prepare a `mask` tensor manually.
            # It only needs to be a boolean tensor
            # with the right shape, i.e. (batch_size, timesteps).
            mask = self.embedding.compute_mask(inputs)
            output = self.lstm(x, mask=mask)  # The layer will ignore the masked values
            return output
    
    
    layer = MyLayer()
    x = np.random.random((32, 10)) * 100
    x = x.astype("int32")
    layer(x)
    

    在定制化层支持 masking

    有时,您可能需要编写生成 masking 的图层(如“Embedding”),或编写需要修改当前 masking 的图层。

    例如,有些层生成的Tensor 与其输入在时间维度上不一致,例如 Concatenate layer 。这时候就需要修改当前masking ,以便下游层能够将时间对齐。

    为此,layer 应实现layer.compute_mask()方法,该会根据当前 masking 的情况下生成一个新的 masking 。

    class TemporalSplit(keras.layers.Layer):
        """Split the input tensor into 2 tensors along the time dimension."""
    
        def call(self, inputs):
            # Expect the input to be 3D and mask to be 2D, split the input tensor into 2
            # subtensors along the time axis (axis 1).
            return tf.split(inputs, 2, axis=1)
    
        def compute_mask(self, inputs, mask=None):
            # Also split the mask into 2 if it presents.
            if mask is None:
                return None
            return tf.split(mask, 2, axis=1)
    
    
    first_half, second_half = TemporalSplit()(masked_embedding)
    print(first_half._keras_mask)
    print(second_half._keras_mask)
    
    tf.Tensor(
    [[ True  True  True]
     [ True  True  True]
     [ True  True  True]], shape=(3, 3), dtype=bool)
    tf.Tensor(
    [[False False False]
     [ True  True False]
     [ True  True  True]], shape=(3, 3), dtype=bool)
    
    

    另外一个例子

    class CustomEmbedding(keras.layers.Layer):
        def __init__(self, input_dim, output_dim, mask_zero=False, **kwargs):
            super(CustomEmbedding, self).__init__(**kwargs)
            self.input_dim = input_dim
            self.output_dim = output_dim
            self.mask_zero = mask_zero
    
        def build(self, input_shape):
            self.embeddings = self.add_weight(
                shape=(self.input_dim, self.output_dim),
                initializer="random_normal",
                dtype="float32",
            )
    
        def call(self, inputs):
            return tf.nn.embedding_lookup(self.embeddings, inputs)
    
        def compute_mask(self, inputs, mask=None):
            if not self.mask_zero:
                return None
            return tf.not_equal(inputs, 0)
    
    
    layer = CustomEmbedding(10, 32, mask_zero=True)
    x = np.random.random((3, 10)) * 9
    x = x.astype("int32")
    
    y = layer(x)
    mask = layer.compute_mask(x)
    
    print(mask)
    

    Opting-in to mask propagation on compatible layers (不会翻译了)

    大多数layer 都不会修改时间维度,因此不需要修改当前 mask。但是,他们可能仍然希望能够将当前的掩mask保持不变地传播到下一层。这是一种可选的行为。默认情况下,自定义层将破坏当前的mask(因为框架无法确定传播该蒙版是否安全)。

    如果您有一个不修改时间维度的自定义layer,并且希望它能够传播当前mask,则应在图层构造函数中设置self.supports_masking = True。在这种情况下,compute_mask()的默认行为是仅传递当前的 mask。

    class MyActivation(keras.layers.Layer):
        def __init__(self, **kwargs):
            super(MyActivation, self).__init__(**kwargs)
            # Signal that the layer is safe for mask propagation
            self.supports_masking = True
    
        def call(self, inputs):
            return tf.nn.relu(inputs)
    

    现在,可以在 masking 生成层(例如Embedding)和masking 消耗层(例如LSTM)之间使用此自定义层,它将穿过masking ,使其到达masking 消耗层。

    inputs = keras.Input(shape=(None,), dtype="int32")
    x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)
    x = MyActivation()(x)  # Will pass the mask along
    print("Mask found:", x._keras_mask)
    outputs = layers.LSTM(32)(x)  # Will receive the mask
    
    model = keras.Model(inputs, outputs)
    

    需要 Masking 信息的层

    有些层是mask使用者:它们在调用中接受mask参数,并使用它确定是否跳过某些时间步长。

    要编写这样的层,只需在呼叫签名中添加mask = None自变量即可。只要有输入,与输入关联的 mask 将被传递到该的 layer。

    以下是一个简单的示例:一个层,该层在输入序列的时间维度(轴1)上计算softmax,同时丢弃掩盖的时间步长。

    class TemporalSoftmax(keras.layers.Layer):
        def call(self, inputs, mask=None):
            broadcast_float_mask = tf.expand_dims(tf.cast(mask, "float32"), -1)
            inputs_exp = tf.exp(inputs) * broadcast_float_mask
            inputs_sum = tf.reduce_sum(inputs * broadcast_float_mask, axis=1, keepdims=True)
            return inputs_exp / inputs_sum
    
    
    inputs = keras.Input(shape=(None,), dtype="int32")
    x = layers.Embedding(input_dim=10, output_dim=32, mask_zero=True)(inputs)
    x = layers.Dense(1)(x)
    outputs = TemporalSoftmax()(x)
    
    model = keras.Model(inputs, outputs)
    y = model(np.random.randint(0, 10, size=(32, 100)), np.random.random((32, 100, 1)))
    

    总结

    • "Masking" 使得 layer 知道如何忽略时间序列上的某些输入
    • 一些层会产生 mask ,比如 Embedding (如果 mask_zero=True)
    • 一些层是mask 消费者,他们在 call 接口中暴露 mask 参数
    • 在 Functional API 和 Sequential API 中, mask 信息都会自动传播
    • 如果使用“单独”的层, 可以手动的将 mask 传递给它们
    • 写一个层去 修改, 生成, 消费mask 是一件很容易的事情

    相关文章

      网友评论

          本文标题:TF2 Keras (10) : Masking and pa

          本文链接:https://www.haomeiwen.com/subject/hgkzoktx.html