本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
作者简介:秃头小苏,致力于用最浅显的言语描绘问题
往期回忆:对立生成网络GAN系列——GAN原理及手写数字生成小事例 对立生成网络GAN系列——DCGAN简介及人脸图像生成事例 对立生成网络GAN系列——AnoGAN原理及缺点检测实战 对立生成网络GAN系列——EGBAD原理及缺点检测实战
近期目标:写好专栏的每一篇文章
支撑小苏:点赞、保藏⭐、留言
CV攻城狮入门VIT(vision transformer)之旅——近年超火的Transformer你再不了解就晚了!
写在前面
近年来,VIT模型真是杀戮各项榜单啊,就像是15年的resnet,不论是物体分类,目标检测仍是语义切割的榜单前几名基本都是用VIT完成的!!!朋友,信任你点进来了也是了解了VIT的强壮,想一睹VIT的风貌。正如我的标题所说,作为一名CV程序员,没有接触过NLP(天然言语处理)的内容,这给了解VIT带来了必定的难度,可是为了紧跟时代潮流,咱们仍是得硬着头皮往transformer的浪潮里冲一冲。那么这儿我预备做一个VIT的入门系列,计划总共分为三篇来叙述,计划如下:
-
榜首篇:
介绍NLP范畴的transformer,这是咱们入门VIT的必经之路,我以为这也是最艰难的一步。当然我会尽或许从一个CV程序员的角度来协助咱们了解,也会秉持我写文章的主旨——浅显易懂,信任你耐性看完会有所收成。 -
第二篇:
介绍VIT,即transformer模型在视觉范畴的运用,当你对榜首篇transformer了解透彻后,这部分难度不大,所谓先苦 后甜,所以咱们仍是要多花些功夫在榜首篇文章了解上。 -
第三篇:
整理VIT的代码,让咱们对VIT有一个愈加清晰的知道。咱们遇到代码也不要有畏难情绪,关于不了解的地方咱们大能够 调试看看输出的变化或许查阅文档,总之办法总比困难多!
那么下面咱们就要开始了,给咱们具体的唠唠transformer!!!预备发车
全体结构
在介绍transformer的全体结构之前,我先来简略说说咱们为什么选用transformer结构,即transformer结构有什么优势呢?在NLP中,在transformer出现之前,干流的结构是RNN和LSTM,但这些结构都有一个一同的缺点,便是程序难以并行化。举个比如,咱们希望用RNN来进行言语的翻译使命,即输入I Love China
,输出我爱我国
。关于RNN来说,要是现在咱们要输出我国
,就必须先输出我
和爱
,这个进程是难以并行的,即咱们必须先得到一些东西才干进行下一步。【注:这儿不知咱们能否听懂哈,但只要知道传统架构有难以并行化的缺点即可】
这样的话,就能够顺理成章的提出transformer了,其最首要便是处理了类似RNN结构难以并行的特点。后文我也会具体介绍transformer是怎样进行并行处理数据的。
现在就让咱们来看看transformer的全体结构,如下图所示:【注:下图图片公式皆为论文中所截,这儿整理到了一同】
看了上图,不必想太多,你便是不了解,我想任谁榜首眼看到这堆玩意都是懵逼的,可是不要紧,后边我会慢慢的解析这个图。
这一部分我想大致介绍一下这篇文章的行文安排,这样咱们应该就不会有很乱的感觉。首要我会介绍self Attention模块和Multi-Head Attention模块。这两部分是transformer的中心,能够这么说,搞懂了这两个部分transformer你基本就掌握大部分了。接着我会解说encoder和decoderr模块,了解的Multi-Head Attention后,其实encoder和decoder模块就非常简略了。终究,我会做一个总结,提出我的一些考虑和观点。
self Attention✨✨✨
在写这部分之前呢,我觉得有必要提醒一下咱们,关于我下面叙述的内容你或许会很难了解self Attention为什么会这么做,我给的意见是咱们先不必过多的介意,而是先了解self Attention的进程,这个进程了解后,你或许就会对self Attention产生自己独特的知道,当然这部分介绍完后我也会给出自己的了解供咱们参阅。此外,这部分我会先给出self Attention的履行进程,然后会结合代码帮咱们更深入的了解这个进程,咱们务必耐性看完!!!
【注:履行进程部分的图都为自己所画,一方面希望能用自己的思路表述清楚这部分,另一方面也想在锻炼一下自己的作图水平,作图不易,恳请咱们点赞支撑,转载请附链接。代码演示部分参阅这篇文章】
履行进程
step1:获取qi、ki、viq^i、k^i、v^i
下面我就来介绍self Attention的进程了。首要,需求有一系列的输入,以三个输入a1a_1、a2a_2、a3a_3 为例,咱们别离将a1a_1、a2a_2、a3a_3 乘以WqW_q、WkW_k、WvW_v 矩阵得到对应的qq、kk、vv ,如下图所示:
需求留意的是这儿的WqW_q、WkW_k、WvW_v 是同享的。【注:或许你还不了解a1a_1、a2a_2、a3a_3 怎样经过乘一个矩阵变成qq、kk、vv 的,不必忧虑,在履行进程介绍完后,我会举一些特例结合代码帮咱们了解这些进程,所以仍是像我先前说到那样对不了解的点先不必着急,耐性的看完你或许会有所收成!!!】
在每给出一个履行进程后,我都会列出这部分履行的图解公式,其实这些都是一些矩阵运算,如下图所示:
step2:核算attention score
得到这些qq、kk、vv 后,咱们会别离用q去乘每一个kTk^T得到一个数值aija_{ij},即用q1别离乘k1T、k2T、k3Tq_1别离乘k_1^T、k_2^T、k_3^T;q2别离乘k1T、k2T、k3Tq_2别离乘k_1^T、k_2^T、k_3^T;q3别离乘k1T、k2T、k3Tq_3别离乘k_1^T、k_2^T、k_3^T,如下图所示:【注:为便利表明,先运用q1别离乘k1T、k2T、k3Tq_1别离乘k_1^T、k_2^T、k_3^T得到a1,1、a1,2、a1,3a_{1,1}、a_{1,2}、a_{1,3}】
a1,1、a1,2、a1,3a_{1,1}、a_{1,2}、a_{1,3}是一个数值,咱们称为attention score,其表明的是每个输入的重要程度。这部分的图解公式如下:
step3:经过softmax层
这步就比较简略了,即把上步得到的a1,1、a1,2、a1,3a_{1,1}、a_{1,2}、a_{1,3}经过一个softmax层得到输出a1,1′、a1,2′、a1,3′a_{1,1}^{‘}、a_{1,2}^{‘}、a_{1,3}^{‘},如下图所示:
这儿有一点我需求阐明,假如你看attention的论文或许一些文章解读,在经过softmax层前会除了一个dk\sqrt {{{\rm{d}}_k}},起到了一个归一化的效果,我这儿没有除, 由于后边代码举例时不除这个dk\sqrt {{{\rm{d}}_k}}会更便利咱们了解,至于这儿除不除dk\sqrt {{{\rm{d}}_k}}对咱们了解是没有任何影响的,而且不除dk\sqrt {{{\rm{d}}_k}}其实也是一种办法。
这儿在给出此进程的图解公式:
step4:得到输出bib^i
得到a1,1′、a1,2′、a1,3′a_{1,1}^{‘}、a_{1,2}^{‘}、a_{1,3}^{‘}后,会让其别离乘v1、v2、v3v_1、v_2、v_3 再相加得到b1b^1,进程如下:
这部分的图解公式如下:
上文经过q1别离乘k1T、k2T、k3Tq_1别离乘k_1^T、k_2^T、k_3^T终究得到b1b^1 ,同理咱们能够经过q2别离乘k1T、k2T、k3Tq_2别离乘k_1^T、k_2^T、k_3^T和q3别离乘k1T、k2T、k3Tq_3别离乘k_1^T、k_2^T、k_3^T得到b2和b3b^2和b^3。如下图所示:
在上述step2、step3和step4中,由于没有介绍b2和b3b^2和b^3的生成进程,因而只给出了有关 b1b^1的图解公式。这儿再补充上完好的图解公式,如下:
step2:
step3:
step4:
终究,为让咱们了解此进程是并行的,我将进程1到进程4的进程整合在一同,其间II表明输入的向量,经过下图能够很显着的看出这些矩阵运算是能够并行的,即咱们把一切的输入aia_{i}拼在一同成为II,将I输入网络进行一系列的矩阵运算。
代码演示
这部分会根据上述的理论进程结合代码加深各位的了解。此外,这部分我也会分进程介绍,但会细化理论部分的进程,这样咱们了解起来会更舒畅,但全体的进程是没有变的。
step1:预备输入
咱们定义的输入有三个,它们的维度都是14的,将它们放在一同构成一个34的输入张量,代码如下:
import torch
x = [
[1, 0, 1, 0], # Input 1
[0, 2, 0, 2], # Input 2
[1, 1, 1, 1] # Input 3
]
x = torch.tensor(x, dtype=torch.float32)
咱们来看看输入x的成果:
## 输出成果
tensor([[1., 0., 1., 0.],
[0., 2., 0., 2.],
[1., 1., 1., 1.]])
step2:初始化权重矩阵
咱们知道要拿输入x和权重矩阵WqW_q、WkW_k、WvW_v别离相乘得到qq、kk、vv,而x的维度是34,为保证矩阵可乘,可设WqW_q、WkW_k、WvW_v的维度都为43,这样得到的qq、kk、vv都为33维。
w_query = [
[1, 0, 1],
[1, 0, 0],
[0, 0, 1],
[0, 1, 1]
]
w_key = [
[0, 0, 1],
[1, 1, 0],
[0, 1, 0],
[1, 1, 0]
]
w_value = [
[0, 2, 0],
[0, 3, 0],
[1, 0, 3],
[1, 1, 0]
]
##将w_query、w_key、w_value变成张量形式
w_query = torch.tensor(w_query, dtype=torch.float32)
w_key = torch.tensor(w_key, dtype=torch.float32)
w_value = torch.tensor(w_value, dtype=torch.float32)
step3:生成Q、K、VQ、K、V
这步便是矩阵的乘法,留意@表明矩阵的乘法,*表明矩阵按位相乘。代码如下:
querys = x @ w_query
keys = x @ w_key
values = x @ w_value
相同的,咱们能够看看此步得到的Q、K、VQ、K、V成果:
## Q
tensor([[1., 0., 2.],
[2., 2., 2.],
[2., 1., 3.]])
## K
tensor([[0., 1., 1.],
[4., 4., 0.],
[2., 3., 1.]])
## V
tensor([[1., 2., 3.],
[2., 8., 0.],
[2., 6., 3.]])
step4:核算attention score
核算attention score其实便是核算Q⋅KTQ \cdot K^T ,代码如下:
attn_scores = querys @ keys.T
核算得到的attn_scores
成果如下:
##attn_scores
tensor([[ 2., 4., 4.],
[ 4., 16., 12.],
[ 4., 12., 10.]])
留意,上图只画出了q1⋅KTq_1 \cdot K^T的核算成果,为[2.,4.,4.][2., 4., 4.] ,同理你能够得到q2⋅KTq_2 \cdot K^T 和q1⋅KTq_1 \cdot K^T的成果,别离为[4.,16.,12.][4., 16., 12.] 和[4.,12.,10.][4., 12., 10.] ,将它们组合在一同即得到了attn_scores
矩阵,其维度为33。
step5:attn_score矩阵经过softmax层
将上步得到的attn_scores
输入softmax层,代码如下:
from torch.nn.functional import softmax
attn_scores_softmax = softmax(attn_scores, dim=-1)
咱们能够来看看attn_scores_softmax
的成果:
tensor([[6.3379e-02, 4.6831e-01, 4.6831e-01],
[6.0337e-06, 9.8201e-01, 1.7986e-02],
[2.9539e-04, 8.8054e-01, 1.1917e-01]])
上面的成果有效数字太多了,后文欠好教学展现,因而咱们对attn_scores_softmax
的成果取小数点后一位,即attn_scores_softmax
变成下列形式:
attn_scores_softmax = [
[0.0, 0.5, 0.5],
[0.0, 1.0, 0.0],
[0.0, 0.9, 0.1]
]
##转换为tensor格局
attn_scores_softmax = torch.tensor(attn_scores_softmax)
##输出attn_scores_softmax成果
#tensor([[0.0000, 0.5000, 0.5000],
# [0.0000, 1.0000, 0.0000],
# [0.0000, 0.9000, 0.1000]])
step6:将attn_scores_softmax与矩阵V相乘
这部分代码如下:
outputs = attn_scores_softmax@values
这儿能够看一下这部分的输出:
# outputs成果
tensor([[2.0000, 7.0000, 1.5000],
[2.0000, 8.0000, 0.0000],
[2.0000, 7.8000, 0.3000]])
留意:这部分不是按照参阅链接所给代码写的,参阅链接中把这步拆分红了两个部分,还触及到了三维矩阵的乘法,我以为是欠好了解的,感兴趣的能够自己去看看。
特别留意
代码演示这部分的代码和图是参阅Illustrated: Self-Attention 这篇文章,我觉得写的非常好,图文并茂的展现了self Attention的进程。可是我以为这个比如好像是有一些缺点的,当然了,这儿所说的缺点并没有针对作者对self Ateention的解说,而是这个比如不能对应咱们下文提出的encoder和decoder模块,我现在说encoder 和decoder 模块你肯定还不了解说的是什么,可是我这儿先提出这个比如的缺点,咱们有个印象就好。
那究竟是什么缺点呢?咱们能够直接来看上文step7中图片,能够发现咱们输入的是3个4维向量,即维度为34;而输出为3个三维向量,即维度为33。这儿的维度是不同的,这首要是由于咱们在由输入生成Q、K、VQ、K、V时所乘的权重矩阵WqW_q、WkW_k、WvW_v维度导致的。那么输入输出的维度不共同为什么会在encoder 和 decoder 出现问题呢?其实啊,在Attention操作后都会接上一个残差模块,这就要求Attention 操作前后输入输出的维度共同。
讲到这儿,我信任咱们现已知道问题就出在输入输出的维度上的,那么后文咱们就会默许经过Attention模块后输入输出的维度保持不变。
这部分我没有修正这部分代码及图片一方面是偷了个懒,另一方面是想让咱们愈加深刻的意识到这个输入输出维度的问题。还有一点需求留意,在下文介绍Multi-Head Attention时是终究经过乘一个WoW^o矩阵完成的,在相关部分我也会介绍。
小结
终究咱们来对照全体结构的榜首张图来看看self Attention的进程,如下图:
关于上图其实有两点和咱们上文叙述的有所差异,榜首点是红色底框中的Mask是可选的(opt.),咱们并没有选用,关于这个Mask我会在后文叙述decoder模块部分进行解说;还有一点是上图选用的是Scaled Dot-Product Attention,而咱们选用的是Dot-Product Attention,这两个有什么差异呢?其实差异咱们在step3:经过softmax层有说到,即没有除以dk\sqrt {{{\rm{d}}_k}}。
到这儿,self Attention的内容就介绍完了。我自以为解说得算是比较清楚的了,希望能对咱们有所协助。
Multi-Head Attention✨✨✨
Multi-Head Attention称为多头留意力机制,其实你了解了上文的自留意力机制(self Attention),再来看这部分其实就很简略了,下面就跟着我一同来学学吧!!!
step1:获取qi、ki、viq^i、k^i、v^i
首要榜首步和self Attention如出一辙,获取qi、ki、viq^i、k^i、v^i,如下图所示:
step2:分裂产生多个qi,j、ki,j、vi,jq^{i,j}、k^{i,j}、v^{i,j}
以下以两个head为例进行论述,行将q1q^1分裂成两个q1,1和q1,2q^{1,1}和q^{1,2},将q2q^2分裂成两个q2,1和q2,2q^{2,1}和q^{2,2},将q3q^3分裂成两个q3,1和q3,2q^{3,1}和q^{3,2}如下图所示:
那么这个进程是怎样进行的呢,其实也很简略,只需求别离乘上两个矩阵W1QW_1^Q和W2QW_2^Q即可。【留意:
q1、q2、q3乘W1Qq_1、q_2、q_3乘W_1^Q会别离得到q1,1、q2,1、q3,1;q1、q2、q3乘W2Qq^{1,1}、q^{2,1}、q^{3,1};q_1、q_2、q_3乘W_2^Q会别离得到q1,2、q2,2、q3,2q^{1,2}、q^{2,2}、q^{3,2}】
为了便利咱们了解,结合特例作图如下:即咱们只需有W1QW_1^Q和W2QW_2^Q矩阵即可将qq分红多个。
同理,咱们能够将k和vk和v选用相同的办法,即都相应的乘以两个矩阵进行分裂,成果如下图所示:
step3:对一切head运用self Attention
咱们能够将上述成果分红两个head进行处理,如下图所示:
你会发现head1和head2便是咱们前面所说的self Attention里边的元素,这样会从head1和head2得到对应输出,如下图所示:
step4:拼接一切head输出的成果
这一步咱们会将上一步不同head输出的成果进行Concat拼接,如下图所示:
step5:Concat后的成果乘上WoW^o矩阵
这一步会乘上WoW^o矩阵,其效果首要是融合之前多个head的成果,并使咱们的输出和输入时维度保持共同,如下图所示:【注:这儿是不是和咱们介绍Self Attention模块时讲的特别留意部分很像呢,即Multi-Head Attention是经过WoW^o矩阵操控输入输出维度共同的】
小结
相同的,这儿咱们也来对照全体结构中的图片来看看Multi-Head Attenton的进程,如下图所示:
你会发现这副图像的比较抽象,用虚影表明出多个head的情形,我想咱们是能够了解的。需求留意的一点是上图中的Linear操作其实便是指咱们对原数据乘一个矩阵进行改换。
那么到这儿,Multi-Head Attention的内容就介绍完了,希望能对咱们有所协助。
encoder
encoder模块结构如下图黄色虚线框内所示:
首要咱们要先介绍以下输入,即上图Input Embedding + Positional Encoding 部分,由于这部分我以为内容仍是挺多的,因而放在了附录
部分,咱们可先点击检查。
了解了输入,其实就剩下了灰色框部分,其实这部分还蛮简略的,其首要由两个小部分组成,其一是Multi-Head Attention+Add&Norm,其二是Feed Forward+Add&Norm。
咱们先来介绍榜首小部分,假定输入是维度为(N,d)的矩阵,用 II 来表明,首要会进入一个Multi-Head Attention模块,这部分咱们上文现已具体介绍过了,这儿不再论述,经过Multi-Head Attention模块后得到输出BB ,其维度相同是(N,d)。接着运用一个残差模块将 II 和 BB 加到一同得到 B‘,终究对B‘{{\rm{B}}^`} , 终究对{{\rm{B}}^`} 进行Layer Normalization操作得到输出O1O_1,其维度相同是(N,d)。【关于Layer Normalization不了解的能够参阅我的这篇文章:Batch_Normalization 、Layer_Normalization 、Group_Normalization你分的清楚吗 】
这部分操作的表达式如下:
O1=LayerNormalization(I+Multi-HeadAttention(I))O_1=Layer \ Normalization(I + Multi\text{-}Head Attention(I))
是不是发现这种表达式一下子就把上图的结构都展现出来了呢,所以数学真的很美妙!!!
接下来来介绍第二小部分。这回的输入即为O1O_1,维度为(N,d)。首要会进入一个Feed Forward网络,这是什么呢,其实很简略,便是两个全衔接层,如下图所示:
经过Feed Forward层后,咱们的输出为O11O_1^1,前后尺寸保持不变。接着咱们相同会进行Add和Layer Normalization操作,终究得到输出O2O_2,此刻O2O_2的维度相同为(N,d)。
这部分操作的表达式如下:
O2=LayerNomalization(O1+FeedForwardNetwork(O1))O_2=Layer \ Nomalization(O_1+Feed \ Forward \ Network(O_1))
这样咱们就算是把一个encoder网络介绍完了,仔细的同学或许会发现encoder结构图傍边写了个NN,没错啦,和咱们想的相同,咱们会将这个结构重复N次。重复N次就不要我讲了叭,但需求着重一点,一个网络结构要能够重复堆叠,那么它的输入输出的维度应该是共同的,很显然咱们上面介绍的结构满意这已条件。
这部分是不是发现还蛮简略滴,相同,希望咱们都有所收成!!!
decoder
decoder的结构如下图黄色虚线框内所示:
decoder的结构相较于encoder就难多了,总共包括四个子结构(灰色框中三个),别离为Masked Multi-head Attention+Add&Norm 、Multi-Head Attention+Add&Norm 、 Feed Forward+Add&Norm 和 Linear+Softmax。
我觉得这部分最难了解的便是练习和测验是不同的,下面我将分为练习阶段和测验阶段来为咱们解说这个decoder模块。
练习阶段
咱们先来讲讲decoder的练习阶段是怎样运行的。首要要明确咱们的使命——将“我有一只猫”翻译成“I have a cat”。选用这个比如也是我看网上材料基本都是这个比如,图片都是于此相关的,这部分我实在是不想再画图了,这篇文章确实写的太久了,也太累了,所以也就偷个懒,就借用一下他人的图啦!!!【这儿的参阅链接我放在终究那部分,由于我看谈论区博主说这些图片是一篇英文博客上的,不过我没找到原始博客】
接着咱们来看看decoder的输入和输出是什么:
- 输入:encoder的输出和decoder本身的输出
- 输出:输出词的概率散布
关于这个输入输出你现在或许还不是很了解,接下来我会慢慢剖析。
我觉得很有必要的一点是让咱们清楚decoder结构首要做了什么?——decoder会根据之前的翻译,求得现在最有或许的翻译成果。例如输入“”猜测出榜首个单词为“I”,输入“ I”猜测下一个单词为“have”。如下图所示:【注:这儿的是开始的标志,是要加在咱们的输入中的。】
这儿不知道咱们能否了解,我其时看这部分时仍是有所困惑的,即咱们的使命不是将“我有一只猫”翻译成“I have a cat”嘛,为什么这儿输入和输出都是英文啊?这块我没看到相关的解说,或许时咱们CV程序员对NLP的了解有所欠缺,我谈谈自己的观点——我以为咱们和我进入了一个误区,即decoder的输入究竟是什么?经过我上文的咱们能够知道decoder输入为encoder的输出和decoder本身的输出
。能够看到,decoder根本就没有把“我是一只猫”作为输入,它会先输入一个开始标志,这样会输出“I”;接着这个“I”又反过来加到后,构成“ I”,这时将“ I”作为输入,会得到输出“have”。这样描绘咱们是否能了解了呢?其实啊,“我是一只猫”这个输入只存在encoder的输入中,在decoder中可没有用到喔。
假如咱们觉得自己了解了这一部分,先给自己点个赞!!!然后我再来问咱们一个问题看看你是否是真的了解了呢——为什么咱们输入输出的会是“I”,输入“ I”输出会是“have”?仔细想想喔,下天然段为咱们回答。
傻瓜!!!这当然是咱们练习的成果啦!!!否则这傻瓜机器怎样会这么智能。我简略的画个图为咱们解说解说。
上图展现了咱们练习的大致进程,即咱们输入经Decoder会得到输出,然后这个输出会和咱们希望的实在值比较,接着便是更新各种参数使这个输出愈加挨近“I”。然后咱们将输出放在后构成新的输入送入Decoder网络得到输出,此刻再拿输出和希望的输出“have”比较,使两者类似。依此类推……
这会咱们是不是对Decoder的了解更近一步了呢?假如是的话,我就再来问咱们一个问题——咱们输入得到输出,尽管咱们希望这个输出与实在值“I”尽或许挨近,但很或许咱们练习的成果不那么精确,比如终究输出的不是“I”而是“L”,接着咱们将“L”拼在后边构成“ L”,再将其作为输入,此刻输入都有偏差,大概率会导致此刻的输出离预期成果距离更大,这样下去,终究的成果就愈加离谱了,这就像是一步错步步错。那么这应该用什么办法处理呢?不卖关子了,这儿咱们会每次都把正确的单词序列作为输入,即不论你一步输出的是“I”仍是“L”,咱们都会将实在成果“I”拼在后构成下一步输入,后边都是这样。这种办法被称为teacher-forcing,就像是一个教师在看着你,让你每次都强制输入正确的成果。【注:这部分只在练习部分运用,由于咱们在测验阶段是没有实在值的】
到这儿,我信任咱们对decoder全体的练习现已有了一个较清晰的知道。下面我就来结合decoder的结构图来看看decoder里究竟都有些什么。
首要是输入部分,这部分我在上文中叙述的现已够清楚了。在练习阶段咱们会将“ I have a cat”这五个单词的词向量作为输入,需求留意的是这儿相同加上了方位编码,可是加了方位编码后的维度仍是相同的,后文就不再特别着重是否加入了方位编码。接下来会将输入送到Masked Multi-Head Attention中,是不是发现和前面讲的Multi-Head Attention有些不相同呢,多了一个Masked。那为什么要选用这个Masked呢,这是由于练习时咱们输入的是一切的GT(Ground Truth),即“ I have a cat”五个词向量,可是在测验时并不会这样做,而是一个一个的输入,由于此刻的输入必须包括上一步的输出,而不全是GT。选用Masked会在练习时掩盖某个单词后边的词向量,即猜测第 i 个输出时,就要将第 i+1 之后的单词掩盖住,这样就防止了练习时某个单词接触了未来的信息,导致和测验时不共同。下面我将一步步带咱们看看Masked Multi-Head Attention的进程。【注:下面运用0 1 2 3 4
散布代表“ I have a cat ”,是完毕标志】
-
得到输入矩阵和Mask矩阵,两者维度共同。图中显示遮挡方位的值为0。能够发现单词0只能运用单词0的信息,单词1能够运用单词0和单词1的信息。
-
经过输入矩阵X核算得到Q、K、VQ、K、V并核算Q⋅KTQ \cdot K^T
-
Q⋅KTQ \cdot K^T 与Mask矩阵按位相乘,得到MaskQ⋅KTMask \ \ Q \cdot K^T
-
对MaskQ⋅KTMask \ \ Q \cdot K^T 进行Softmax操作,使MaskQ⋅KTMask \ \ Q \cdot K^T矩阵的每一行都为相加为1
-
MaskQ⋅KTMask \ \ Q \cdot K^T与矩阵V相乘,得到输出Z
上述进程只展现的是一个Head的情况,输出了Z,终究应该把一切Head的成果拼接,使终究的Z和输入X的维度共同。
Masked Multi-Head Attention完毕后使一个Add&LayerNormalization层,这个我在encoder中现已叙述的很清楚了,这儿不再赘述。经过Add&LayerNormalization层后的输出维度仍和输入X维度共同。
接着会进入第二个Multi-Head Attention层,留意此刻的K、VK、V来自于encoder,而QQ来自decoder。这样做的优点使在decoder时,每一个词都能够运用encoder中一切单词的信息。接着相同是一个Add&LayerNormalization层。
然后会进入Feed Forward+Add&Norm层,接着会将整个结构重复N次。
终究会进入Linear+Softmax层,终究输出猜测的单词,由于 Mask 的存在,使得单词 0 的输出 Z(0,) 只包括单词 0 的信息,如下:
Softmax 根据输出矩阵的每一行猜测下一个单词,如下图所示:
这部分我引荐咱们听听李宏毅教师的课程:台大李宏毅21年机器学习课程 self-attention和transformer
测验阶段
了解了上文练习阶段decoder是怎样工作的,那么测验阶段就很简略了解了。其实我在测验阶段也有提及,首要差异便是此刻咱们不是一次将“ I have a cat”一同作为输入,而是一个一个词的输入,并把输出加到下一次输入中,进程如下:
- 输入,decoder输出 I 。
- 输入前面现已解码的和 I,decoder输出have。
- 输入现已解码的“ I have a cat”,decoder输出解码完毕标志位,每次解码都会运用前面现已解码输出的一切单词嵌入信息。
那么很显着测验阶段咱们是无法做并行化处理的!!!
总结
终于算是把transformer的内容讲完了,这儿我给出一张Transformer的全体结构图,我觉得画的非常好,如下图所示:【图片来历于此篇文章】
别的,作为CV程序员的咱们,往往对CNN网络是愈加熟悉的。那么CNN和Transformer中的self-Attention是否有什么联络呢?咱们能够去网上找找材料,其实CNN能够看作是一种简化版的self-Attention,或许说self-Attention是一种杂乱化的CNN,它们的大致联系如下:
咱们知道越杂乱的模型,往往就需求更多的参数来练习,因而在练习Transformer时就需求更多的数据,关于这一点在后边叙述的VIT模型中会有体现,敬请期待吧!!!
终究的终究,仍是希望咱们有所收成!!!别的,假如文章对你有所协助,希望得到你小小的赞,这是对创造最大的支撑
论文下载地址
Attention Is All You Need
参阅衔接
1、Transformer中Self-Attention以及Multi-Head Attention详解
2、台大李宏毅21年机器学习课程 self-attention和transformer
3、Transformer论文逐段精读【论文精读】
4、ViT论文逐段精读【论文精读】
5、shusheng wang 解说 Transformer模型
6、Illustrated: Self-Attention
7、Vision Transformer 超具体解读 (原理剖析+代码解读) (一)
8、Transformer Decoder详解
9、Transformer模型详解(图解最完好版)
附录
input输入解析
这部分来谈谈encoderr的输入部分,其结构示意图如下:
上图首要包括两个概念,一个是Input Embedding ,一个是Positional Encoding。下面就来逐一的进行介绍。
Input Embedding
咱们先来看Input Embedding,何为Input Embedding呢?这儿我先卖个关子,先不介绍这个概念,而是先从咱们的输入一点点谈起。现假定咱们要完成一个文本翻译使命,假定具体使命为将汉字“秃 头 小 苏 ”翻译成拼音“tu tou xiao su”,这儿咱们只关注输入,此刻的输入应该是“秃 头 小 苏”四个汉字,可是作为程序猿的咱们应该知道,这四个汉字核算机是不知道的,那么就需求将“秃 头 小 苏”转化为核算机知道的言语,一种常见的做法是独热编码(one-hot编码),如下图所示:【关于独热编码不熟悉的自行百度,这儿不再介绍】
能够看出,上图能够用一串数字表明出“秃 头 小 苏”这四个汉字,如用1 0 0 0
表明“秃”,用0 1 0 0
表明“头”……
可是这种表明办法是否存在缺点呢?咱们都能够考虑考虑,我给出两点如下:
- 这种编码办法关于我这个事例来说貌似是还蛮不错的,可是咱们有没有想过,关于一个文本翻译使命来说,往往里边有许多许多的汉字,假定有10000个,那么一个单独的字,如“秃”就需求一个110000维的矩阵来表明,而且矩阵中有9999个0,这无疑是对空间的一种糟蹋。
- 这种编码办法无法表明两个相关单词的联系,如“秃”和“头”这两个单词显着是有某种内涵的联系的,可是独热编码却无法表明这种联系。
那么咱们选用什么办法来缓解这种问题呢?答案便是Embedding!!!那么何为Embedding呢,我的了解便是改变本来输入input的维度,。比如咱们现在别离先用“1”,“2”,“ 3”,“ 4” 别离代表“秃”,“头”,“小”,“苏”这四个字,然后将“1”,“2”,“ 3”,“ 4”送入embedding层,代码如下:
import torch
import torch.nn as nn
embedding = nn.Embedding(5, 3)
input = torch.IntTensor([[1,2,3,4]])
上文代码(5,3)中的3就代表咱们输出每个单词的维度,能够看一下输出成果,如下图所示:
输出矩阵的每一行都代表了一个词,如榜首行[0.2095 -0.6338 0.5679]代表1,即代表“秃”。
咱们能够修正一下Embedding的参数,将(5,3)换成(5,4),如下:
import torch
import torch.nn as nn
embedding = nn.Embedding(5, 4)
input = torch.IntTensor([[1,2,3,4]])
这时咱们在来看看输出成果,此刻每个词便是一个4维向量:
经过上面代码的演示,不知咱们有没有体会到Embedding能够操控输入维度的效果呢。有关Embedding函数的运用请参照pytorch官网对此部分的解读,点击☞☞☞了解概况。
终究咱们来大致看看经过Embedding后会达到怎样的效果:
能够看出,“秃”和“头”在某个空间中离的比较近,阐明这两个词的相关性较大。即Embedding不仅能够操控咱们输入的维度,还能够从较高的维度去考虑一些词,那么会发现一些词之前存在某种关联。
Positional Encoding(方位编码)
首要谈谈咱们为什么要选用方位编码,还记得咱们前文所说的Attention操作嘛,其选用的是并行化的操作,即会将输入一同输入Attention,这种并行化就会导致在输入是没有是没有顺序的。相同拿输入“秃 头 小 苏”为列,没有加入方位编码时,咱们不论时输入“秃 头 小 苏”、“小 头 苏 秃”或其它等等,对咱们的输出成果是没有任何影响的,这部分此篇文章还简略的做了个小实验,咱们能够
参阅一下。
经过上文的介绍,咱们知道没有方位编码会导致不论咱们的输入顺序怎样改换,关于终究的成果是没有影响的,这肯定不是咱们希望看到的。那咱们就给它整个方位编码呗!可是咱们应该选用什么办法的方位编码呢?我想咱们能够很天然的想到一个,那便是一个词标一个数字就得了呗,如下表所示:
词 | 编码 |
---|---|
秃 | 0 |
头 | 1 |
小 | 2 |
苏 | 3 |
这种编码操作简略,可是编码长度是不可控的,即词的个数越多,后边编码词越大,这样的办法其实不是抱负的。
那咱们还能够运用什么编码办法呢?已然上述所述编码规则是编码长度不可控,那么就能够经过除以词的长度将其操控在0-1的规模内呀,如下表所示:
词 | 编码 |
---|---|
秃 | 04=0.00\frac{0}{4}=0.00 |
头 | 14=0.25\frac{1}{4}=0.25 |
小 | 24=0.50\frac{2}{4}=0.50 |
苏 | 34=0.75\frac{3}{4}=0.75 |
你或许觉得这种编码办法仍是蛮不错的,可是呢这种办法会导致成果的尺寸会跟着词的长度改换而不断改换,即上例中咱们每个词编码成果的距离是0.25,可是要是咱们有100个词,有100个词时,这个距离又会变成多少呢?这种标准的不一致,对模型的练习是不友好的。
“你一会介绍这个办法,这个办法不可;一会介绍那个办法,那个办法不可。那究竟行不可!!!”,~~呜呜,大佬们别喷啊,我这是想让咱们看看有哪些思路,况且论文中所给的编码办法也不必定是最好的,咱们都能够多想想嘛。那么下面就给各位老大爷带来论文中关于此部分的方位编码办法,公式如下:
PEpos,2i=sin(pos/(100002i/dmodel))PE_{pos,2i}=sin(pos/(10000^{2i/d_{model}}))
PEpos,2i+1=cos(pos/(100002i/dmodel))PE_{pos,2i+1}=cos(pos/(10000^{2i/d_{model}}))
不知道咱们看到这个公式做何感想呢?反正对我来说我是懵的。下面就为咱们来介绍介绍。首要来解说一下公式中符号的意义:pos
表明词的方位,相同拿“秃 头 小 苏”为例,pos=0
表明榜首个词“秃”,pos=1
表明第二个词“头”。2i
和2i+1
表明Positional Encoding(方位编码)的维度,这个怎样了解呢,咱们知道2i
是偶数位,2i+1
是奇数位,假定咱们现在对“秃”字进行方位编码,那么方位编码向量的第0个方位,即偶数位选用的是PEpos,2i=sin(pos/(100002i/dmodel))PE_{pos,2i}=sin(pos/(10000^{2i/d_{model}}))这个公式,而方位编码向量的第1个方位,即奇数位选用的公式为PEpos,2i+1=cos(pos/(100002i/dmodel))PE_{pos,2i+1}=cos(pos/(10000^{2i/d_{model}})) 。dmodeld_{model}表明输入的维度大小,即咱们上小节所述的Input Embedding。【注id的取值规模为[0,…,dmode/2][0,…,d_{mode/2}]】
知道了这些符号意义,不知道咱们是否有所感悟。假如感觉还差一点的话也不要紧,我信任我再举两个比如咱们就了解了。首要仍是“秃 头 小 苏”这个比如,咱们先来看看榜首个词“秃”的方位编码:【注:设dmodel d_{model}=512】
再来看看“头”的编码,如下:
我信任经过上面的比如你应该现已对这种办法的方位编码有所了解了,即你知道了怎样用这种办法来对某个词进行编码。可是你或许会问,为什么用这个办法来进行方位编码呢?即这种方位编码的优势在哪里呢?这儿我为咱们出现3点:
- 每个方位都有唯一的一个方位编码
- 能够习惯比练习集里边一切语句更长的语句,假定练习集里边最长的语句是有 100 个单词,突然来了一个长度为101 的语句,则运用公式核算的办法能够核算出第101位的 Embedding。
- 能够让模型很简略的核算出相对方位。【这一点好像比较难了解,我具体的为咱们说说】
第3点阐明:
第3点说能够让模型很简略的核算出相对方位,怎样了解呢,其实便是说任意方位的PEpos+kPE_{pos+k}都能够被PEposPE_{pos}和PEkPE_{k}表明。这时候许多材料就给咱们列出了一个谁都知道的三角公式,如下:
sin(+)=sin()cos()+cos()sin()sin(\alpha + \beta)=sin(\alpha)cos(\beta)+cos(\alpha)sin(\beta)
cos(+)=cos()cos()−sin()sin()cos(\alpha + \beta)=cos(\alpha)cos(\beta)-sin(\alpha)sin(\beta)
后边就没有解说了,这或许便是专家视角吧!!!以为谁都知道,可是我却不以为咱们都能了解其间的意义,至少我其时就没了解。【大佬请疏忽】下面我就为咱们解说解说为什么经过这两个三角公式就会使任意方位的PEpos+kPE_{pos+k}都能够被PEposPE_{pos}和PEkPE_{k}表明,如下图所示:【注:为便利公式书写,这儿令100002i/dmodel=M{10000^{2i/{d_{model}}}} = M】
经过上图能够看出,关于pos+k
方位的方位编码能够表明位pos
方位和k
方位的线性组合。这样的线性组合意味着某个方位向量蕴含了其它方位向量的信息。
【注:或许许多人会问为什么这个M,即100002i/dmodel{10000^{2i/{d_{model}}}} 中的10000有什么考究嘛,其实吧,也没必要选用这个10000,之前看过一篇英文文章,就对这个数进行过剖析,可是我现在找不着链接了,总之咱们不必特别纠结这个10000】
小结
终究,咱们再来看看这张图:
能够看出咱们终究的输入会将Input Embedding 和Positional Encoding进行相加,那么这就要求Input Embedding 和Positional Encoding的维度使共同的。这儿咱们会不会有这样的疑问呢,咱们将Input Embedding 和Positional Encoding相加,不是会将本来表明方位信息的Positional Encoding混入到Input Embedding中了,这样不就感觉很难再找到Positional Encoding的信息了嘛?好像选用concat(拼接)愈加合适吧!!!这儿给出一种解说,参阅的是这篇文章:【Positional Encoding用eie^i表明,Input Embedding 用aia^i表明】
咱们先给每一个方位的 xi∈R(1,d)x^i \in R(1,d) append一个方位编码的向量 pi∈R(1,N)p^i \in R(1,N) ,得到一个新的输入向量 pi∈R(1,d+N)p^i\in R(1,d+N) ,这个向量作为新的输入,乘以一个transformation matrix
W=[WIWp]∈R(d+N,d)W=\left[ \begin{array}{} {W^I}\\ {{\rm{W}}^p} \end{array} \right]\in R(d+N,d) 。那么:
xpi⋅W=[xipi]⋅[WIWp]=xi⋅WI+pi⋅WP=ai+eix_p^i⋅ W =[x^ip^i]⋅\left[ \begin{array}{} {W^I}\\ {{\rm{W}}^p} \end{array} \right]=x^i⋅W^I+p^i⋅W^P=a^i+e^i
所以,eie^i 与aia^i相加就等同于把本来的输入 xix^i concat一个表明方位的方位编码pi p^i ,再做transformation。
咱们觉得这个解说怎样样呢?我其时看到就觉得这实在是太妙了。那么这部分就为咱们出现这么多了,相同希望咱们都收成满满喔!!!
如若文章对你有所协助,那就