บันทึกวันที่! Google I / O ส่งคืนวันที่ 18-20 พฤษภาคม ลงทะเบียนตอนนี้
หน้านี้ได้รับการแปลโดย 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

บทนำ

การมาสก์ เป็นวิธีการบอกเลเยอร์การประมวลผลตามลำดับว่าการประทับเวลาบางอย่างในอินพุตขาดหายไปดังนั้นจึงควรข้ามไปเมื่อประมวลผลข้อมูล

Padding เป็นรูปแบบพิเศษของการมาสก์โดยขั้นตอนที่มาสก์จะอยู่ที่จุดเริ่มต้นหรือจุดสิ้นสุดของลำดับ Padding มาจากความจำเป็นในการเข้ารหัสข้อมูลลำดับเป็นแบทช์ที่ต่อเนื่องกัน: เพื่อที่จะทำให้ลำดับทั้งหมดในแบตช์มีความยาวมาตรฐานที่กำหนดจำเป็นต้องเพิ่มหรือตัดลำดับบางลำดับ

ลองมาดูใกล้ ๆ

ข้อมูลลำดับการเติม

เมื่อประมวลผลข้อมูลลำดับเป็นเรื่องปกติมากที่ตัวอย่างแต่ละตัวอย่างจะมีความยาวต่างกัน พิจารณาตัวอย่างต่อไปนี้ (โทเค็นข้อความเป็นคำ):

[
  ["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_zero=True
  • ส่งผ่านอาร์กิวเมนต์มาส mask ด้วยตนเองเมื่อเรียกเลเยอร์ที่สนับสนุนอาร์กิวเมนต์นี้ (เช่นเลเยอร์ RNN)

หน้ากากสร้างชั้น: 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)

ดังที่คุณเห็นจากผลลัพธ์ที่พิมพ์มาสก์คือเทนเซอร์บูลีน 2 มิติที่มีรูปร่าง (batch_size, sequence_length) ซึ่งแต่ละรายการ False ระบุว่าควรละเว้นการประทับเวลาที่สอดคล้องกันในระหว่างการประมวลผล

การแพร่กระจายมาสก์ใน Functional API และ Sequential API

เมื่อใช้ API หน้าที่หรือ API ลำดับหน้ากากที่สร้างขึ้นโดย Embedding หรือ Masking ชั้นจะได้รับการแพร่กระจายผ่านเครือข่ายชั้นใด ๆ ที่มีความสามารถในการใช้พวกเขา (เช่นชั้น RNN) Keras จะดึงมาสก์ที่ตรงกับอินพุตโดยอัตโนมัติและส่งต่อไปยังเลเยอร์ใด ๆ ที่รู้วิธีใช้

ตัวอย่างเช่นในรูปแบบลำดับต่อไปนี้เลเยอร์ LSTM จะได้รับมาสก์โดยอัตโนมัติซึ่งหมายความว่าจะไม่สนใจค่าเบาะ:

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

นอกจากนี้ยังเป็นกรณีสำหรับโมเดล Functional 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([[-0.0071368 ,  0.00202324,  0.00393163, ..., -0.00365972,
        -0.00194294, -0.00275828],
       [ 0.00865301, -0.00411554, -0.00328279, ...,  0.00395685,
         0.01023738, -0.0013066 ],
       [ 0.0115475 , -0.00367757, -0.0049072 , ...,  0.00312295,
         0.00557074,  0.00681297],
       ...,
       [ 0.00537544, -0.00517081,  0.00668133, ...,  0.00428408,
         0.00251086, -0.00211114],
       [ 0.00286667, -0.00301991, -0.0095289 , ...,  0.00381294,
         0.00675705, -0.00599195],
       [-0.0045211 ,  0.0019338 , -0.00031986, ...,  0.00275819,
        -0.00126366, -0.00347176]], 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 False  True]
 [ True  True  True  True  True  True  True  True  True  True]
 [ True  True  True False  True  True  True  True  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: 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)))

สรุป

นั่นคือทั้งหมดที่คุณต้องรู้เกี่ยวกับ padding & masking ใน Keras สรุป:

  • "การกำบัง" คือวิธีที่เลเยอร์ต่างๆสามารถรู้ได้ว่าเมื่อใดควรข้าม / ละเว้นการประทับเวลาบางอย่างในอินพุตลำดับ
  • ชั้นบางคนมีหน้ากากกำเนิด: Embedding สามารถสร้างหน้ากากจากค่าที่ป้อนเข้า (ถ้า mask_zero=True ) และเพื่อให้สามารถ Masking ชั้น
  • บางเลเยอร์เป็นผู้บริโภคมาสก์: แสดงอาร์กิวเมนต์มาส mask ในเมธอด __call__ นี่เป็นกรณีของเลเยอร์ RNN
  • ใน Functional API และ Sequential API ข้อมูลมาสก์จะถูกเผยแพร่โดยอัตโนมัติ
  • เมื่อใช้เลเยอร์ในแบบสแตนด์อโลนคุณสามารถส่งผ่านอาร์กิวเมนต์มาส mask ไปยังเลเยอร์ได้ด้วยตนเอง
  • คุณสามารถเขียนเลเยอร์ที่ปรับเปลี่ยนมาสก์ปัจจุบันที่สร้างมาสก์ใหม่หรือใช้มาสก์ที่เกี่ยวข้องกับอินพุตได้อย่างง่ายดาย