深入浅出Transformer原理与实现

来与去

它解决的是哪个领域的问题?这个领域没有它之前是什么样子,有了它之后,这个领域怎么发展?

早期序列学习任务的痛点

机器学习模型有很多种分类方式,其中有一种简单粗暴的:按照输入输出的长度进行分类,分为:

  1. One to One (如输入图片输出类别)
  2. One to Many (如输入图片输出图片描述或者是字幕)
  3. Many to One (如输入微博评论输出情感类别)
  4. Many to Many (分为M to N(如机器翻译) 与 M to M(自编码学习))
    ML task classification
    众所周知,RNN在面对长距离的依赖时,反向传播会因为多项激活函数导数相乘(使得接近于0),相当于过长序列的学习能力不足:梯度消失问题。

根本原因:循环结构,就会导致链式求导时链变长,导致连乘。

那么,怎么不用循环结构,也能做到序列学习?
它带来了两个好处:

  1. 串行改并行了,计算效率和可扩展性大大提升
  2. 彻底解决了梯度消失问题

Transformer是2017年Google brain提出的模型,用注意力机制做到了这一点

Transformer的发展

Transformer是一种基本模型,可以在它的基础上做各种魔改
如今各种高效Transformer结构如雨后春笋,连每个月的综述都层出不穷,工业界更是早已大规模应用。
不光是应用于语言(NLP)、语音(ASR)等领域(一开始RNN应用在这些NLP ASR领域)
现在更多的用于视觉领域,甚至融合领域(一个模型又会搞NLP又会搞CV)

注意力机制

早期的注意力机制

2014年在Bahdanau的这篇论文里首次提出,解决的是Neural machine translation领域里当时常用模型Seq2Seq的一个瓶颈问题: 随着encoder长度增长,decoder从encoder里获取的信息仍然非常有限。

encoder到decoder的流转例子,这里给出两幅图:
nmt_seq2seq

encoder->decoder

那有了注意力机制之后呢?
encoder_decoder_with_attention
详细看看这个attention layer:
attention_layer

可以看到就是在模型输出的时候,构造了一个包含上下文信息的本质是encoder各向量的权重和向量,这样decoder能从encoder获取到的信息就多了,而且可以想获取哪个多一点就获取哪个多点,学习权重就可以了,权重高代表那个位置的input vector和decoder这个位置的相关度高。

具体细节:主要是3个部分:

  1. Alignment scores
    e t , i = a ( s t − 1 , h i ) e_{t, i} = a(s_{t-1}, h_i) et,i=a(st1,hi)
    h i h_i hi是encoder的隐状态向量, s t − 1 s_{t-1} st1是上一步的decoder的输出, e t , i e_{t,i} et,i刻画的是这个输入向量和当前位置的输出对齐得有多好
  2. Weights
    α t , i = s o f t m a x ( e t , i ) \alpha_{t, i} = softmax(e_{t, i}) αt,i=softmax(et,i)
    因为每个时刻 t t t其实可以找多个 h i h_i hi获取想要的信息,所以将上述系数进行归一化,就可知道要去每个encoder的 i i i时刻采样的比例了。
  3. Context vector
    c t = ∑ i = 1 T α t , i h i c_t = \sum_{i=1}^T \alpha_{t, i} h_i ct=i=1Tαt,ihi
    上下文信息向量,即加权隐状态向量和

注意力机制的统一形式

注意力机制没有必要只是组合不同步骤的RNN隐状态,而是可以包含任意种类的信息。
这个时候,注意力机制分为3个组件: Q(queries)、K(keys)、V(values)

在早期形式里,Q可以类比为上一步的decoder输出即 s t − 1 s_{t-1} st1,V可以类比为encoder的隐状态向量 h i h_i hi ,整个注意力机制可以描述为用 s t − 1 s_{t-1} st1去查询key-value对,这里key是向量,value是隐状态向量 h i h_i hi

  1. 查询向量 q = s t − 1 q=s_{t-1} q=st1:
    匹配数据库里的key来计算一个分数,匹配过程可以看作一个点积:
    e q , k i = q ⋅ k i e_{q, k_i} = q \cdot k_i eq,ki=qki
  2. softmax操作,将分数转化为权重
    α q , k i = s o f t m a x ( e q , k i ) \alpha_{q, k_i} = softmax(e_q, k_i) αq,ki=softmax(eq,ki)
  3. 注意力计算
    a t t e n t i o n ( q , k , V ) = ∑ i α q , k i V k i attention(q, k, V) = \sum_i \alpha_{q, k_i} V_{k_i} attention(q,k,V)=iαq,kiVki

在NMT任务里,输入句子的每个词都对应自身的query, key 和 value向量,是3个参数矩阵(要学习的量)去乘上这个时刻的词的表示(即embedding)得到的,它捕捉的是这个word和序列中其它词的相关性。

注意力机制的实现

代码:

from numpy import array
from numpy import random
from scipy.special import softmax

# 模拟4个单词的embedding
word1 = array([1, 0, 0])
word2 = array([0, 1, 0])
word3 = array([1, 1, 0])
word4 = array([0, 0, 1])

words = array([word1, word2, word3, word4])  # 堆叠起来形成一个单一的数组(4 * 3)

# 模拟生成3个权重矩阵,分别用来计算每个词对应的Q, K, V
random.seed(2022)
WQ = random.randint(3, size=(3, 3))
WK = random.randint(3, size=(3, 3))
WV = random.randint(3, size=(3, 3))

# python3.5后 @可以表示矩阵乘法,即dot(words, WQ)
# 计算得到3个4×3的矩阵,每一行分别代表某个词的Q, K, V
Q, K, V = words @ WQ, words @ WK, words @ WV

# 遍历,每个词都去查一遍所有的词,生成查询结果(相关性),打分
scores = Q @ K.transpose()

# 将每个词A对所有词的打分进行softmax变成采样系数
weights = softmax(scores / K.shape[1] ** 0.5, axis=1)

# 采样系数乘以每个词的内容(value),即每个词都得到了对所有词的采样,这就是注意力矩阵
attention = weights @ V

print(attention)

手绘的原理阐释…
描述
这个阶段,也叫做self-attention机制,每个词都可以采样下所有词的信息

multi-head attention

有几个head,就有几个 W Q , W K , W V WQ,WK,WV WQWKWV的集合,也就是说同时存在多个self-attention
这样加强了对不同空间的表示能力
下图为有2个head的multi-head attention,每次注意力机制也会得到两组attention矩阵

multi head
得到的多个attention矩阵(下图为 Z i Z_i Zi),再拼接好,乘上一个新的权重矩阵 W O W^O WO,得到一个总的attention矩阵 Z Z Z:
multi-head attention into a mat

注意力丢掉的位置信息

使用上述方法去取代原来的seq2seq,还需要有一点需要注意,这里每个词得到了它的上下文相关的信息,但是因为使用的是权重和,仍然丢失掉了词语间的位置信息。
所以引入了一个新的组件叫做: 位置编码

位置可以看作一个维度和词向量embedding的维度大小相同的时序编码向量,将时序信息与词编码向量相加即得到带时序信息的词向量表示:
embedding_with_time

那么这个时序向量具体长什么样呢,有一些例子直观感受下:
positional encoding
下图是一个可以表示从0到19这20个位置(20行)的位置编码向量,每个格子的颜色值从-1到1,一共有512维(512列,根据实际情况的word embedding维度来)
为什么左右看上去像两张图?因为左边是sin生成的右边是cos生辰的。
时序信号
可以看到这个位置编码的生成没有太多好的理论支撑,所以后续也有非常多的工作试图提供更好的位置编码信息,比如:
不是简单的左右拼接而是交织在一起
位置编码2

残差模块

Residual模块的主要作用就是用来防止深度网络的退化,它至少能保证深层网络的效果不会比更低层的训练效果更差。
下图是transformer里的encoder结构图,可以看到有个Add & Normalize的结构,其实就是残差结构,即输出 Z Z Z和输入 X X X相加,并且再进行了一次LayerNorm操作
encoder
这里LayerNorm的作用也是想尽可能地让深度学习底层网络做的不是无用功,具体可以看详解深度学习中的Normalization

encoder + decoder

这里的动图详解了encoder + decoder如何工作:
gif

encoder_decoder2

需要注意的是,解码阶段里,最下面的decoder的输入是所有encoder的输出, 并且会给decoder 输入含位置编码的向量。且在decoder阶段,self-attention层只被允许比t时刻早期的被采样到,所以会用到mask技术
mask
给注意力权重加一个mask,白色的部分即赋值为 − ∞ - \infin ,即代表在输出阶段, t t t时刻的注意力只能采样 < = t <=t <=t时刻的向量信息(很符合常识,因为 > t >t >t时刻的向量还没生成)

"Encoder-Decoder Attention"层工作原理非常类似于多头注意力机制,除了它的查询矩阵Q是从下面的decoder传过来的(而不是前一时刻的 s t − 1 s_{t-1} st1),还有K和V是来源于encoder的输出

至于最后的输出怎么转化为词,就靠linear + softmax一条龙了:
output word

损失函数

这里和传统的Seq2Seq一样,即比较两个概率分布(预测词的概率分布 vs 真实词的概率分布)的距离,使用交叉熵损失函数或Kullback-Leibler散度函数

Transformer架构一览

transformer

使用6个Encoder, 6个Decoder
对每个Encoder: 拥有两个subnet,1个是Multi-Head Attention层,1个是全连接层
每个subnet都加上了Residual block,意味着每个subnet的输出是:
L a y e r N o r m ( x + s u b N e t ( x ) ) LayerNorm(x + subNet(x)) LayerNorm(x+subNet(x))

对每个Decoder:
同样由6个Decoder模块组成,每个Decoder层由两个subent + 1个额外的subnet组成,即在第一个subnet层多了Masked Multi-Head Attention,防止注意力关注后面还没输出的位置的信息,至于具体的处理方法就是上述提到的使用Masked 位置编码向量来实现,保证位置 i i i产生的预测只依赖于已知的 < i <i <i的输出

注意力机制,即上述提到的self-attention,没有区别:
attention

这里还要除以一个 d k \sqrt{d_k} dk ,为什么要除以 d k \sqrt{d_k} dk 呢,是因为点积有可能会产生非常大的数,导致softmax反向传播会把梯度推向一个非常小的区域值,为了对抗这个影响作者决定除以维度大小。

多头注意力机制:
也和上面讲到的没有区别
multi head attention

使用位置编码:
siccos

和上面位置编码的第一张图一样,左边sin右边cos,后期有很多优化这方面的工作

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值