欢迎访问个人网络日志知行空间
论文:Attention Is All You Need
这是Google
2017年06月份发表的文章,在这篇文章中作者提出了后来对CV和NLP都产生了影响很大的Transformer
网络结构,成为继MLP
和RNN
后又一倍受关注的基础模型。用于序列化数据的学习以输出序列化的预测结果,如应用在NLP领域。Transformer
最早的提出就是应用在机器翻译领域,在WMT2014 英语翻译成德语的任务上,BLEU
指标达到了28.4
,比之前的SOTA
提升了2
个点。Transformer
中使用多头注意力层替换了之前序列转录模型中使用循环神经网络单元。
在RNN中,如上图,要计算 h t h_t ht必须先计算 h t − 1 h_{t-1} ht−1及其之前的所有输出,这导致模型的计算无法在时间上并行,导致运算效率比较低。此外,因时序信息是一步步向后传递的,因此对于序列早期的信息在后面的计算中有可能会丢掉,而存储 h t h_t ht当序列长度过长时又会占用过多的内存。而Transformer
结构使用自注意力机制,使得模型能够进行并行化计算,提升训练速度。
对于序列数据的学习,经典的结构就是编码-解码结构,编码器将输入序列 ( x 1 , x 2 , . . . , x n ) (x_1,x_2,...,x_n) (x1,x2,...,xn)映射成 ( z 1 , z 2 , . . . , z n ) (z_1,z_2,...,z_n) (z1,z2,...,zn),解码器以 z z z为输入得到 ( y 1 , y 2 , . . . , y m ) (y_1,y_2,...,y_m) (y1,y2,...,ym)作为输出,这里的输出过程是先输出 y 1 y_1 y1,再根据 y 1 y_1 y1输出 y 2 y_2 y2,再根据 y 1 , y 2 y_1,y_2 y1,y2再输出 y 3 y_3 y3,也称这种方式为自回归(auto-regressive)。Transformer
也是编码解码结构,其中编码解码模型都是由自注意力层和全连接层组成。其网络结构如下图:
如上图,编码器中的一个block由两个子层sublayer组成,分别是MultiHead Attension
层和MLP
层组成。MLP
层中使用了残差结构,并使用了Layer Normalization
,表示为 L a y e r N o r m ( x + S u b l a y e r ( x ) ) LayerNorm(x + Sublayer(x)) LayerNorm(x+Sublayer(x))。
解码器中除了使用了于编码器中相同的两个子层外还引入了第三种子层Masked Multi-Head Attention
层用于模型自回归的学习,保证在模型训练时t时刻不会看到 t t t时刻以后的序列信息,从而保证训练和预测的时候行为是一致的。
下面对上图中的各个组成单元分别进行介绍:
Embedding
这个词字面意思表示嵌入,这里介绍,Embedding
是将高维数据转换成低维数据,借此可将字词的稀疏向量进行向量化表示。常见的Embedding
由自然语言处理中的word embedding
,图神经网络中的node embedding
等。在这篇文章中作者介绍了NLP
中的Word Embedding
。
注意力函数可以看成是query
值和key-value
对到输出output
的一个映射,query/key/value
都是向量,output
和value
维度相同,output
是value
的加权和,每个value
的权重通过计算query
和key
的相似度得到的,相似度的计算也被称为compatibility function
,不同的注意力机制有不同的计算方法。
transformer
中使用的query
和key
是等长的,维度都为 d k d_k dk,output
和value
的维度是 d v d_v dv。transformer
中使用的query
和key
的相似度计算方式很简单,就是计算两个向量的内积再除以向量的维度,然后做softmax
得到权重值。
实际计算中,会将多个query/key/value
向量打包计算,写成矩阵的形式为:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dkQKT)V
这里因为有除以 d k \sqrt{d_k} dk,因此被称为scaled dot product attention
。之所以除以 d k \sqrt{d_k} dk是为了当序列长度比较大的时候还能比较好的衡量query
和key
之间的相似度,减少尺度导致的误差变化。
多头注意力机制 Multi-Head Attention
将前面介绍的attention
中的query/key/value
通过可学习参数的线性变换投影h
次,得到h个query/key/value
函数,将每个函数的输出并到一起再经过线性投影得到最终的输出。
计算公式为:
M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 , . . . , h e a d h ) W O MultiHead(Q,K,V) = Concat(head_1,...,head_h)W^O MultiHead(Q,K,V)=Concat(head1,...,headh)WO
其中,
h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) head_i=Attention(QW_i^Q, KW_i^K,VW^V_i) headi=Attention(QWiQ,KWiK,VWiV)
W i Q ∈ R d m o d e l × d k , W i K ∈ R d m o d e l × d k , W i V ∈ R d m o d e l × d v , W O ∈ R h d v × d m o d e l W_i^Q\in\mathbb{R}^{d_{model}\times d_k},W_i^K\in\mathbb{R}^{d_{model}\times d_k},W_i^V\in\mathbb{R}^{d_{model}\times d_v},W^O\in\mathbb{R}^{hd_{v}\times d_{model}} WiQ∈Rdmodel×dk,WiK∈Rdmodel×dk,WiV∈Rdmodel×dv,WO∈Rhdv×dmodel是线性投影的可学习参数。
从网络结构图中可以看到,在编码器中的注意力层和解码器的第一个注意力层,Q/K/V
使用的是同一个输入,因此这种注意力机制被称为自注意力机制。
普通的全连接层,其输入的shape:[N,C]
其中,N表示的是样本的数量,C
表示每个特征的维度,而point_wise
全连接层的输入shape:[N,L,C]
其中N表示的是样本的数量,L
表示句子的长度,C
表示的是单词的个数,然后每次全连接是作用在最后一个维度C
上的。
pytorch
中的nn.Linear
函数在处理3d
tensor时默认是作用在最后一维上的,可以写成下面形式:
fc = nn.Sequential(
nn.Linear(512, 12),
nn.ReLU(),
nn.Linear(12, 28),
)
t = torch.randn((3, 16, 512))
fc(t).shape
# torch.Size([3, 16, 28])
计算公式如下:
F F N ( x ) = m a x ( 0 , x W 1 + b 1 ) W 2 + b 2 FFN(x) = max(0, xW_1+b_1)W_2+b_2 FFN(x)=max(0,xW1+b1)W2+b2
前面介绍的attention中只是使用query/key
的形式将输出表示成了value
的加权和,这里没有输入序列的顺序信息,在RNN中是通过逐个词输出来学习序列信息的,而transformer
中是将一个序列一次性输入到模型中,并没有序列中每个单词的信息,因此,这里引入位置编码来表示输入序列的时序信息,并将其作为模型的输入。
对于长度为L的输入序列,要标识每个单词的位置信息,一种方式是给每个位置生成一个唯一的表示位置的向量。transformer
中使用如下的方式来计算输入序列的位置编码:
P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d m o d e l ) P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos, 2i)}=sin(pos/10000^{2i/d_{model}})\\ PE_{(pos, 2i+1)}=cos(pos/10000^{2i/d_{model}}) PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)
其中, d m o d e l d_{model} dmodel表示的是位置向量的维度,和Input Embedding
后得到的每个词的维度相同。 p o s pos pos表示长度为 L L L的序列中的第 p o s pos pos个单词, i i i表示位置向量 d m o d e l d_{model} dmodel维度上的第 i i i维。
使用pytorch
实现的位置编码函数为:
import torch
def position_encoding(
seq_len: int, dim_model: int, device: torch.device = torch.device("cpu"),
) -> Tensor:
pos = torch.arange(seq_len, dtype=torch.float, device=device).reshape(1, -1, 1)
dim = torch.arange(dim_model, dtype=torch.float, device=device).reshape(1, 1, -1)
phase = pos / 1e4 ** (dim // dim_model)
return torch.where(dim.long() % 2 == 0, torch.sin(phase), torch.cos(phase))
从上面的代码可以看到,Position Encoding
没有使用需要学习的参数,只是手动设计了表示序列位置信息的编码方式。
以transformer
用于翻译任务为例:
输入: x = I am cold
输出: y = 我冷
输入句子的dictionary
中有3
个词,则输入可以表示成:
word2index = {"I":0,"am":1,"cold":2}
输入句子的向量表示为:
x = [[[0],[1],[2]]
transformer
中输入的处理主要有input embedding
和position encoding
这两步,如下图:
对输入句子序列处理结束后将其输入到attention
中进行处理,其处理过程如下图所示:
上图中 L L L表示的序列的长度, d k d_k dk是attention
中使用的权重的维度, d k d_k dk的大小决定了模型的大小。上图,只描述了Single Head
的计算过程,对于Multi Head
,使用多组 W Q , W K , W V WQ,WK,WV WQ,WK,WV进行计算,然后将计算得到的结果再进行concatenate
即可。
上图描述了attention
的计算过程,在attention
之后的计算是point wise feed forward
。其计算过程表示如下图:
可以看到这里的FFN是作用在输入样本序列的每个单词向量上的,与之前常见的FFN
作用在整个样本上不同。
pytorch
中的nn.Linear
层处理3d
向量时,默认作用在最后一维进行计算,因此可以将attention
层输出的结果直接输入到nn.Linear
中。
fc = nn.Sequential(
nn.Linear(512, 12)
)
t = torch.randn((3, 16, 512))
fc(t).shape
# torch.Size([3, 16, 12])
使用pytorch
实现的transformer
可以将代码仓库。
- 1.http://colah.github.io/posts/2015-08-Understanding-LSTMs/
- 2.https://zhuanlan.zhihu.com/p/164502624
- 3.https://www.bilibili.com/video/BV1pu411o7BE/?spm_id_from=333.337.search-card.all.click&vd_source=e75f432df49764db96371bce27ab9fd5
欢迎访问个人网络日志知行空间