השב / י לאירוע TensorFlow Everywhere המקומי שלך היום!
דף זה תורגם על ידי Cloud Translation API.
Switch to English

מיסוך וריפוד עם קרס

צפה ב- TensorFlow.org הפעל בגוגל קולאב צפה במקור ב- GitHub הורד מחברת

להכין

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

מבוא

מיסוך הוא דרך לספר לשכבות עיבוד רצף כי חסר צעדים מסוימים בקלט, ולכן יש לדלג עליו בעת עיבוד הנתונים.

ריפוד הוא צורה מיוחדת של מיסוך כאשר הצעדים המוסווים נמצאים בתחילתו או בסוף הרצף. ריפוד נובע מהצורך לקודד נתוני רצף לקבוצות רציפות: על מנת להפוך את כל הרצפים במנה שמתאימה לאורך תקני נתון, יש צורך לרפד או לקטוע כמה רצפים.

בואו נסתכל מקרוב.

נתוני רצף ריפוד

בעת עיבוד נתוני רצף, מקובל מאוד שלדגימות בודדות יש אורכים שונים. שקול את הדוגמה הבאה (טקסט מסומן כמילים):

[
  ["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) במקרה זה), יש (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

מתחת למכסה המנוע, שכבות אלה ייצרו טנסור מסכה (טנסור דו-ממדי עם צורה (batch, sequence_length) אורך (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) , כאשר כל ערך False בודד מציין שיש להתעלם (batch_size, sequence_length) המתאים במהלך העיבוד.

התפשטות מסכות ב- API הפונקציונלי וב- 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),]
)

זה המקרה גם למודל ה- 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__ לשיטת __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)

הצטרפות למסכת התפשטות בשכבות תואמות

רוב השכבות אינן משנות את ממד הזמן, ולכן אינן צריכות לשנות את המסכה הנוכחית. עם זאת, יתכן שהם עדיין ירצו להיות מסוגלים להפיץ את המסכה הנוכחית, ללא שינוי, לשכבה הבאה. זוהי התנהגות opt-in. כברירת מחדל, שכבה מותאמת אישית תהרוס את המסכה הנוכחית (מכיוון שלמסגרת אין שום דרך לדעת אם ניתן להפיץ את המסכה בצורה בטוחה).

אם יש לך שכבה מותאמת אישית שאינה משנה את מימד הזמן, ואם אתה רוצה שהיא תוכל להפיץ את מסכת הקלט הנוכחית, עליך להגדיר 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)))

סיכום

זה כל מה שאתה צריך לדעת על ריפוד ומסכות בקרס. לסיכום:

  • "מיסוך" הוא כיצד שכבות מסוגלות לדעת מתי לדלג על / להתעלם מצעדים מסוימים בכניסות רצף.
  • שכבות מסוימות הן מחוללות מסיכות: Embedding יכולה ליצור מסכה מערכי קלט (אם mask_zero=True ), וכך גם שכבת Masking .
  • שכבות מסוימות הן צרכני מסכות: הם חושפים טיעון mask בשיטת __call__ שלהם. זה המקרה לשכבות RNN.
  • ב- API הפונקציונלי וב- API הסיקוונציאלי, מידע המסכה מועבר באופן אוטומטי.
  • בעת שימוש בשכבות באופן עצמאי, תוכל להעביר את ארגומנטים mask לשכבות באופן ידני.
  • אתה יכול לכתוב בקלות שכבות המשנות את המסכה הנוכחית, שיוצרות מסכה חדשה, או שצורכות את המסכה המשויכת לקלטים.