论文小结

  这是17年的老论文了,Transformer的出处,刚发布时的应用场景是文字翻译。BLUE是机器翻译任务中常用的一个衡量标准。

  在此论文之前,序列翻译的主导模型是RNN或者使用编解码器结构的CNN。本文提出的Transformer结构不需要使用循环和卷积结构,是完全基于注意力机制的模型。Transformer在序列转换上具有高并行度,在两个机器翻译的任务上都得到了卓越的成果,且其训练时间显著减少。在WMT2024的英语转法语翻译任务上,本文的Transformer模型在

8

8

8张P100 GPU上训练

3.5

3.5

3.5天达到了收敛目标。

  同时,作者表示Transformer框架可以泛化到其他任务上,效果都能很好。现实也是如此,ViT在各种任务上也是一次次刷新指标,与CNN相印上升。

与其他结构的对比

RNN结构存在的问题

  RNN的结构,是2017年之前常用于处理时序问题的。但其存在两个问题:

(1)在

t

t

t时刻的输入有前一个时刻的隐藏层输出

h

t

1

h_{t-1}

ht−1​,所以时序计算是串行的,难以并行化进行加速计算。因此,RNN模型在GPU/TPU这种并行化设计的硬件上性能会较差。(2)RNN结构中的时序信息是逐步传递的,当时序信息较长时,隐藏层状态

h

t

h_t

ht​可能会遗失历史信息。如果想让

h

t

h_t

ht​尽可能保留历史信息,则要让

h

t

h_t

ht​尽量大,就会让内存的开销更大。

  Transformer结构能够优化训练时难以并行训练的问题,能够大大减少并行时间。Transformer能够并行的原因是没有使用RNN的循环结构。同时,Transformer架构可以一次将所有的输入信息放入编码器中处理,能够一个结构就让第一个向量和最后一个向量联系起来,因而也能缓解第二个遗失历史信息的问题。

  Attention机制在RNN中已经有所应用,主要是在循环中将编码器(encoder)的信息传递到解码器(decoder)中。

CNN结构处理时序计算存在的问题

  CNN结构,如果要让任意位置的输入和任意位置的输出产生联系(如果产生长远信息联系),则需要将网络的深度拉大。因为CNN使用的滑动窗口是比较小的(

3

×

3

,

5

×

5

3\times 3, 5\times 5

3×3,5×5),能看到的单层信息比较少。这就使得长远位置之间的依赖关系学习变得更加困难。

  在Transformer中,使用的attention机制可以将输入的任意位置信息联系起来。尽管使用attention机制会将有效维度降低(类似于CNN中的卷积,一个kernel输出一个channel),使用多头注意力机制(Multi-Head Attention)来解决这个问题。

Self-Attention

  self-attention,自注意力,是一种将单个序列的不同位置相关联的注意力机制。形象而言,是不添加额外输入的情况下,自己能够完成的抽象Attention动作的结构。

Transformer结构

  大多数具有竞争力的转换模型都是由编码器-解码器(encoder-decoder)结构组成。模型是自回归模型。自回归的意思为上一时刻的输出要参与当前时刻的输入组成部分(除了原始输入,还需要上一时刻的输入)。   原始输入序列是长度为

n

n

n的词,表示为

x

=

(

x

1

,

.

.

.

,

x

n

)

x=(x_1,...,x_n)

x=(x1​,...,xn​),将其编码成长度为

n

n

n的向量

z

=

(

z

1

,

.

.

.

,

z

n

)

z=(z_1,...,z_n)

z=(z1​,...,zn​),输出的目标是长度为

m

m

m的序列

y

=

(

y

1

,

.

.

.

,

y

m

)

y=(y_1,...,y_m)

y=(y1​,...,ym​)。

  Transformer的架构设计也是encoder-decoder方式。具体来说,就是将self-attention和全连接层堆叠在一起。Transformer架构如下图所示。

结构解析

  如上图所示,Transformer的整体结构是比较简单明了的。就是一个编码器,一个解码器组成。每个编码器和解码器都重复各自的模块N次。参数

N

N

N在Transformer中默认为

6

6

6。

Encoder

  Encoder里面由

N

N

N个相同结构的块组成,每个块由两个组成部分:(1)多头自注意力机制(multi-head self-attention mechanism);(2)全连接层(Feed Forward)网络。每个子块都使用残差链接和 Layer Normalization(LN),表示为

O

u

t

=

L

N

(

I

n

+

S

u

b

l

a

y

e

r

(

x

)

)

\mathcal{Out}=\mathbb{LN}(\mathcal{In + \mathcal{Sublayer}(x)})

Out=LN(In+Sublayer(x))。为了方便残差连接的使用,所有模块的输入输出维度

d

m

o

d

e

l

=

512

d_{model}=512

dmodel​=512。整个模型的可调参数只有两个,为

N

N

N和

d

m

o

d

e

l

d_{model}

dmodel​。(后面的bert、GPT基本也是可调参数只有

N

N

N和

d

m

o

d

e

l

d_{model}

dmodel​)。

  Embedding Layer,就是前面所说的词变成向量的过程。

Decoder

  Decoder中的块与Encoder类似,只是多了一个 Masked Multi-Head Attention 。Decoder的输入包含Encoder的输出,以及模型在

t

t

t时刻前的输出。   Decoder是一个自回归结构,即当前时刻的输入集里面有前面一些时刻的输出(图中的shifted right即输出从前往后移)。为了让训练和预测时候对应上(预测的时候是不知道当前时刻及未来时刻的输出的),就添加了一个Masked模块来掩膜后面的输出(解码器的输入Outputs就是之前时刻的输出,作为当前时刻的输入)。   注意力机制里面,能够一次性看到完整的输入。在训练的时候,Decodes的输入不能直接使用当前时刻及未来时刻的输出,因而使用了个Mask。

  前面时刻的输出在Decoder中作为Query,Encoder的输出作为Decoder中的Key和Value。【Query/Key/Value是作者解释Attention中的概念,后续会提及】

Attention

  Attention函数,在本文是通过Key/Value/Query的方式来解释的。将Query与一组键值对映射到输出,类似于线性代数中每个输入向量都可以由一组基向量加权和组合而成。

  在推理时,Key和Value固定,而用户的Query是变化的。把Value看做是基准向量,通过计算Query和每个Key的相似度,来为每个Value赋予权重,最终得到带权重的Value,相加得到该Query的输出。   自注意力机制,就是Key和Value不变,由于Query改变,得到不一样的权重,导致输出不一样。而不同的相似度函数导致不一样的注意力版本。

  本文的Attention名字为Scaled Dot-Product Attention",实际上就是一个点积的注意力机制,只不过多了一个系数,所以名字会带有一个"Scaled"。

  当

d

k

d_k

dk​较小时,加性注意力机制和点积注意力机制表现得相近 。当

d

k

d_k

dk​较大时,加性注意力机制由于不受

d

k

d_k

dk​数值变化的影响,所以要优于点积注意力机制。作者怀疑随着

d

k

d_k

dk​的增大,点积的幅度会变大,从而将Softmax函数推入梯度极小的区域(经过softmax后较大值趋于

1

1

1,其余值趋于

0

0

0,这样反向传播的梯度也小)。为了抵消这种影响,作者将点积缩放

1

d

k

\frac1{\sqrt{d_k}}

dk​

​1​。

  两种注意力机制在理论复杂性差不多。但点积注意力在实际上会更快一些和空间利用更有效一些,因为可以利用矩阵乘法进行并行计算的性质。这也是选择点积注意力机制的原因。

处理流程

  输入的组成部分有Query,Key和Value,其中Query, Key的维度为

d

k

d_k

dk​,Value的维度为

d

v

d_v

dv​。Query和Key做内积计算相似度,再除以

d

k

\sqrt{d_k}

dk​

​,经过

S

o

f

t

m

a

x

\mathcal{Softmax}

Softmax得到归一化的最终权重。将权重作用到Value上,则可达到最终输出。

处理细节

  向量的计算变成矩阵的计算来并行计算加速是很自然的事情。Q是一个长度为

n

n

n,维度为

d

k

d_k

dk​的矩阵,K是一个长度为

m

m

m,维度为

d

k

d_k

dk​的矩阵,这样内积的结果为长度为

n

×

m

n\times m

n×m的矩阵。再与长度为

m

m

m,维度为

d

v

d_v

dv​的矩阵V相乘,得到输出矩阵为

n

×

d

v

n\times d_v

n×dv​。公式如下图所示

A

t

t

e

n

t

i

o

n

(

Q

,

K

,

V

)

=

s

o

f

t

m

a

x

(

Q

K

T

d

k

)

V

(1)

Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V\tag{1}

Attention(Q,K,V)=softmax(dk​

​QKT​)V(1)

Multi-Head Attention

  这个multi-head的概念,和CNN中channel的概念是类似的。就是各通道各司其职,想要学习不同方面的信息。

  作者认为与其做一个单个的注意力函数,不如把Query、Key、Value都投影到低维空间,投影

h

h

h次,然后做

h

h

h次的注意力函数。将

h

h

h个输出 concat 到一起,如上图

2

2

2右所示。这样每个线性层的输出维度为

d

m

o

d

e

l

h

\frac{d_{model}}{h}

hdmodel​​,因为Transformer中每个模块的输出

d

m

o

d

e

l

=

512

d_{model}=512

dmodel​=512是固定的。在本作的实验中,将

h

h

h设为

8

8

8,

d

k

=

d

v

=

d

m

o

d

e

l

h

=

64

d_k=d_v=\frac{d_{model}}{h}=64

dk​=dv​=hdmodel​​=64,总体计算量和single-head attention差不多。

  至于为什么要做多头注意力机制。实际上,在Transformer的attention结构中(Scaled Dot- Product Attention)是没有什么科学的参数组的。加入投影的概念,可以让模型在投影空间中自适应匹配不同的模式,这样也像CNN中多个输出通道的感觉。

Attention的应用

  Self-Attention在CNN(SENET)中的应用,大概是下图所示,将输入经过投影(例如此图中的池化层+FC层堆叠),得到一个自适应矩阵,再与自身进行计算。

  Transformer中的attention也是大概如此。但由于Transformer设计的Attention有三个输入,在实际应用时在三个不同位置有不同情况的实现。   在 "encoder-decoder attention"层中,Query来自于前面的decoder层,Key和Value来自于encoder的输出。和前面介绍的一样,这样解码器的Query每个位置,都可以直接与编码器中的每个位置进行计算,这就是Attention的作用。   在编码器中,所有的Keys、Value、Query都在同一个位置,来自于前一层的输出,编码器中的每个位置可以关注编码器上一层的位置。Keys、Value、Query都由输入

X

X

X经过线性变换得到,输入是同一个,经过不同的线性变化得到。所以这也叫Self-Attention。   类似的,在解码器中的自注意力层允许访问输入的所有位置。因为需要保持解码器的左向信息流以保持自回归属性,所以作者通过屏蔽(masking out, setting to

-\infty

−∞)softmax中输入中未来时刻的信息。

Position-wise Feed-Forward Networks

  该网络实际上就是FC蹭,中间有一个ReLU激活函数。

F

F

N

(

x

)

=

m

a

x

(

0

,

x

W

1

+

b

1

)

W

2

+

b

2

(2)

FFN(x)=max(0, xW_1+b_1)W_2+b_2\tag{2}

FFN(x)=max(0,xW1​+b1​)W2​+b2​(2)

  也可以认为是Conv

1

×

1

1\times1

1×1,子层的输入输出都是

d

m

o

d

e

l

=

512

d_{model}=512

dmodel​=512,FC的中间层维度为

d

f

f

=

2048

d_{ff}=2048

dff​=2048。

Embeddings and Softmax

  Embeddings这个层,在序列转换上的模型基本都是类似的,就是学习嵌入(learn embeddings)将输入标记和输出标记(input token和output token)转换为

d

m

o

d

e

l

d_{model}

dmodel​的向量,实际上就是一组权重来映射到制定维度。

  然后使用线性层和softmax将decoder的输出转换为预测的下一个token概率,即Softmax之前也有一个embedding层的意思。

  在本文的模型中,在两个输入embedding层(encoder的输入embedding,和decoder的输入(output)embedding,以及softmax前的embedding)使用的是同一组共享的参数权重。

  三个embedding层是一样的权重,这样训练会简单一点。由于

L

2

L2

L2正则化会让维度大的权重变下(维度变大,向量归一化后,单个值就会很小)。所以权重乘以一个

d

m

o

d

e

l

\sqrt{d_{model}}

dmodel​

​,让学习的更均衡一点(后续会有label smoothing)。

  从最基本的One-Hot编码,到PCA降维,从Word2Vec到Item2Vec,Embedding将稀疏的One-hot编码稠密化,再输入到DNN中。

Positional Encoding

  本文的模型不包含递归和卷积,所以本文的Attention结构是不包含时序信息的。Attention的输出是Value的加权和,其权重是Query和Key的距离。

  为了使模型能够利用序列的顺序,所以我们必须注入一些有关序列中标记的相对或绝对位置的信息。因此,作者选择将“位置编码”添加到编码器和解码器堆栈底部的输入embedding中,因此可以将两者相加。位置编码有多种选择,有学习的和固定的。

  本文中使用不同频率的正弦和余弦函数。

P

E

(

p

o

s

,

2

i

)

=

s

i

n

(

p

o

s

/

1000

0

2

i

/

d

m

o

d

e

l

)

P

E

(

p

o

s

,

2

i

+

1

)

=

c

o

s

(

p

o

s

/

1000

0

2

i

/

d

m

o

d

e

l

)

\begin{aligned}%aligned命令对齐,在对齐的地方用"&" PE_{(pos, 2i)}&=sin(pos/10000^{2i/d_{model}}) \\ PE_{(pos, 2i+1)}&=cos(pos/10000^{2i/d_{model}}) \end{aligned}

PE(pos,2i)​PE(pos,2i+1)​​=sin(pos/100002i/dmodel​)=cos(pos/100002i/dmodel​)​

  作者还尝试了基于学习的位置嵌入,发现两个版本产生几乎相同的结果。作者选择了正弦版本,因为它可以允许模型推断出比训练期间遇到的序列长度更长的序列长度。

论文实验和结构分析

  Transformer的Attention结构和其他结构进行对比如下图所示,可以看出Transformer在计算复杂度和时序计算上、时序交流上有较为直观的优势。

  对于英语转德语,训练集使用的是标准的WMT 2014英语转德语数据集,大概有

450

450

450万个句子对。句子用BPE(btye-pair encoding),该编码格式下具有

37000

37000

37000个标记的共享源-目标词汇(英语和德语共享的,这样就可以让decoder和encoder的输入端使用同一个embedding层,结构看起来简单点)。   每个训练批次包含一个句子对,其中包含大约

25000

25000

25000个源标记和

25000

25000

25000个目标标记。

  至于为什么使用BPE而不是单词,是因为如果使用单词为一个组(token),则会出现一个动词的不同形态在模型中表示不一样的问题(模型不明白不同形态之间的区别及关系)。所以这里采用词根的方式,这样能让字典小一点。

  训练本文模型使用了

8

8

8个NVIDIA P100 GPU。对于base model(参数较少的模型),使用前面所述的参数,每个训练step花费

0.4

s

0.4s

0.4s,一共训练

100

,

000

100,000

100,000个step,耗时

12

12

12小时。对于更大一点的模型(表

3

3

3的下面那行),每一个step耗时

1.0

s

1.0s

1.0s,一共训练

300

,

000

300,000

300,000个step,耗时

3.5

3.5

3.5天。

  对于Transformer模型,可以认为其使用了更少的先验信息,对整个模型的假设做的更少,所以需要更多的数据和更大的模型才能训练出来CNN和RNN相似的效果。

  优化器使用Adam,

β

1

=

0.9

\beta_1=0.9

β1​=0.9,

β

2

=

0.98

\beta_2=0.98

β2​=0.98,

ϵ

=

1

0

9

\epsilon=10^{-9}

ϵ=10−9,设

w

a

r

m

u

p

_

s

t

e

p

s

=

4000

warmup\_steps=4000

warmup_steps=4000学习率策略公式如下所示

l

r

a

t

e

=

d

m

o

d

e

l

0.5

m

i

n

(

s

t

e

p

_

n

u

m

0.5

,

s

t

e

p

_

n

u

m

w

a

r

m

u

p

_

s

t

e

p

s

1.5

)

lrate=d_{ model}^{-0.5}*min(step\_num^{-0.5}, step\_num*warmup\_steps^{-1.5})

lrate=dmodel−0.5​∗min(step_num−0.5,step_num∗warmup_steps−1.5)

  正则化采用了两种。一种是Dropout,应用方式为残差Dropout,在每个子层的残差连接和LN之前使用,Dropout率为

P

d

r

o

p

=

0.1

P_{drop}=0.1

Pdrop​=0.1。另一种是标签平滑(Label Smoothing),让正确的位置标签为

0.1

0.1

0.1,其余位置为

0.9

/

l

e

n

g

t

h

0.9/{length}

0.9/length。标签平滑会让模型学习的方向确定性下降,但从结果来说能提高准确率和BLEU分数。

关于标签平滑的阐述

  关于标签平滑,原始的标签(Softmax结果的目标)是正确位置为

1

1

1,其余位置为

0

0

0。但实际上Softmax函数(

e

z

i

e

z

j

+

ϵ

\frac{e^{z_i}}{\sum e^{z_j}+\epsilon}

∑ezj​+ϵezi​​)很难接近于

1

1

1。只有目标位置趋于无穷大时,softmax趋于

1

1

1。这样会让训练变得困难,所以一般处理方式会让标签的正确位置下降一些,比如

0.8

0.8

0.8。本文设置的

0.1

0.1

0.1可能是因为标签的类别太多了,所以才会降的这么狠。

论文实验

  Transformer和其他模型在英语到德语和英语到法语转换的对比结果。可以看出,BLEU分数和训练时间都较大程度地优化了。

  Transformer各组件的参数消融学习如下图所示:

  词汇关联度Query和Keys的Attention直观展示如下图所示:不同的词汇之间的自适应关联程度。

伪代码角度解析结构

总体解析

  编码器输入 src_input 的Embedding,解码器输入 dec_input 的Embedding 和解码器的输出 dec_output的输出投影(FC层)共用一套权重。【Embedding实际上就是将数字进行one-hot向量化,再经过投影(FC)到一个指定维度,从而得到一个密集向量的过程。这里输出的维度默认为

512

512

512】

  Positional Encoding,依据输入维度和滑动窗口词袋大小,建立一个有大致规律的正弦滤波,再与原输入进行一个元素和的操作,从而叠加了一个位置信息。【这里原文中也有其他常识,比如基于学习类的位置信息,最终效果也差不多。所以可以认为在该任务上,这种先验位置信息的叠加是个很不错的经验。】

  整个模型forward的过程,基本是解析完输入端的输入src_input,对输出端的输入dec_input做自回归循环,直到遇到终止符。

src_input

--> Encoder(src_input)

--> Decoder(src_output, dec_input)

--> Linear(dec_output)

--> Scale(1/sqrt(d_model))

--> Softmax

dec_output

子模块

Multi-Head Attention

  (1)输入Q,K和V,各自经过一个FC层,再分别reshape成

8

8

8组(n_head),每组维度为

64

64

64。   (2)Q/K/V输入到点积Attention层进行矩阵乘法运算,过程如上面公式(1)所示。Dropout添加的位置为Softmax之后,与V的矩阵乘法之前。   (3)attention的输出attn经过FC层用于调整模块输出维度(n_head*d_v -> 512),得到输出out   (4)Dropout应用在这里;   (5)残差连接,但只对原始的输入Q_INPUT进行残差,

O

U

T

=

o

u

t

+

Q

_

I

N

P

U

T

OUT=out+Q\_INPUT

OUT=out+Q_INPUT;   (6)LN模块;

MultiHeadAttention(q, k, v):

Q = fc1(q).reshape(b, c, n_head, d_k)

K = fc2(k).reshape(b, c, n_head, d_k)

V = fc3(v).reshape(b, c, n_head, d_v)

attn = ScaleDotAttention(Q, K, V)

out = dropout(fc4(attn))

out = out + q

out = LN(out)

return out

PositionwiseFeedForward

  这个模块比较简单,就是堆叠的两层FC层,中间有个非线性层ReLU,后面那个FC输出后有Dropout,以及常规的残差及LN;

PositionwiseFeedForward(x):

--> fc1

--> relu

--> fc2

--> dropout

--> Residual(x)

--> LN

Encoder

--> Embedding(Nword->512)

--> Scale(*\sqrt{512})

--> Position Encoding

--> Dropout

--> LN

--> N x Block_Encoder

  Block_Encoder

--> MultiHeadAttention(In, In, In)

--> PositionWiseFeedForward

Decoder

--> Embedding(Nword->512)

--> Scale(*\sqrt{512})

--> Position Encoding

--> Dropout

--> LN

--> N x Block_Decoder

  Block_Decoder

--> dec_out1=MultiHeadAttention(dec_in, dec_in, dec_in)

--> dec_out2=MultiHeadAttention(dec_out1, enc_out, enc_out)

--> PositionWiseFeedForward(dec_out2)

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: