讲一下自注意力机制的原理,参考李宏毅老师的视频

        假设你有一个句子,'你是人',现在要求翻译‘是’这个字,那在注意力机制中这三个字分别代表什么呢?

q:query ,也就是目标词,是

k:key,目标词周围的词,也可以说是上下文,你,人

v:value,目标词输出的向量,在自注意力机制中和k维度相同,你,人

自注意力机制,就是一个句子中的每一个词都当成,q,k,v参与计算,假设你输入的句子通过word embedding层得到了I矩阵,由a1,a2....ai组成,其中a1,a2....ai都是词向量。

qi = Wq * ai

同理ki = Wk * ai,vi = Wv * ai。

由此我们得到了三个矩阵Q = Wq * I, K = Wk * I, V = Wv * I

各个矩阵的维度:

        I : (batch_size, sentence_len, embedding_size)

        Q:(batch_size, sentence_len, q_size)

        K:(batch_size, sentence_len, k_size = q_size)

        V:(batch_size, sentence_len, v_size)

由Q * KT得到的矩阵A我们称为Attention块,再通过一个激活函数得到矩阵A'

        A,A':(batch_size, sentence_len, sentence_len) 

 

 由上图的过程,我们可以推得公式:

output = softmax(\frac{Q*K^{T}}{\sqrt{dk}})*V

有些人可能要问了,这个\sqrt{dk}是什么意思呢,在Q*KT这一步中,假设q与k的原分布是均值为0方差为1的分布,那A = Q* KT这一步中就把方差扩大成了dk,则需要除以\sqrt{dk}来维持正态分布,使得softmax的梯度更加平缓,反向传播时候的参数更替没有那么剧烈。

 pytorch代码

class SelfAttention(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, Q, K, V, attn_mask):
        '''
        Q: [batch_size, n_heads, len_q, d_k]
        K: [batch_size, n_heads, len_k, d_k]
        V: [batch_size, n_heads, len_v(=len_k), d_v]
        attn_mask: [batch_size, n_heads, seq_len, seq_len]
        '''
        scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)  # scores : [batch_size, n_heads, len_q, len_k]
        scores.masked_fill_(attn_mask, -1e9)  # Fills elements of self tensor with value where mask is True.

        attn = nn.Softmax(dim = - 1)(scores)
        context = torch.matmul(attn, V)  # [batch_size, n_heads, len_q, d_v]
        return context

 代码里面有一个attn_mask,

上面 Self Attention 的计算过程中,我们通常使用 mini-batch 来计算,也就是一次计算多句话,即 X的维度是 [batch_size, sequence_length],sequence_length​是句长,而一个 mini-batch 是由多个不等长的句子组成的,我们需要按照这个 mini-batch 中最大的句长对剩余的句子进行补齐,一般用 0 进行填充,这个过程叫做 padding

但这时在进行 softmax 就会产生问题。回顾 softmax 函数,e0 是 1,是有值的,这样的话 softmax 中被 padding 的部分就参与了运算,相当于让无效的部分参与了运算,这可能会产生很大的隐患。因此需要做一个 mask 操作,让这些无效的区域不参与运算,一般是给无效区域加一个很大的负数偏置,即-1e9.

def get_attn_pad_mask(seq_q, seq_k):

    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    # eq(zero) is PAD token
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)  # [batch_size, 1, len_k], True is masked
    return pad_attn_mask.expand(batch_size, len_q, len_k)  # [batch_size, len_q, len_k]

这样我们就完成了一个简单的单头注意力机制.

多头注意力机制,嗯~~,原理其实和单头一样,唯一不同的就是把embedding_size进行了拆分,使得模型可以提取多重语义,然后增加了一个超参数n_heads,其他没什么特别的,代码也贴一下吧.

class MultiHeadAttention(nn.Module):
    def __init__(self):
        super().__init__()
        self.W_Q = nn.Linear(embeding_size, d_k * n_heads, bias=False)
        self.W_K = nn.Linear(embeding_size, d_k * n_heads, bias=False)
        self.W_V = nn.Linear(embeding_size, d_k * n_heads, bias=False)
        self.fc = nn.Linear(n_heads * d_k, embeding_size, bias=False)
        self.ln = nn.LayerNorm(embeding_size)
        self.attn = SelfAttention()
    def forward(self, input_Q, input_K, input_V, attn_mask):
        '''
        input_Q: [batch_size, len_q, d_model]
        input_K: [batch_size, len_k, d_model]
        input_V: [batch_size, len_v(=len_k), d_model]
        attn_mask: [batch_size, seq_len, seq_len]
        '''
        residual, batch_size = input_Q, input_Q.size(0)
        # (B, S, D) -proj-> (B, S, D_new) -split-> (B, S, H, W) -trans-> (B, H, S, W)
        Q = self.W_Q(input_Q).view(batch_size, -1, n_heads, d_k).transpose(1,2)  # Q: [batch_size, n_heads, len_q, d_k]
        K = self.W_K(input_K).view(batch_size, -1, n_heads, d_k).transpose(1,2)  # K: [batch_size, n_heads, len_k, d_k]
        V = self.W_V(input_V).view(batch_size, -1, n_heads, d_k).transpose(1,2)  # V: [batch_size, n_heads, len_v(=len_k), d_v]

        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1) # attn_mask : [batch_size, n_heads, seq_len, seq_len]

        # context: [batch_size, n_heads, len_q, d_v], attn: [batch_size, n_heads, len_q, len_k]
        context = self.attn(Q, K, V, attn_mask)
        context = context.transpose(1, 2).reshape(batch_size, -1, n_heads * d_k) # context: [batch_size, len_q, n_heads * d_v]
        output = self.fc(context) # [batch_size, len_q, d_model]
        return self.ln(output + residual)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值