这层比较简单,就是实现上面的公式,直接看代码吧:
# Position-wise Feed-Forward Networks
class PositionwiseFeedForward(nn.Module):
"实现FFN函数"
def __init__(self, d_model, d_ff, dropout=0.1):
super(PositionwiseFeedForward, self).__init__()
self.w_1 = nn.Linear(d_model, d_ff)
self.w_2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
return self.w_2(self.dropout(F.relu(self.w_1(x))))
总的来说Encoder 是由上述小encoder layer 6个串行叠加组成。encoder sub layer主要包含两个部分:
SubLayer-1 做 Multi-Headed Attention
SubLayer-2 做 Feed Forward Neural Network
来看下Encoder主架构的代码:
def clones(module, N):
"产生N个相同的层"
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
class Encoder(nn.Module):
"""N层堆叠的Encoder"""
def __init__(self, layer, N):
super(Encoder, self).__init__()
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.size)
def forward(self, x, mask):
"每层layer依次通过输入序列与mask"
for layer in self.layers:
x = layer(x, mask)
return self.norm(x)
5 Decoder
Decoder与Encoder有所不同,Encoder与Decoder的关系可以用下图描述(以机器翻译为例):
Decoder的代码主要结构:
# Decoder部分
class Decoder(nn.Module):
"""带mask功能的通用Decoder结构"""
def __init__(self, layer, N):
super(Decoder, self).__init__()
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.size)
def forward(self, x, memory, src_mask, tgt_mask):
for layer in self.layers:
x = layer(x, memory, src_mask, tgt_mask)
return self.norm(x)
Decoder子结构(Sub layer):
Decoder 也是N=6层堆叠的结构。被分为3个 SubLayer,Encoder与Decoder有三大主要的不同:
(1)Decoder SubLayer-1 使用的是 “Masked” Multi-Headed Attention 机制,防止为了模型看到要预测的数据,防止泄露。
(2)SubLayer-2 是一个 Encoder-Decoder Multi-head Attention。
(3) LinearLayer 和 SoftmaxLayer 作用于 SubLayer-3 的输出后面,来预测对应的 word 的 probabilities 。
5.1 Mask-Multi-Head-Attention
Mask 的目的是防止 Decoder “seeing the future”,就像防止考生偷看考试答案一样。这里mask是一个下三角矩阵,对角线以及对角线左下都是1,其余都是0。下面是个10维度的下三角矩阵:
tensor([[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]], dtype=torch.uint8)
Mask的代码实现:
def subsequent_mask(size):
mask后续的位置,返回[size, size]尺寸下三角Tensor
对角线及其左下角全是1,右上角全是0
attn_shape = (1, size, size)
subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
return torch.from_numpy(subsequent_mask) == 0
当mask不为空的时候,attention计算需要将x做一个操作:scores = scores.masked_fill(mask == 0, -1e9)。即将mask==0的替换为-1e9,其余不变。
5.2 Encoder-Decoder Multi-head Attention
这部分和Multi-head Attention的区别是该层的输入来自encoder和上一次decoder的结果。具体实现如下:
class DecoderLayer(nn.Module):
"Decoder is made of self-attn, src-attn, and feed forward (defined below)"
def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
super(DecoderLayer, self).__init__()
self.size = size
self.self_attn = self_attn
self.src_attn = src_attn
self.feed_forward = feed_forward
self.sublayer = clones(SublayerConnection(size, dropout), 3)
def forward(self, x, memory, src_mask, tgt_mask):
"将decoder的三个Sublayer串联起来"
m = memory
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
return self.sublayer[2](x, self.feed_forward)
注意:self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask)) 这行就是Encoder-Decoder Multi-head Attention。
query = x,key = m, value = m, mask = src_mask,这里x来自上一个 DecoderLayer,m来自 Encoder的输出。
5.3 Linear and Softmax to Produce Output Probabilities
Decoder的最后一个部分是过一个linear layer将decoder的输出扩展到与vocabulary size一样的维度上。经过softmax 后,选择概率最高的一个word作为预测结果。假设我们有一个已经训练好的网络,在做预测时,步骤如下:
(1)给 decoder 输入 encoder 对整个句子 embedding 的结果 和一个特殊的开始符号 </s>。decoder 将产生预测,在我们的例子中应该是 ”I”。
(2)给 decoder 输入 encoder 的 embedding 结果和 “</s>I”,在这一步 decoder 应该产生预测 “am”。
(3)给 decoder 输入 encoder 的 embedding 结果和 “</s>I am”,在这一步 decoder 应该产生预测 “a”。
(4)给 decoder 输入 encoder 的 embedding 结果和 “</s>I am a”,在这一步 decoder 应该产生预测 “student”。
(5)给 decoder 输入 encoder 的 embedding 结果和 “</s>I am a student”, decoder应该生成句子结尾的标记,decoder 应该输出 ”</eos>”。
(6)然后 decoder 生成了 </eos>,翻译完成。
这部分的代码实现:
class Generator(nn.Module):
Define standard linear + softmax generation step。
定义标准的linear + softmax 生成步骤。
def __init__(self, d_model, vocab):
super(Generator, self).__init__()
self.proj = nn.Linear(d_model, vocab)
def forward(self, x):
return F.log_softmax(self.proj(x), dim=-1)
在训练过程中,模型没有收敛得很好时,Decoder预测产生的词很可能不是我们想要的。这个时候如果再把错误的数据再输给Decoder,就会越跑越偏。这个时候怎么办?
(1)在训练过程中可以使用 “teacher forcing”。因为我们知道应该预测的word是什么,那么可以给Decoder喂一个正确的结果作为输入。
(2)除了选择最高概率的词 (greedy search),还可以选择是比如 “Beam Search”,可以保留topK个预测的word。 Beam Search 方法不再是只得到一个输出放到下一步去训练了,我们可以设定一个值,拿多个值放到下一步去训练,这条路径的概率等于每一步输出的概率的乘积。
6 Transformer的优缺点
6.1 优点
(1)每层计算复杂度比RNN要低。
(2)可以进行并行计算。
(3)从计算一个序列长度为n的信息要经过的路径长度来看, CNN需要增加卷积层数来扩大视野,RNN需要从1到n逐个进行计算,而Self-attention只需要一步矩阵计算就可以。Self-Attention可以比RNN更好地解决长时依赖问题。当然如果计算量太大,比如序列长度N大于序列维度D这种情况,也可以用窗口限制Self-Attention的计算数量。
(4)从作者在附录中给出的栗子可以看出,Self-Attention模型更可解释,Attention结果的分布表明了该模型学习到了一些语法和语义信息。
6.2 缺点
在原文中没有提到缺点,是后来在Universal Transformers中指出的,主要是两点:
(1)实践上:有些RNN轻易可以解决的问题transformer没做到,比如复制string,或者推理时碰到的sequence长度比训练时更长(因为碰到了没见过的position embedding)。
(2)理论上:transformers不是computationally universal(图灵完备),这种非RNN式的模型是非图灵完备的的,无法单独完成NLP中推理、决策等计算问题(包括使用transformer的bert模型等等)。
7 References
1 http://jalammar.github.io/illustrated-transformer/
2 https://zhuanlan.zhihu.com/p/48508221
3 https://zhuanlan.zhihu.com/p/47063917
4 https://zhuanlan.zhihu.com/p/80986272
5 https://arxiv.org/abs/1706.03762