اخفاء وحشو مع Keras

تنظيم صفحاتك في مجموعات يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

يثبت

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

مقدمة

اخفاء هو وسيلة لمعرفة طبقات تجهيز تسلسل أن بعض timesteps في مدخلا مفقودة، وبالتالي يجب أن يتم تخطي عند معالجة البيانات.

الحشو هو شكل خاص من اخفاء حيث هي الخطوات ملثمين في بداية أو نهاية التسلسل. يأتي الحشو من الحاجة إلى تشفير بيانات التسلسل إلى دفعات متجاورة: من أجل جعل جميع التسلسلات في الدفعة تتناسب مع طول قياسي معين ، من الضروري حشو بعض التسلسلات أو اقتطاعها.

دعونا نلقي نظرة فاحصة.

بيانات تسلسل الحشو

عند معالجة بيانات التسلسل ، من الشائع جدًا أن يكون للعينات الفردية أطوال مختلفة. خذ بعين الاعتبار المثال التالي (النص الذي تم ترميزه على هيئة كلمات):

[
  ["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 طبقة مع 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 يشير دخول ان خطوة زمنية المقابلة يجب أن يتم تجاهل أثناء المعالجة.

انتشار القناع في واجهة برمجة التطبيقات الوظيفية وواجهة برمجة التطبيقات المتسلسلة

عند استخدام API الوظيفي أو API متسلسل، قناع التي تم إنشاؤها بواسطة Embedding أو Masking سيتم نشر طبقة من خلال الشبكة لأي طبقة قادرة على استخدامها (على سبيل المثال، والطبقات RNN). سيقوم Keras تلقائيًا بجلب القناع المقابل لإدخال ما وتمريره إلى أي طبقة تعرف كيفية استخدامه.

على سبيل المثال، في النموذج التتابعي التالي، 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 طبقة) لديها 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 طبقة يسلسل على البعد الزمني، سوف تحتاج إلى تعديل القناع الحالي بحيث طبقات المصب سوف يكون قادرا على اتخاذ صحيح 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.
  • في API الوظيفية و API المتسلسل ، يتم نشر معلومات القناع تلقائيًا.
  • عند استخدام طبقات بطريقة مستقل، يمكنك تمرير mask الحجج إلى طبقات يدويا.
  • يمكنك بسهولة كتابة الطبقات التي تعدل القناع الحالي ، والتي تنشئ قناعًا جديدًا ، أو التي تستهلك القناع المرتبط بالمدخلات.