首先,引入我看了四个transformer的源代码后选择的github博客。 这个作者的写法非常容易理解,代码质量很高。 3359 github.com/se parius/Bert-keras
本文主要与大家分享四个要点:多头机制(multi-head )、LN和GELU以及位置代码。
这还给了大家安利的几个博客,方便大家更具体地了解注意力的内在原理。
3359砖局域网. zhi Hu.com/p/44121378
3359砖兰.智惠.com/p/47282410 (精华() ) ) ) ) ) )。
3359 www.cn blogs.com/Robert-dlut/p/8638283.html
变换是自我激励的落地或扩张,多头机制充分发挥自我提醒机制。 transformer最亮的地方是完全放弃了包含LSTM等其他变体的常规链式RNN结构。 也就是说,是并行计算能力特别弱的计算方法。 它应该是早期NLP训练技术和当期技术的里程碑。 本来人就是BERT吧。 刷新了几项记录。 XLNET又刷新了BERT的记录,这也证实了这一设计理念的优秀。 真优秀啊。 [斜眼笑]。
言归正传!
一、自张紧机构(自附着)和多头机构(多头) )。
通常的语言生成模型是这样长的
下一个单词的生成依赖于上一个单词的输出状态和当前输入的输入状态,并且依赖于。 也就是说,虽然预测值在某种程度上只对一个单词有很大关系,但自我注意力模型大致如下。
该图意味着每个字的生成都与每个字(encode时)有关,这被称为“注意力”机制。 在文字的生成过程中,可以对所有文字进行加权。 为什么“可能”是因为有掩码。 随机屏蔽单词,不要在意屏蔽的东西。 这样的好处有两个。
一是能“照顾”所有的语言,也就是我们理解的“语境”,
例如:句子1 :“优秀! 这很优秀! 做梦也没想到像他这样的175个子能在中场投篮! ”;
文2 :“优秀! 这很优秀! 他这185个子在篮下那么好的位置也没有进球! ”。
同一位置的话“优秀! ”,一模一样的字,意思完全相反(中华文化博大精深),自我提醒机制,即使后来胡说八道,也需要学习这句话是褒义词还是贬义词。 换句话说,在判断优秀的是褒奖还是贬损的话时,需要看最后几个重要的语气的话来判断,但这个功能在RNN系列机型上做不到。 在数学意义上可以称为“贡献度”。
第二,现在语言可以并行计算了。 至少这个步骤可以并行计算了。
OK,注意力大致是这样的流程,多头又是什么鬼! 很简单。 经过嵌入层,每个词都有多个维度。 代码嵌入在768列中。 这些维度都分为n_head(12 ),每个维度都做这样的事情就是多头机制。 简而言之,就是集中力的模式。 我复制了几次。 这个“几次”是“多头”,12次是12只。 但是,不是复制,第二个是分割,都分12次进行注意力的计算。
你明白原理了吧。 看看人是怎么实现的。
(1) funcs的multihead_attention函数
_q,_k,_ v=x [ : n _ state ],x [ :n_state:2 * n_state],x [ 33333653653650,] 768 //n_head,max] L# [B,n_head,max_len,768//n_head]v=split_heads(v,n_head ) x是嵌入式的
)2) funcs的split_heads函数
#-1,max_len,768]x_shape=shape_list(x ) #768m=x_shape(-1 ) #-1,max_len,n_head,778 map
rn K.permute_dimensions(new_x, [0, 2, 3, 1] if k else [0, 2, 1, 3])
这里对q和v拆分成[B, 12, max_len, 768 // 12] = [B, 12, max_len, 64]的长度,k拆成[B, 12, 64, max_len]
也就是说,每个词拆成了12等分,每一等分特征由之前的768变成了64。整个分成了B个batch,12的小batch,每个小batch的句子是max_len个长度的词组成,每个词有64的特征。
(3)这里咱们细讲一下这个permute,permute是自注意力计算逻辑最核心最抽象的地方之一。先上一张图
permute跟numpy的transpose是异曲同工的,都可以理解为转置。只是我们在对bldxh做转置的时候,用常规的二维矩阵的思维去理解比较难。但是!解释还是得用二维矩阵的方法去解释!
假设我们的q是一个2X6的矩阵,每一个字被嵌入成6个特征。k跟q一毛一样,但是我把k做一下转置,变成6X2的矩阵。这样的形式,我们用矩阵相乘的方法,把2×6的矩阵跟6×2的矩阵相乘,是不是变成了2×2,这个2×2是不是就可以理解为2个字对自己的排列组合。换句话说,所有的字与字之间的加权值得出来了。如图:优跟优的加权值是50(1×1+2×2+3×3+4×4+5×5),优跟秀的加权值是70(1×2+2×3+3×4+4×5+5×6),秀跟优的加权值是70(1×2+2×3+3×4+4×5+5×6),秀跟秀的加权值是90(2×2+3×3+4×4+5×5+6×6)。
(4)funcs的scaled_dot_product_attention_tf函数
先看这个函数:w = K.batch_dot(q, k)
这一步就是上一步所说的矩阵乘法。算法中,对每一组嵌入数据
[B, max_len, 768*3]通过均分,拆成3份[B, max_len, 768],分别作为q、k和v的前身;通过reshape,都变成[B, max_len, 12, 64];在通过permute变成q=[B, 12, max_len, 64]、k=[B, 12, 64, max_len]和v=[B, 12, max_len, 64]。
其中,q和k进行矩阵乘法,把max_len个字彼此求加权值(q=[B, 12, max_len, 64] * k=[B, 12, max_len, 64]),变成w=[B, 12, max_len, max_len]。这里的max_len放在“优秀”一组词里面就是2,即:当前句子的长度。
接着,继续对w和v做矩阵乘法,再求一次加权值(至于这一步有什么实际的原理,我不太确定),这一次矩阵乘法,在数学上把维度还原到[B, 12, max_len, 64],以方便后期继续还原成输入的shape。我揣测,这一步w*v的意义跟全连接层的意义是接近的。只不过在设计的出发点和可解释性上,要稍强糟糕的萝莉连接层的意义。
所以,我用我自己组织的方式,给大家解释一下整个自注意力的过程
a、首先是split_heads,就是切分出qkv
b、矩阵乘法求权值,即:scaled_dot_product_attention_tf
c、merge_heads
经过上述两步,O(output)=[B, 12, max_len, 64],在这个函数里面,还原成[B, max_len, 64]
到此,多头+自注意力机制暂告一段落!
接下来你想在这一步重复多少次就重复多少次,因为输入输出都是一个shape。
二、LN(Layer Normalization)
https://blog.csdn.net/weixin_42078618/article/details/90730488
往这看!!!
三、GELU
一个公式说明一切(这个是近似函数,不是本征函数)
然后看看图像
当我看到这个图像的时候,我第一反应想起了Swish激活函数
他俩真的是异曲同工,几乎长得都一毛一样。从论文来看,GELU函数的收敛性会比RELU和ELU都有略好。
四、位置编码
位置信息对于理解一句话来说,也是很重要的。比如:
a、难受!滑板坏了,水还洒身上了!
b、滑板坏了,难受!水还洒身上了!
对于句子b,其实表达出来的意思,难受的重心是滑板,水是附加的负面影响。对于a,可能整体对心情造成的负面影响是差不多的。这就是词在不同位置可能对语境带了影响的一种情况。
常用的位置编码一般无外乎两种:一种是词嵌入,相当于先加一步全连接层,并且该层参数可学;另一种是自己设计位置编码方法。比如我印象中bert是用正余弦函数做编码的,以后看到再跟大家分享;或者,做一个递进的简单累加也不是不行哇,哈哈。
这里的Transformer阶段的位置编码只是使用了简单的词嵌入的方式,你也可以理解其实就是全连接层的一种应用方式。
结语:transformer以及后来的bert模型最核心的地方就是自注意力机制,大家能把自注意力的实现原理看懂,其核心思想也就一通百通了。