Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Maskowanie i wypełnienie Kerasem

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ustawiać

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

Wprowadzenie

Maskowanie to sposób na poinformowanie warstw przetwarzania sekwencji, że brakuje pewnych kroków czasowych w danych wejściowych i dlatego należy je pominąć podczas przetwarzania danych.

Wypełnienie jest specjalną formą maskowania, w której zamaskowane kroki znajdują się na początku lub na początku sekwencji. Wypełnienie wynika z potrzeby zakodowania danych sekwencji w ciągłe partie: aby wszystkie sekwencje w partii pasowały do ​​określonej standardowej długości, konieczne jest dopełnienie lub obcięcie niektórych sekwencji.

Przyjrzyjmy się bliżej.

Dane sekwencji wypełnienia

Podczas przetwarzania danych sekwencji bardzo często zdarza się, że poszczególne próbki mają różne długości. Rozważmy następujący przykład (tekst tokenizowany jako słowa):

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

Po wyszukaniu słownictwa, dane mogą być wektoryzowane jako liczby całkowite, np .:

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

Dane to zagnieżdżona lista, w której poszczególne próbki mają odpowiednio długość 3, 5 i 6. Ponieważ dane wejściowe dla modelu głębokiego uczenia muszą być pojedynczym tensorem (o kształcie, np. (batch_size, 6, vocab_size) w tym przypadku), próbki krótsze niż najdłuższy element należy (batch_size, 6, vocab_size) jakąś wartością zastępczą (alternatywnie może również obcinać długie próbki przed wypełnieniem krótkich próbek).

Keras udostępnia funkcję narzędziową do obcinania i tf.keras.preprocessing.sequence.pad_sequences list Pythona do wspólnej długości: 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]]

Maskowanie

Teraz, gdy wszystkie próbki mają jednakową długość, model musi zostać poinformowany, że jakaś część danych jest faktycznie wypełniona i należy ją zignorować. Ten mechanizm maskuje .

Istnieją trzy sposoby wprowadzania masek wprowadzania w modelach Keras:

  • Dodaj warstwę keras.layers.Masking .
  • Konfigurowanie keras.layers.Embedding warstwę z mask_zero=True .
  • Przekaż argument mask ręcznie podczas wywoływania warstw, które obsługują ten argument (np. Warstwy RNN).

Warstwy generujące maski: Embedding i Masking

Pod maską warstwy te utworzą tensor maski (tensor 2D z kształtem (batch, sequence_length) ) i dołączą go do wyjścia tensora zwróconego przez warstwę Masking lub 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)

Jak widać na wydrukowanym wyniku, maska ​​jest tensorem logicznym 2D o kształcie (batch_size, sequence_length) , gdzie każdy pojedynczy wpis False wskazuje, że odpowiedni krok czasu powinien zostać zignorowany podczas przetwarzania.

Propagacja maski w funkcyjnym i sekwencyjnym interfejsie API

Podczas korzystania z funkcji Functional API lub Sequential API maska ​​wygenerowana przez warstwę Embedding lub Masking będzie propagowana przez sieć dla dowolnej warstwy, która może z nich korzystać (na przykład warstwy RNN). Keras automatycznie pobierze maskę odpowiadającą wejściu i przekaże ją do dowolnej warstwy, która wie, jak jej używać.

Na przykład w następującym modelu sekwencyjnym warstwa LSTM automatycznie otrzyma maskę, co oznacza, że ​​będzie ignorować wartości LSTM :

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

Dotyczy to również następującego modelu funkcjonalnego interfejsu 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)
 

Przekazywanie tensorów maski bezpośrednio do warstw

Warstwy, które mogą obsługiwać maski (takie jak warstwa LSTM ), mają argument mask w swojej metodzie __call__ .

W międzyczasie warstwy, które tworzą maskę (np. Embedding ), ujawniają compute_mask(input, previous_mask) , którą można wywołać.

W ten sposób można przekazać dane wyjściowe metody compute_mask() warstwy tworzącej maskę do metody __call__ warstwy konsumującej maskę, na przykład:

 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.23938816e-02, -2.56234198e-03, -6.73396792e-03, ...,
        -1.35504175e-02, -1.52427994e-03, -1.59765780e-02],
       [-8.63644481e-03, -1.14387823e-02, -1.27494612e-04, ...,
        -4.77430644e-03,  1.40938920e-03,  4.08594217e-03],
       [ 8.44790041e-03,  8.88171140e-03, -5.05019212e-03, ...,
         1.47258900e-02, -3.83494212e-03, -5.23220515e-03],
       ...,
       [-5.98606095e-03,  2.55754311e-03, -7.82385250e-05, ...,
        -3.93631449e-03,  3.79255065e-03, -7.22388038e-04],
       [ 7.77800847e-03, -2.89243367e-03,  5.89419063e-03, ...,
         1.15940338e-02, -4.65224823e-03,  8.93148128e-03],
       [-1.65581878e-03,  1.24931317e-02, -2.41932622e-03, ...,
         7.16447271e-03,  8.52450961e-04,  6.47593383e-03]], dtype=float32)>

Obsługa maskowania w warstwach niestandardowych

Czasami może być konieczne napisanie warstw generujących maskę (np. Embedding ) lub warstw, które wymagają modyfikacji bieżącej maski.

Na przykład każda warstwa, która tworzy tensor z innym wymiarem czasu niż jej dane wejściowe, taka jak warstwa Concatenate , która łączy się z wymiarem czasu, będzie musiała zmodyfikować bieżącą maskę, aby kolejne warstwy mogły prawidłowo wykonywać zamaskowane kroki czasowe w konto.

Aby to zrobić, twoja warstwa powinna zaimplementować layer.compute_mask() , która tworzy nową maskę na podstawie danych wejściowych i aktualnej maski.

Oto przykład warstwy TemporalSplit , która wymaga modyfikacji bieżącej maski.

 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)

Oto kolejny przykład warstwy CustomEmbedding , która może generować maskę na podstawie wartości wejściowych:

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

Włączenie maskowania propagacji na zgodnych warstwach

Większość warstw nie modyfikuje wymiaru czasu, więc nie trzeba modyfikować bieżącej maski. Jednak mogą nadal chcieć móc propagować bieżącą maskę, niezmienioną, do następnej warstwy. To jest zachowanie wyrażające zgodę. Domyślnie warstwa niestandardowa zniszczy obecną maskę (ponieważ framework nie ma możliwości określenia, czy propagowanie maski jest bezpieczne).

Jeśli masz niestandardową warstwę, która nie modyfikuje wymiaru czasu i jeśli chcesz, aby mogła propagować bieżącą maskę wprowadzania, należy ustawić self.supports_masking = True w konstruktorze warstwy. W tym przypadku domyślnym zachowaniem compute_mask() jest po prostu przepuszczenie bieżącej maski.

Oto przykład warstwy, która znajduje się na białej liście do propagacji maski:

 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)

 

Możesz teraz użyć tej niestandardowej warstwy pomiędzy warstwą generującą maskę (jak Embedding ) a warstwą pochłaniającą maskę (jak LSTM ) i będzie ona przekazywała maskę dalej, tak aby dotarła do warstwy maskującej.

 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)

Pisanie warstw, które wymagają informacji o masce

Niektóre warstwy są konsumentami maski: akceptują argument mask w call i używają go do określenia, czy pominąć określone kroki czasowe.

Aby napisać taką warstwę, możesz po prostu dodać argument mask=None w sygnaturze call . Maska skojarzona z danymi wejściowymi zostanie przekazana do Twojej warstwy, gdy tylko będzie dostępna.

Oto prosty przykład poniżej: warstwa, która oblicza softmax w wymiarze czasu (oś 1) sekwencji wejściowej, jednocześnie odrzucając zamaskowane kroki czasowe.

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

Podsumowanie

To wszystko, co musisz wiedzieć o wyściółce i maskowaniu w Keras. Przypomnę:

  • „Maskowanie” to sposób, w jaki warstwy mogą wiedzieć, kiedy pominąć / zignorować określone kroki czasowe w sekwencjach wejściowych.
  • Niektóre warstwy są generatorami masek: Embedding może generować maskę na podstawie wartości wejściowych (jeśli mask_zero=True ), podobnie jak warstwa Masking .
  • Niektóre warstwy są konsumentami maski: ujawniają argument mask w swojej metodzie __call__ . Tak jest w przypadku warstw RNN.
  • W funkcyjnym i sekwencyjnym interfejsie API informacje o maskach są propagowane automatycznie.
  • Korzystając z warstw w sposób niezależny, można ręcznie przekazać argumenty mask do warstw.
  • Możesz łatwo pisać warstwy, które modyfikują bieżącą maskę, generują nową maskę lub używają maski skojarzonej z danymi wejściowymi.