大体概述

目前BERT很火,所以我决定了解一下BERT所用的技术基础:Transformer。Transformer就是变形金刚,性能非常强大,下面记录一下我对Transformer的理解。

在Sequence-to-Sequence应用里面,最常被用到的模型肯定是RNN网络。但RNN无法并行计算,速度很慢。当然,可以使用CNN来代替RNN,但效果依旧比不上RNN。

而Transformer利用self-attention结构,则可以完全代替RNN来完成seq2seq任务,而且性能更好。

如果不了解细节,可以认为,Transformer和RNN一样可以做seq2seq任务,只是内部结构从无法并行的RNN变成了可以并行的self-attension layer。

在RNN中,无法并行计算每一个输出,例如计算$b^2$前,需要先计算出$b^2$的结果,而在self-attention layer里,可以并行计算全部的输出$b^1,b^2,b^3,b^4$。


计算细节

self-attention针对每个时间步的输入$x^i$,会先乘一个矩阵$W$,计算出$a^i$,接着,对$a^i$计算出3个向量$q$,$k$,$v$。

  • $q:$query,用于主动计算与其他时间步的匹配度的向量
    $q^i=W^qa^i$
  • $k$:key,用来被其他时间步计算匹配度的向量
    $k^i=W^ka^i$
  • $v$:提取的信息向量
    $v^i=W^va^i$

其中$x^i$是word的on-hot形式,$a^i$是word的embedding形式

对一个word而言,最基础的表达方式肯定是on-hot,但on-hot的维度太高,因此需要将on-hot转换为Embedding的形式。

在NLP中,Embedding有两种使用方法:

  • 使用一组预训练过的权重$W$,来将word转换为embedding的格式,且在训练期间权重是固定的,不会改变
  • 使用一组随机初始化的(或者预训练过的)权重$W$,但这组权重在训练期间是trainable的

权重$W$的shape是$(vocabulary\_size, embedding\_dim)$,$vocabulary\_size$是on-hot的维度,$embedding\_dim$是embedding的维度,在transformer paper里的设置是512

在 transformer paper里,选择的是第二种方法,在训练期间会改变$W$的权重。

接着,会利用$q^i$与$k^j$两个向量,来计算第$i$个时间步与第$j$个时间步的相关性,记为$\alpha_{i,j}$。

例如计算第1个时间步与其他时间步的相关性,则公式为:

$$ \alpha_{1,i}=q^1\cdot k^i/\sqrt d $$

其中,$d$是两个向量的维度,因为维度越高那么它们的差距越大,用$\sqrt d$可以降低维度太高带来的影响。

GIF 2021-2-17 21-27-58

最后,做一个softmax,将$\alpha$转换为$\hat \alpha$

公式为:

$$ \hat\alpha_{1,i}=\exp(a_{1,i})/\sum_j{\exp(a_{1,j})} $$

1613568969002

最终的输出$b^1$则是这些向量与v^i的点乘的和

$$ b^1=\sum_i{\hat \alpha_{1,i}\cdot v^i} $$

可以看到,$b^1$输出会考虑整个sequence中的每个时间步。

接下来,用同样的方法算出$b^2,b^3$……可以看到,$b^2$的计算与$b^1$可以并行的来做。

首先:

  • 矩阵$q,k,v$可以用并行计算出来

1613569370418

  • 所有的$\alpha_{ij}$也可以一个矩阵运算并行的计算出来:

1613569442448

也就是说,并行的计算这些矩阵运算,快速并行的算出$b^1, b^2,b^3,b^4$。


Multi-head Self-attention

可以在每个时间步计算多组$q,k,v$向量,例如下面这个例子,是2 head的 self-attention结构。这样就可以计算出2个$b$。

1613570072304

最后可以利用矩阵$W^o$把两个向量$b$变换为一个:

1613570208546


位置信息

目前看来,transformer性能很强大,每个时间步的输出都考虑到了全部时间步,但有一个问题,就是这些时间步不知道自己的顺序,而不像RNN,会考虑到前面的输出。

为了让模型考虑的时间性,会把一个顺序相关的向量$e^i$加到$a^i$中,这个$e^i$是人工设计的,而不是可学习参数。

实际上在原论文中,尝试过使用一个可学习的向量和公式计算的固定向量,发现没什么区别,所以就选择了固定的向量。其公式如下:

$$ PE(pos,2i)=sin(pos/10000^{2i/d_{model}})\\ PE(pos,2i)=cos(pos/10000^{2i/d_{model}}) $$

其中

  • $pos$是单词在句子中的位置,例如第3个单词的$pos$就是3
  • $i$是$a^j$向量的第$i$维,例如$a^j$有512维,那么$i$就会从1到512

1613570386702

这里有一个问题,为什么不直接把位置信息直接加到$x^i$里,作为维度呢?实际上,直接$e^i+a^i$与直接加到$x^i$里有异曲同工之妙。

假设这样做了,把一个on-hot向量$p_i$追加到$x_i$里面,那么$Wx_i=a_i$就变成了:

$$ [W^I|W^P][x^{iT}|p^{iT}]^T $$

如下图所示,可以转换为

$$ W^Ix_i+W^Pp_i $$

那么也相当于$W^Ix_i$就是$a^i$,而$W^Pp^i$就是$e^i$,结果是一样的。

1613570494971


完整的Encoding

输入$x_i$获取$b_i$的过程是encoding的过程的一步,例如中文翻译英文的模型,会分为两步:

  1. 把中文encode成embedding
  2. 将embedding做decoding,转换成英文

实际上,完整的encoding过程不只是输出$b$就ok了,而是要叠加多个self-attention layer。

完整的Transformer大概的结构如下:

1613571262999

可以看到,Transformer的encoding部分,会重复$N$次Multi-Head Attention+Add&Norm。

其中,Add&Norm会把输出的$b^i$和输入$a^i$加在一起,计算出$b'$ ,然后做一个Layer Normalization,关于Layer Normalization的介绍,可以参考各种 Normalization 的简介及区分这篇文章,它也是RNN中最常用的方法。

Add&Norm是为了实现残差连接(residual connect),与ResNet的思想一样。

再之后,会有Feed Forward步骤,来处理每个时间步的输出,之后再做一个Add&Norm。


完整的Decoding

在encoding计算出embedding之后,会做decoding,例如中文转换为embedding之后,再把embedding利用decoding转换为英文。

可以看到,decoding里面,第一层是masked multi-head attention,因为做decoding的时候,默认是看不到未来数据的,所以在做self attention的时候,decoder只会得到已经产生出来的sequence,所以叫做masked attention。

后面的multi-head attention则会考虑到encoder的输出。


补充

论文中使用的optimizer是Adam optimizer。在最初的几轮,学习率线性增长。在经过几轮后,学习率会程$\sqrt{step}$的反比下降。

下面是decoder的self-attention的分布。可以看到输入为The animal didn't cross the street because it was too tired.时,animal和it的相关性最高,而把tired转换为wide时,变成了streetit的相关性最高。可以发现,self-attention可以自行发现词语的相关性!

1613571886827

参考资料

李宏毅-2020年机器学习课程-transformer
Self-attention
Layer Normalization
Batch Normalization

最后修改:2021 年 07 月 15 日 05 : 48 PM
如果觉得我的文章对你有用,请随意赞赏