keras开发者文档 10:理解 masking & padding

设置

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

介绍

Masking 是一种告诉序列处理层输入中某些时间步丢失的方法,因此在处理数据时应将其跳过。

Padding是Masking 的一种特殊形式,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。 由于深度学习模型的输入数据必须是单个张量(在这种情况下,其形状为例如(batch_size,6,vocab_size)的形状),因此,比最长的项目短的样本需要填充一些占位符值(或者,一个占位符)。 可能还会在填充短样本之前截断长样本)。

Keras提供了一个实用程序函数来截断和填充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)

Masking

现在所有样本都具有统一的长度,必须告知模型该数据的某些部分实际上是填充的,应该忽略。 该机制正在掩盖。

有三种方法可以在Keras模型中引入输入掩码:

  • 添加一个keras.layers.Masking层。
  • 使用mask_zero = True配置keras.layers.Embedding层。
  • 在调用支持该参数的图层(例如RNN图层)时,手动传递一个mask参数。

Mask生成层:Embedding和Masking

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

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)

从打印结果中可以看到,mask 是形状为(batch_size,sequence_length)的2D布尔张量,其中每个False条目表示在处理过程中应忽略相应的时间步长。

功能API和顺序API中的掩码传播

使用功能性API或顺序性API时,对于可以使用它们的任何层(例如,RNN层),将通过网络传播由“Embedding ”或“Masking ”层生成的mask 。 Keras将自动获取与输入相对应的mask ,并将其传递给知道如何使用该mask 的任何层。

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

model = keras.Sequential(
    [layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True), layers.LSTM(32),]
)

以下功能性API模型也是如此:

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)

将mask 张量直接传递到图层

可以处理masks 的层(例如LSTM层)在其__call__方法中具有masks 参数。

同时,产生masks (例如嵌入)的图层会公开您可以调用的compute_mask(input,previous_mask)方法。

因此,您可以将masks 生成层的compute_mask()方法的输出传递给masks 使用层的__call__方法,如下所示:

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

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

例如,产生张量的时间维度与其输入不同的任何层,例如在时间维度上Concatenate 的连接层,都将需要修改当前mask ,以便下游层能够将掩码时间步长适当地纳入 帐户。

为此,您的图层应实现layer.compute_mask()方法,该方法将在给定输入和当前mask的情况下生成一个新的mask。

这是需要修改当前mask的TemporalSplit图层的示例。

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)

这是CustomEmbedding图层的另一个示例,该图层能够根据输入值生成mask:

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)

选择mask 兼容层上的传播

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

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

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

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)

编写需要mask 信息的图层

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

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

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

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)))

总结

那就是您需要了解有关Keras中的padding 和masking 的全部信息。 回顾一下:

  • “Masking”是各层如何知道何时跳过/忽略序列输入中的某些时间步长的方法。
  • 一些层是Masking生成器:嵌入可以根据输入值(如果mask_zero = True)生成Masking,Masking层也可以。
  • 有些层是mask-consumers:它们在__call__方法中公开mask参数。 RNN层就是这种情况。
  • 在Functional API和Sequential API中,Masking信息会自动传播。
  • 当以独立方式使用图层时,可以将mask参数手动传递给图层。
  • 您可以轻松地编写修改当前Masking,生成新Masking或使用与输入关联的Masking的图层。

参考文献

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值