TensorFlow2教程-掩碼和填充
在構建深度學習模型,特別是進行序列的相關任務時,經常會用到掩碼和填充技術
原文地址:https://doit-space.blog.csdn.net/article/details/102885635
tensorflow2.0完整教程:https://blog.csdn.net/qq_31456593/article/details/88606284
1 填充序列數據
處理序列數據時,常常會遇到不同長度的文本,例如處理某些句子時:
[
["The", "weather", "will", "be", "nice", "tomorrow"],
["How", "are", "you", "doing", "today"],
["Hello", "world", "!"]
]
[['The', 'weather', 'will', 'be', 'nice', 'tomorrow'],
['How', 'are', 'you', 'doing', 'today'],
['Hello', 'world', '!']]
計算機無法理解字符,我們一般將詞語轉換爲對應的id
[
[83, 91, 1, 645, 1253, 927],
[73, 8, 3215, 55, 927],
[71, 1331, 4231]
]
[[83, 91, 1, 645, 1253, 927], [73, 8, 3215, 55, 927], [71, 1331, 4231]]
由於深度信息模型的輸入一般是固定的張量,所以我們需要對短文本進行填充(或對長文本進行截斷),使每個樣本的長度相同。
Keras提供了一個API,可以通過填充和截斷,獲取等長的數據:
tf.keras.preprocessing.sequence.pad_sequences
raw_inputs = [
[83, 91, 1, 645, 1253, 927],
[73, 8, 3215, 55, 927],
[711, 632, 71]
]
# 默認左填充
padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(raw_inputs)
print(padded_inputs)
# 右填充需要設 padding='post'
padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(raw_inputs,
padding='post')
print(padded_inputs)
[[ 83 91 1 645 1253 927]
[ 0 73 8 3215 55 927]
[ 0 0 0 711 632 71]]
[[ 83 91 1 645 1253 927]
[ 73 8 3215 55 927 0]
[ 711 632 71 0 0 0]]
2 掩碼
現在所有樣本都獲得了同樣的長度,在求損失函數或輸出的時候往往需要知道那些部分是填充的,需要忽略。這種忽略機制就是掩碼。
keras中三種添加掩碼的方法:
- 添加一個keras.layer.Masking的網絡層
- 配置keras.layers.Embedding層的mask_zero=True
- 在支持mark的網絡層中,手動傳遞參數
2.1 掩碼生成層:Embedding和Masking
Embedding和Masking會創建一個掩碼張量,附加到返回的輸出中。
embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
mask_output= embedding(padded_inputs)
print(mask_output._keras_mask)
tf.Tensor(
[[ True True True True True True]
[ True True True True True False]
[ True True True False False False]], shape=(3, 6), dtype=bool)
# 使用掩碼層
masking_layer = layers.Masking()
unmasked_embedding = tf.cast(
tf.tile(tf.expand_dims(padded_inputs, axis=-1), [1, 1, 16]),
tf.float32)
masked_embedding = masking_layer(unmasked_embedding)
print(masked_embedding._keras_mask)
tf.Tensor(
[[ True True True True True True]
[ True True True True True False]
[ True True True False False False]], shape=(3, 6), dtype=bool)
mark是帶有2D布爾張量(batch_size, sequence_length),其中每個單獨的False指示在處理過程中應忽略相應的時間步數據。
2.2 函數API和序列API中使用掩碼
使用函數式API或序列API時,由Embedding或Masking層生成的掩碼將通過網絡傳播到能夠使用它們的任何層(例如RNN層)。Keras將自動獲取與輸入相對應的掩碼,並將其傳遞給知道如何使用該掩碼的任何層。
請注意,在子類化模型或圖層的call方法中,掩碼不會自動傳播,因此將需要手動將mask參數傳遞給需要的圖層。
下面展示了LSTM層怎麼自動接受掩碼
# 序列式API
model = tf.keras.Sequential([
layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True),
layers.LSTM(32),
])
# 函數式API
inputs = tf.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 = tf.keras.Model(inputs, outputs)
也可以直接在層間傳遞掩碼參數
可以處理掩碼的圖層(例如LSTM圖層)在其圖層中的__call__方法中有一個mask參數。
同時,產生掩碼的圖層(例如Embedding)會公開compute_mask(input, previous_mask),以獲取掩碼。
因此,可以執行以下操作:
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)
# 也可手動構造掩碼
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: id=4712, shape=(32, 32), dtype=float32, numpy=
array([[ 0.00255451, 0.00382517, -0.01020782, ..., -0.00271396,
-0.00487344, -0.00039328],
[ 0.00373022, 0.00497113, -0.00294418, ..., 0.00122669,
-0.00351841, 0.00316206],
[ 0.00103166, 0.00395402, -0.00160181, ..., 0.00493634,
-0.0062202 , -0.00349879],
...,
[-0.00031897, -0.00225923, 0.00185411, ..., 0.00372968,
-0.00407859, 0.00160439],
[ 0.00099225, 0.00042474, -0.00234293, ..., 0.00738544,
-0.00303122, -0.00415802],
[-0.0019643 , 0.00317807, 0.00983389, ..., -0.00375287,
-0.00127929, -0.00616587]], dtype=float32)>
在自定義網絡層中支持掩碼
有時,可能需要編寫生成掩碼的圖層(例如Embedding),或需要修改當前掩碼的圖層。
例如,產生張量的時間維度與其輸入不同的任何層,都需要修改當前掩碼,以便下游層能夠適當考慮掩碼的時間步長。
爲此,自建網絡層應實現layer.compute_mask()方法,該方法將根據輸入和當前掩碼生成一個新的掩碼。
大多數圖層都不會修改時間維度,因此無需擔心掩蓋。compute_mask()在這種情況下,默認行爲是僅通過當前蒙版。
TemporalSplit修改當前蒙版的圖層的示例。
# 數據拆分時的掩碼
class TemporalSplit(tf.keras.layers.Layer):
def call(self, inputs):
# 將數據沿時間維度一分爲二
return tf.split(inputs, 2, axis=1)
def compute_mask(self, inputs, mask=None):
# 將掩碼也一分爲二
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(
[[ True True True]
[ True True False]
[False False False]], shape=(3, 3), dtype=bool)
自己構建掩碼,把爲0的掩掉
class CustomEmbedding(tf.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 False False False]
[False True True True True True True True True True]], shape=(3, 10), dtype=bool)
直接用網絡層實現掩碼功能:call函數中接受一個mask參數,並用它來確定是否跳過某些時間步。
要編寫這樣的層,只需在call函數中添加一個mask=None參數即可。只要有可用的輸入,與輸入關聯的掩碼將被傳遞到圖層。
class MaskConsumer(tf.keras.layers.Layer):
def call(self, inputs, mask=None):
pass