มาส์กและแพดดิ้งด้วย Keras

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

ติดตั้ง

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

บทนำ

กาวเป็นวิธีที่จะบอกชั้นลำดับการประมวลผลที่ timesteps บางอย่างในการป้อนข้อมูลจะหายไปและทำให้ควรจะข้ามเมื่อประมวลผลข้อมูลที่

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

ลองมาดูอย่างใกล้ชิด

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

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

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

หลังจากค้นหาคำศัพท์ ข้อมูลอาจถูก vectorized เป็นจำนวนเต็ม เช่น:

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

ข้อมูลเป็นรายการที่ซ้อนกันซึ่งแต่ละตัวอย่างมีความยาว 3, 5 และ 6 ตามลำดับ ตั้งแต่การป้อนข้อมูลสำหรับรูปแบบการเรียนรู้ลึกต้องเป็นเมตริกซ์เดียว (ของรูปร่างเช่น (batch_size, 6, vocab_size) ในกรณีนี้) ตัวอย่างที่สั้นกว่ารายการที่จำเป็นที่ยาวที่สุดที่จะมีเบาะที่มีค่าตัวยึดบางคน (อีกทางเลือกหนึ่ง อาจตัดตัวอย่างยาวก่อนที่จะเติมตัวอย่างสั้นๆ)

Keras ให้ฟังก์ชั่นโปรแกรมที่จะตัดและแผ่นรายการงูใหญ่ความยาวเหมือนกัน: 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]]

Masking

เมื่อตัวอย่างทั้งหมดมีความยาวเท่ากันแล้ว แบบจำลองต้องได้รับแจ้งว่าข้อมูลบางส่วนเป็นช่องว่างภายในจริง ๆ และควรละเว้น กลไกที่เป็นที่กำบัง

มีสามวิธีในการแนะนำรูปแบบการป้อนข้อมูลในโมเดล Keras:

  • เพิ่ม keras.layers.Masking ชั้น
  • กำหนดค่า keras.layers.Embedding ชั้นกับ 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)

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

การเผยแพร่มาสก์ใน 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([[-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)>

รองรับการกำบังในเลเยอร์ที่คุณกำหนดเอง

บางครั้งคุณอาจจะต้องเขียนชั้นที่สร้างหน้ากาก (เช่น Embedding ) หรือชั้นที่จำเป็นต้องปรับเปลี่ยนหน้ากากปัจจุบัน

ยกตัวอย่างเช่นชั้นใด ๆ ที่ก่อให้เมตริกซ์ที่มีมิติเวลาที่แตกต่างกันกว่าปัจจัยการผลิตเช่น Concatenate ชั้นที่ concatenates ในมิติเวลาที่จะต้องปรับเปลี่ยนหน้ากากปัจจุบันเพื่อให้ชั้นปลายน้ำจะสามารถที่จะถูกต้อง timesteps สวมหน้ากากเข้า บัญชีผู้ใช้.

การทำเช่นนี้ชั้นของคุณควรใช้ 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  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)

การเลือกใช้การปกปิดการแพร่กระจายบนเลเยอร์ที่เข้ากันได้

เลเยอร์ส่วนใหญ่ไม่แก้ไขมิติเวลา ดังนั้นจึงไม่จำเป็นต้องแก้ไขมาสก์ปัจจุบัน แต่พวกเขาอาจจะยังคงต้องการที่จะสามารถที่จะเผยแพร่หน้ากากปัจจุบันไม่เปลี่ยนแปลงไปยังชั้นถัดไป นี่เป็นพฤติกรรมการเลือกรับ ตามค่าเริ่มต้น เลเยอร์ที่กำหนดเองจะทำลายมาสก์ปัจจุบัน (เนื่องจากเฟรมเวิร์กไม่มีวิธีบอกได้ว่าการเผยแพร่มาสก์นั้นปลอดภัยหรือไม่)

ถ้าคุณมีชั้นที่กำหนดเองที่ไม่ได้ปรับเปลี่ยนมิติเวลาและถ้าคุณต้องการที่จะสามารถเผยแพร่รูปแบบการป้อนปัจจุบันคุณควรตั้ง 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)))

สรุป

นั่นคือทั้งหมดที่คุณต้องรู้เกี่ยวกับการเติมและการกำบังใน Keras สรุป:

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