赞
踩
MultiHeadAttention多头注意力作为Transformer的核心组件,其主要由多组自注意力组合构成。
在NLP任务中,自注意力能够根据上下文词来重新构建目标词的表示,其之所以被称之为注意力,在于从上下文词中去筛选目标词更需要关注的部分,比如"他叫小明","他"这个词更应该关注"小明"这个上下文。
上图提示了一个输入为两个单词[Thinking, Matchines]的序列在经过自注意力构建后的变换过程:
完成了结构的分析后,接下来,我们考虑一个新的问题,为什么自注意力机制会有效?通过三组矩阵Q、K、V我们获得了原来的输入三种不同表征形式,其通过query-key的比较来衡量目标词和上下文词的相似性关联,通过value来提取词的本质特征,最终通过自注意力机制,我们建立了结合上下文信息的词的新特征向量,其本质是特征提取器。
多头注意力是多组自注意力构件的组合,上文已经提到自注意力机制能帮助建立包括上下文信息的词特征表达,多头注意力能帮忙学习到多种不同类型的上下文影响情况,比如"今天阳光不错,适合出去跑步",在不同情景下,"今天"同"阳光"、"跑步"的相关性是不同,特别是头越多,越有利于捕获更大更多范围的相关性特征,增加模型的表达能力。
上图描述了多头注意力的处理过程,其实际上将多个自注意机制的产出再经过参数矩阵得到一个新输出。我们将上述自注意步骤引入多头情况,介绍如何通过矩阵来计算,其由3组自注意力组合,输入为2个单词的序列。
上图左侧为单点积注意力Dot-Product Attention组件的结构(当Q,K,V为同一输入时称之为自注意力),右侧为多个单注意力组件组成多头注意力,以下是paddle的实现代码:
- class DotProductAttention(nn.Layer):
- def __init__(self, query_size, query_vec_size, hidden_vec_size, **kwargs):
- """
- input:
- query_size: int, 序列长度(词数)
- query_vec_size: int, 词向量长度
- hidden_vec_size: int, 输出词向量长度
- """
- super(DotProductAttention, self).__init__(**kwargs)
- self.query_size = query_size
- self.query_vec_size = query_vec_size
- self.hidden_vec_size = hidden_vec_size
- # 线性变换层
- self.W_q = nn.Linear(query_vec_size, hidden_vec_size)
- self.W_k = nn.Linear(query_vec_size, hidden_vec_size)
- self.W_v = nn.Linear(query_vec_size, hidden_vec_size)
-
- def forward(self, queries, keys, values, valid_lens):
- """
- input:
- queries,keys,values: tensor([batch_size, query_size, query_vec_size]), 输入
- valid_lens: tensor([batch_size]), 序列中有效长度
- output:
- output: tensor([batch_size, query_size, hidden_vec_size]), 输出
- """
- #1. Linear: queries, keys, values的线性变换, out shape: [batch_size, query_size, hidden_vec_size]
- queries = self.W_q(queries)
- keys = self.W_k(keys)
- values = self.W_v(values)
- # 2. score, shape: [batch_size, query_size, query_size]
- scores = paddle.bmm(queries, keys.transpose((0, 2, 1))) / math.sqrt(self.hidden_vec_size)
- scores = scores.reshape([-1, self.query_size])
- # 3. mask, shape: [batch_size * query_size, query_size]
- mask = paddle.arange(self.query_size, dtype=paddle.float32)[None, :] < paddle.repeat_interleave(valid_lens, self.query_size)[:, None]
- scores[~mask] = float(-1e6)
- # 4. softmax [batch_size, query_size, query_size]
- scores = scores.reshape([-1, self.query_size, self.query_size])
- scores = nn.functional.softmax(scores, axis=-1)
- # 5. output [batch_size, query_size, query_size] * [batch_size, query_size, hidden_vec_size]
- return paddle.bmm(scores, values)

在实际中,多个单注意力组件的计算可以通过同一矩阵进行并行计算,如第2节所描述,以下完成最终多头注意力的代码,可以看出其同单注意力的代码几乎差不多:
- class MultiHeadAttention(nn.Layer):
- def __init__(self, query_size, query_vec_size, hidden_vec_size, output_vec_size, head_num, **kwargs):
- """
- input:
- query_size: int, 序列长度(词数)
- query_vec_size: int, 词向量长度
- hidden_vec_size: int, 变换层词向量长度
- output_vec_size: int, 输出层词向量长度
- head_num: int, 头数
- """
- super(MultiHeadAttention, self).__init__(**kwargs)
- self.query_size = query_size
- self.query_vec_size = query_vec_size
- self.hidden_vec_size = hidden_vec_size
- self.output_vec_size = output_vec_size
- self.head_num = head_num
- # 线性变换层
- self.W_q = nn.Linear(query_vec_size, hidden_vec_size * head_num)
- self.W_k = nn.Linear(query_vec_size, hidden_vec_size * head_num)
- self.W_v = nn.Linear(query_vec_size, hidden_vec_size * head_num)
- self.W_o = nn.Linear(hidden_vec_size * head_num, output_vec_size)
-
- def forward(self, queries, keys, values, valid_lens):
- """
- input:
- queries,keys,values: tensor([batch_size, query_size, query_vec_size]), 输入
- valid_lens: tensor([batch_size]), 序列中有效长度
- output:
- output: tensor([batch_size, query_size, hidden_vec_size]), 输出
- """
- #1. Linear: queries, keys, values的线性变换, out shape: [batch_size, query_size, hidden_vec_size * head_num]
- queries = self.W_q(queries)
- keys = self.W_k(keys)
- values = self.W_v(values)
- # 2. score, shape: [batch_size * head_num, query_size, query_size]
- queries = queries.reshape([-1, self.query_size, self.hidden_vec_size, self.head_num])\
- .transpose((0, 3, 1, 2))\
- .reshape([-1, self.query_size, self.hidden_vec_size])
- keys = keys.reshape([-1, self.query_size, self.hidden_vec_size, self.head_num])\
- .transpose((0, 3, 1, 2))\
- .reshape([-1, self.query_size, self.hidden_vec_size])
- values = values.reshape([-1, self.query_size, self.hidden_vec_size, self.head_num])\
- .transpose((0, 3, 1, 2))\
- .reshape([-1, self.query_size, self.hidden_vec_size])
- scores = paddle.bmm(queries, keys.transpose((0, 2, 1))) / math.sqrt(self.hidden_vec_size)
- scores = scores.reshape([-1, self.query_size])
- # 3. mask, shape: [batch_size * head_num * query_size, query_size]
- mask = paddle.arange(self.query_size, dtype=paddle.float32)[None, :] < paddle.repeat_interleave(valid_lens, self.query_size * self.head_num)[:, None]
- scores[~mask] = float(-1e6)
- # 4. softmax [batch_size, query_size * head_num, query_size]
- scores = scores.reshape([-1, self.head_num, self.query_size, self.query_size])
- scores = nn.functional.softmax(scores, axis=-1)
- # 5. output [batch_size, query_size, head_num * hidden_vec_size]
- z = paddle.bmm(scores.reshape([-1, self.query_size, self.query_size]), values)
- z = z.reshape([-1, self.head_num, self.query_size, self.hidden_vec_size]).transpose((0, 2, 1, 3))
- # 6. output linear
- return self.W_o(z.reshape([-1, self.query_size, self.head_num * self.hidden_vec_size]))

按论文Attention Is All You Need的观点,上图为 self-attention同cnn、rnn在复杂度上的比较,其中n是指序列的长度,d是序列词向量的维度,k表示卷积核的大小,我们为什么将self-attention作为序列数据特征编码或解码器,主要基于三点理由:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。