MultiHeadAttention attention_mask [Keras,Tensorflow]示例

3okqufwl  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(124)

我正在努力为MultiHeadAttention Layer屏蔽我的输入。我正在使用Keras文档中的Transformer Block,注意力集中。到目前为止,我在网上找不到任何示例代码,如果有人能给我给予一个代码片段,我将不胜感激。
this页面中的Transformer块:

class TransformerBlock(layers.Layer):
    def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1):
        super(TransformerBlock, self).__init__()
        self.att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)
        self.ffn = keras.Sequential(
            [layers.Dense(ff_dim, activation="relu"), layers.Dense(embed_dim),]
        )
        self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)
        self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)
        self.dropout1 = layers.Dropout(rate)
        self.dropout2 = layers.Dropout(rate)

    def call(self, inputs, training):
        attn_output = self.att(inputs, inputs)
        attn_output = self.dropout1(attn_output, training=training)
        out1 = self.layernorm1(inputs + attn_output)
        ffn_output = self.ffn(out1)
        ffn_output = self.dropout2(ffn_output, training=training)
        return self.layernorm2(out1 + ffn_output)

屏蔽的文档可以在this链接下找到:
attention_mask:形状为[B,T,S]的布尔掩码,防止注意某些位置。布尔掩码指定哪些查询元素可以关注哪些关键元素,1表示关注,0表示不关注。对于缺失的批次维度和标头维度,可能会发生广播。
我唯一可以运行的是在层类外部创建一个掩码,作为numpy数组:

mask = np.ones((observations, sequence_length, sequence_length))
mask[X[:observations,:,0]==0]=0

然后在调用层时输入,Transformer块中唯一的变化是:

def call(self, inputs, mask, training):
    attn_output = self.att(inputs, inputs, attention_mask=mask)

然而,当在拟合时给定batch_size时,这当然不起作用,并且只对我的记忆中的5个观察值起作用,所以它没有任何意义。除此之外,我不认为这是屏蔽输入正确-一般来说,我很困惑如何屏蔽,给定的形状的注意力_掩码(观察,序列长度,序列长度)。我输入的形状是(observation,sequence_length,features)。这个输入被零填充,但是,当它到达Transformer块时,它已经通过了嵌入层和CNN。我尝试了各种方法来编写一个函数,该函数在使用不同的Tensor或Keras对象进行训练时创建遮罩。但我每次都犯错误。
我希望Tensorflow/Keras更流利的人能够提供一个例子。或者有人告诉我,考虑到我的架构,掩蔽是无用的。该模型运行良好。然而,我希望屏蔽可以帮助加快计算速度。我就是不明白。

bvjveswy

bvjveswy1#

也许有点晚了,但对于任何最终在这篇文章上寻找解决方案的人来说,这可能会有所帮助。
使用Transformer的一个典型场景是在NLP问题中,其中您有一批句子(为了简单起见,让我们假设它们已经被标记化)。请考虑以下示例:

sentences = [['Lorem', 'ipsum', 'dolor', 'sit', 'amet'], ['Integer', 'tincidunt', 'in', 'arcu', 'nec', 'fringilla', 'suscipit']]

正如你所看到的,我们有两个不同长度的句子。为了在tensorflow模型中学习它们,我们可以用一个特殊的token填充最短的token,比如说'[PAD]',然后将它们馈送到Transformer模型中,就像你提议的那样。因此:

sentences = tf.constant([['Lorem', 'ipsum', 'dolor', 'sit', 'amet', '[PAD]', '[PAD]'], ['Integer', 'tincidunt', 'in', 'arcu', 'nec', 'fringilla', 'suscipit']])

同样假设我们已经有了从某个语料库中提取的标记词汇表,例如1000标记词汇表,我们可以定义一个StringLookup层,将我们的一批句子转换为给定词汇表的数字投影。我们可以指定哪个token用于masking

lookup = tf.keras.layers.StringLookup(vocabulary=vocabulary, mask_token='[PAD]')
x = lookup(sentences)
# x is a tf.Tensor([[2, 150, 19, 997, 9, 0, 0], [72, 14, 1, 1, 960, 58, 87]], shape=(2, 7), dtype=int64)

我们可以看到[PAD]标记Map到词汇表中的0值。
典型的下一步是将此Tensor馈送到Embedding层中,类似于以下内容:

embedding = tf.keras.layers.Embedding(input_dim=lookup.vocabulary_size(), output_dim=64, mask_zero=True)

这里的关键是参数mask_zero。根据documentation,这个参数意味着:
布尔值,输入值0是否是一个特殊的“填充”值,应该屏蔽掉.
这允许embedding层为后续层生成掩码,以指示哪些位置应该关注,哪些不应该关注。该掩码可以通过以下方式访问:

mask = embedding.compute_mask(sentences)
# mask is a tf.Tensor([[True, True, True, True, True, False, False], [True, True, True, True, True, True, True]], shape=(2, 7), dtype=bool)

嵌入的Tensor具有以下形式:

y = embedding(sentences)
# y is a tf.Tensor of shape=(2, 7, 64), dtype=float32)

为了在MultiHeadAttention层中使用mask,必须重新塑造掩膜以满足形状要求,根据文档,形状要求为[B, T, S],其中B表示 * 批量大小 * T表示查询大小(在我们的示例中为7),S表示键大小(如果我们使用self attention,则仍然为7)。同样,在多头注意层中,我们必须注意头的数量H。使用此输入创建兼容掩码的最简单方法是通过广播:

mask = mask[:, tf.newaxis, tf.newaxis, :]
# mask is a tf.Tensor of shape=(2, 1, 1, 7), dtype=bool) -> [B, H, T, S]

最后我们可以像下面这样填充MultiHeadAttention层:

mha = tf.keras.layers.MultiHeadAttention(num_heads=4, key_dim=64)
z = mha(y, y, attention_mask=mask)

因此,为了使用带有掩码的TransformerBlock层,您应该向call方法添加mask参数,如下所示:

def call(self, inputs, training, mask=None):
    attn_output = self.att(inputs, inputs, attention_mask=mask)
    ...

在调用MultiHeadAttention层的层/模型中,必须传递/传播Embedding层生成的掩码。

相关问题