이 페이지는 Cloud Translation API를 통해 번역되었습니다.
Switch to English

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) 하므로 가장 긴 항목보다 짧은 샘플은 일부 자리 표시 자 값 (또는 1 짧은 샘플을 채우기 전에 긴 샘플을자를 수도 있습니다.)

Keras는 Python 목록을 tf.keras.preprocessing.sequence.pad_sequences 공통 길이로 tf.keras.preprocessing.sequence.pad_sequences 유틸리티 함수 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 레이어를 추가합니다.
  • mask_zero=Truekeras.layers.Embedding 레이어를 구성합니다.
  • 이 인수를 지원하는 레이어 (예 : RNN 레이어)를 호출 할 때 mask 인수를 수동으로 전달합니다.

마스크 생성 레이어 : EmbeddingMasking

내부적으로 이러한 레이어는 마스크 텐서 (모양이있는 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)

인쇄 된 결과에서 볼 수 있듯이 마스크는 (batch_size, sequence_length) 모양의 2D 부울 텐서입니다. 여기서 각 개별 False 항목은 해당 타임 스텝이 처리 중에 무시되어야 함을 나타냅니다.

Functional API 및 Sequential API의 마스크 전파

Functional API 또는 Sequential API를 사용하는 경우 Embedding 또는 Masking 레이어에서 생성 된 마스크는이를 사용할 수있는 모든 레이어 (예 : RNN 레이어)에 대해 네트워크를 통해 전파됩니다. Keras는 입력에 해당하는 마스크를 자동으로 가져와 사용 방법을 알고있는 모든 레이어에 전달합니다.

예를 들어, 다음 Sequential 모델에서 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 레이어)에는 __call__ 메서드에 mask 인수가 있습니다.

한편 마스크를 생성하는 레이어 (예 : 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.2506623e-03,  2.8538904e-03, -4.8929523e-03, ...,
        -4.5514232e-03, -2.8215561e-04, -5.8676982e-03],
       [ 3.7283185e-03,  9.1536617e-04,  5.8427174e-03, ...,
        -6.7112967e-03,  1.2682604e-02, -7.9504177e-03],
       [-4.0013772e-03, -7.2822911e-03,  1.1787381e-02, ...,
         4.9432334e-03, -7.4552847e-03, -1.2599707e-03],
       ...,
       [-1.8312503e-05,  4.1940063e-03,  8.3795683e-03, ...,
        -4.5610245e-04, -2.9614315e-04,  4.0195161e-03],
       [-1.1378762e-02,  8.8228982e-05, -4.0666489e-03, ...,
         4.9830792e-03,  4.5853746e-03,  5.9270794e-03],
       [ 1.3646588e-03, -5.5452948e-03, -9.0900948e-03, ...,
        -6.4386385e-03, -9.9968808e-03, -1.0266920e-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]
 [False  True  True  True  True  True False  True  True  True]
 [ True  True  True  True  True  True  True False  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 레이어의 경우입니다.
  • Functional API 및 Sequential API에서는 마스크 정보가 자동으로 전파됩니다.
  • 독립 실행 형 방식으로 레이어를 사용하는 경우 mask 인수를 레이어에 수동으로 전달할 수 있습니다.
  • 현재 마스크를 수정하거나 새 마스크를 생성하거나 입력과 관련된 마스크를 사용하는 레이어를 쉽게 작성할 수 있습니다.