TensorFlow2.0 Guide官方教程 学习笔记7- Masking and padding with Keras

本笔记参照TensorFlow官方教程,主要是对‘Recurrent Neural Networks (RNN) with Keras’教程内容翻译和内容结构编排,原文链接:Masking and padding with Keras
目录
创建环境(Setup)
序列数据填充(Padding)
掩码(Masking)
掩码层(Mask-generating layers):嵌入和掩码
功能API和序列API中的掩码传播
给层直接传递掩码张量
编写需要掩码信息的层
扼要复述(Recap)


创建环境(Setup)

from __future__ import absolute_import, division, print_function, unicode_literals

import numpy as np

try:
  # %tensorflow_version only exists in Colab.
  %tensorflow_version 2.x
except Exception:
  pass
import tensorflow as tf

from tensorflow.keras import layers

一、序列数据填充
当处理序列数据时,通常每个样本的长度不同。考虑下面的例子(文本标记为单词):

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

在词汇表查找后,数据可能被向量化为整数,例如:

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

数据是一个2D列表,其中各个样本的长度分别为6、5和3。由于深度学习模型的输入数据必须是一个张量(如形状。(batch_size, 6 ,vocab_size)在这种情况下),比最长的条目短的样品项需要填充一些占位符值(或者,也可以填充短样品之前截断长样品)。

Keras提供一个API来快速地截断并填充序列,以使它们达到相同长度:tf.keras.preprocessing.sequence.pad_sequences.

raw_inputs = [
  [83, 91, 1, 645, 1253, 927],
  [73, 8, 3215, 55, 927],
  [711, 632, 71]
]

# 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)
[[  83   91    1  645 1253  927]
 [  73    8 3215   55  927    0]
 [ 711  632   71    0    0    0]]

二、掩码
既然所有的样本有统一的长度,模型就必须被告知数据的一部分实际上是填充的,应该被忽略。这个机制就叫做掩码。
在Keras模型中引入掩码有三种方法:

  • 增加一个keras.layers.Masking层
  • 配置一个keras.layers.Embedding层,层参数‘mask_zero=True’
  • 如果调用的层(如RNN层)支持掩码参数的话,手动传递一个掩码参数

三、‘掩码生成’层:嵌入和掩码
在底层,这些层将创建一个掩模张量(带有形状的2D张量(batch,sequence_length)),并将它附加到掩码或嵌入层返回的张量输出上。

embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
masked_output = embedding(padded_inputs)

print(masked_output._keras_mask)

tf.Tensor(
[[ True  True  True  True  True  True]
 [ True  True  True  True  True False]
 [ True  True  True False False False]], shape=(3, 6), dtype=bool)
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  True  True  True]
 [ True  True  True  True  True False]
 [ True  True  True False False False]], shape=(3, 6), dtype=bool)

从输出的结果中很容易看出,掩码是一个2D布尔张量,形状为(batch_size,sequence_length),其中每个‘False’条目都表示对应的时间不长应该被忽略。

四、功能API和序列API中的掩码传播
当使用功能API或序列API时,由嵌入或掩码层生成的掩码将通过网络传播到能够使用它们的任何层(例如RNN层)。Keras将自动获取与输入对应的掩码,并将其传递给任何知道如何使用它的层。

注意:对子类化的模型或层的‘call’方法中,掩码不能被自动传播,所以你需手动将掩码参数传递任何需要它的层。

下面的例子是一个序列模型,LSTM层将自动接收掩码,也就是说它将忽略填充的值:

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

功能API模型使用方法如下:

inputs = tf.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 = tf.keras.Model(inputs, outputs)

五、直接将掩码张量传递给层
能够处理掩码的层(如LSTM),在它们的‘call’方法里有一个‘mask’参数。同时,产生掩码的层(如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)

六、在定制层里支持掩码
有时候我们需要编写产生掩码的层(如Embedding),或者需要修正当前掩码的层。
例如,任何产生一个时间维度不同于其输入的张量的层,例如‘Concatenate’(连接时间维度的连接层),都需要修改当前掩码,以便下游的层能够适当地考虑掩码的时间步长。
要实现上面说的,我们应该执行layer.compute_mask()方法,它可以根据输入和当前掩码生产一个新掩码。
大多数层不修正时间维度,所以不用担心掩码。compute_mask()的默认行为就是在这种情况下传递当前掩码。
下面是一个‘TemporalSplit’层需要修正当前掩码的例子:

class TemporalSplit(tf.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(
[[ True  True  True]
 [ True  True False]
 [False False False]], shape=(3, 3), dtype=bool)

下面是另外一个‘CustomEmbedding’层例子,它可以从输入值中产生掩码:

class CustomEmbedding(tf.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)
tf.Tensor(
[[ True  True  True False False  True  True  True  True  True]
 [ True  True  True  True  True  True  True  True  True  True]
 [ True  True  True  True  True  True  True  True  True  True]], shape=(3, 10), dtype=bool)

七、编写需要掩码信息的层
有些层需要掩码,他们在‘call’方法中接受掩码参数,然后用它去决定是否跳过当前时间步。
要写这种层,我们只需要在‘call’签名中添加‘mask=None’参数。当可以的时候,与输入关联的掩码将传递到我们编写的层里。

class MaskConsumer(tf.keras.layers.Layer):

  def call(self, inputs, mask=None):
    ...

八、扼要复述
下面是所有我们需要知道的关于keras掩码的知识点。

  • ‘掩码’是告诉层何时跳过或忽略序列输入中某些时间步长。
  • 有些层是掩码生产层:Embedding可以从输入值中生成掩码(如果mask_zero=True),同样Masking层也可以。
  • 有些层是掩码消费层:他们在他们的‘call’方法中暴露‘mask’参数。比如RNN层。
  • 在功能API和序列API中,掩码信息是自动传播的
  • 当编写子类模型或者以独立方式使用层时,手动将掩码参数传递给层。
  • 我们可以轻松地编写修改当前掩码、生成新掩码或使用与输入相关的掩码的层。

你可能感兴趣的:(TensorFlow学习笔记)