开胃菜
这一部分主要阐述,写这篇文章的动机,所以是开胃菜。
实际工业界里,很难在论文中找到 模型+数据 都直接符合需求的NLP model。如果要在某类task上开始一轮新的工作,最好在相似的任务上“魔改”操作。
总的来说,新的paper经过学术界同行监督,总是会有新的进步,比如:
如何去做呢?
15-16年,RNN架构非常火爆。直到现在,我个人首选的sequence tagging任务model依然是bi-lstm+crf
。无他,效果稳定,模型简单。
然而,尽管后续LSTM、GRU、SRU都算作是RNN的衍生物,但统统都不能解决推理时间过长的问题(上线的痛,懂的自然懂!)。因为语言模型处理输入:序列,天然的要求前后依赖。输入就是那么长,每个time step会顺序地做计算,并行化:“不存在的”!
RNN架构先天不足:难以并行化处理序列输入,因为后一个time step总是依赖于前一个time step的输出作为输入
而Transformer,本身在架构设计上就是奔着并行化的目标去的。如果读者了解过CNN中卷积、池化的概念,应该就可以顺畅理解:尽管二维数据比一维序列更加复杂,但在感受野并行处理的情况下,完美利用GPU,速度飞快。
(扯远了,为什么Transformer处理序列数据也够快,会重新开新文章说的~)
“天下苦Transformer久矣”(
这是万能必备句式,划重点
)!
今天这篇文章,会总结式的记录几个基于Transformer魔改的新架构,全是“当前浪”献上的满满厚爱!
主食
what’s Transformer?
一言以蔽之:seq2seq model with self-attention
Transformer的核心在最右侧的两张图中,分别是被框起来的 encoder和decoder部分。
从左向右、从下向上的看:
1.首先实际序列中的每个 c h a r i char_i chari,通过一个固定size的映射矩阵,转换为某个固定维度的embedding向量 x i x_i xi
2.向量 x i x_i xi经过一个矩阵变化得到 a i = W x i a_i=Wx_i ai=Wxi,而 a i a_i ai再加上每个 c h a r i char_i chari的位置向量 e i e_i ei就可以得到对encoder不断循环部分的输入了
通过 [ q i k i v i ] − 1 = [ W q W k W v ] − 1 ( a i + e i ) [q_i k_i v_i]^{-1} = [W_q W_k W_v]^{-1}(a_i + e_i) [qikivi]−1=[WqWkWv]−1(ai+ei)的运算将输入序列中的每个 c h a r i char_i chari,转换为三种描述,分别是: q u e r y i , k e y i , v a l u e i query_i,key_i,value_i queryi,keyi,valuei。
通过每两个位置的char间的query和key两两比较,度量出一个相关程度 α 1 , j \alpha_{1,j} α1,j,然后 通过softmax得到 α ^ 1 , j \hat \alpha_{1,j} α^1,j。即从全局视野考察,最后确定输出其他位置的输入和1号位的相关程度。
最后以 α ^ 1 , j \hat \alpha_{1,j} α^1,j为权重,将对应位置的 v a l u e i value_i valuei做weighted sum,就可以得到self-attention的输出 b i b_i bi,类似的所有位置都经过上述过程。
4.add: 普通的add; Norm: layer Norm
,而不是batch Norm
5.蓝色的 前馈神经网络:Fully connected network
而decoder,又多增加了一个 交叉注意力机制(右下角masked multi-head self-attention的输出中,最终只有Q接到了上层)
可能单纯为了想得到一个效果更好的transformer。self-attention特征提取能力比较强,而网络偏底层的部分,恰好是特征较为集中的地方。所以,“不如多叠几层self-attention试试看?”。
“reorder” the sub-layer module,调整self-attention和fully connected层的顺序。
可以看到s层:self-attention层向左靠拢,聚集在一起了;与之对应f层:fully connected layer靠右聚集;中间部分则不变化。
Transformer在翻译、句法分析上表现很好;而一旦测试序列长于训练数据,因为没见过相对应position embedding 就没有办法了。而且从理论上说transformer不是图灵完备的,不能做重复某些字符串之类的工作(很难理解)。
所以universal transformer想要改进其表现,使用新颖高效的时间并行循环方式将标准 Transformer 扩展为计算通用(图灵完备)模型,从而可在更广泛的任务中产生更强的结果。
2. detail of Switch Unit
输入的两个元素,如图分别进入上下两条通路:
而u则像是一个forget gate,决定:1. 非线性变换信息 2. 交换过的信息有,分别有多少会被传递至下一组unit中
( [ a b ] , [ c d ] ) = [ [ a d ] , [ c b ] ] \left(\left[\begin{array}{l} a \\ b \end{array}\right],\left[\begin{array}{l} c \\ d \end{array}\right]\right)=\left[\left[\begin{array}{l} a \\ d \end{array}\right],\left[\begin{array}{l} c \\ b \end{array}\right]\right] ([ab],[cd])=[[ad],[cb]]
BERT: bidirectional encoder representations from transformers
从全称中就已经可以明确得到指示:bert 就是把transformer中的decoder部分拿出来,通过预训练得到一组真正有效的embedding表示。
预测两个句子是否有上下文的关系
bert 的内部是transformer——“天涯若比邻”,所以cls安置在句子的开头还是结尾完全是没有差别的
降低参数消耗
ALBERT:预训练的task在NSP中有所差异
NSP(Next Sentence Prediction):下一句预测, 正样本=上下相邻的2个句子,负样本=随机2个句子
SOP (Sentence ):句子顺序预测,正样本=正常顺序的2个相邻句子,负样本=调换顺序的2个相邻句子
对于NLI自然语言推理任务。研究发现NSP任务效果并不好,主要原因是因为其任务过于简单。
NSP其实包含了两个子任务,主题预测与关系一致性预测,但是主题预测相比于关系一致性预测简单太多了。
因为只要模型发现两个句子的主题不一样就行了,而SOP预测任务能够让模型学习到更多的信息。SOP因为是在同一个文档中选的,其只关注句子的顺序并没有主题方面的影响。
google对attention机制的完善。就是把Q,K,V通过参数矩阵映射一下,然后再做Attention,把这个过程重复做h次,结果拼接起来就行了,可谓“大道至简”。PS:这个应该在transfomer里讲的,忘记了。
reformer 针对长输入序列做改进:LSH attention
因为self-attention内部QKV运算的空间复杂度是 n 2 n^2 n2,attention score的结果矩阵会迅速增大,但是其实并非所有的score都是重要的。
一个 query 和其他的所有的token的计算 attention score主要是取决于高相似度的几个tokens之间,那么就针对这里做改进。
两个新概念:
在标准Transformer中,Q,K,V是由激活结果A分别通过三个线性层映射得到。
但是这里引入了LSH attention,我们需要Q和K是相同的(备注:其实这里让Q和K相同并不是LSH必须,LSH只需要让Q、K变成单位向量即可,因为要在单位球面上进行相似查找,本文让Q和K一样只是为了方便批处理,加速计算),让Q和K通过相同的线性映射即可实现该目的。
我们称这样的模型为shared-QK Transformer,实验结果表明共享Q、K并没有影响Transformer的表现效果。
正如上面介绍的,我们每一次只计算一个qi和K的结果,但是我们需要和K中的每一个元素都计算吗?其实不是,我们只需要关心与qi相近的keys即可,K中的每一个元素从宏观上理解就是一个word。假设K的长度为64K,也就是有64K个tokens,我们只需要考虑其中的32或者64个最近的keys,那效率将大大提升。如何得到这最近的keys呢?利用Locality sensitive hashing就可以实现,它的基本思路就是距离相近的向量能够很大概率hash到一个桶内,而相距较远的向量hash到一个桶内的概率极低。
使用hash函数的分桶策略,要求attend之后靠近在一起的几个query-key score分在一个bucket中,然后在bucket内部进行self-attention。
h ( x ) = argmax [ x R ; − x R ] h(x)=\operatorname{argmax}[x R ;-x R] h(x)=argmax[xR;−xR]
其中:x是 d k d_k dk维度的query or key,R是 [ d k , b 2 ] \left[d_{k}, \frac{b}{2}\right] [dk,2b]大小的变换矩阵,而b是目标bucket的数量。
右边图中,黑点代表在self-attention后softmax中占主导地位的score
- 我们注意到对角线的点为空心,这是因为我们虽然在正常实现上,我们的q会attend to本身位置的value.
- 但是在share-QK的实现下,如果attend to本身,会导致其值特别大,其他的值特别小,经过softmax之后,其他都是0,就自己本身是1。
- 所以为了避免这种情况,我们q不会去attend 自身位置的值,除非只有自己本身可以attend to(例如图3/4的 q1 )
单个hash函数,总不可避免的会出现个别相近的items却被分到不同的桶里,多轮hash并行执行就可以避免。
假设输入字是1024维度,现在每个bucket size变成32,复杂度由n方变为nlogn
出发点:做误差反传时,每一层的权重、输出都要在内存中做记录,这其实占据了很大的空间。这里reversible layer要说的是,只要记住最后一层layer的信息,来回推前面层的信息就够了。
“时间换空间”
在RevNet中,首先将输入 x x xcopy为两个部分 x 1 x_1 x1和 x 2 x_2 x2然后通过不同residual functions: F ( ⋅ ) F(\cdot) F(⋅)和 G ( ⋅ ) G(\cdot) G(⋅)得到输出 y 1 y_1 y1和 y 2 y_2 y2。
y 1 = x 1 + F ( x 2 ) y 2 = x 2 + G ( y 1 ) \begin{array}{l} y_{1}=x_{1}+F\left(x_{2}\right) \\ y_{2}=x_{2}+G\left(y_{1}\right) \end{array} y1=x1+F(x2)y2=x2+G(y1)
而反向计算时:
x 2 = y 2 − G ( y 1 ) x 1 = y 1 − F ( x 2 ) \begin{array}{l} x_{2}=y_{2}-G\left(y_{1}\right) \\ x_{1}=y_{1}-F\left(x_{2}\right) \end{array} x2=y2−G(y1)x1=y1−F(x2)
F是fully connected的输出,而g是Reversible layer的输出。通过如上的操作,并行的存了两份值,然后就可以相互作差、作和计算出前一层的输出。
声明:文章主体框架来自“李宏毅-深度学习”2020网络课程,文字、图片部分包含了个人理解、讲授内容和网络博客。欢迎交流,时间仓促,请联系我!