Self-Attention时间复杂度: O ( n 2 d ) O(n^2d) O(n2d),这里,n是序列的长度,d是embedding的维度。
Self-Attention包括三个步骤:相似度计算,softmax和加权平均,它们分别的时间复杂度是:
相似度计算可以看作大小为(n,d)和(d,n)的两个矩阵相乘: ( n , d ) ∗ ( d , n ) = O ( n 2 d ) (n,d)*(d,n)=O(n^2d) (n,d)∗(d,n)=O(n2d),得到一个(n,n)的矩阵
softmax就是直接计算了,时间复杂度为 O ( n 2 ) O(n^2) O(n2)
加权平均可以看作大小为(n,n)和(n,d)的两个矩阵相乘: ( n , n ) ∗ ( n , d ) = O ( n 2 d ) (n,n)*(n,d)=O(n^2d) (n,n)∗(n,d)=O(n2d) ,得到一个(n,d)的矩阵
因此,Self-Attention的时间复杂度是 O ( n 2 d ) O(n^2d) O(n2d)。
这里再分析一下Multi-Head Attention,它的作用类似于CNN中的多核。
多头的实现不是循环的计算每个头,而是通过 transposes and reshapes,用矩阵乘法来完成的。
In practice, the multi-headed attention are done with transposes and reshapes rather than actual separate tensors. —— 来自 google BERT 源码
Transformer/BERT中把 d ,也就是hidden_size/embedding_size这个维度做了reshape拆分,可以去看Google的TF源码或者上面的pytorch源码:
hidden_size (d) = num_attention_heads (m) * attention_head_size (a),也即 d=m*a
并将 num_attention_heads 维度transpose到前面,使得Q和K的维度都是(m,n,a),这里不考虑batch维度。
这样点积可以看作大小为(m,n,d)和(m,d,n)的两个张量相乘,得到一个(m,n,n)的矩阵,其实就相当于m个头,时间复杂度是 O ( m n 2 d ) O(m n^2d) O(mn2d)。
张量乘法时间复杂度分析参见:矩阵、张量乘法的时间复杂度分析
因此Multi-Head Attention时间复杂度就是 O ( m n 2 d ) O(m n^2d) O(mn2d),而实际上,张量乘法可以加速,因此实际复杂度会更低一些。
训练时一个 batch 的句子是一起生成的,而且每个句子的每个词也是一起生成的。encoder是并行的,训练的时候decoder也是并行的,但是inference的时候不是,因为你没有golden label,只能一个一个产生,所以decoder端跟RNN一样还是自回归的。细节看源码吧,tensor2tensor或者THUMT的transformer实现。
Batch Normalization 的处理对象是对一批样本, Layer Normalization 的处理对象是单个样本。Batch Normalization 是对这批样本的同一维度特征做归一化, Layer Normalization 是对这单个样本的所有维度特征做归一化。
众所周知,无论在CV还是NLP中,深度模型都离不开归一化技术(Normalization)。在CV中,深度网络中一般会嵌入批归一化(BatchNorm,BN)单元,比如ResNet;而NLP中,则往往向深度网络中插入层归一化(LayerNorm,LN)单元,比如Transformer。
为什么在归一化问题上会有分歧呢?一个最直接的理由就是,BN用在NLP任务里实在太差了(相比LN),此外,BN还难以直接用在RNN中[1],而RNN是前一个NLP时代的最流行模型。
虽然有大量的实验观测,表明NLP任务里普遍BN比LN差太多,但是迄今为止,依然没有一个非常严谨的理论来证明LN相比BN在NLP任务里的优越性。甚至,连BN自身为什么work的问题都一直存在争议。
早期对BN有效性的解释是其有助于缓解神经网络“内部协方差漂移”(Internal Covariance Shift,ICS)问题。即,后面的层的学习是基于前面层的分布来的,只有前面一层的分布是确定的,后面的层才容易学习到有效的模式,然而,由于前面的层的分布会随着batch的变化而有所变动,导致了后面的层看来“前面一直在动,我无法安心学习呀”。
而BatchNorm这类归一化技术,目的就是让每一层的分布稳定下来,让后面的层可以在前面层的基础上安心学习知识。顾名思义,BatchNorm就是通过对batch size这个维度归一化来让分布稳定下来。LayerNorm则是通过对Hidden size这个维度归一化来让某层的分布稳定。
BN、LN可以看作横向和纵向的区别。
经过归一化再输入激活函数,得到的值大部分会落入非线性函数的线性区,导数远离导数饱和区,避免了梯度消失,这样来加速训练收敛过程。
BatchNorm这类归一化技术,目的就是让每一层的分布稳定下来,让后面的层可以在前面层的基础上安心学习知识。
为什么要舍弃 BN 改用 LN 呢?朴素版的 BN 是为 CNN 任务提出的,需要较大的 BatchSize 来保证统计量的可靠性,并在训练阶段记录全局的 μ \mu μ 和 σ \sigma σ供预测任务使用。对于天然变长的 RNN 任务,需要对每个神经元进行在每个时序的状态进行统计。这不仅把原本非常简单的 BN 流程变复杂,更导致偏长的序列位置统计量不足。相比之下,LN 的使用限制就小很多,不需要在预测中使用训练阶段的统计量,即使 BatchSize = 1 也毫无影响。
个人理解,对于 CNN 图像类任务,每个卷积核可以看做特定的特征抽取器,对其输出做统计是有理可循的;对于 RNN 序列类任务,统计特定时序每个隐层的输出,毫无道理可言——序列中的绝对位置并没有什么显著的相关性。相反,同一样本同一时序同一层内,不同神经元节点处理的是相同的输入,在它们的输出间做统计合理得多。
传统词表示方法无法很好的处理未知或罕见的词汇(OOV问题)
传统词tokenization方法不利于模型学习词缀之间的关系
E.g. 模型学到的“old”, “older”, and “oldest”之间的关系无法泛化到“smart”, “smarter”, and “smartest”。
Character embedding作为OOV的解决方法粒度太细
Subword粒度在词与字符之间,能够较好的平衡OOV问题
BPE(字节对)编码或二元编码是一种简单的数据压缩形式,其中最常见的一对连续字节数据被替换为该数据中不存在的字节[2]。 后期使用时需要一个替换表来重建原始数据。OpenAI GPT-2 与Facebook RoBERTa均采用此方法构建subword vector.
优点
可以有效地平衡词汇表大小和步数(编码句子所需的token数量)。
缺点
基于贪婪和确定的符号替换,不能提供带概率的多个分片结果。
WordPiece算法可以看作是BPE的变种。不同点在于,WordPiece基于概率生成新的subword而不是下一最高频字节对。
From https://zhuanlan.zhihu.com/p/86965595
Transformer在训练的时候是并行执行的,所以在decoder的第一个sublayer里需要seq mask,其目的就是为了在预测未来数据时把这些未来的数据屏蔽掉,防止数据泄露。如果我们非要去串行执行training,seq mask其实就不需要了。
From: https://www.zhihu.com/question/369075515/answer/994819222
(1)Encoder和Decoder间的Embedding层权重共享;
(2)Decoder中Embedding层和FC层权重共享。
对于(1),《Attention is all you need》中Transformer被应用在机器翻译任务中,源语言和目标语言是不一样的,但它们可以共用一张大词表,对于两种语言中共同出现的词(比如:数字,标点等等)可以得到更好的表示,而且对于Encoder和Decoder,嵌入时都只有对应语言的embedding会被激活,因此是可以共用一张词表做权重共享的。
“In our model, we share the same weight matrix between the two embedding layers and the pre-softmax linear transformation, similar to [30].”
论文中,Transformer词表用了bpe来处理,所以最小的单元是subword。英语和德语同属日耳曼语族,有很多相同的subword,可以共享类似的语义。而像中英这样相差较大的语系,语义共享作用可能不会很大。
但是,共用词表会使得词表数量增大,增加softmax的计算时间,因此实际使用中是否共享可能要根据情况权衡。
该点参考:https://www.zhihu.com/question/333419099/answer/743341017
对于(2),Embedding层可以说是通过onehot去取到对应的embedding向量,FC层可以说是相反的,通过向量(定义为 x)去得到它可能是某个词的softmax概率,取概率最大(贪婪情况下)的作为预测值。
那哪一个会是概率最大的呢?在FC层的每一行量级相同的前提下,理论上和 x 相同的那一行对应的点积和softmax概率会是最大的(可类比本文问题1)。
因此,Embedding层和FC层权重共享,Embedding层中和向量 x 最接近的那一行对应的词,会获得更大的预测概率。实际上,Decoder中的Embedding层和FC层有点像互为逆过程。
通过这样的权重共享可以减少参数的数量,加快收敛。
但开始我有一个困惑是:Embedding层参数维度是:(v,d),FC层参数维度是:(d,v),可以直接共享嘛,还是要转置?其中v是词表大小,d是embedding维度。
查看 pytorch 源码发现真的可以直接共享:
fc = nn.Linear(d, v, bias=False) # Decoder FC层定义
weight = Parameter(torch.Tensor(out_features, in_features)) # Linear层权重定义
Linear 层的权重定义中,是按照 (out_features, in_features) 顺序来的,实际计算会先将 weight 转置在乘以输入矩阵。所以 FC层 对应的 Linear 权重维度也是 (v,d),可以直接共享。
BERT在第一句前会加一个[CLS]标志,最后一层该位对应向量可以作为整句话的语义表示,从而用于下游的分类任务等。
为什么选它呢,因为与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义。
这里补充一下bert的输出,有两种:
一种是get_pooled_out(),就是上述[CLS]的表示,输出shape是[batch size,hidden size]。
一种是get_sequence_out(),获取的是整个句子每一个token的向量表示,输出shape是[batch_size, seq_length, hidden_size],这里也包括[CLS],因此在做token级别的任务时要注意它。
概括来说,两个原因:
具体解释:
Self-Attention的核心是用文本中的其它词来增强目标词的语义表示,从而更好的利用上下文的信息。
self-attention中,sequence中的每个词都会和sequence中的每个词做点积去计算相似度,也包括这个词本身。
如果不乘QKV参数矩阵,那这个词对应的q,k,v就是完全一样的。
在相同量级的情况下,qi与ki点积的值会是最大的(可以从“两数和相同的情况下,两数相等对应的积最大”类比过来)。
那在softmax后的加权平均中,该词本身所占的比重将会是最大的,使得其他词的比重很少,无法有效利用上下文信息来增强当前词的语义表示。
而乘以QKV参数矩阵,会使得每个词的q,k,v都不一样,能很大程度上减轻上述的影响。
当然,QKV参数矩阵也使得多头,类似于CNN中的多核,去捕捉更丰富的特征/信息成为可能。
BERT采用的Masked LM,会选取语料中所有词的15%进行随机mask,论文中表示是受到完形填空任务的启发,但其实与CBOW也有异曲同工之妙。
从CBOW的角度,这里有一个比较好的解释是:在一个大小为 的窗口中随机选一个词,类似CBOW中滑动窗口的中心词,区别是这里的滑动窗口是非重叠的。
那从CBOW的滑动窗口角度,10%~20%都是还ok的比例。
上述非官方解释,是来自我的一位朋友提供的一个理解切入的角度,供参考。
前馈层的gelu激活函数和self-attention,self-attention是非线性的,感谢评论区指出。
举例: 在阅读理解问题中,article 常常长达1000+, 而Bert 对于这个量级的表示并不支持, 诸位有没有什么好的解决办法, 除了分段来做?或者提一提如何分段来做。感谢诸位大佬。
这是个好问题,可以看下Amazon今年EMNLP的这篇文章:Multi-passage BERT 主要思路是global norm + passage rank + sliding window实验做的很扎实,从ablation study看这几个trick都很有用。如果不想切passages就上XLNet吧。
简单回答就是,多头保证了transformer可以注意到不同子空间的信息,捕捉到更加丰富的特征信息。其实本质上是论文原作者发现这样效果确实好。举个例子例如“川普”
Embedding的数学本质,就是以one hot为输入的单层全连接。请参考: https://kexue.fm/archives/4122也就是说,世界上本没什么Embedding,有的只是one hot。现在我们将token,position,segment三者都用one hot表示,然后concat起来,然后才去过一个单层全连接,等价的效果就是三个Embedding相加
作者:苏剑林
链接:https://www.zhihu.com/question/374835153/answer/1042845667
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
假设向量 q和 k 的各个分量是互相独立的随机变量,均值是0,方差是1,那么点积qk 的均值是0,方差是 d k d_k dk。将方差控制为1,也就有效地控制了前面提到的梯度消失的问题。这里我给出一点更详细的推导:
这是Google BERT预训练模型初始设置的原因,前者对应Position Embeddings,后者对应Segment Embeddings
在BERT中,Token,Position,Segment Embeddings 都是通过学习来得到的,pytorch代码中它们是这样的
self.word_embeddings = Embedding(config.vocab_size, config.hidden_size)
self.position_embeddings = Embedding(config.max_position_embeddings, config.hidden_size)
self.token_type_embeddings = Embedding(config.type_vocab_size, config.hidden_size)
上述BERT pytorch代码来自:https://github.com/xieyufei1993/Bert-Pytorch-Chinese-TextClassification,结构层次非常清晰。
而在BERT config中
"max_position_embeddings": 512
"type_vocab_size": 2
因此,在直接使用Google 的BERT预训练模型时,输入最多512个词(还要除掉[CLS]和[SEP]),最多两个句子合成一句。这之外的词和句子会没有对应的embedding。
当然,如果有足够的硬件资源自己重新训练BERT,可以更改 BERT config,设置更大max_position_embeddings 和 type_vocab_size值去满足自己的需求。