Маскирование и заполнение Keras

Оптимизируйте свои подборки Сохраняйте и классифицируйте контент в соответствии со своими настройками.

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на 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) в данном случае), образцы, которые короче самой длинная необходимость элемента , чтобы быть дополнены с некоторым значением заполнителя ( в качестве альтернативы, один также может обрезать длинные сэмплы перед заполнением коротких сэмплов).

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:

  • Добавить keras.layers.Masking слой.
  • Настройка keras.layers.Embedding слой с mask_zero=True .
  • Проходят mask аргумент вручную при вызове слоев , которые поддерживают этот аргумент (например , РНН слоев).

Маска-генерирующие слои: Embedding и Masking

Под капотом, эти слои будут создавать тензор маски (2D тензор с формой (batch, sequence_length) ), и прикрепить ее к выходу тензора , возвращаемого 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)

Как вы можете видеть из напечатанного результата, маска представляет собой 2D булева тензор с формой (batch_size, sequence_length) , где каждая отдельная False запись указует , что соответствующий временный шаг должен быть проигнорирован во время обработки.

Распространение маски в Functional API и Sequential API

При использовании функционального API или последовательного API, маска , порожденное Embedding или Masking слоя будет распространяться через сеть для любого слоя , который способен их (например, РНН слои) , используя. 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([[-3.6287602e-04,  8.8942451e-03, -4.5623952e-03, ...,
         3.6509466e-04, -4.3871473e-03, -1.7532009e-03],
       [ 2.6261162e-03, -2.5420082e-03,  7.6517118e-03, ...,
         5.8210879e-03, -1.5617531e-03, -1.7562184e-03],
       [ 6.8687932e-03,  1.2330032e-03, -1.2028826e-02, ...,
         2.0486799e-03,  5.7172528e-03,  2.6641595e-03],
       ...,
       [-3.4327951e-04,  1.3967649e-03, -1.2102776e-02, ...,
         3.8406218e-03, -2.3374180e-03, -4.9669710e-03],
       [-2.3023323e-03,  1.8474255e-03,  2.7329330e-05, ...,
         6.1798934e-03,  4.2709545e-04,  3.9026213e-03],
       [ 7.4090287e-03,  1.9879336e-03, -2.0261200e-03, ...,
         8.2100276e-03,  8.7051848e-03,  9.9167246e-03]], dtype=float32)>

Поддержка маскирования в ваших пользовательских слоях

Иногда вам может понадобиться слоев записи , которые генерируют маску (например , Embedding ), или слои , которые необходимо изменить текущую маску.

Так , например, любой слой , который производит тензор с другим измерением времени , чем его вход, такие как 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  True  True  True]
 [ True  True  True  True  True False  True  True  True  True]
 [ True  True  True  True  True  True  True  True False  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: KerasTensor(type_spec=TensorSpec(shape=(None, None), dtype=tf.bool, name=None), name='Placeholder_1:0')

Запись слоев, которым нужна информация о маске

Некоторые слои потребителей маски: они принимают mask аргумент в call и использовать его , чтобы определить , следует ли пропускать определенные временные шаги.

Для того, чтобы написать такой слой, вы можете просто добавить mask=None аргумент в вашем call подписи. Маска, связанная с входными данными, будет передана вашему слою всякий раз, когда она будет доступна.

Вот простой пример ниже: слой, который вычисляет softmax по измерению времени (ось 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_exp * 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 слой.
  • Некоторые слои маски-потребители: они выставляют mask аргумент в их __call__ методом. Это относится к слоям RNN.
  • В Functional API и Sequential API информация о маске распространяется автоматически.
  • При использовании слоев в автономном образом, вы можете передать mask аргументы слоев вручную.
  • Вы можете легко создавать слои, которые изменяют текущую маску, создают новую маску или используют маску, связанную с входными данными.