TensorFlow2教程-掩碼和填充

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章