文章目录
设置
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的图层。