![]() | ![]() | ![]() | ![]() |
להכין
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 מספק פונקציית תועלת לרשימות 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
טיעון ידני כאשר קוראים שכבות שתומכות בטיעון הזה (למשל שכבות 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
בעת שימוש בממשק ה- Functional או ה- 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. - ב-Functional API וב-Sequential API, מידע המסיכה מופץ באופן אוטומטי.
- בעת שימוש בשכבות באופן עצמאי, אתה יכול לעבור את
mask
טיעונים לשכבות ידנית. - אתה יכול בקלות לכתוב שכבות שמשנות את המסכה הנוכחית, שמייצרות מסיכה חדשה, או שצורכות את המסכה המשויכת לכניסות.