原创不易,转载请注明出处
paper:Attention Is All You Need
论文中给出Transformer的定义是:
Transformer is the first transduction model relying entirely on self-attention to compute representations of its input and output without using sequence aligned RNNs or convolution。
发展动机:
transformer为何优于RNN及RNN的一系列变体?
如上图所示,不管中间用的是RNN还是GRU,它们都无法避免一种情况:它们是前后相互依赖的(图中箭头),对于后面的输出,它依赖的是前面的状态和当前的输入。
这就意味着,我们想要得到这个输出,那就必须得到前面的状态。所以必须要走完上一步,才能走下一步。所以说它的计算被限制为顺序,阻碍了样本的并行化训练。
跳跃连接
,充分发掘DNN模型的特性,提升模型准确率。方便讲解,和原论文一致是机器翻译场景
。
比如输入一段法语,经过transformer,翻译成对应的英语。
如果是第一次接触机器翻译的同学可能会觉得云里雾里,那么博主再以中英翻译为例举个例子帮助小伙伴们理解:
在一个机器翻译场景中的数据预处理部分一般包括以下几个步骤:
这里没什么好讲的,直接上示例代码
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import math
'''句子的输入部分:编码端的输入、解码端的输入、解码端的真实标签'''
# P指padding、S指起始符、E指结束符
sentences = [['我 喜 欢 你 P', 'S i like you', 'i like you E'],
['我 喜 欢 你 P', 'S i like you', 'i like you E'],
['我 喜 欢 你 P', 'S i like you', 'i like you E']]
'''构建词表,编码端和解码端可以共用一个词表,这里方便演示分开构建'''
# encoder端词表及词表大小
src_vocab = {'P': 0, '我': 1, '喜': 2, '欢': 3, '你': 4}
src_vocab_size = len(src_vocab)
# decoder端词表及词表大小
tgt_vocab = {'P': 0, 'i': 1, 'like': 2, 'you': 3, 'S': 4, 'E': 5}
tgt_vocab_size = len(tgt_vocab)
src_len = 5 # length of source
tgt_len = 4 # length of target
'''将单词与词表映射,这里可以理解为label编码'''
enc_inputs, dec_inputs, target_batch = make_batch(sentences)
print(enc_inputs)
print(dec_inputs)
print(target_batch)
tensor([[1, 2, 3, 4, 0],
[1, 2, 3, 4, 0],
[1, 2, 3, 4, 0]])
tensor([[4, 1, 2, 3],
[4, 1, 2, 3],
[4, 1, 2, 3]])
tensor([[1, 2, 3, 5],
[1, 2, 3, 5],
[1, 2, 3, 5]])
'''将单词与词表映射,这里可以理解为label编码,把单词序列转换为数字序列'''
def make_batch(sentences):
input_batch, output_batch, target_batch = [], [], []
for sentence in sentences:
input_batch.append([src_vocab[n] for n in sentence[0].split()])
output_batch.append([tgt_vocab[n] for n in sentence[1].split()])
target_batch.append([tgt_vocab[n] for n in sentence[2].split()])
return torch.LongTensor(input_batch), torch.LongTensor(output_batch), torch.LongTensor(target_batch)
这里简单的来说一下什么是embedding,用一个向量来表征一个单词或是一个句子,这就是embedding,解决了传统onehot离散化带来的稀疏性问题。embedding作为网络的第一层被输入。
比如上述例子,“我喜欢你”,对这句话做一个512维的embedding,如下图:
# 其中src_vocab_size指词表大小,d_model为emb维度
self.src_emb = nn.Embedding(num_embeddings=src_vocab_size, embedding_dim=d_model)
在forward
里,我们需要知道emb后的维度为:[batch_size, src_len, d_model],上述例子中就是:[3, 5, 512]
enc_outputs = self.src_emb(enc_inputs)
tensor([[[-0.6629, 0.7175, -1.2013, ..., 1.3913, -0.7109, 0.4084],
[-0.0628, -0.0403, -0.0125, ..., -0.7531, 1.2500, -0.6480],
[-1.0983, -0.2127, -0.1055, ..., -0.2792, -1.1022, -1.6856],
[ 0.7872, -0.0841, -0.0297, ..., 0.1816, -0.4747, -0.6163],
[ 0.8541, -0.0226, 0.3261, ..., -0.8943, -1.6848, 1.0269]],
[[-0.6629, 0.7175, -1.2013, ..., 1.3913, -0.7109, 0.4084],
[-0.0628, -0.0403, -0.0125, ..., -0.7531, 1.2500, -0.6480],
[-1.0983, -0.2127, -0.1055, ..., -0.2792, -1.1022, -1.6856],
[ 0.7872, -0.0841, -0.0297, ..., 0.1816, -0.4747, -0.6163],
[ 0.8541, -0.0226, 0.3261, ..., -0.8943, -1.6848, 1.0269]],
[[-0.6629, 0.7175, -1.2013, ..., 1.3913, -0.7109, 0.4084],
[-0.0628, -0.0403, -0.0125, ..., -0.7531, 1.2500, -0.6480],
[-1.0983, -0.2127, -0.1055, ..., -0.2792, -1.1022, -1.6856],
[ 0.7872, -0.0841, -0.0297, ..., 0.1816, -0.4747, -0.6163],
[ 0.8541, -0.0226, 0.3261, ..., -0.8943, -1.6848, 1.0269]]],
grad_fn=) torch.Size([3, 5, 512])
从图中我们发现,embedding后要加上位置编码,那么为什么要假如位置编码呢?
这就要引入并行化计算带来的问题,也就是传统RNN循环网络的特点:
在本文的最开始也提到过,它是有前后依赖的语义关系的,在生成”爱“之前必须生成”我“,生成”爱“之后才能生成”你“。
具体的,由于句子中单词的序列关系,所以后一个timestep的输入必须等于前一个timestep的输出,它不具备并行处理的能力;
并且RNN中的每个timestep共享一套参数 u , w , v u,w,v u,w,v,所以会出现梯度消失或梯度爆炸的问题;
相对于attention,它具有并行处理的能力,但并不具有位置信息显示的能力。
ps:这里插一个面试小问题,RNN的梯度消失有什么不同?
连乘效应导致梯度消失放在RNN这里不是太准确,RNN的梯度是一个总的梯度和,它的梯度消失不单单是梯度和变为0,而是说总体的梯度被近距离梯度主导,被远距离梯度忽略不计,这才是RNN梯度消失的真正含义。
所以就有了位置编码,解决方案:引入绝对位置信息和相对位置信息。
上图就是偶数位置使用 sin 函数,奇数位置使用 cos 函数。
根据位置编码公式,可以将emb向量的每个值来区分开来,也可以将每句话来区分开来,比如告诉模型 ”我“ 是在 “喜” 之前的。
将位置编码后的向量和embedding进行对位相加,作为整个transformer的输入
所以根据公式,就能计算出emb向量的位置编码张量。
观察可以发现:
位置编码跟句子⻓度seq_len有关,它由pos决定,句子有多⻓就决定了pos从多少到多少;其次还和emb向量的维度有关。
一旦这两个确定了,那么位置编码就根据公式唯一的确定了。它跟emb向量内的值没有关系。只和seq_len和d_model有关系。
总结来说,当d_model和seq_length确定,位置编码即可确定。
引入位置编码的同时,也引入了绝对位置信息与相对位置信息。
如下图:
def plot_position_embedding(position_embedding):
# 绘制位置编码
plt.pcolormesh(position_embedding[0],cmap='RdBu') # 【50*512】
plt.xlabel('Depth')
plt.xlim((0,512))
plt.ylabel('Position')
plt.colorbar()
plt.show()
position_embedding = positional_encoding(50,512)
plot_position_embedding(position_embedding)
position_embedding
可以这样认为,从下往上看:
根据三角函数公式:
s i n ( α + β ) = s i n ( α ) c o s ( β ) + c o s ( α ) s i n ( β ) c o s ( α + β ) = c o s ( α ) c o s ( β ) + s i n ( α ) s i n ( β ) sin(α + \beta) = sin(\alpha)cos(\beta) + cos(\alpha)sin(\beta) \\ cos(\alpha+\beta) = cos(\alpha)cos(\beta) + sin(\alpha)sin(\beta) sin(α+β)=sin(α)cos(β)+cos(α)sin(β)cos(α+β)=cos(α)cos(β)+sin(α)sin(β)
上面的公式说明:对于词汇之间的位置偏移 k k k, P E ( p o s + k ) PE(pos+k) PE(pos+k)可以表示成 P E ( p o s ) PE(pos) PE(pos)和 P E ( k ) PE(k) PE(k)的组合形式,这就是表达相对位置的能力。
{ P E ( p o s + k , 2 i ) = P E ( p o s , 2 i ) ∗ P E ( k , 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 ) \begin{cases} PE(pos + k, 2i) = PE(pos, 2i) * PE(k, 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{cases} {PE(pos+k,2i)=PE(pos,2i)∗PE(k,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)
说的更直白一点,上图中,红框的位置编码数值可以由绿框、紫框、粉框、蓝框推得而来,也就是表达了相对位置的效果。
ps:这里有一个小trick,就是在位置编码之前加一个缩放:
当emb和位置编码相加了之后,我们希望emb占多数,比如将emb放大10倍,那么在相加后的张 量里,emb就会占大部分。
因为主要的语义信息是蕴含在emb当中的,我们希望位置编码带来的影响不要超过emb。所以对 emb进行了缩放再和位置编码相加。
由位置编码公式可知,它们的一个共同的部分: p o s / 1000 0 2 i / d m o d e l pos / 10000^{2i / d_{model}} pos/100002i/dmodel
我们用log将次方拿下来方便计算:
p o s / 1000 0 2 i / d m o d e l = p o s ∗ 1000 0 − 2 i / d m o d e l = p o s ∗ e − 2 i / d m o d e l ∗ l n ( 10000 ) pos / 10000^{2i / d_{model}} \\ = pos * 10000^{-2i / d_{model}} \\ = pos * e^{-2i / d_{model} * ln(10000)} pos/100002i/dmodel=pos∗10000−2i/dmodel=pos∗e−2i/dmodel∗ln(10000)
''' 3. PositionalEncoding 代码实现 '''
class PositionalEncoding(nn.Module):
'''
位置编码的实现其实很简单,直接对照着公式去敲代码就可以,下面这个代码只是其中一种实现方式;
从理解来讲,需要注意的就是偶数和奇数在公式上有一个共同部分,我们使用log函数把次方拿下来,方便计算;
pos代表的是单词在句子中的索引,这点需要注意;比如max_len是128个,那么索引就是从0,1,2,...,127
假设我的d_model是512,2i那个符号中i从0取到了255,那么2i对应取值就是0,2,4...510
'''
def __init__(self, d_model, dropout=0.1, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len, d_model)
# 生成0~max_len-1的索引位置张量,[max_len] -> [max_len, 1]
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
# 这里需要注意的是pe[:, 0::2]这个用法,就是从0开始到最后面,步长为2,其实代表的就是偶数位置
pe[:, 0::2] = torch.sin(position * div_term)
# 这里需要注意的是pe[:, 1::2]这个用法,就是从1开始到最后面,步长为2,其实代表的就是奇数位置
pe[:, 1::2] = torch.cos(position * div_term)
# 上面代码获取之后得到的pe:[max_len * d_model]
# 下面这个代码之后,我们得到的pe形状是:[max_len * 1 * d_model]
pe = pe.unsqueeze(0).transpose(0, 1)
# 定一个缓冲区,其实简单理解为这个参数不更新就可以
self.register_buffer('pe', pe)
def forward(self, x):
"""
x: [seq_len, batch_size, d_model]
"""
x = x + self.pe[:x.size(0), :]
return self.dropout(x)
受篇幅和排版限制,下面各部分代码不再本文贴出,文章的末尾会放出Git连接,需要的小伙伴可自行下载。
Encoder部分输入是单词的Embedding,再加上位置编码,然后进入一个统一的结构,这个结构可以循环很多次(N次),也就是说有很多层(N层)。每一层又可以分成Attention层和全连接层,再额外加了一些处理,比如Skip Connection,做跳跃连接,然后还加了Normalization层。其实它本身的模型还是很简单的。
关于Attention部分
(Attention、Self-Attention、Multi-Head Attention)
这里不再详细讲解,直接已经讲过了,没看过的同学可以看下:https://mp.weixin.qq.com/s?__biz=Mzk0MzIzODM5MA==&mid=2247484067&idx=1&sn=cae143a546985413507d3bc750f5f7d6&chksm=c337bf3af440362c67f9ac26e82a5a537c1ea09c9041dfc7cfeae35fe93a9b797700bafe7db4#rd
这里只是对于Attention的举例简述,如果没看懂的同学可以去上面的链接详细的了解Attention
我们这里以三个向量举例理解Attention:
"我"
和“宣”
之间的关系,和“宣”
和“我”
之间的关系,是不相等的。
“我”
和“我”
可能会觉得相关性比较大,所以 a 我 我 a_{我我} a我我可能会比较大,“我”
这个向量,需要从“我”
里抽取多少语义信息出来、从“宣”
中抽取多少语义信息出来、从“你”
中抽取多少语义信息出来。抽取后全部加起来形成新的向量去表示“我”
。那么这样一来,“我”
的向量就结合了上下文的语义信息。就不再是原来的emb向量仅仅代表“我”
这个字的含义。公式如下:
再啰嗦一句,上面的例子都是向量,用了小写。这里是矩阵的形式,是张量,所以是大写。
缩放是指除以 d k \sqrt d_k dk, d k d_k dk指的是embedding的维度。
首先我们要明白的一点是:softmax是一种非常明显的⻢太效应 强者越强,弱者越弱。
而缩放后,注意力值就分散些,这样就可以获得更好的泛化能力;
举个例子:
print(tf.math.softmax([[1.0, 2.0, 3.0]]))
print(tf.math.softmax([[10.0, 20.0, 30.0]]))
print(tf.math.softmax([[100.0, 200.0, 300.0]]))
tf.Tensor([[0.09003057 0.24472848 0.66524094]], shape=(1, 3), dtype=float32)
tf.Tensor([[2.061060e-09 4.539787e-05 9.999546e-01]], shape=(1, 3), dtype=float32)
tf.Tensor([[0.0e+00 3.8e-44 1.0e+00]], shape=(1, 3), dtype=float32)
“我”
和“我”
、“我”
和“宣”
一点关系都没有,“我”
和“你”
却有100%的关系。很明显这是不合理的,与我们引入上下文语义信息相悖。这里论中也给出了为什么要这么做:
为什么非要将均值和方差拉到0和1呢?
这是ICS内部协变量偏移问题:
所以我们现在的问题是:
有两个向量:
q = [ q 1 , q 2 , . . . , q d k ] k = [ k 1 , k 2 , . . . , k d k ] 随 机 变 量 q i 、 k i 的 取 值 均 服 从 标 准 正 态 分 布 其 中 d k 为 e m b 维 度 q = [q_1, q_2, ..., q_{dk}] \\ k = [k_1, k_2, ..., k_{dk}] \\ \\ 随机变量 q_i、k_i的取值均服从标准正态分布 \\ 其中dk为emb维度 q=[q1,q2,...,qdk]k=[k1,k2,...,kdk]随机变量qi、ki的取值均服从标准正态分布其中dk为emb维度
求:
( 1 ) q ⊙ k = [ q 1 k 1 , q 2 k 2 , . . . , q d k k d k ] 中 随 机 变 量 q i k i 所 服 从 的 分 布 的 期 望 与 方 差 (1)q \odot k = [q_1k_1, q_2k_2, ..., q_{dk}k_{dk}] \\ 中随机变量 q_ik_i 所服从的分布的 期望 与 方差 \\ (1)q⊙k=[q1k1,q2k2,...,qdkkdk]中随机变量qiki所服从的分布的期望与方差
( 2 ) 设 Z = q ⋅ k T = q 1 k 1 + q 2 k 2 + . . . + q d k k d k 的 E ( Z ) 、 D ( Z ) (2)设 Z = q \cdot k^T = q_1k_1 + q_2k_2 + ... + q_{dk}k_{dk} \\ 的 E(Z)、D(Z) (2)设Z=q⋅kT=q1k1+q2k2+...+qdkkdk的E(Z)、D(Z)
1.获取条件,设置定义
对 于 ∀ i ∈ d k 设 随 机 变 量 X = q i , Y = k i , X Y = q i k i 有 : { E ( X ) = 0 E ( Y ) = 0 { D ( X ) = 1 D ( Y ) = 1 对于 ∀_i ∈ dk \\ 设随机变量 X=q_i,Y=k_i,XY=q_ik_i \\ 有:\\ \begin{cases} E(X) = 0 \\ E(Y) = 0 \end{cases} \quad \quad \begin{cases} D(X) = 1 \\ D(Y) = 1 \end{cases} 对于∀i∈dk设随机变量X=qi,Y=ki,XY=qiki有:{E(X)=0E(Y)=0{D(X)=1D(Y)=1
则 有 : E ( X Y ) = E ( X ) ⋅ E ( Y ) = 0 含 义 : 随 机 变 量 q i k i 服 从 均 值 为 0 的 分 布 即 : q ⊙ k = [ q 1 k 1 , q 2 k 2 , . . . , q d k k d k ] 只 要 d k 足 够 大 , m e a n ( q ⊙ k ) = 0 则有:\\ E(XY) = E(X) \cdot E(Y) = 0 \\ 含义:随机变量 q_ik_i 服从均值为0的分布 \\ 即:q \odot k = [q_1k_1, q_2k_2, ..., q_{dk}k_{dk}] \\ 只要dk足够大,mean(q \odot k) = 0 则有:E(XY)=E(X)⋅E(Y)=0含义:随机变量qiki服从均值为0的分布即:q⊙k=[q1k1,q2k2,...,qdkkdk]只要dk足够大,mean(q⊙k)=0
由 公 式 D ( X ) = E ( X 2 ) − E 2 ( X ) 得 : D ( X Y ) = E ( X 2 Y 2 ) − [ E ( X Y ) ] 2 = E ( X 2 ) E ( Y 2 ) − [ E ( X ) E ( Y ) ] 2 = [ E ( X 2 ) − 0 ] [ E ( Y 2 ) − 0 ] − [ E ( X ) E ( Y ) ] 2 = [ E ( X 2 ) − E 2 ( X ) ] [ E ( Y 2 ) − E 2 [ Y ] ] − [ E ( X ) E ( Y ) ] 2 = D ( X ) D ( Y ) − [ E ( X ) E ( Y ) ] 2 = 1 ∗ 1 − 0 = 1 含 义 : 随 机 变 量 q i k i 服 从 方 差 为 1 的 分 布 即 : q ⊙ k = [ q 1 k 1 , q 2 k 2 , . . . , q d k k d k ] 只 要 d k 足 够 大 , 方 差 ( q ⊙ k ) = 1 由公式 \quad D(X) = E(X^2) - E^2(X) \\ 得:\\ \begin{aligned} D(XY) =& E(X^2Y^2) - [E(XY)]^2 \\ =& E(X^2)E(Y^2) - [E(X)E(Y)]^2 \\ =& [E(X^2) - 0][E(Y^2) - 0] - [E(X)E(Y)]^2 \\ =& [E(X^2) - E^2(X)][E(Y^2) - E^2[Y]] - [E(X)E(Y)]^2 \\ =& D(X)D(Y) - [E(X)E(Y)]^2 \\ =& 1 * 1 - 0 \\ =& 1 \end{aligned} \\ 含义:随机变量q_ik_i服从方差为1的分布 \\ 即:q \odot k = [q_1k_1, q_2k_2, ..., q_{dk}k_{dk}] \\ 只要dk足够大,方差(q \odot k) = 1 由公式D(X)=E(X2)−E2(X)得:D(XY)=======E(X2Y2)−[E(XY)]2E(X2)E(Y2)−[E(X)E(Y)]2[E(X2)−0][E(Y2)−0]−[E(X)E(Y)]2[E(X2)−E2(X)][E(Y2)−E2[Y]]−[E(X)E(Y)]2D(X)D(Y)−[E(X)E(Y)]21∗1−01含义:随机变量qiki服从方差为1的分布即:q⊙k=[q1k1,q2k2,...,qdkkdk]只要dk足够大,方差(q⊙k)=1
E ( Z ) = E ( X Y ) = E ( q 1 k 1 ) + E ( q 2 k 2 ) + . . . + E ( q d k k d k ) = 0 + 0 + . . . + 0 = 0 D ( Z ) = D ( X Y ) = D ( q 1 k 1 ) + D ( q 2 k 2 ) + . . . + D ( q a k k a k ) = 1 + 1 + . . . + 1 = d k 因 为 有 d k 项 \begin{aligned} E(Z) =& E(XY) \\ =& E(q_1k_1) +E(q_2k_2) + ... + E(q_{dk}k_{dk}) \\ =& 0 + 0 + ... + 0 \\ =& 0 \\ D(Z) =& D(XY)\\ =& D(q_1k_1) + D(q_2k_2) + ... + D(q_{ak}k_{ak}) \\ =& 1 + 1 + ... + 1 \\ =& dk \\ & 因为有dk项 \end{aligned} E(Z)====D(Z)====E(XY)E(q1k1)+E(q2k2)+...+E(qdkkdk)0+0+...+00D(XY)D(q1k1)+D(q2k2)+...+D(qakkak)1+1+...+1dk因为有dk项
经过推导,所以这里的数值对应的就是均值为0,方差为dk。
我们需要将方差重新变为1
由 D ( Z ) = d k 设 α 为 线 性 变 换 因 子 ( 常 数 ) D ( α Z ) = 1 则 有 D ( α Z ) = α 2 D ( Z ) = α 2 d k = 1 得 α = 1 d k 因 此 , 只 需 将 Z = Q K T , 乘 上 一 个 1 d k 即 可 ! ! ! \begin{aligned} & 由 \quad D(Z) = dk \\ & 设 \quad α \quad 为线性变换因子(常数) \\ & D(αZ) = 1 \\ & 则有 \quad D(αZ) = α^2D(Z) = α^2dk = 1 \\ & 得 \quad α = \frac{1}{\sqrt dk} \\ & 因此,只需将 \quad Z=QK^T,乘上一个 \quad \frac{1}{\sqrt dk} \quad 即可!!! \end{aligned} 由D(Z)=dk设α为线性变换因子(常数)D(αZ)=1则有D(αZ)=α2D(Z)=α2dk=1得α=dk1因此,只需将Z=QKT,乘上一个dk1即可!!!
以上就是从前向传播角度推导为何要除以 d k \sqrt dk dk,当然Markdown里公式打起来比较费劲,排版不太好看,各位小伙伴体谅一下。
其实很容易理解:不除以的话,注意力得分score是一个很大的值,softmax在反向传播时,容易造成梯度消失。
以上面马太效应的例子来看下
print(tf.math.softmax([[100.0, 200.0, 300.0]]))
# tf.Tensor([[0.0e+00 3.8e-44 1.0e+00]], shape=(1, 3), dtype=float32)
根据softmax公式,求偏导可得:
a j = s o f t m a x ( x j ) = e x j ∑ i = 1 e x j ∂ a j ∂ x j = a j ( 1 − a j ) \begin{aligned} & a_j = softmax(x_j) = \frac{e^{x_j}}{\sum_{i=1}e^{x_j}} \\ & \frac{\partial a_j}{\partial x_j} = a_j(1-a_j) \end{aligned} aj=softmax(xj)=∑i=1exjexj∂xj∂aj=aj(1−aj)
所以说,如果不除以的话,由于softmax的马太效应,在求偏导计算梯度的时候,梯度值为0,导致参数无法更新,即梯度消失。
经过缩放之后, a j a_j aj就不再是0或是1了,梯度值就能够正常的进行参数的更新。
其实就是矩阵运算,上述例子为方便起见是向量,下图为对应的矩阵运算:
当然这里还有一个更漂亮的图:
将上面的self-attention弄懂了,多头也就懂一大半了,而encoder和decoder部分是一样的,论文的名字也是attention is all you need
,attention就是精华。
理论上,多头指的就是多套Q、K、V。比如论文原文是8个头,那就是8套Q、K、V。
多个头就会有多个输出:
多头信息输出,由于多套参数得到了多个信息,然而我们还是只需要一个信息,因此可以通过某种方法(例如矩阵相乘)把多个信息汇总为一个信息:
看过源码的小伙伴会发现实际上我们并不会这么干
如果按照上面的方法,那么8个头就需要8套 W W W,但是一套 W W W包含了 W Q 、 W K 、 W V W_Q、W_K、W_V WQ、WK、WV,这样的话训练开销会非常大,并且线上推断的时间也会很长。
所以实际用的时候,我们会将矩阵按照头数来进行切分,比如下面例子是2个头
上面的图例为了方便演示,下图为实际分头的图例,
假设inpt维度为(batch_size, seq_len, d_model)=(2, 6, 4)
分成两头之后维度为(batch_size, head_num, seq_len, depth)=(2, 2, 6, 2)
前面也简单的提到了一点,这里总结下,加深理解:
简单说padding mask的作用就是标记padding项的位置。
目的是消除padding项带来的影响。
我们先回到上面attention的例子中,在最后加了padding项,看会有怎样的影响:
绿色部分就是和padding相关的
”我“
为例,不影响 q 我 k 我 、 q 我 k 宣 、 q 我 k 你 q_我k_我、q_我k_宣、q_我k_你 q我k我、q我k宣、q我k你的运算。只多了个padding,它不会影响其它项的生成。”我“
和padding存在某种联系,这是不合理的。因为padding本身就是没有意义的, 只是我们的填充项而已。这里为什么不将padding项变为0,然后和QK的结果相乘?
我们不会让它们相乘,是让它们相加,我们会让1变成一个非常小的数,比如 − 1 0 9 -10^9 −109。
至此,padding mask讲完了,它的作用就是将padding产生的影响给消除。
Add部分没什么好讲的,就是残差网络的跳跃连接思想。
作用就是加深网络层数,通过跳跃连接缓解梯度消失。
上面其实就已经给出讲解了,这里再接着上面的例子加深理解:
LayerNorm简称LN,BatchNorm简称BN。
它俩的作用都是消除量纲影响,加快模型收敛。
”我“
字的emb词向量,第二行是”宣“
字的emb词向量,经过attention之后,形成的是包含语义信息的向量,每一列代表的含义并不相同了。这里也是没什么好讲的,就是一个两层的全连接。通过激活函数引入非线性变换,变换了Attention output的空间, 从而增加了模型的表现能力。
把FFN去掉模型也是可以用的,但是效果差了很多。
encoder和decoder结构相似,只需要关注decoder部分的attention就可以了,其它的就不再赘述了。
其中Decoder部分可以分为2个讲解:
Encoder和Decoder部分都有padding mask,在Decoder部分还多了一个look ahead mask,就是masked Multi head attention的mask的含义。
篇幅太长了,我不想分两篇写,所以这里先回顾一下decoder部分的工作流程:
假设这是decoder部分的多头注意力,我们来看一下如果不做mask会是什么样:
”我“
字还会和其它字交互,但是decoder在预测出”宣“
的时候,是看不到后面的信息的。"宣"、“你”
和padding项产生的交互都要被mask。padding项不会对前面的计算产生影响所以不用考虑
在矩阵里,它就是如上图的样子,第一行有3个需要被mask、第2行有2个需要被mask、第3行有1个需要被mask。 是一个倒三⻆的形状。
所以look ahead mask的生成,只要将需要掩盖的地方为1,其它不需要掩盖的地方为0即可。
”你“
也是padding项,那么和padding有关的将用绿色表示 ;
这里涉及到自动广播,不明白的同学可以自行查阅,此处不再介绍。
Decoder的多头注意力和Encoder的多头注意力的不同地方就在于使用的mask不同。
我们发现,其它多头注意力的Q、K、V是来自同一个的。但是在红框的Q、K、V的来源就不再是同一个东西了。
我们来通过下图例子理解:
需要说明的是,
例子中不包含起始符和结束符
padding项都是绿色
q是decoder部分提供、k和v是encoder部分输出
计算qk,以 q I q_I qI为例:
”我宣你“
这句话之间的计算,计算每个单词和其它单词的相关性。”I“
分别和”我“
、”宣“
、”你“
的相关性。经过softmax将得分归一化,映射成注意力权重。
然后和v相乘,含义也是一样的,从”我“
、”宣“
、”你“
中提取多少信息。
这样一来通过不同语言间相关性的匹配就能从中提取出翻译的信息,达到一个翻译的效果。
为什么这里的padding mask和encoder部分的padding mask处理方法一致?
这里可能会有疑问,为什么这里使用padding mask,而不使用look ahead mask?
“我”
字为例,“宣”
、“你”
,都还没有出现,所以 q 我 k 宣 、 q 我 k 你 q_我k_宣、q_我k_你 q我k宣、q我k你是不合理的。“我”
、“宣”
、“你”
。坚持看到这里的小伙伴相信肯定是收获满满,动动发财的小手关注博主一波,一起进步。
以上就讲完了transformer的编码、解码两大模块,那么如何将“我喜欢你”
,翻译成“I Like You”
呢?
换个问题,解码器Decoder输出本来是一个浮点型的张量,怎么转化成“I Like You”
这几个词呢?
概况的来讲:
损失依旧是交叉熵,为什么这里会讲一下,因为有padding。
也就是说我们需要注意的是,损失计算也要有mask,去消除padding项带来的损失。
在计算损失的时候,需要做:
Predictions矩阵,是经过softmax后的预测概率值
这是论文中提到的一种自定义学习率方法:
最后的效果就是一个先增后减的学习率(和学习率衰减思想一致)
https://github.com/WGS-note/transformer-note
如果觉得文章还不错,读完后收获颇丰,动动小伙伴发财的小手帮博主start一下~
https://s3.us-west-2.amazonaws.com/secure.notion-static.com/501fb338-a6b0-484a-8a16-713dd40251de/Attention_is_All_You_Need.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220522T014504Z&X-Amz-Expires=86400&X-Amz-Signature=180db501219c968fdd116b27d6b44bed0eed6e912755d300cd7db8e957937e1b&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Attention%2520is%2520All%2520You%2520Need.pdf%22&x-id=GetObject
https://ugirc.blog.csdn.net/article/details/120394042
https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer#multi-headed-attention
https://zhuanlan.zhihu.com/p/353381965
https://www.bilibili.com/video/BV1pu411o7BE?spm_id_from=333.337.search-card.all.click
https://www.bilibili.com/video/BV1Kq4y1H7FL?spm_id_from=333.337.search-card.all.click
https://zhuanlan.zhihu.com/p/153183322
原创不易,转载请注明出处