Trang này được dịch bởi Cloud Translation API.
Switch to English

Đắp mặt nạ và độn bằng Keras

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ tay

Thiết lập

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

Giới thiệu

Che dấu là một cách để thông báo cho các lớp xử lý trình tự rằng các bước thời gian nhất định trong một đầu vào bị thiếu và do đó nên được bỏ qua khi xử lý dữ liệu.

Padding là một dạng mặt nạ đặc biệt, trong đó các bước được che ở đầu hoặc ở đầu chuỗi. Việc đệm xuất phát từ nhu cầu mã hóa dữ liệu trình tự thành các lô liền nhau: để làm cho tất cả các trình tự trong một lô phù hợp với một độ dài tiêu chuẩn nhất định, cần phải đệm hoặc cắt bớt một số trình tự.

Chúng ta hãy xem xét kỹ lưỡng.

Dữ liệu trình tự đệm

Khi xử lý dữ liệu trình tự, rất phổ biến các mẫu riêng lẻ có độ dài khác nhau. Hãy xem xét ví dụ sau (văn bản được mã hóa dưới dạng từ):

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

Sau khi tra cứu từ vựng, dữ liệu có thể được vector hóa dưới dạng số nguyên, ví dụ:

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

Dữ liệu là một danh sách lồng nhau trong đó các mẫu riêng lẻ có độ dài tương ứng là 3, 5 và 6. Vì dữ liệu đầu vào cho một mô hình học sâu phải là một tensor duy nhất (ví dụ: hình dạng (batch_size, 6, vocab_size) trong trường hợp này), các mẫu ngắn hơn mục dài nhất cần được đệm bằng một số giá trị giữ chỗ (cách khác, một cũng có thể cắt bớt các mẫu dài trước khi đệm các mẫu ngắn).

Keras cung cấp một chức năng tiện ích để cắt ngắn và đệm danh sách Python thành một độ dài chung: 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]]

Đắp mặt nạ

Bây giờ tất cả các mẫu đều có độ dài đồng nhất, mô hình phải được thông báo rằng một số phần dữ liệu thực sự là phần đệm và nên được bỏ qua. Cơ chế đó là mặt nạ .

Có ba cách để giới thiệu dấu hiệu nhập trong các mô hình Keras:

  • Thêm một lớp keras.layers.Masking .
  • Cấu hình một keras.layers.Embedding lớp với mask_zero=True .
  • Truyền đối số mask theo cách thủ công khi gọi các lớp hỗ trợ đối số này (ví dụ: các lớp RNN).

Các lớp tạo Masking : Embedding và tạo Masking

Dưới mui xe, các lớp này sẽ tạo ra một tensor mặt nạ (tensor 2D có hình dạng (batch, sequence_length) ) và gắn nó vào đầu ra tensor được trả về bởi lớp Masking hoặc 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)

Như bạn có thể thấy từ kết quả được in, mặt nạ là một tensor boolean 2D có hình dạng (batch_size, sequence_length) , trong đó mỗi mục nhập False riêng lẻ chỉ ra rằng bước thời gian tương ứng nên được bỏ qua trong quá trình xử lý.

Truyền mặt nạ trong API chức năng và API tuần tự

Khi sử dụng API chức năng hoặc API tuần tự, một mặt nạ được tạo ra bởi một Embedding hoặc Masking lớp sẽ được phổ biến qua mạng cho bất kỳ lớp nào mà có khả năng sử dụng chúng (ví dụ, RNN lớp). Keras sẽ tự động lấy mặt nạ tương ứng với một đầu vào và chuyển nó cho bất kỳ lớp nào biết cách sử dụng nó.

Ví dụ: trong mô hình Tuần tự sau, lớp LSTM sẽ tự động nhận một mặt nạ, có nghĩa là nó sẽ bỏ qua các giá trị được đệm:

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

Đây cũng là trường hợp của mô hình API chức năng sau:

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)

Truyền trực tiếp bộ căng mặt nạ đến các lớp

Các lớp có thể xử lý mặt nạ (chẳng hạn như lớp LSTM ) có đối số mask trong phương thức __call__ của chúng.

Trong khi đó, các lớp tạo ra một mặt nạ (ví dụ: Embedding ) hiển thị một compute_mask(input, previous_mask) mà bạn có thể gọi.

Do đó, bạn có thể chuyển đầu ra của phương thức compute_mask() của một lớp tạo mặt nạ cho phương thức __call__ của một lớp sử dụng mặt nạ, như sau:

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

Hỗ trợ tạo mặt nạ trong các lớp tùy chỉnh của bạn

Đôi khi, bạn có thể cần viết các lớp tạo mặt nạ (như Embedding ) hoặc các lớp cần sửa đổi mặt nạ hiện tại.

Ví dụ: bất kỳ lớp nào tạo ra tensor có thứ nguyên thời gian khác với đầu vào của nó, chẳng hạn như lớp Concatenate nối trên thứ nguyên thời gian, sẽ cần phải sửa đổi mặt nạ hiện tại để các lớp hạ lưu có thể thực hiện đúng các bước thời gian được che vào tài khoản.

Để thực hiện điều này, lớp của bạn nên triển khai phương thức layer.compute_mask() , phương thức này tạo ra một mặt nạ mới được cung cấp đầu vào và mặt nạ hiện tại.

Đây là một ví dụ về lớp TemporalSplit cần sửa đổi mặt nạ hiện tại.

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)

Đây là một ví dụ khác về lớp CustomEmbedding có khả năng tạo mặt nạ từ các giá trị đầu vào:

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)

Chọn tham gia truyền mặt nạ trên các lớp tương thích

Hầu hết các lớp không sửa đổi thứ nguyên thời gian, vì vậy không cần sửa đổi mặt nạ hiện tại. Tuy nhiên, họ vẫn có thể muốn truyền mặt nạ hiện tại, không thay đổi, sang lớp tiếp theo. Đây là một hành vi chọn tham gia. Theo mặc định, một lớp tùy chỉnh sẽ phá hủy mặt nạ hiện tại (vì khung công tác không có cách nào để biết liệu việc lan truyền mặt nạ có an toàn hay không).

Nếu bạn có một lớp tùy chỉnh không sửa đổi thứ nguyên thời gian và nếu bạn muốn nó có thể phổ biến dấu hiệu nhập hiện tại, bạn nên đặt self.supports_masking = True trong hàm tạo lớp. Trong trường hợp này, hành vi mặc định của compute_mask() là chỉ truyền mặt nạ hiện tại qua.

Dưới đây là một ví dụ về một lớp được đưa vào danh sách trắng để truyền mặt nạ:

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)

Bây giờ bạn có thể sử dụng lớp tùy chỉnh này ở giữa lớp tạo mặt nạ (như Embedding ) và lớp sử dụng mặt nạ (như LSTM ), và nó sẽ vượt qua mặt nạ để đến lớp sử dụng mặt nạ.

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)

Viết các lớp cần thông tin mặt nạ

Một số lớp là người tiêu dùng mặt nạ: họ chấp nhận đối số mask trong call và sử dụng nó để xác định xem có bỏ qua các bước thời gian nhất định hay không.

Để viết một lớp như vậy, bạn chỉ cần thêm đối số mask=None vào chữ ký call của mình. Mặt nạ được liên kết với các đầu vào sẽ được chuyển đến lớp của bạn bất cứ khi nào nó có sẵn.

Đây là một ví dụ đơn giản bên dưới: một lớp tính toán softmax theo thứ nguyên thời gian (trục 1) của trình tự đầu vào, trong khi loại bỏ các bước thời gian bị che.

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

Tóm lược

Đó là tất cả những gì bạn cần biết về padding & mask trong Keras. Tóm lại:

  • "Mặt nạ" là cách các lớp có thể biết khi nào cần bỏ qua / bỏ qua các bước thời gian nhất định trong đầu vào trình tự.
  • Một số lớp là mặt nạ-máy phát điện: Embedding thể tạo ra một mặt nạ từ các giá trị đầu vào (nếu mask_zero=True ), và do đó có thể các Masking lớp.
  • Một số lớp là người tiêu dùng mặt nạ: chúng hiển thị đối số mask trong phương thức __call__ của họ. Đây là trường hợp của các lớp RNN.
  • Trong API chức năng và API tuần tự, thông tin mặt nạ được truyền tự động.
  • Khi sử dụng các lớp theo cách độc lập, bạn có thể chuyển các đối số mask cho các lớp theo cách thủ công.
  • Bạn có thể dễ dàng viết các lớp sửa đổi mặt nạ hiện tại, tạo mặt nạ mới hoặc sử dụng mặt nạ được liên kết với các đầu vào.