تاریخ را ذخیره کنید! 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 نوعی خاص از پوشاندن است که در آن مراحل نقاب دار در ابتدا یا انتهای یک دنباله قرار دارند. پدینگ از نیاز به رمزگذاری داده های توالی در دسته های مجاور ناشی می شود: برای اینکه همه توالی ها در یک دسته با یک طول استاندارد مشخص مطابقت داشته باشند ، لازم است برخی توالی ها را پد یا کوتاه کنید.

بیایید نگاهی دقیق بیندازیم.

داده های توالی بالشتک

هنگام پردازش داده های توالی ، بسیار معمول است که نمونه های مختلف دارای طول های مختلف هستند. مثال زیر را در نظر بگیرید (متن را به صورت کلمات به صورت توکن در آورده اید):

[
  ["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 یک تابع سودمند برای کوتاه کردن و پد کردن لیست های پایتون به یک طول مشترک فراهم می کند: 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 پیکربندی کنید. لایه keras.layers.Embedding شده با mask_zero=True .
  • هنگام فراخوانی لایه هایی که از این استدلال پشتیبانی می کنند ، یک آرگومان mask به صورت دستی منتقل کنید.

لایه های تولید ماسک: Embedding و Masking

در زیر کاپوت ، این لایه ها یک سنسور ماسک ایجاد می کنند (تنسور 2 بعدی با شکل (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 با شکل (batch_size, sequence_length) ، که در آن هر ورودی False نشان می دهد که مرحله زمان مربوطه نباید در هنگام پردازش نادیده گرفته شود.

انتشار ماسک در API عملکردی و Sequential API

هنگام استفاده از 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(input, previous_mask) که می توانید با آن تماس بگیرید.

بنابراین ، می توانید خروجی روش compute_mask() یک لایه تولید کننده ماسک را به روش __call__ یک لایه ماسک __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 که در بعد زمان 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 (مانند 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 خود اضافه کنید. ماسک مرتبط با ورودی ها هر زمان که به لایه شما منتقل شود.

در اینجا یک مثال ساده در زیر آورده شده است: لایه ای که در کنار فاصله زمانی های پوشانده شده ، مقدار نرم افزاری را در بعد زمان (محور 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)))

خلاصه

این همان چیزی است که شما باید در مورد بالشتک گذاری و ماسک گذاری در کراس بدانید. به طور خلاصه:

  • "پوشاندن" این است که چگونه لایه ها می توانند بدانند که از چه قدم های خاصی در ورودی های توالی رد می شوند / آنها را نادیده می گیرند.
  • بعضی از لایه ها مولد ماسک هستند: Embedding می تواند از مقادیر ورودی یک ماسک ایجاد کند (اگر mask_zero=True ) و همچنین لایه Masking نیز می تواند تولید کند.
  • بعضی از لایه ها مصرف کننده ماسک هستند: آنها یک استدلال mask را در روش __call__ خود __call__ دهند. این مورد در مورد لایه های RNN وجود دارد.
  • در API عملکرد و Sequential API ، اطلاعات ماسک به طور خودکار منتشر می شود.
  • هنگام استفاده از لایه ها به صورت مستقل ، می توانید آرگومان های mask را به صورت دستی به لایه ها منتقل کنید.
  • می توانید به راحتی لایه هایی را بنویسید که ماسک فعلی را اصلاح می کنند ، ماسکی جدید تولید می کنند یا ماسک مربوط به ورودی ها را مصرف می کنند.