BERT复现-详解Attention
本篇文章介绍使用PyTorch实现BERT中的Self-Attention,本文适用范围:阅读过transformer和bert论文,了解其架构,但是不了解其代码实现的学习者
BERT中的Self-Attention架构
BERT中的Self-Attention使用的是与Tranformer中相同的架构,在transformer论文中其示意图如下:
其公式表述为 $Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V$
因此其输入为Query、Key、Value三个Tensor, 其大小为[batch_size, head_num, seq_len, d_model/head_num], 其中d_model/head_num表示多头中拆分到每一个头里的参数大小head_size,seq_len表示文本长度,以及可选的mask,输出为这几个向量经过多次运算后的值。
整体流程如下:
- 首先Query与Key之间进行矩阵乘法,将Key的Size调整为[batch_size, head_num, d_model/head_num, seq_len], 相乘后得到大小为[batch_size, head_num, seq_len, seq_len]的tensor
- 然后做scale,即除以$\sqrt{head_size}$ 前两步合并,即代码:
1
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(query.size(-1))
- Mask操作通过tensor自带的mask_fill方法实现,该函数会根据mask中为1的元素所在的索引,在tensor中相同的的索引处替换为特定的value。此处将所有mask为0处的值替换为1e-9,在softmax时其值会趋近于0。
1 2
if mask is not None: scores = scores.masked_fill(mask == 0, -1e9)
- 然后是Softmax操作,指定维度为-1
1
p_attn = F.softmax(scores, dim=-1)
- 最后p_attn与value做矩阵乘法, 结果的Size仍为为[batch_size, head_num, d_model/head_num, seq_len]
因此最终代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch.nn as nn
import torch
import math
import torch.nn.functional as F
class Attention(nn.Module):
def __init__(self):
super().__init__()
def forward(self, query, key, value, mask=None, dropout=None):
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(query.size(-1))
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
p_attn = F.softmax(scores, dim=-1)
if dropout is not None:
p_attn = dropout(p_attn)
return torch.matmul(p_attn, value), p_attn
至于为什么要返回一个p_attn,这就涉及到cross-attention的操作了,后续会写文章阐明
This post is licensed under CC BY 4.0 by the author.