![]() | ![]() | ![]() | ![]() |
ติดตั้ง
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
บทนำ
การมาสก์ เป็นวิธีการบอกเลเยอร์การประมวลผลตามลำดับว่าการประทับเวลาบางอย่างในอินพุตขาดหายไปดังนั้นจึงควรข้ามไปเมื่อประมวลผลข้อมูล
Padding เป็นรูปแบบพิเศษของการมาสก์โดยขั้นตอนที่มาสก์จะอยู่ที่จุดเริ่มต้นหรือจุดสิ้นสุดของลำดับ 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 จัดเตรียมฟังก์ชันยูทิลิตี้เพื่อตัดทอนและวางรายการ 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_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)
ดังที่คุณเห็นจากผลลัพธ์ที่พิมพ์มาสก์คือเทนเซอร์บูลีน 2 มิติที่มีรูปร่าง (batch_size, sequence_length)
ซึ่งแต่ละรายการ False
ระบุว่าควรละเว้นการประทับเวลาที่สอดคล้องกันในระหว่างการประมวลผล
การแพร่กระจายมาสก์ใน 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([[-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)
การเลือกใช้เพื่อปิดบังการแพร่กระจายบนเลเยอร์ที่เข้ากันได้
เลเยอร์ส่วนใหญ่ไม่ได้แก้ไขมิติเวลาดังนั้นไม่จำเป็นต้องแก้ไขมาสก์ปัจจุบัน อย่างไรก็ตามพวกเขาอาจยังคงต้องการที่จะ เผยแพร่ มาสก์ปัจจุบันโดยไม่เปลี่ยนแปลงไปยังเลเยอร์ถัดไป นี่คือพฤติกรรมการเลือกเข้าร่วม ตามค่าเริ่มต้นเลเยอร์ที่กำหนดเองจะทำลายมาสก์ปัจจุบัน (เนื่องจากเฟรมเวิร์กไม่มีทางบอกได้ว่าการเผยแพร่มาสก์นั้นปลอดภัยหรือไม่)
หากคุณมีเลเยอร์แบบกำหนดเองที่ไม่ได้ปรับเปลี่ยนมิติเวลาและหากคุณต้องการให้สามารถเผยแพร่รูปแบบการป้อนข้อมูลปัจจุบันได้คุณควรตั้งค่า 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)))
สรุป
นั่นคือทั้งหมดที่คุณต้องรู้เกี่ยวกับ padding & masking ใน Keras สรุป:
- "การกำบัง" คือวิธีที่เลเยอร์ต่างๆสามารถรู้ได้ว่าเมื่อใดควรข้าม / ละเว้นการประทับเวลาบางอย่างในอินพุตลำดับ
- ชั้นบางคนมีหน้ากากกำเนิด:
Embedding
สามารถสร้างหน้ากากจากค่าที่ป้อนเข้า (ถ้าmask_zero=True
) และเพื่อให้สามารถMasking
ชั้น - บางเลเยอร์เป็นผู้บริโภคมาสก์: แสดงอาร์กิวเมนต์มาส
mask
ในเมธอด__call__
นี่เป็นกรณีของเลเยอร์ RNN - ใน Functional API และ Sequential API ข้อมูลมาสก์จะถูกเผยแพร่โดยอัตโนมัติ
- เมื่อใช้เลเยอร์ในแบบสแตนด์อโลนคุณสามารถส่งผ่านอาร์กิวเมนต์มาส
mask
ไปยังเลเยอร์ได้ด้วยตนเอง - คุณสามารถเขียนเลเยอร์ที่ปรับเปลี่ยนมาสก์ปัจจุบันที่สร้างมาสก์ใหม่หรือใช้มาสก์ที่เกี่ยวข้องกับอินพุตได้อย่างง่ายดาย