このページは Cloud Translation API によって翻訳されました。
Switch to English

Kerasによるマスキングとパディング

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロード

セットアップ

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

前書き

マスキングは、入力の特定のタイムステップが欠落していることをシーケンス処理レイヤーに通知する方法であるため、データを処理するときにスキップする必要があります。

パディングは、マスクされたステップがシーケンスの開始時または開始時にある特殊な形式のマスキングです。パディングは、シーケンスデータを連続したバッチにエンコードする必要があるためです。バッチ内のすべてのシーケンスを特定の標準の長さに合わせるには、一部のシーケンスをパディングまたは切り捨てる必要があります。

よく見てみましょう。

パディングシーケンスデータ

シーケンスデータを処理する場合、個々のサンプルの長さが異なることは非常に一般的です。次の例(単語としてトークン化されたテキスト)について考えてみます。

[
  ["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)である必要があるため、最長のアイテムよりも短いサンプルには、プレースホルダー値(または1つ)を埋め込む必要があります。短いサンプルをパディングする前に、長いサンプルを切り捨てることもあります)。

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)

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

マスキング

すべてのサンプルの長さが均一になったので、データの一部が実際にパディングされていることをモデルに通知する必要があり、無視する必要があります。そのメカニズムはマスキングです。

Kerasモデルに入力マスクを導入する方法は3つあります。

  • keras.layers.Maskingレイヤーを追加します。
  • mask_zero=Truemask_zero=Truekeras.layers.Embeddingレイヤーを構成しmask_zero=True
  • この引数をサポートするレイヤー(RNNレイヤーなど)を呼び出すときに、 mask引数を手動で渡します。

マスク生成レイヤー: EmbeddingMasking

ボンネットの下で、これらのレイヤーはマスクテンソル(形状(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)
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ブールテンソルです。個々のFalseエントリは、対応するタイムステップを処理中に無視する必要があることを示します。

FunctionalAPIおよびSequentialAPIでのマスク伝播

FunctionalAPIまたはSequentialAPIを使用する場合、 EmbeddingレイヤーまたはMaskingレイヤーによって生成されたマスクは、それらを使用できるすべてのレイヤー(RNNレイヤーなど)のネットワークを介して伝播されます。 Kerasは、入力に対応するマスクを自動的にフェッチし、その使用方法を知っている任意のレイヤーに渡します。

たとえば、次のシーケンシャルモデルでは、 LSTMレイヤーは自動的にマスクを受け取ります。つまり、埋め込み値は無視されます。

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)

マスクテンソルを直接レイヤーに渡す

(のようなマスクを扱うことができるレイヤーLSTM層)が持っているmask自分で引数__call__メソッドを。

一方、マスクを生成するレイヤー( Embedding )は、呼び出すことができるcompute_mask(input, previous_mask)メソッドを公開します。

したがって、次のように、マスク生成レイヤーのcompute_mask()メソッドの出力をマスク消費レイヤーの__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)
<tf.Tensor: shape=(32, 32), dtype=float32, numpy=
array([[-1.0486022e-02, -3.4827946e-03, -1.1125054e-02, ...,
         9.0141175e-03, -8.3585093e-03,  9.5337434e-03],
       [ 3.3862798e-03, -1.7598980e-03, -1.4025946e-03, ...,
         2.9608735e-03,  2.6525410e-03, -7.6429715e-04],
       [-3.8523132e-03,  1.2728614e-03,  7.1079744e-04, ...,
         4.7224545e-05,  2.4815148e-03,  6.7755133e-03],
       ...,
       [-5.3204019e-03,  1.2874496e-02,  1.8557823e-04, ...,
         3.9382367e-03,  2.6499005e-03, -3.0199424e-03],
       [ 1.4345241e-03,  3.6518679e-03,  6.7753191e-03, ...,
         5.4177251e-03,  5.5437838e-03, -4.9513532e-03],
       [ 6.2698883e-04, -5.5408280e-04,  2.0114302e-03, ...,
         4.3189530e-03,  9.3608703e-03,  7.2836089e-03]], dtype=float32)>

カスタムレイヤーでのマスキングのサポート

場合によっては、マスクを生成するレイヤー( Embedding )や、現在のマスクを変更する必要のあるレイヤーを作成する必要があります。

たとえば、入力とは異なる時間ディメンションを持つテンソルを生成するレイヤー(時間ディメンションでConcatenateするConcatenateレイヤーなど)は、現在のマスクを変更して、ダウンストリームレイヤーがマスクされたタイムステップを適切に取得できるようにする必要があります。アカウント。

これを行うには、レイヤーでlayer.compute_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)
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)

入力値からマスクを生成できるCustomEmbeddingレイヤーの別の例を次に示します。

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

互換性のあるレイヤーでの伝播をマスクするためのオプトイン

ほとんどのレイヤーは時間ディメンションを変更しないため、現在のマスクを変更する必要はありません。ただし、現在のマスクを変更せずに次のレイヤーに伝播できるようにしたい場合もあります。これはオプトイン動作です。デフォルトでは、カスタムレイヤーは現在のマスクを破棄します(フレームワークにはマスクの伝播が安全かどうかを判断する方法がないため)。

時間ディメンションを変更しないカスタムレイヤーがあり、現在の入力マスクを伝播できるようにする場合は、レイヤーコンストラクターでself.supports_masking = Trueを設定する必要があります。この場合、 compute_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)

これで、このカスタムレイヤーをマスク生成レイヤー( Embedding )とマスク消費レイヤー( LSTM )の間に使用でき、マスクを通過させてマスク消費レイヤーに到達します。

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 found: Tensor("embedding_4/NotEqual:0", shape=(None, None), dtype=bool)

マスク情報が必要なレイヤーの作成

一部のレイヤーはマスクコンシューマーです。 call mask引数を受け入れ、それを使用して特定のタイムステップをスキップするかどうかを決定します。

このようなレイヤーを作成するには、 callシグネチャにmask=None引数を追加するだけです。入力に関連付けられたマスクは、使用可能な場合は常にレイヤーに渡されます。

以下に簡単な例を示します。マスクされたタイムステップを破棄しながら、入力シーケンスの時間次元(軸1)にわたってソフトマックスを計算するレイヤー。

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のパディングとマスキングについて知っておく必要があるのはこれだけです。要点をまとめると:

  • 「マスキング」とは、シーケンス入力の特定のタイムステップをスキップ/無視するタイミングをレイヤーが知る方法です。
  • 一部のレイヤーはマスクジェネレーターです。 Embeddingは入力値からマスクを生成でき( mask_zero=True場合)、 Maskingレイヤーも同様です。
  • 一部のレイヤーはマスクコンシューマーです。それらは__call__メソッドでmask引数を公開します。これはRNNレイヤーの場合です。
  • FunctionalAPIおよびSequentialAPIでは、マスク情報が自動的に伝播されます。
  • スタンドアロンでレイヤーを使用する場合は、 mask引数を手動でレイヤーに渡すことができます。
  • 現在のマスクを変更するレイヤー、新しいマスクを生成するレイヤー、または入力に関連付けられたマスクを使用するレイヤーを簡単に作成できます。