一切故事开始于2017年谷歌的一篇论文:attention is all you need ,由于讲解transformer结构的视频、博客很多,推荐阅读以下内容:
以上两个参考内容足以学会什么是transformer。以下记录比较容易忽略的几个知识点:
首先因为transformer模型不包含recurrence and convolution,为了模型能够利用序列的顺序信息,引入了【pos encoding】,在encoder和decoder底部的输入embedding中添加“位置编码-position encoding”。position encoding与word embedding具有相同的维度d_model,因此可以将两者相加. position encoding得方法有很多种,这里采用了不同频率的sine 和cosine 函数
其中pos为位置,i是维度,例如一个句子长度为L,则 p o s = 0 , 1 , 2 , 3 , . . . , L − 1 pos=0,1,2,3,...,L-1 pos=0,1,2,3,...,L−1, 若 d m o d e l = 512 d_{model}=512 dmodel=512, 则 i = 0 , 1 , 2 , 3..255 i =0,1,2,3..255 i=0,1,2,3..255
单词的位置和顺序是任何语言的基本组成部分。它们定义了语法,从而定义了句子的实际语义。递归神经网络 (RNN) 本质上考虑了词的顺序;他们以顺序的方式逐字解析一个句子。这会将单词的顺序整合到 RNN 的主干中。
但是 Transformer 架构抛弃了递归机制,转而采用多头自注意力机制。避免 RNN 的重复方法将导致训练时间大幅加快。并且理论上,它可以在一个句子中捕获更长的依赖关系。
由于句子中的每个单词同时流经 Transformer 的编码器/解码器堆栈,所以模型本身对每个单词没有任何位置/顺序感。因此,仍然需要一种方法将单词的顺序合并到我们的模型中。
给模型一些秩序感的一种可能的解决方案是向每个单词添加一条关于其在句子中的位置的信息。我们称之为“信息片段”,即位置编码。
可能会想到的第一个想法是为 [0, 1] 范围内的每个时间步分配一个数字,其中 0 表示第一个单词,1 是最后一个时间步。你能弄清楚它会导致什么样的问题吗?它将引入的问题之一是您无法弄清楚特定范围内存在多少个单词。换句话说,时间步长增量在不同句子之间没有一致的含义,也就是不同长度的句子之间即使是相同的两个词相邻,它们之间的间隔也不一定相同,句子越长间隔越短。
另一个想法是线性地为每个时间步分配一个数字。也就是说,第一个单词被赋予“1”,第二个单词被赋予“2”,以此类推。这种方法的问题在于,不仅值会变得非常大,而且我们的模型可以面对比训练中的句子更长的句子。此外,我们的模型可能不能看到任何具有特定长度的样本,这会损害我们模型的泛化能力。
理想情况下,应满足以下标准:
作者使用的Postion Encoding 函数符合以上设计,首先不是一个简单的数字,而是一个包含句子中指定位置信息的d-维向量,其次,encoding没有集成到模型本身,相反这个向量是用来匹配句子中每一个词的位置信息。换句话说,我们通过注入单词顺序信息提升了模型输入。
根据作者给出的公式 P E ( p o s , 2 i ) = s i n ( p o s / 100 0 2 i / d m o d e l ) PE(pos,2i)=sin(pos/1000^{2i/d_{model}}) PE(pos,2i)=sin(pos/10002i/dmodel),我们简化一下:
{ P E ( p o s , 2 i ) = s i n ( p o s ∗ w i ) , 偶 数 位 P E ( p o s , 2 i + 1 ) = s i n ( p o s ∗ w i ) , 奇 数 位 其 中 , w i = 1 1000 0 2 i / d m o d e l \begin{array}{l} \left\{\begin{matrix} PE(pos,2i)=sin(pos*w_{i}),偶数位\\ PE(pos,2i+1)=sin(pos*w_{i}),奇数位\\ 其中,w_i =\frac{1}{10000^{2i/d_{model}}} \end{matrix}\right. \end{array} ⎩⎨⎧PE(pos,2i)=sin(pos∗wi),偶数位PE(pos,2i+1)=sin(pos∗wi),奇数位其中,wi=100002i/dmodel1
由论文所述,postional encoding与Embedding 具有相同的维度 d m o d e l d_{model} dmodel,因此为了两者匹配,对于 w i w_i wi 而言, 2 i ∈ [ 0 , d m o d e l ] , 则 i ∈ [ 0 , d 2 − 1 ] 2i \in [0,d_{model}],则 i \in [0,\frac{d}{2}-1] 2i∈[0,dmodel],则i∈[0,2d−1],并且i 是随着位置移动而增大,则 w i w_i wi是逐渐变小的,根据根据三角函数 y = A s i n ( w x + b ) y=Asin(wx+b) y=Asin(wx+b),其周期为 2 π w \frac{2\pi}{w} w2π,对于本式而言,
{ min i = 0 ⇒ max w i = 1 ⇒ 2 π w i = 1 ∗ π max i = d 2 ( 近 似 值 , 假 设 可 以 被 整 除 ) ⇒ min w i = 1 10000 ⇒ 2 π w i = 10000 ∗ 2 π \begin{array}{l} \left\{\begin{matrix} \min i=0 \Rightarrow \max w_i=1 \Rightarrow \frac{2\pi }{w_i}=1*\pi \\ \max i=\frac{d}{2}(近似值,假设可以被整除) \Rightarrow \min w_i=\frac{1}{10000} \Rightarrow \frac{2\pi}{w_i}=10000*2\pi \end{matrix}\right. \end{array} {mini=0⇒maxwi=1⇒wi2π=1∗πmaxi=2d(近似值,假设可以被整除)⇒minwi=100001⇒wi2π=10000∗2π
这也是作者所说波形长度从 2 π 2\pi 2π到 20000 π 20000\pi 20000π变化的原因。
同时根据三角函数性质:
{ sin ( α + β ) = sin α cos β + sin β cos α cos ( α + β ) = cos α cos β − sin α sin β \left\{\begin{matrix} \sin (\alpha +\beta)=\sin\alpha \cos\beta+\sin \beta \cos \alpha \\ \cos(\alpha +\beta )=\cos\alpha \cos\beta -\sin\alpha \sin\beta \end{matrix}\right. {sin(α+β)=sinαcosβ+sinβcosαcos(α+β)=cosαcosβ−sinαsinβ
可知:
{ P E ( p o s + k , 2 i ) = P E ( p o s , 2 i ) ∗ P E ( p o s , 2 i + 1 ) + P E ( p o s , 2 i + 1 ) ∗ P E ( k , 2 i ) P E ( p o s + k , 2 i + 1 ) = P E ( p o s , 2 i + 1 ) ∗ P E ( k , 2 i + 1 ) − P E ( p o s , 2 i ) ∗ P E ( k , 2 i ) \left\{\begin{matrix} PE(pos+k,2i)=PE(pos,2i)*PE(pos,2i+1)+PE(pos,2i+1)*PE(k,2i) \\ PE(pos+k,2i+1)=PE(pos,2i+1)*PE(k,2i+1)-PE(pos,2i)*PE(k,2i) \end{matrix}\right. {PE(pos+k,2i)=PE(pos,2i)∗PE(pos,2i+1)+PE(pos,2i+1)∗PE(k,2i)PE(pos+k,2i+1)=PE(pos,2i+1)∗PE(k,2i+1)−PE(pos,2i)∗PE(k,2i)
可以看出,对于 p o s + k pos+k pos+k位置向量的某一维 2 i 2i 2i或 2 i + 1 2i+1 2i+1而言,可以表示为 p o s pos pos位置与K位置的向量的 2 i 2i 2i与 2 i + 1 2i+1 2i+1的线性组合,这样的线性组合意味着位置向量中蕴含了相对位置信息。
现在可以想象在embedding t的向量表示(假设d可以被2整除):
那么如何使用sin、cos交替表示位置信息:
以4位二进制(等同于 d m o d e l = 4 d_{model}=4 dmodel=4),表示L=16的位置信息如下:
可以发现最后一位二进制数在每个数字之间都发生变化,次一位二进制数在每两个数之间发生变化,再次一位是4个数发生一次变化,以此类推。但是在浮点数表示的空间使用二进制有点浪费,可使用浮点对应的连续函数-正弦函数。通过sin、cos交替变换,
假设句子 [ w 1 , w 2 , w 3 , . . . w n ] [w_1,w_2,w_3,...w_n] [w1,w2,w3,...wn]中的某个词 w t w_t wt ,其对应的word Embedding 为 ψ ( w t ) \psi(w_t) ψ(wt),相应的位置编码为 p t → \overrightarrow{p_t} pt,则对应操作可以表示为:
Ψ ′ ( w t ) = ψ ( w t ) + p t → ( 代 表 : p o s e m b e d d i n g ) \Psi^{\prime}(w_t) =\psi (w_t)+\overrightarrow{p_t} (代表 :pos \space embedding) Ψ′(wt)=ψ(wt)+pt(代表:pos embedding)
为了使这种求和成为可能,保持位置嵌入的维度等于词嵌入的维度: d p o s t i o n a l e m b e d d i n g = d w o r d e m b e d d i n g d_{postional \space embedding}=d_{word \space embedding} dpostional embedding=dword embedding
简单理解如下,对于频率为 w k w_k wk的sine-cosine对,这里有一个线性转换 M ∈ R 2 x 2 M\in R^{2x2} M∈R2x2(独立于t)遵循下面公式:
$$
$$
发现求解得到的M矩阵不依赖于t ,且与旋转矩阵很相似,同样对于其他sin-cos对,最终允许我们把 p t + ϕ → \overrightarrow{p_{t+\phi}} pt+ϕ表示为针对固定偏移 ϕ \phi ϕ的位置向量 p t → \overrightarrow{p_t} pt的线性函数。这个属性,让模型可以轻易学习相对位置信息。
正弦位置编码的另一个特性是相邻时间步长之间的距离是对称的,并且随时间很好地衰减。
我找不到这个问题的任何理论原因。由于求和(与连接相反)保存了模型的参数,因此将最初的问题改成“将位置嵌入添加到单词有什么缺点吗?”是合理的。我会说,不一定!
最初,如果我们注意图 2,我们会发现只有整个嵌入的前几个维度用于存储有关位置的信息(请注意,尽管我们的玩具示例很小,但报告的嵌入维度是 512)。而且由于 Transfomer 中的嵌入是从头开始训练的,因此参数的设置方式可能是单词的语义不会存储在前几个维度中,以避免干扰位置编码。
出于同样的原因,我认为最终的 Transformer 可以将单词的语义与其位置信息分开。此外,没有理由将可分离性视为优势。也许总和为模型学习提供了一个很好的特征来源。
有关更多信息,我建议您查看以下链接:链接 1、链接 2。
幸运的是,Transformer 搭配使用了残差连接。因此,来自模型输入(包含位置嵌入)的信息可以有效地传播到处理更复杂交互的进一步层。
我个人认为,只有同时使用正弦和余弦,我们才能将正弦(x+k)和余弦(x+k)表示为sin(x)和cos(x)的线性变换。似乎你不能用单个正弦或余弦做同样的事情。
reference :
1.https://kazemnejad.com/blog/transformer_architecture_positional_encoding/
2. https://www.zhihu.com/question/347678607
3.Self-Attention with Relative Position Representations
mask 表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer 模型里面涉及两种 mask,分别是 padding mask 和 sequence mask。其中,padding mask 在所有的 scaled dot-product attention 里面都需要用到,而 sequence mask 只有在 decoder 的 self-attention 里面用到。
什么是 padding mask 呢?因为每个批次输入序列长度是不一样的也就是说,我们要对输入序列进行对齐。具体来说,就是给在较短的序列后面填充 0。但是如果输入的序列太长,则是截取左边的内容,把多余的直接舍弃。因为这些填充的位置,其实是没什么意义的,所以我们的attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。
具体的做法是,把这些位置的值加上一个非常大的负数(负无穷),这样的话,经过 softmax,这些位置的概率就会接近0.
而我们的 padding mask 实际上是一个张量,每个值都是一个Boolean,值为 false 的地方就是我们要进行处理的地方。
文章前面也提到,sequence mask 是为了使得 decoder 不能看见未来的信息。也就是对于一个序列,在 time_step 为 t 的时刻,我们的解码输出应该只能依赖于 t 时刻之前的输出,而不能依赖 t 之后的输出。因此我们需要想一个办法,把 t 之后的信息给隐藏起来。
那么具体怎么做呢?也很简单:产生一个上三角矩阵,上三角的值全为0。把这个矩阵作用在每一个序列上,就可以达到我们的目的。
reference: