Đắ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ổ ghi chép

Thành lập

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

Giới thiệu

Mặt nạ là một cách để nói với lớp chuỗi chế biến mà timesteps 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.

Đệm là một dạng đặc biệt của mặt nạ nơi các bước đeo mặt nạ là lúc bắt đầu hoặc kết thúc của một 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. Kể từ khi dữ liệu đầu vào cho một mô hình học tập sâu phải là một tensor duy nhất (hình dạng ví dụ (batch_size, 6, vocab_size) trong trường hợp này), các mẫu được ngắn hơn so với nhu cầu mục dài nhất đượ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 xén và pad danh sách Python đến một chiều 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 keras.layers.Masking lớp.
  • Cấu hình một keras.layers.Embedding lớp với mask_zero=True .
  • Vượt qua một mask luận bằng tay khi gọi lớp hỗ trợ lập luận này (ví dụ RNN lớp).

Lớp mặt nạ-tạo: EmbeddingMasking

Dưới mui xe, các lớp sẽ tạo ra một mặt nạ tensor (tensor 2D với hình dạng (batch, sequence_length) ), và gắn nó vào đầu ra tensor trả về bởi các Masking hoặc Embedding lớp.

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ể nhìn thấy từ kết quả in, mặt nạ là một 2D boolean tensor với hình dạng (batch_size, sequence_length) , trong đó mỗi cá nhân False entry chỉ ra rằng timestep tương ứng nên bỏ qua trong quá trình chế biến.

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 tìm nạp 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, LSTM lớp sẽ tự động nhận được một chiếc mặt nạ, mà có nghĩa là nó sẽ bỏ qua các giá trị đệ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

Lớp có thể xử lý mặt nạ (như LSTM layer) có một mask luận trong họ __call__ phương pháp.

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

Vì vậy, bạn có thể vượt qua đầu ra của compute_mask() phương pháp của một lớp mặt nạ sản xuất đến __call__ phương pháp của một lớp mặt nạ tốn, như thế này:

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

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 phải lớp ghi rằng tạo ra một mặt nạ (như Embedding ), hoặc lớp mà cần phải sửa đổi các mặt nạ hiện hành.

Ví dụ, bất kỳ lớp nào đó tạo ra một tensor với một chiều thời gian khác nhau hơn so với đầu vào của nó, chẳng hạn như một Concatenate lớp rằng concatenates trên chiều thời gian, sẽ cần phải sửa đổi các mặt nạ hiện để lớp hạ lưu sẽ có thể lấy đúng timesteps đeo mặt nạ vào tài khoản.

Để làm điều này, lớp của bạn nên thực hiện các layer.compute_mask() phương pháp, trong đó sản xuất một mặt nạ mới cho đầu vào và mặt nạ hiện hành.

Dưới đây là một ví dụ về một TemporalSplit lớp mà cần phải sửa đổi các mặt nạ hiện hành.

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)

Dưới đây là một ví dụ về một CustomEmbedding lớp đó là khả năng tạo ra một 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  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)

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 để có thể tuyên truyền mặt nạ hiện nay, không thay đổi, đến 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 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 mà không thay đổi kích thước thời gian, và nếu bạn muốn nó để có thể tuyên truyền mặt nạ đầu vào hiện tại, bạn nên đặt self.supports_masking = True trong constructor lớp. Trong trường hợp này, hành vi mặc định của compute_mask() là chỉ cần vượt qua mặt nạ dòng điện 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 một lớp mặt nạ-tạo (như Embedding ) và một lớp mặt nạ tốn (như LSTM ), và nó sẽ vượt qua mặt nạ cùng để nó đạt đến lớp mặt nạ 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: KerasTensor(type_spec=TensorSpec(shape=(None, None), dtype=tf.bool, name=None), name='Placeholder_1:0')

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

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

Để viết một lớp như vậy, bạn có thể chỉ cần thêm một mask=None tranh cãi trong bạn call chữ ký. 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_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)))

Bản tóm tắt

Đó 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à mặt nạ-người tiêu dùng: họ tiếp xúc với một mask luận trong họ __call__ phương pháp. Đâ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 trong một cách độc lập, bạn có thể vượt qua các mask đối số cho lớp bằng tay.
  • 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.