­

深入分析transformer(待续)

  • 2021 年 3 月 25 日
  • AI

作为bert based model的基本组件,transformer还是很有必要彻头彻尾地进行分析和理解,否则在后续的各类花式bert的世界里很容易迷失自我。。。

The Illustrated Transformerjalammar.github.io图标

transformer的抽象结构就是encoder+decoder的seq2seq结构。encoder输入序列数据,decoder输出序列数据。

bert这类模型大体上的架构就是在transformer上进行各种堆叠和multihead:

encoder可以继续拆解为:

下面我们从数据流向的层面来介绍整个transformer的运行机制,从input到output层面,我觉得这也是常见的最方便快捷地理解模型地方式了。

首先是inputs,transformer的这个结构其实也可以很方便地适配多元多步时间序列预测。还是先从nlp讲起再过渡到时间序列预测,为了方面描述还是给个具体的例子。

假设我们要完成一个机器翻译的任务,源句子为”我 爱 中国“,目标句子为 ”I love China“

首先,”我 爱 中国“先经过word embedding层变成4个向量,假设这四个向量为

[1,1,1,1],[2,2,2,2],[3,3,3,3],[4,4,4,4](当然这个embedding层也可以用word2vec之类的预训练权重来进行初始化的,这里的word embedding和textcnn,textrnn中的word embedding的意义是一样的)

除此之外,因为transformer不像RNN一样能够天然地表达词的先后顺序关系,

比如“我爱你“和“你爱我“在Attention机制的视野里完全一样。所以需要某种方法将位置信息编码进语义编码中。

为了将这种语序关系的信息囊括进来,引入了position encoding来曲线救国,这部分《attention is all you need》使用的是公式直接生成,也是让我很费解的一个地方,而facebook的fairseq和google的bert都是直接初始化一个position embedding(注意position encoding和position embededing是两个东西)来让模型学习,效果也很好,说明这里的position encoding of 公式并不是非必须这么做的。

那么为什么position encoding可以用于表示词序信息,这要先介绍一下LSTM为什么可以表达词序信息,举个例子”我 爱 中国 我 是 中国人“进入LSTM,当这个句子经过embedding层的时候,第一个”我“和第五个”我“的embedding向量是完全一样的,但是经过LSTM之后,第一个”我“的hidden state(或者说LSTM对word的语义编码)和第五个”我“是不一样的,因为第5个”我“的语义编码包含了第五个”我“的embedding向量和第四个word的hidden state,LSTM的结构天然地能够将语序信息考虑到语义编码中,其实说白了就是语义编码的结果会随着不同的词的位置而改变,极端的例子就是一个句子里完全相同的n个词,经过LSTM之后,每一个词的embedding向量虽然一样,但是hidden state(语义编码)都是完全不同的,除此之外,相同的word如果前面的words完全不一样,则即使这个word在句子中的顺序相同,最终得到的语义编码hidden state也是不一样的。 这就是所谓的RNN能够考虑词序的一个最直观的体现。

(补充

根据position encoding的代码可以推断出 position embedding矩阵就是根据句子的最大长度构建的句子,size为 (max_len,d_emb),比如最长句限制为512个词,word embedding维度为256,则 position embedding的size为(512,256);

关于position encoding和position embedding的部分打算重新写一篇文章总结一下相对位置编码、绝对位置编码等相关概念,考虑到文章篇幅这里就不继续深入描述了。具体可见:

马东什么:Position encoding和Position embeddingzhuanlan.zhihu.com图标

Anyway,当我们把word embedding和position encoding/embedding对应的信息加起来的时候,同一个句子中相同的n个词,他们的位置一定不相同,因此求和之后的结果一定不相同,通过这种方式模拟了RNN的序列信息的cover能力从而将时序信息考虑进来。

那么到这里其实都不难,word embedding+position embedding/encoding得到new word embedding向量,还是继续上面的例子,假设”我 爱 中国“得到的new word embedding为:

[1.1,1.1,1.1,1.1],[2.2,2.2,2.2,2.2],[3.3,3.3,3.3,3.3],[4.4,4.4,4.4,.4.4]

首先这是一个残差结构,[1.1,1.1,1.1,1.1],[2.2,2.2,2.2,2.2],[3.3,3.3,3.3,3.3],[4.4,4.4,4.4,.4.4] 分为两支,一支进入 add&norm部分,一支进入multi-head attention部分。

先看这个部分:

三个箭头代表了三个线性变换的矩阵wq,wk,wv。

关于这里的线性矩阵wq,wk和wv,其实纵观attention的发展历程都会发现,attention不是直接对input进行attention score的计算的,往往是在input经过RNN或者CNN的encode之后变成一个新的语义编码向量,然后在这个语义编码向量上进行attention score的计算的,这里self attention因为放弃了RNN和CNN的结构,所以使用一个线性变换层来进行语义编码。

如果要问为什么self attention这里要先做线性变换语义编码,这就涉及到为啥attention based model都是使用rnn或者cnn组件进行语义编码之后再做attention计算。

说实话我也理解不了,attention 机制最初就是针对机器翻译中的LSTM的hidden state(语义编码)来进行attention score的计算的,目前来说,其实感觉我们对attention机制的效果还是没有透彻地理解,比如说这里引申出另一个问题,为什么q和k不用同一个线性变换层?

实际上reformer就把q和k用同一个线性变换层来表示了,这个地方纠结不出个所以然来,就好比LSTM中的门控机制为什么那么设置?大家解释了半天之后发现GRU效果也不错,其门控机制和LSTM又不一样,所以个人观点就是,这个部分很难解释为啥要语义编码之后再attention计算,说不定不语义编码直接对input进行attention计算也可以出效果。

接着来将multi attention head,这里先讲一个head的情况:

放大来看就是下面的图:

这里就是每一个head的q、v、k对应的wq,wv,wk矩阵都是不同独立的不是shared的,这里做映射的一个好处是可以把input的embedding的dimension经过线性变换后降低维度,然后在低维的q、v、k上进行attention计算,则计算的复杂度就可以得到较好的缓解。

上图介绍的计算过程非常直观了,q和k使用了score dot,也就是先点积得到112,然后除以q,v,k的dimension的维度,原文使用64维的向量所以这里除以根号64=8.

这里为什么要除以根号dimension,有一个非常好的求证:

transformer中的attention为什么scaled?www.zhihu.com图标

这主要是attention的softmax计算的问题存在的,比如说我们仅仅对3维的向量做softmax,则[1,1,1]则softmax之后:

我们可以做个简单的实验:

import numpy as np
def softmax(x):
   
    x_row_max = x.max(axis=-1)
    
    x_row_max = x_row_max.reshape(list(x.shape)[:-1]+[1])
    
    x = x - x_row_max
    
    x_exp = np.exp(x)
    
    x_exp_row_sum = x_exp.sum(axis=-1).reshape(list(x.shape)[:-1]+[1])
    
    softmax = x_exp / x_exp_row_sum
    
    return softmax


a=np.ones(10)
softmax(a)

得到:

array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1])

a=np.ones(10000)
softmax(a)

得到:

array([0.0001, 0.0001, 0.0001, …, 0.0001, 0.0001, 0.0001])

显然,dimension越大,则每一个值最终softmax之后得到的值就越小,如果原始的输入大小不一,有一些很大有一些很小,则在大dimension的情况下,小的数经过softmax之后会变得非常的小。

下面我们用autograd来计算softmax函数的梯度:

from autograd import numpy as np
from autograd import elementwise_grad as egrad


def softmax(a):
    """解决softmax函数的溢出问题,利用c(输入的最大值),softmax函数减去这个最大值保证数据不溢出,softmax函数运算时加上或者
    减去某个常数并不会改变运算的结果"""
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

def cross_entropy_error(y, x):
    delta = 1e-07 # 设置一个微小值,避免对数的参数为0导致无穷大
    return - np.mean(x * np.log(y + delta)) # 注意这个log对应的是ln


y = np.array([1,0,0])
x = np.array([[10,7,5]])
x = softmax(x)
cross_entropy_error(y,x)

loss输出为

然后求一下一阶梯度:

softmax_grad=egrad(cross_entropy_error,0) 
softmax_grad(y,x)

输出为:

可以看到,梯度的差异已经比较大了。。

我们再换个维度高一点的:

y=np.array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1])
x=np.array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20])
softmax_grad(y,x)

可以看到,梯度的差异已经很大了。。。

下面我们把x除以根号dimension,这里是根号20

可以看到,整体的梯度差异缩小了一些,如果除以一个很大的数呢:

可以看到,大部分的梯度量纲都比较接近,对于权重来说不同的权重之间获得的梯度更新量整体不会差异太大,权重之间的大小也会保持在比较稳定的区间里。

回到transformer,这里的根号Dimension感觉更像是一个经验数值,其实也可以取别的数,比如tabnet里就使用了简单乘以0.5进行网络输出的放缩,这里可能是为了适配不同的q、v、k的dimension而使用的一个网络小组件。


现在假设我们的input:

[1.1,1.1,1.1,1.1],[2.2,2.2,2.2,2.2],[3.3,3.3,3.3,3.3],[4.4,4.4,4.4,.4.4] 经过单头的self attention,得到的attention score的矩阵为

[X,X,X,X]

[X,X,X,X]

[X,X,X,X]

[X,X,X,X]

self attention的attention score矩阵,简单来说也可以称之为相关系数矩阵,必定是方阵,,因为是自己和自己计算,所以形式大概如上图,假设我们的input经过了加权求和之后变成了

[1.2,1.4,1.5,1.2],[2.2,2.4,2.5,2.2],[2.2,2.4,2.5,2.2],[2.2,4.4,4.5,4.2],然后就和残差结构的输出

[1.1,1.1,1.1,1.1],[2.2,2.2,2.2,2.2],[3.3,3.3,3.3,3.3],[4.4,4.4,4.4,.4.4] 一起进入add&norm部分。

add部分就是这个了,residual 的结构也是比较常见的帮助深层模型进行训练的组件,其实和transformer本身的大含义关系不大,换句话说如果以后有了另外一种不同的帮助深层模型训练的组件,也可以替换的,就好比tabnet里用了glu,也是一种缓解梯度弥散的网络组件,都是为了帮助模型更好更快的收敛用的。

然后是layernormalization,关于这些网络组件,常见的比如结构化数据里用batchnorm比较多,nlp里layernorm比较多,就好比embedding层用spaitialdropout比较多而用dropout的比较少,简单来说就是使用之后的效果决定的,关于为啥用layernorm:

transformer 为什么使用 layer normalization,而不是其他的归一化方法?www.zhihu.com图标

这老哥太猛了。。。

主要问题是在前向传播和反向传播中,batchnorm的统计量和其贡献的梯度都会呈现一定的不稳定性,而layernorm则较好的解决梯度不稳定的问题。

关于XXnormalization层的比较之前写过:

马东什么:各种各样的normalization with keras(instant bn层还没看待续)zhuanlan.zhihu.com图标

另外这个回答我觉得更好理解: