蒋思源到场

基于当心力机制,中文字幕AV带你了解与教练神经板滞翻译系统

本文是中文字幕AV GitHub 完成项目,我们依据谷歌的 Transformer 原论文与 Harvard NLP 所完成的代码进修构修了一个神经板滞翻译系统。于是,我们期望列位读者也能依据这篇作品了解 Transformer 的架构,并入手完成一个神经板滞翻译系统。

自客岁 6 缘垒「Attention is All You Need」发外以后,Transformer 受到越来越众的体恤。它除了能分明晋升翻译质料,同时还为许众 NLP 义务供应了新的架构。这篇论文放弃了古板基于 RNN 或 CNN 的深度架构,并只保管了当心力(Attentaion)机制,虽然原论文这一方面描画地比较分明,但要准确地完成如许的新型架贡ボ够十分艰难。

这篇作品中,我们从当心力机制神经板滞翻译系统标清楚完成 Transformer 的架构与代码,并借帮这些完成了解原论文。中文字幕AV拾掇了通通完成,并依据我们对原论文与完成的了解添加少许标明。通通作品便是一个可运转的 Jupyter Notebook,读者可直接 Colaboratory 中阅读作品与运转代码。

  • 中文字幕AV完成地址:https://github.com/jiqizhixin/ML-Tutorial-Experiment 

  • 原完成地址:https://github.com/harvardnlp/annotated-transformer

本文所有的代码都可以谷歌 Colab 上运转,且读者也可以 GitHub 中下载通通的代码当地运转。这篇作品十分适合于研讨者与感兴味的开辟者,代码很洪流平上都依赖于 OpenNMT 库。

运转模子前,我们需求确保有对应的状况。假如当地运转,那么需求确保以下基本库的导入不会报错,若 Colab 上运转,那么起首需求运转以下第一个 pip 语句安装对应的包。Colab 的状况配备十分简单,一般只需求运用 conda 或 pip 命令就能完毕。另外,Colab 语句前面加上「!」外示这是命令行,而不加慨叹号则外示这个代码框是 Python 代码。

# !pip install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp36-cp36m-linux_x86_64.whl numpy matplotlib spacy torchtext seaborn 
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import math, copy, time
from torch.autograd import Variable
import matplotlib.pyplot as plt
import seaborn
seaborn.set_context(context="talk")
%matplotlib inline

小序

淘汰序列盘算的义务目标构成了 Extended Neural GPU、ByteNet 和 ConvS2S 的根底,它们都是运用卷积神经收集举措基本构修块,因此能对所有输入与输出位置的躲藏外征施行并行盘算。这些模子中,两个恣意输入与输出位置的信号联系所需求的运算数目与它们的位置间隔成正比,关于 ConvS2S 为线性增加,关于 ByteNet 为对数增加。这种现象使得进修较远位置的依赖联系十分艰难。而 Transformer 中,这种资本会淘汰到一个固定的运算数目,尽管平均当心力位置加权会淘汰有用外征力,但运用 Multi-Head Attention 当心力机制可以抵消这种资本。

自当心力(Self-attention),有时也称为内部当心力,它是一种涉及单序列差别位置的当心力机制,并能盘算序列的外征。自当心力众种义务中都有十分成功的运用,比如阅读了解、摘要轮廓、文字包含和语句外征等。自当心力这种序列内部施行 Attention 的方法可以视为搜寻序列内部的躲藏联系,这种内部联系关于翻译以及序列义务的功用十分主要。

然而就我们所晓得的,Transformer 是第一种完备依赖于自当心力以盘算输入与输出外征的方法,这意味着它没有运用序列对齐的 RNN 或卷积收集。从 Transformer 的构培养可以看出,它并没有运用深度收集抽取序列特征,顶众运用几个线性变换对特征举行变换。

本文主要从模子架构、教练配备和两个实行翻译模子开端先容 Ashish Vaswani 等人的原论文与 Harvard NLP 团队完成的代码。模子架构中,我们将议论编码器、解码器、当心力机制以及位置编码等要害构成部分,而教练配备将议论怎样抽取批量数据、设定教练轮回、挑选最优化方法和正则化器等。着末我们将跟从 Alexander Rush 等人的完成教练两个神经板滞翻译系统,此中一个仅运用简单的合成数据,而另一个则是实的 IWSLT 德语-英语翻译数据集。

模子架构

大大都神经序列模子都运用编码器-解码器框架,此中编码器将外征符号的输入序列 (x_1, …, x_n) 映照到延续外征 z=(z_1, …, z_n)。给定中心变量 z,解码器将会生成一个输出序列 (y_1,…,y_m)。每一个时间步上,模子都是自回归的(auto-regressive),当生成序列中的下一个元素时,先宿世成的元素会举措输入。

以下展现了一个标准的编码器-解码器框架,EncoderDecoder 类定义了先编码后解码的进程,比如先将英文序列编码为一个隐向量,基于这个中心外征解码为中文序列。

class EncoderDecoder(nn.Module):
 """
 A standard Encoder-Decoder architecture. Base for this and many 
 other models.
 """
 def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
 super(EncoderDecoder, self).__init__()
 self.encoder = encoder
 self.decoder = decoder
 self.src_embed = src_embed
 self.tgt_embed = tgt_embed
 self.generator = generator

 def forward(self, src, tgt, src_mask, tgt_mask):
 "Take in and process masked src and target sequences."
 return self.decode(self.encode(src, src_mask), src_mask,
 tgt, tgt_mask)

 def encode(self, src, src_mask):
 return self.encoder(self.src_embed(src), src_mask)

 def decode(self, memory, src_mask, tgt, tgt_mask):
 return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)
class Generator(nn.Module):
 "Define standard linear + softmax generation step."
 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)

Transformer 的全体架构也采用了这种编码器-解码器的框架,它运用了众层自当心力机制和层级归一化,编码器息争码器都会运用全连接层和残差连接。Transformer 的全体构造如下图所示:

如上所示,左侧为输入序列的编码器。输入序列起首会转换为词嵌入向量,与位置编码向量相加后可举措 Multi-Head Attention 模块的输入,该模块的输出与输入相加后将加入层级归一化函数,得出的输出馈赠到全连接层后可得出编码器模块的输出。如许相同的 6 个编码器模块(N=6)可构成通通编码器架构。解码器模块起首同样构修了一个自当心力模块,然后再联合编码器的输出完成 Multi-Head Attention,着末加入全连接收集并输出预测词概率。

这里只是简单地先容了模子的大约进程,许众如位置编码、Multi-Head Attention 模块、层级归一化、残差链接和逐位置前馈收集等看法都需求读者精细阅读下文,着末再回过头了解完备的进程。

编码器与解码器堆栈

  • 编码器

编码器由相同的 6 个模块堆叠而成,每一个模块都有两个子层级构成。此中第一个子层级是 Multi-Head 自当心绪制,此中自当心力外示输入和输出序列都是同一条。第二个子层级采用了全连接收集,主要感化于当心子层级的特征。另外,每一个子层级都会添加一个残差连接和层级归一化。

以下定义了编码器的主体框架, Encoder 类中,每一个 layer 外示一个编码器模块,这个编码器模块由两个子层级构成。layer 函数的输出外示颠末层级归一化的编码器模块输出,通过 For 轮回堆叠层级就能完毕通通编码器的构修。

def clones(module, N):
 "Produce N identical layers."
 return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
class Encoder(nn.Module):
 "Core encoder is a stack of N layers"
 def __init__(self, layer, N):
 super(Encoder, self).__init__()
 self.layers = clones(layer, N)
 self.norm = LayerNorm(layer.size)

 def forward(self, x, mask):
 "Pass the input (and mask) through each layer in turn."
 for layer in self.layers:
 x = layer(x, mask)
return self.norm(x)

如编码器的构造图所示,每个子层级都会会添加一个残差连接,并随后传入层级归一化。上面构修的主体架构也调用了层级归一化函数,以下代码展现了层级归一化的定义。

class LayerNorm(nn.Module):
 "Construct a layernorm module (See citation for details)."
 def __init__(self, features, eps=1e-6):
 super(LayerNorm, self).__init__()
 self.a_2 = nn.Parameter(torch.ones(features))
 self.b_2 = nn.Parameter(torch.zeros(features))
 self.eps = eps

 def forward(self, x):
 mean = x.mean(-1, keepdim=True)
 std = x.std(-1, keepdim=True)
return self.a_2 * (x - mean) / (std + self.eps) + self.b_2

层级归一化可以通过改正每一层内激活值的均值与方差而大大淘汰协方差偏离题目。简单来说,一个层级的均值可以通过盘算该层所有神经元激活值的平均值而得出,然后再依据均值盘算该层所有神经元激活值的方差。着末依据均值与方差,我们可以对这一层所有输出值举行归一化。

如上 LayerNorm 类所示,我们起首需求运用方法 mean 求输入 x 着末一个维度的均值,keepdim 为真外示求均值后的维度保持稳定,而且均值会播送操作到对应的维度。同样运用 std 方法盘算标准差后,该层所有激活值区分减去均值再除以标准差就能完成归一化,分母加上一个小值 eps 可以避免分母为零。

于是,每一个子层的输出为 LayerNorm(x+Sublayer(x)),此中 Sublayer(x) 外示由子层本身完成的函数。我们运用 Dropout 将每一个子层的输出随机失活,这一进程会加上子层输入和施行归一化之前完毕。

以下定义了残差连接,我们会加入层级归一化函数前将子层级的输入与输出相加。为了运用这些残差连接,模子中所有的子层和嵌入层的输出维度都是 d_model=512。

class SublayerConnection(nn.Module):
 """
 A residual connection followed by a layer norm.
 Note for code simplicity the norm is first as opposed to last.
 """
 def __init__(self, size, dropout):
 super(SublayerConnection, self).__init__()
 self.norm = LayerNorm(size)
 self.dropout = nn.Dropout(dropout)

 def forward(self, x, sublayer):
 "Apply residual connection to any sublayer with the same size."
return x + self.dropout(sublayer(self.norm(x)))

上述代码定义中,x 外示上一层添加了残差连接的输出,这一层添加了残差连接的输出需求将 x 施行层级归一化,然后馈赠到 Multi-Head Attention 层或全连接层,添加 Dropout 操作后可举措这一子层级的输出。着末将该子层的输出向量与输入向量相加取得下一层的输入。

编码器每个模块有两个子层,第一个为 multi-head 自当心力层,第二个为简单的逐位置全连接前馈收集。以下的 EncoderLayer 类定义了一个编码器模块的进程。

class EncoderLayer(nn.Module):
 "Encoder is made up of self-attn and feed forward (defined below)"
 def __init__(self, size, self_attn, feed_forward, dropout):
 super(EncoderLayer, self).__init__()
 self.self_attn = self_attn
 self.feed_forward = feed_forward
 self.sublayer = clones(SublayerConnection(size, dropout), 2)
 self.size = size

 def forward(self, x, mask):
 "Follow Figure 1 (left) for connections."
 x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
return self.sublayer[1](x, self.feed_forward)

以上代码叠加了自当心力层与全连接层,此中 Multi-Head Attention 机制的输入 Query、Key 和 Value 都为 x 就外示自当心力。

  • 解码器

解码器也由相同的 6 个模块堆叠而成,每一个解码器模块都有三个子层构成,每一个子层同样会加上残差连接与层级归一化运算。第一个和第三个子层区分与编码器的 Multi-Head 自当心力层和全连接层相同,而第二个子层所采用的 Multi-Head Attention 机制运用编码器的输出举措 Key 和 Value,采用解码模块第一个子层的输出举措 Query

我们同样需求改正编码器堆栈中的自当心力子层,以避免目今位置当心到后续序列位置,这一改正可通过掩码完成。以下的解码器的主体堆叠构造和编码器相似,只需求简单地堆叠解码器模块就能完毕。

class Decoder(nn.Module):
 "Generic N layer decoder with masking."
 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)

以下展现了一个解码器模块的架构,第一个 Multi-Head Attention 机制的三个输入都是 x,于是它是自当心力。第二个 Multi-Head 当心力机制输入的 Key 和 Value 是编码器的输出 memory,输入的 Query 是上一个子层的输出 x。着末叠加一个全连接收集以完毕一个编码器模块的构修。

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):
 "Follow Figure 1 (right) for connections."
 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)

关于序列修模来说,模子应当只可查看有限的序列新闻。比如时间步 i,模子能读取通通输入序列,但只可查看时间步 i 及之前的序列新闻。关于 Transformer 的解码器来说,它会输入通通目标序列,且当心力机制会当心到通通目标序列各个位置的新闻,于是我们需求限制当心力机制能看到的新闻。

如上所述,Transformer 当心力机制中运用 subsequent_mask 函数以避免目今位置当心到后面位置的新闻。因为输出词嵌入是位置的一个偏移,于是我们可以确保位置 i 的预测仅取决于位置 i 之前的已知输出。

def subsequent_mask(size):
 "Mask out subsequent positions."
 attn_shape = (1, size, size)
 subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
 return torch.from_numpy(subsequent_mask) == 0

以下为当心力掩码的可视化,此中每一方法一个词,每一列则外示一个位置。下图展现了每一个词容许查看的位置,教练中词是不行当心到未来词的。

plt.figure(figsize=(5,5))
plt.imshow(subsequent_mask(20)[0])
None

当心力机制

谷歌原论文中展现了当心力机制的大众化定义,即它和 RNN 或 CNN 相同也是一种编码序列的方案。一个当心力函数可以描画为将 Query 与一组键值对(Key-Value)映照到输出,此中 Query、Key、Value 和输出都是向量。输出可以通过值的加权和而盘算得出,此平分派到每一个值的权重可通过 Query 和对应 Key 的顺应度函数(compatibility function)盘算。

翻译义务中,Query 可以视为原语词向量序列,而 Key 和 Value 可以视为目标语词向量序列。一般的当心力机制可标明为盘算 Query 和 Key 之间的相似性,并应用这品种似性确定 Query 和 Value 之间的当心力联系。

以下是点积当心力的构造示企图,我们称这种特别的构造为「缩放点积当心力」。它的输入由维度是 d_k 的 Query 和 Key 构成,Value 的维度是 d_v。如下所示,我们会先盘算 Query 和所有 Key 的点乘,并每一个都除上 squre_root(d_k) 以避免乘积结果过大,然后再馈赠到 Softmax 函数以取得与 Value 对应的权重。依据如许的权重,我们就可以配备 Value 向量而得出着末的输出。

Image(filename='images/ModalNet-19.png')

上图中,Q 和 K 的运算有一个可选的 Mask 进程。编码器中,我们不需求运用它限制当心力模块所体恤的序列新闻。而解码器中,我们需求它限制当心力模块只可当心到目今时间步及之前时间步的新闻。这一个进程可以很简明地外示为函数 Attention(Q, K, V)。

Attention(Q, K, V) 函数输入矩阵 Q、K 和 V 的状况下可盘算 Query 序列与 Value 序列之间的当心力联系。此中 Q 的维度为 n×d_k,外示有 n 条维度为 d_k 的 Query、K 的维度为 m×d_k、V 的维度为 m×d_v。这三个矩阵的乘积可得出 n×d_v 维的矩阵,它外示 n 条 Query 对应当心到的 Value 向量。

上式中 Q 与 K 的点积会除上 squre_root(d_k) 以完成缩放。原论文作家发明,当每一条 Query 的维度 d_k 比较小时,点乘当心力和加性当心力的功用相似,但跟着 d_k 的增大,加性当心力的功用会超越点乘当心力机制。不过点乘当心力有一个强大的属性,即它可以应用矩阵乘法的并行运算大大加速斗嗽糍度。

原论文作家认为点乘当心力效果欠好的启事是 d_k 比较大的状况下,乘积结果会十分大,于是会导致 Softmax 疾速饱和并只可供应十分小的梯度来更新参数。以是他们采用了根号下 d_k 来缩小点乘结果,并避免 Softmax 函数饱和。

为了标明为什么点积的量级会变得很大,我们假设元素 q 和 k 都是均值为 0、方差为 1 的独立随机变量,它们的点乘 qk=∑q_i*k_i 有 0 均值和 d_k 的方差。为了抵消这种影响,我们可以通过除上 squre_root(d_k) 以归一化点乘结果。

以下函数定义了一个标准的点乘当心力,该函数最终会返回立室 Query 和 Key 的权重或概率 p_attn,以及最终当心力机制的输出序列。

def attention(query, key, value, mask=None, dropout=None):
 "Compute 'Scaled Dot Product Attention'"
 d_k = query.size(-1)
 scores = torch.matmul(query, key.transpose(-2, -1)) \
 / math.sqrt(d_k)
 if mask is not None:
 scores = scores.masked_fill(mask == 0, -1e9)
 p_attn = F.softmax(scores, dim = -1)
 if dropout is not None:
 p_attn = dropout(p_attn)
return torch.matmul(p_attn, value), p_attn

上述函数中,query 矩阵的列数即维度数 d_k。盘算点乘并缩放后,我们可以着末一个维度施行 Softmax 函数以取得概率 p_attn。

两个最常睹的当心力函数是加性当心力(additive attention)和点乘(乘法)当心力。除了要除上缩放因子 squre_root(d_k),标准的点乘当心力与原论文中所采用的是相同的。加性当心力会运用单躲藏层的前馈收集盘算顺应度函数,它们表面繁杂度上是相似的。点积当心力实行中更疾速且参数空间更高效,因为它能通过高度优化的矩阵乘法库并行地盘算。

Multi-head Attention 

下图展现了 Transformer 中所采用的 Multi-head Attention 构造,它实便是众个点乘当心力并行地处理并着末将结果拼接一同。一般而言,我们可以对三个输入矩阵 Q、V、K 区分举行 h 个差别的线性变换,然后区分将它们加入 h 个点乘当心力函数并拼接所有的输出结果。

Image(filename='images/ModalNet-20.png')

Multi-head Attention 容许模子联合体恤差别位置的差别外征子空间新闻,我们可以了解为参数不共享的状况下,众次施行点乘当心力。Multi-head Attention 的外达如下所示:

此中 W 为对应线性变换的权重矩阵,Attention() 便是上文所完成的点乘当心力函数。

原论文和完成中,研讨者运用了 h=8 个并行点乘当心力层而完毕 Multi-head Attention。关于每一个当心力层,原论文运用的维度是 d_k=d_v=d_model/h=64。因为每一个并行当心力层的维度低沉,总的盘算资本和单个点乘当心力全维度上的资本十分临近。

以下定义了 Multi-head Attention 模块,它完成了上图所示的构造:

class MultiHeadedAttention(nn.Module):
 def __init__(self, h, d_model, dropout=0.1):
 "Take in model size and number of heads."
 super(MultiHeadedAttention, self).__init__()
 assert d_model % h == 0
 # We assume d_v always equals d_k
 self.d_k = d_model // h
 self.h = h
 self.linears = clones(nn.Linear(d_model, d_model), 4)
 self.attn = None
 self.dropout = nn.Dropout(p=dropout)

 def forward(self, query, key, value, mask=None):
 "Implements Figure 2"
 if mask is not None:
 # Same mask applied to all h heads.
 mask = mask.unsqueeze(1)
 nbatches = query.size(0)

 # 1) Do all the linear projections in batch from d_model => h x d_k 
 query, key, value = \
 [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
 for l, x in zip(self.linears, (query, key, value))]

 # 2) Apply attention on all the projected vectors in batch. 
 x, self.attn = attention(query, key, value, mask=mask, 
 dropout=self.dropout)

 # 3) "Concat" using a view and apply a final linear. 
 x = x.transpose(1, 2).contiguous() \
 .view(nbatches, -1, self.h * self.d_k)
 return self.linears[-1](x)

以上代码中,起首我们会取 query 的第一个维度举措批量样本数,然后再完成众个线性变换将 d_model 维的词嵌入向量压缩到 d_k 维的躲藏向量,变换后的矩阵将举措点乘当心力的输入。点乘当心力输出的矩阵将着末一个维度拼接,即 8 个 n×64 维的矩阵拼接为 n×512 维的大矩阵,此中 n 为批量数。如许我们就将输出向量恢复为与词嵌入向量相等的维度。

前面我们曾司了解到 Transformer 运用了大宗的自当心力机制,即 Attention(X, X, X )。简单而言,Transformer 运用自当心力替代 RNN 或 CNN 抽取序列特征。关于板滞翻译义务而言,自当心力输入的 Query、Key 和 Value 都是相同的矩阵,那么 Query 和 Key 之间的运算就相当于盘算输入序列内部的相似性,并依据这品种似性或权重当心到序列本身(Value)的内部联络。

这种内部联络可以是主语当心到谓语和宾语的新闻或其它躲藏句子内部的构造。Transformer 神经板滞翻译和阅读了解等义务上的精良功用,都标明序列内部构造的主要性。

Transformer 以三种差别的方法运用 multi-head Attention。起首编码器到解码器的层级中,Query 根源于前面解码器的输出,而记忆的 Key 与 Value 都来自编码器的输出。这容许解码器中的每一个位置都当心输入序列中的所有位置,于是它实行上模拟了序列到序列模子中典范的编码器-解码器当心力机制

其次,编码器包罗了自当心力层,且该层中的所有 Value、Key 和 Query 都是相同的输入矩阵,即编码器的前层输出。着末,解码器中的自当心力层容许解码器中的每一个位置都当心到包罗目今位置的所有合法位置。这可以通过上文定义的 Mask 函数完成,从而避免发生左向新闻流来保持自回归属性。

逐位置的前馈收集

为了当心子层,每一个编码器息争码器模块着末都包罗一个全连接前馈收集,它独立且相同地运用于每一个位置。这个前馈收集包罗两个线性变换和一个非线性激活函数,且教练进程中我们可以两层收集之间添加 Dropout 方法:

假如我们将这两个全连接层级与残差连接和层级归一化联合,那么它便是每一个编码器与解码器模块着末所必需的子层。我们可以将这一子层外示为:LayerNorm(x + max(0, x*w1 + b1)w2 + b2)。

尽管线性变换所有差别的位置上都相同,但差别的层级中运用差别的参数,这种变换实同样可以描画为核大小为 1 的两个卷积。输入和输出的维度 d_model=512,而内部层级的维度 d_ff=2018。

如下所示,前馈收集的定义和常规的方法并没有什么区别,不过这个收集没有添加偏置项,且对第一个全连接的输出完成了 Dropout 以避免过拟合

class PositionwiseFeedForward(nn.Module):
 "Implements FFN equation."
 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))))

词嵌入和 Softmax

与其它序列模子相似,我们可以运用学得的词嵌入将输入和输出的词汇转换为维度等于 d_model 的向量。我们还可以运用一般的线性变换和 Softmax 函数将解码器的输出转化为预测下一个词汇的概率。愿论文的模子中,两个嵌入层和 pre-softmax 线性变换的权重矩阵是共享的。词嵌入层中,我们将所有权重都乘以 squre_root(d_model)。

class Embeddings(nn.Module):
 def __init__(self, d_model, vocab):
 super(Embeddings, self).__init__()
 self.lut = nn.Embedding(vocab, d_model)
 self.d_model = d_model

 def forward(self, x):
 return self.lut(x) * math.sqrt(self.d_model)

位置编码

位置编码是 Transformer 模子中着末一个需求当心的构造,它对运用当心力机制完成序列义务也好坏常主要的部分。如上文所述,Transformer 运用自当心力机制抽取序列的内部特征,但这种替代 RNN 或 CNN 抽取特征的方法有很大的范围性,即它不行捕捉序列的序次。如许的模子即时ボ依据语境翻译出每一个词的原理,那也组不可完备的语句。

为了令模子能应用序列的序次新闻,我们必需植入少许关于词汇序列中相对或绝对位置的新闻。直观来说,假如语句中每一个词都有特定的位置,那么每一个词都可以运用向量编码位置新闻。将如许的位置向量与词嵌入向量相联合,那么我们就为每一个词引入了必定的位置新闻,当心力机制也就能区分出差别位置的词。

谷歌研讨者将「位置编码」添加到输入词嵌入中,位置编码有和词嵌入相同的维度 d_model,每一个词的位置编码与词嵌入向量相加可得出这个词的最终编码。目前有许众种位置编码,包罗通过进修和固定外达式构修的。

这一项实行中,谷歌研讨者运用差别频率的正弦和预先函数:

此中 pos 为词的位置,i 为位置编码向量的第 i 个元素。给定词的位置 pos,我们可以将词映照到 d_model 维的位置向量,该向量第 i 个元素就由上面两个式子盘算得出。也便是说,位置编码的每一个维度对应于正弦弧线,波长构成了从 2π到 100002π的等比数列。

上面构修了绝对位置的位置向量,但词的相对位置同样十分主要,这也便是谷歌研讨者采用三角函数外征位置的精妙之处。正弦与余弦函数容许模子进修相对位置,这主要依据两个变换:sin(α+β)=sinα cosβ+cosα sinβ 以及 cos(α+β)=cosα cosβsinα sinβ。

关于词汇间固定的偏移量 k,位置向量 PE(pos+k) 可以通过 PE(pos) 与 PE(k) 的组合外示,这也就外示了言语间的相对位置。

以下定义了位置编码,此中我们对词嵌入与位置编码向量的和运用 Dropout,默承认令_drop=0.1。div_term 完成的是分母,而 pe[:, 0::2] 外示第二个维度从 0 开端以间隔为 2 取值,即偶数。

class PositionalEncoding(nn.Module):
 "Implement the PE function."
 def __init__(self, d_model, dropout, max_len=5000):
 super(PositionalEncoding, self).__init__()
 self.dropout = nn.Dropout(p=dropout)

 # Compute the positional encodings once in log space.
 pe = torch.zeros(max_len, d_model)
 position = torch.arange(0, max_len).unsqueeze(1)
 div_term = torch.exp(torch.arange(0, d_model, 2) *
 -(math.log(10000.0) / d_model))
 pe[:, 0::2] = torch.sin(position * div_term)
 pe[:, 1::2] = torch.cos(position * div_term)
 pe = pe.unsqueeze(0)
 self.register_buffer('pe', pe)

 def forward(self, x):
 x = x + Variable(self.pe[:, :x.size(1)], 
 requires_grad=False)
return self.dropout(x)

以下将基于一个位置将差别的正弦弧线添加到位置编码向量中,弧线的频率和偏移量每个维度上都差别。

plt.figure(figsize=(15, 5))
pe = PositionalEncoding(20, 0)
y = pe.forward(Variable(torch.zeros(1, 100, 20)))
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
plt.legend(["dim %d"%p for p in [4,5,6,7]])
None

谷歌等研讨者原论文中外示他们同样对基于进修的位置编码举行了实行,并发明这两种方法会发生确实相等的结果。以是按照性价比,他们照旧挑选了正弦弧线,因为它容许模子教练中推测更长的序列。

模子全体

下面,我们定义了一个函数以构修模子的通通进程,此中 make_model 输入原语词汇外和目标语词汇外后会构修两个词嵌入矩阵,而其它参数则会构修通通模子的架构。

def make_model(src_vocab, tgt_vocab, N=6, 
 d_model=512, d_ff=2048, h=8, dropout=0.1):
 "Helper: Construct a model from hyperparameters."
 c = copy.deepcopy
 attn = MultiHeadedAttention(h, d_model)
 ff = PositionwiseFeedForward(d_model, d_ff, dropout)
 position = PositionalEncoding(d_model, dropout)
 model = EncoderDecoder(
 Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),
 Decoder(DecoderLayer(d_model, c(attn), c(attn), 
 c(ff), dropout), N),
 nn.Sequential(Embeddings(d_model, src_vocab), c(position)),
 nn.Sequential(Embeddings(d_model, tgt_vocab), c(position)),
 Generator(d_model, tgt_vocab))

 # This was important from their code. 
 # Initialize parameters with Glorot / fan_avg.
 for p in model.parameters():
 if p.dim() > 1:
 nn.init.xavier_uniform(p)
 return model

以上的代码中,make_model 函数将调用上面我们定义的各个模块,并将它们组合一同。我们会将 Multi-Head Attention 子层、全连叫∮层和位置编码等构造传入编码器与解码器主体函数,再依据词嵌入向量与位置编码向量完毕输入与标注输出的构修。以下简单地示例了怎样运用 make_model 函数构修模子:

# Small example model.
tmp_model = make_model(10, 10, 2)
None

教练

这一部分将描画模子的教练方案。起首需求先容少许教练标准编码器解码器模子的东西,比如定义一个批量的目标以储存原语序列与目标语序列,并举行教练。前文的模子架构与函数定义我们主要参考的原论文,然后面的精细教练进程则主要参考了 Alexander 的完成体验。

批量和掩码

以下定义了保管一个批量数据的类,而且它会运用 Mask 教练进程中限制目标语的拜访序列。

class Batch:
 "Object for holding a batch of data with mask during training."
 def __init__(self, src, trg=None, pad=0):
 self.src = src
 self.src_mask = (src != pad).unsqueeze(-2)
 if trg is not None:
 self.trg = trg[:, :-1]
 self.trg_y = trg[:, 1:]
 self.trg_mask = \
 self.make_std_mask(self.trg, pad)
 self.ntokens = (self.trg_y != pad).data.sum()

 @staticmethod
 def make_std_mask(tgt, pad):
 "Create a mask to hide padding and future words."
 tgt_mask = (tgt != pad).unsqueeze(-2)
 tgt_mask = tgt_mask & Variable(
 subsequent_mask(tgt.size(-1)).type_as(tgt_mask.data))
 return tgt_mask

我们下一步需求创立一般的教练和评分函数,以继续追踪耗损的改造。构修一般的耗损函数后,我们就能依据它更新参数

如下定义了教练中的迭代轮回,我们运用 loss_compute() 函数盘算耗损函数,而且每运转 50 次迭代就输出一次教练耗损,这有利于监控教练状况。

def run_epoch(data_iter, model, loss_compute):
 "Standard Training and Logging Function"
 start = time.time()
 total_tokens = 0
 total_loss = 0
 tokens = 0
 for i, batch in enumerate(data_iter):
 out = model.forward(batch.src, batch.trg, 
 batch.src_mask, batch.trg_mask)
 loss = loss_compute(out, batch.trg_y, batch.ntokens)
 total_loss += loss
 total_tokens += batch.ntokens
 tokens += batch.ntokens
 if i % 50 == 1:
 elapsed = time.time() - start
 print("Epoch Step: %d Loss: %f Tokens per Sec: %f" %
 (i, loss / batch.ntokens, tokens / elapsed))
 start = time.time()
 tokens = 0
 return total_loss / total_tokens

教练数据与分批

Alexander 等人的模子标准的 WMT 2014 英语-德语数据集上举行教练,这个数据集包罗 450 万条语句对。语句曾经运用双字节编码(byte-pair encoding)处理,且具有约为 37000 个符号的原语-目标语共享词汇库。关于英语-法语的翻译义务,. 原论文作家运用了更大的 WMT 2014 英语-法语数据集,它包罗 3600 万条语句,且将符号支解为包罗 32000 个 word-piece 的词汇库。

原论文外示所有语句对将一同施行分批操作,并迫近序列长度。每一个教练批量包罗一组语句对,大约区分有 25000 个原语词汇和目标语词汇。

Alexander 等人运用 torch text 举行分批,精细的细节将后面议论。下面的函数运用 torchtext 函数创立批量数据,并确保批量大小会填充到最大且不会超越阈值(运用 8 块 GPU,阈值为 25000)。

global max_src_in_batch, max_tgt_in_batch
def batch_size_fn(new, count, sofar):
 "Keep augmenting batch and calculate total number of tokens + padding."
 global max_src_in_batch, max_tgt_in_batch
 if count == 1:
 max_src_in_batch = 0
 max_tgt_in_batch = 0
 max_src_in_batch = max(max_src_in_batch, len(new.src))
 max_tgt_in_batch = max(max_tgt_in_batch, len(new.trg) + 2)
 src_elements = count * max_src_in_batch
 tgt_elements = count * max_tgt_in_batch
return max(src_elements, tgt_elements)

batch_size_fn 将抽取批量数据,且每一个批量都抽取最大原语序列长度和最大目标语序列长度,假如长度不敷就运用零填充添加。

硬件与计谋

原论文一台板滞上运用 8 块 NVIDIA P100 GPU 教练模子,基本模子运用了论文中描画的超参数,每一次迭代大约需求 0.4 秒。基本模子着末迭代了 100000 次,共花了 12 个小时。而关于大模子,每一次迭代需求花 1 秒钟,以是教练 300000 个迭代大约需求三天半。但我们后面的实案例并不需求运用云云大的盘算力,因为我们的数据集相对要小少许。

优化器

原论文运用了 Adam 优化器,其掷致_1=0.9、 β_2=0.98 和 =10^{9}。教练中,研讨者会改动进修率为 l_rate=d0.5modelmin(step_num0.5,step_numwarmup_steps1.5)。

进修率的这种改造对应于预热教练中线性地添加进修率,然后再与迭代数的平方根成比例地减小。这种 1cycle 进修计谋实行中有十分好的效果,一般运用这种计谋的模子要比古板的方法收敛更速。这个实行中,模子采用的预热迭代数为 4000。当心,这一部分十分主要,我们需求以以下配备教练模子。

class NoamOpt:
 "Optim wrapper that implements rate."
 def __init__(self, model_size, factor, warmup, optimizer):
 self.optimizer = optimizer
 self._step = 0
 self.warmup = warmup
 self.factor = factor
 self.model_size = model_size
 self._rate = 0

 def step(self):
 "Update parameters and rate"
 self._step += 1
 rate = self.rate()
 for p in self.optimizer.param_groups:
 p['lr'] = rate
 self._rate = rate
 self.optimizer.step()

 def rate(self, step = None):
 "Implement `lrate` above"
 if step is None:
 step = self._step
 return self.factor * \
 (self.model_size ** (-0.5) *
 min(step ** (-0.5), step * self.warmup ** (-1.5)))

def get_std_opt(model):
 return NoamOpt(model.src_embed[0].d_model, 2, 4000,
 torch.optim.Adam(model.parameters(), lr=0, betas=(0.9, 0.98), eps=1e-9))

运用差别模子大小和最优化超参数下的改造弧线:

# Three settings of the lrate hyperparameters.
opts = [NoamOpt(512, 1, 4000, None), 
 NoamOpt(512, 1, 8000, None),
 NoamOpt(256, 1, 4000, None)]
plt.plot(np.arange(1, 20000), [[opt.rate(i) for opt in opts] for i in range(1, 20000)])
plt.legend(["512:4000", "512:8000", "256:4000"])
None

正则化

  • 标签腻滑

教练中,Alexander 等人运用了标签腻滑的方法,且腻滑值_ls=0.1。这可以会有损疑心度,因为模子将变得更加不确定它所做的预测,不过如许照旧晋升了准确度和 BLEU 分数。

Harvard NLP 最终运用 KL 散度完成了标签腻滑,与其运用 one-hot 目标分布,他们挑选了创立一个瞄准确词有置信度的分布,而其它腻滑的概率质料分布将贯穿通通词汇库。

class LabelSmoothing(nn.Module):
 "Implement label smoothing."
 def __init__(self, size, padding_idx, smoothing=0.0):
 super(LabelSmoothing, self).__init__()
 self.criterion = nn.KLDivLoss(size_average=False)
 self.padding_idx = padding_idx
 self.confidence = 1.0 - smoothing
 self.smoothing = smoothing
 self.size = size
 self.true_dist = None

 def forward(self, x, target):
 assert x.size(1) == self.size
 true_dist = x.data.clone()
 true_dist.fill_(self.smoothing / (self.size - 2))
 true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
 true_dist[:, self.padding_idx] = 0
 mask = torch.nonzero(target.data == self.padding_idx)
 if mask.dim() > 0:
 true_dist.index_fill_(0, mask.squeeze(), 0.0)
 self.true_dist = true_dist
 return self.criterion(x, Variable(true_dist, requires_grad=False))

下面,我们可以了解到概率质料怎样基于置信度分派到词。

# Example of label smoothing.
crit = LabelSmoothing(5, 0, 0.4)
predict = torch.FloatTensor([[0, 0.2, 0.7, 0.1, 0],
 [0, 0.2, 0.7, 0.1, 0], 
 [0, 0.2, 0.7, 0.1, 0]])
v = crit(Variable(predict.log()), 
 Variable(torch.LongTensor([2, 1, 0])))

# Show the target distributions expected by the system.
plt.imshow(crit.true_dist)
None

标签腻滑实行上模子对某些选项十分有决心的时分会惩办它。

crit = LabelSmoothing(5, 0, 0.1)
def loss(x):
 d = x + 3 * 1
 predict = torch.FloatTensor([[0, x / d, 1 / d, 1 / d, 1 / d],
 ])
 #print(predict)
 return crit(Variable(predict.log()),
 Variable(torch.LongTensor([1]))).data[0]
plt.plot(np.arange(1, 100), [loss(x) for x in range(1, 100)])
None

简单的序列翻译案例

我们可以从简单的复制义务开端实验。若从小词汇库给定输入符号的一个随机汇合,我们的目标是反向生成这些相同的符号。

def data_gen(V, batch, nbatches):
 "Generate random data for a src-tgt copy task."
 for i in range(nbatches):
 data = torch.from_numpy(np.random.randint(1, V, size=(batch, 10)))
 data[:, 0] = 1
 src = Variable(data, requires_grad=False)
 tgt = Variable(data, requires_grad=False)
 yield Batch(src, tgt, 0)

盘算模子耗损

class SimpleLossCompute:
 "A simple loss compute and train function."
 def __init__(self, generator, criterion, opt=None):
 self.generator = generator
 self.criterion = criterion
 self.opt = opt

 def __call__(self, x, y, norm):
 x = self.generator(x)
 loss = self.criterion(x.contiguous().view(-1, x.size(-1)), 
 y.contiguous().view(-1)) / norm
 loss.backward()
 if self.opt is not None:
 self.opt.step()
 self.opt.optimizer.zero_grad()
 return loss.data[0] * norm

贪婪解码

# Train the simple copy task.
V = 11
criterion = LabelSmoothing(size=V, padding_idx=0, smoothing=0.0)
model = make_model(V, V, N=2)
model_opt = NoamOpt(model.src_embed[0].d_model, 1, 400,
 torch.optim.Adam(model.parameters(), lr=0, betas=(0.9, 0.98), eps=1e-9))

for epoch in range(10):
 model.train()
 run_epoch(data_gen(V, 30, 20), model, 
 SimpleLossCompute(model.generator, criterion, model_opt))
 model.eval()
 print(run_epoch(data_gen(V, 30, 5), model, 
 SimpleLossCompute(model.generator, criterion, None)))
Epoch Step: 1 Loss: 3.023465 Tokens per Sec: 403.074173
Epoch Step: 1 Loss: 1.920030 Tokens per Sec: 641.689380
1.9274832487106324
Epoch Step: 1 Loss: 1.940011 Tokens per Sec: 432.003378
Epoch Step: 1 Loss: 1.699767 Tokens per Sec: 641.979665
1.657595729827881
Epoch Step: 1 Loss: 1.860276 Tokens per Sec: 433.320240
Epoch Step: 1 Loss: 1.546011 Tokens per Sec: 640.537198
1.4888023376464843
Epoch Step: 1 Loss: 1.278768 Tokens per Sec: 433.568756
Epoch Step: 1 Loss: 1.062384 Tokens per Sec: 642.542067
0.9853351473808288
Epoch Step: 1 Loss: 1.269471 Tokens per Sec: 433.388727
Epoch Step: 1 Loss: 0.590709 Tokens per Sec: 642.862135
0.34273059368133546

这些代码将简单地运用贪婪解码预测译文。

def greedy_decode(model, src, src_mask, max_len, start_symbol):
 memory = model.encode(src, src_mask)
 ys = torch.ones(1, 1).fill_(start_symbol).type_as(src.data)
 for i in range(max_len-1):
 out = model.decode(memory, src_mask, 
 Variable(ys), 
 Variable(subsequent_mask(ys.size(1))
 .type_as(src.data)))
 prob = model.generator(out[:, -1])
 _, next_word = torch.max(prob, dim = 1)
 next_word = next_word.data[0]
 ys = torch.cat([ys, 
 torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=1)
 return ys

model.eval()
src = Variable(torch.LongTensor([[1,2,3,4,5,6,7,8,9,10]]) )
src_mask = Variable(torch.ones(1, 1, 10) )
print(greedy_decode(model, src, src_mask, max_len=10, start_symbol=1))
 1 2 3 4 5 6 7 8 9 10
[torch.LongTensor of size 1x10]

实案例

现,我们将运用 IWSLT 德语-英语数据集完成翻译义务。该义务要比论文中议论的 WMT 义务稍微细一点,但足够展现通通系统。我们同样还展现了怎样运用众 GPU 处理来令加速教练进程。

#!pip install torchtext spacy
#!python -m spacy download en
#!python -m spacy download de

数据加载

我们将运用 torchtext 和 spacy 加载数据集,并完成分词。

# For data loading.
from torchtext import data, datasets

if True:
 import spacy
 spacy_de = spacy.load('de')
 spacy_en = spacy.load('en')

 def tokenize_de(text):
 return [tok.text for tok in spacy_de.tokenizer(text)]

 def tokenize_en(text):
 return [tok.text for tok in spacy_en.tokenizer(text)]

 BOS_WORD = '<s>'
 EOS_WORD = '</s>'
 BLANK_WORD = "<blank>"
 SRC = data.Field(tokenize=tokenize_de, pad_token=BLANK_WORD)
 TGT = data.Field(tokenize=tokenize_en, init_token = BOS_WORD, 
 eos_token = EOS_WORD, pad_token=BLANK_WORD)

 MAX_LEN = 100
 train, val, test = datasets.IWSLT.splits(
 exts=('.de', '.en'), fields=(SRC, TGT), 
 filter_pred=lambda x: len(vars(x)['src']) <= MAX_LEN and 
 len(vars(x)['trg']) <= MAX_LEN)
 MIN_FREQ = 2
 SRC.build_vocab(train.src, min_freq=MIN_FREQ)
 TGT.build_vocab(train.trg, min_freq=MIN_FREQ)

我们期望有十分平均的批量,且有最小的填充,于是我们必需对默认的 torchtext 分批函数举行改正。这段代码改正了默认的分批进程,以确保我们能搜寻足够的语句以找到紧凑的批量。

数据迭代器

迭代器定义了分批进程的众项操作,包罗数据清洗、拾掇和分批等。

class MyIterator(data.Iterator):
 def create_batches(self):
 if self.train:
 def pool(d, random_shuffler):
 for p in data.batch(d, self.batch_size * 100):
 p_batch = data.batch(
 sorted(p, key=self.sort_key),
 self.batch_size, self.batch_size_fn)
 for b in random_shuffler(list(p_batch)):
 yield b
 self.batches = pool(self.data(), self.random_shuffler)

 else:
 self.batches = []
 for b in data.batch(self.data(), self.batch_size,
 self.batch_size_fn):
 self.batches.append(sorted(b, key=self.sort_key))

def rebatch(pad_idx, batch):
 "Fix order in torchtext to match ours"
 src, trg = batch.src.transpose(0, 1), batch.trg.transpose(0, 1)
 return Batch(src, trg, pad_idx)

众 GPU 教练

着末为了疾速教练,我们运用了众块 GPU。这段代码将完成众 GPU 的词生成,但它并不是针对 Transformer 的精细方法,以是这里并不会精细议论。众 GPU 教练的基本思念即教练进程中将词生因素割为语块(chunks),并传入差别的 GPU 完成并行处理,我们可以运用 PyTorch 并行基元完成这一点。

replicate - split modules onto different gpus.
scatter - split batches onto different gpus
parallel_apply - apply module to batches on different gpus
gather - pull scattered data back onto one gpu.
nn.DataParallel - a special module wrapper that calls these all before evaluating.
# Skip if not interested in multigpu.
class MultiGPULossCompute:
 "A multi-gpu loss compute and train function."
 def __init__(self, generator, criterion, devices, opt=None, chunk_size=5):
 # Send out to different gpus.
 self.generator = generator
 self.criterion = nn.parallel.replicate(criterion, 
 devices=devices)
 self.opt = opt
 self.devices = devices
 self.chunk_size = chunk_size

 def __call__(self, out, targets, normalize):
 total = 0.0
 generator = nn.parallel.replicate(self.generator, 
 devices=self.devices)
 out_scatter = nn.parallel.scatter(out, 
 target_gpus=self.devices)
 out_grad = [[] for _ in out_scatter]
 targets = nn.parallel.scatter(targets, 
 target_gpus=self.devices)

 # Divide generating into chunks.
 chunk_size = self.chunk_size
 for i in range(0, out_scatter[0].size(1), chunk_size):
 # Predict distributions
 out_column = [[Variable(o[:, i:i+chunk_size].data, 
 requires_grad=self.opt is not None)] 
 for o in out_scatter]
 gen = nn.parallel.parallel_apply(generator, out_column)

 # Compute loss. 
 y = [(g.contiguous().view(-1, g.size(-1)), 
 t[:, i:i+chunk_size].contiguous().view(-1)) 
 for g, t in zip(gen, targets)]
 loss = nn.parallel.parallel_apply(self.criterion, y)

 # Sum and normalize loss
 l = nn.parallel.gather(loss, 
 target_device=self.devices[0])
 l = l.sum()[0] / normalize
 total += l.data[0]

 # Backprop loss to output of transformer
 if self.opt is not None:
 l.backward()
 for j, l in enumerate(loss):
 out_grad[j].append(out_column[j][0].grad.data.clone())

 # Backprop all loss through transformer. 
 if self.opt is not None:
 out_grad = [Variable(torch.cat(og, dim=1)) for og in out_grad]
 o1 = out
 o2 = nn.parallel.gather(out_grad, 
 target_device=self.devices[0])
 o1.backward(gradient=o2)
 self.opt.step()
 self.opt.optimizer.zero_grad()
 return total * normalize

下面,我们应用前面定义的函数创立了模子、器量标准、优化器、数据迭代器和并行化:

# GPUs to use
devices = [0, 1, 2, 3]
if True:
 pad_idx = TGT.vocab.stoi["<blank>"]
 model = make_model(len(SRC.vocab), len(TGT.vocab), N=6)
 model.cuda()
 criterion = LabelSmoothing(size=len(TGT.vocab), padding_idx=pad_idx, smoothing=0.1)
 criterion.cuda()
 BATCH_SIZE = 12000
 train_iter = MyIterator(train, batch_size=BATCH_SIZE, device=0,
 repeat=False, sort_key=lambda x: (len(x.src), len(x.trg)),
 batch_size_fn=batch_size_fn, train=True)
 valid_iter = MyIterator(val, batch_size=BATCH_SIZE, device=0,
 repeat=False, sort_key=lambda x: (len(x.src), len(x.trg)),
 batch_size_fn=batch_size_fn, train=False)
 model_par = nn.DataParallel(model, device_ids=devices)
None

下面可以教练模子了,Harvard NLP 团队起首运转了少许预热迭代,可是其它的设修都能运用默认的参数。带有 4 块 Tesla V100 的 AWS p3.8xlarge 中,批量大小为 12000 的状况下每秒能运转 27000 个词。

教练系统

#!wget https://s3.amazonaws.com/opennmt-models/iwslt.pt
if False:
 model_opt = NoamOpt(model.src_embed[0].d_model, 1, 2000,
 torch.optim.Adam(model.parameters(), lr=0, betas=(0.9, 0.98), eps=1e-9))
 for epoch in range(10):
 model_par.train()
 run_epoch((rebatch(pad_idx, b) for b in train_iter), 
 model_par, 
 MultiGPULossCompute(model.generator, criterion, 
 devices=devices, opt=model_opt))
 model_par.eval()
 loss = run_epoch((rebatch(pad_idx, b) for b in valid_iter), 
 model_par, 
 MultiGPULossCompute(model.generator, criterion, 
 devices=devices, opt=None))
 print(loss)
else:
 model = torch.load("iwslt.pt")

一朝教练完毕了,我们就能解码模子并生成一组翻译,下面我们简单地翻译了验证集中的第一句话。该数据集十分小,以是模子通过贪婪搜寻也能取得不错的翻译效果。

for i, batch in enumerate(valid_iter):
 src = batch.src.transpose(0, 1)[:1]
 src_mask = (src != SRC.vocab.stoi["<blank>"]).unsqueeze(-2)
 out = greedy_decode(model, src, src_mask, 
 max_len=60, start_symbol=TGT.vocab.stoi["<s>"])
 print("Translation:", end="\t")
 for i in range(1, out.size(1)):
 sym = TGT.vocab.itos[out[0, i]]
 if sym == "</s>": break
 print(sym, end =" ")
 print()
 print("Target:", end="\t")
 for i in range(1, batch.trg.size(0)):
 sym = TGT.vocab.itos[batch.trg.data[i, 0]]
 if sym == "</s>": break
 print(sym, end =" ")
 print()
 break
Translation: <unk> <unk> . In my language , that means , thank you very much . 
Gold: <unk> <unk> . It means in my language , thank you very much . 

实行结果

WMT 2014 英语到法语的翻译义务中,原论文中的大型的 Transformer 模子完成了 41.0 的 BLEU 分值,它要比以前所有的单模子效果更好,且只要前面顶级的模子 1/4 的教练资本。 Harvard NLP 团队的完成中,OpenNMT-py 版本的模子 EN-DE WMT 数据集上完成了 26.9 的 BLEU 分值。

工程Transformer自然言语处理谷歌板滞翻译
92
相关数据
激活函数技能

盘算收集中, 一个节点的激活函数定义了该节点给定的输入或输入的汇合下的输出。标准的盘算机芯片电道可以看作是依据输入取得"开"(1)或"关"(0)输出的数字收集激活函数。这与神经收集中的线性感知机的方法相似。 一种函数(比如 ReLU 或 S 型函数),用于对上一层的所有输入求加权和,然后生成一个输出值(一般为非线性值),并将其转达给下一层。

权重技能

线性模子中特征的系数,或深度收集中的边。教练线性模子的目标是确定每个特征的抱负权重。假如权重为 0,则相应的特征对模子来说没有任何奉献。

Dropout技能

神经收集教练中避免过拟合的一种技能

神经板滞翻译技能

2013 年,Nal Kalchbrenner 和 Phil Blunsom 提出了一种用于板滞翻译的新型端到端编码器-解码器构造 [4]。该模子可以运用卷积神经收集(CNN)将给定的一段源文本编码成一个延续的向量,然后再运用轮回神经收集(RNN)举措解码器将该形态向量转换成目标言语。他们的研讨效果可以说是神经板滞翻译(NMT)的降生;神经板滞翻译是一种运用深度进修神经收集获取自然言语之间的映照联系的方法。NMT 的非线性映照差别于线性的 SMT 模子,而且是运用了连接编码器息争码器的形态向量来描画语义的等价联系。另外,RNN 应当槐ボ取得无量长句子背后的新闻,从而办理所谓的「长间隔从头排序(long distance reordering)」题目。

词嵌入技能

词嵌入是自然言语处理(NLP)中言语模子与外征进修技能的统称。看法上而言,它是指把一个维数为所有词的数目标高维空间嵌入到一个维数低得众的延续向量空间中,每个单词或词组被映照为实数域上的向量。

参数技能

数学和统计学裡,参数(英语:parameter)是运用通用变量来修立函数和变量之间联系(岛镶种联系很难用方程来阐述时)的一个数目。

收敛技能

数学,盘算机科学和逻辑学中,收敛指的是差别的变换序列有限的时间内抵达一个结论(变换终止),而且得出的结论是独立于抵达它的道径(他们是交融的)。 高深来说,收敛一般是指教练时代抵达的一种形态,即颠末必定次数的迭代之后,教练耗损和验证耗损每次迭代中的改造都十分小或基本没有改造。也便是说,假如采用目今数据举行分外的教练将无法改良模子,模子即抵达收敛形态。深度进修中,耗损值有时会最终下降之前的众次迭代中保持稳定或确实保持稳定,暂时变成收敛的假象。

进修率技能

运用差别优化器(比如随机梯度下降,Adam)神经收集相关教练中,进修速率举措一个超参数掌握了权重更新的幅度,以及教练的速率和精度。进修速率太大容易导致目标(价钱)函数摆荡较大从而难以找到最优,而弱进修速率修立太小,则会导致收敛过慢耗时太长

卷积技能

耗损函数技能

数学优化,统计学,计量经济学,计划表面,板滞进修和盘算神经科学等范畴,耗损函数或资本函数是将一或众个变量的一个事情或值映照为可以直观地外示某种与之相关“资本”的实数的函数。

超参数技能

板滞进修中,超参数是进修进程开端之前修立其值的参数。 相反,其他参数的值是通过教练得出的。 差别的模子斗嗽翥法需求差别的超参数,少许简单的算法(如一般最小二乘回归)不需求。 给定这些超参数,斗嗽翥法从数据中进修参数。相同品种的板滞进修模子可以需求差别的超参数来顺应差别的数据方式,而且必需对其举行调解以便模子可以最优地办理板滞进修题目。 实行运用中一般需求对超参数举行优化,以找到一个超参数元组(tuple),由这些超参数元组变成一个最优化模子,该模子可以将给定的独立数据上预订义的耗损函数最小化。

当心力机制技能

我们可以大约地把神经当心绪制类比成一个可以笃志于输入实质的某一子集(或特征)的神经收集. 当心力机制最早是由 DeepMind 为图像分类提出的,这让「神经收集施行预测义务时可以更众体恤输入中的相关部分,更少体恤不相关的部分」。当解码器生成一个用于构成目标句子的词时,源句子中仅有少部分是相关的;于是,可以运用一个基于实质的当心力机制来依据源句子动态地生成一个(加权的)语境向量(context vector), 然后收集会依据这个语境向量而不是某个固定长度的向量来预测词。

张量技能

张量是一个可用来外示少许矢量、标量和其他张量之间的线性联系的众线性函数,这些线性联系的基本例子有内积、外积、线性映照以及笛卡儿积。其坐标 维空间内,有 个分量的一种量,此中每个分量都是坐标的函数,而坐标变换时,这些分量也按照某些规矩作线性变换。称为该张量的秩或阶(与矩阵的秩和阶均无联系)。 数学里,张量是一种几何实体,或者说广义上的“数目”。张量看法包罗标量、矢量和线性算子。张量可以用坐标系统来外达,记作标量的数组,但它是定义为“不依赖于参照系的挑选的”。张量物理和工程学中很主要。比如扩散张量成像中,外达器官关于水的各个偏向的微分透性的张量可以用来发生大脑的扫描图。工程上最主要的例子可以便是应力张量和应变张量了,它们都是二阶张量,关于一般线性材料他们之间的联系由一个四阶弹性张量来决议。

验证集技能

验证数据集是用于调解分类器超参数(即模子构造)的一组数据集,它有时也被称为开辟集(dev set)。

板滞翻译技能

板滞翻译(MT)是应用板滞的力气「主动将一种自然言语(源言语)的文本翻译成另一种言语(目标言语)」。板滞翻译方法一般可分成三大类:基于规矩的板滞翻译(RBMT)、统计板滞翻译(SMT)和神经板滞翻译(NMT)。

映照技能

映照指的是具有某种特别构造的函数,或泛指类函数思念的范围论中的态射。 逻辑和图论中也有少许不太常规的用法。其数学定义为:两个非空汇合A与B间保管着对应联系f,而且关于A中的每一个元素x,B中总有有独一的一个元素y与它对应,就这种对应为从A到B的映照,记作f:A→B。此中,y称为元素x映照f下的象,记作:y=f(x)。x称为y关于映照f的原象*。*汇合A中所有元素的象的汇合称为映照f的值域,记作f(A)。同样的,板滞进修中,映照便是输入与输出之间的对应联系。

标准化技能

标准化:将属性数据按比例缩放,使之落入一个小的特定区间,如-1.0 到1.0 或0.0 到1.0。 通过将属性数据按比例缩放,使之落入一个小的特定区间,如0.0到1.0,对属性标准化。关于间隔器量分类算法,如涉及神经收集或诸如最临近分类和聚类的分类算法,标准化特别有用。假如运用神经收集后向传达算法举行分类开掘,关于教练样本属性输入值标准化将有帮于加速进修阶段的速率。关于基于间隔的方法,标准化可以帮帮避免具有较大初始值域的属性与具有较小初始值域的属相比较,权重过大。有许大都据标准化的方法,包罗最小-最大标准化、z-score标准化和按小数定标标准化。

过拟合技能

过拟合是指为了取得同等假设而使假设变得过分厉厉。避免过拟合是分类器计划中的一个中心义务。一般采用增大数据量和测试样本集的方法对分类器功用举行评判。

神经元技能

(人工)神经元是一个类比于生物神经元的数学盘算模子,是神经收集的基本构成单位。 关于生物神经收集,每个神经元与其他神经元相连,当它“兴奋”时会向相连的神经元发送化学物质,从而改动这些神经元的电位;神经元的“兴奋”由其电位决议,当它的电位超越一个“阈值”(threshold)便会被激活,亦即“兴奋”。 目前最常睹的神经元模子是基于1943年 Warren McCulloch 和 Walter Pitts提出的“M-P 神经元模子”。 这个模子中,神经元通过带权重的连接接处理来自n个其他神经元的输入信号,其总输入值将与神经元的阈值举行比较,着末通过“激活函数”(activation function)发生神经元的输出。

盘诘技能

一般来说,盘诘是讯问的一种方式。它差别的学科里涵义有所差别。新闻检索范畴,盘诘指的是数据库和新闻系统对新闻检索的准确请求

序列到序列技能

批次技能

模子教练的一次迭代(即一次梯度更新)中运用的样本集。

堆叠技能

堆叠泛化是一种用于最小化一个或众个泛化器的泛化偏向率的方法。它通过推导泛化器相关于所供应的进修集的偏向来发挥其感化。这个推导的进程包罗:第二层中将第一层的原始泛化器对部分进修集的猜念举行泛化,以及实验对进修集的盈余部分举行猜念,而且输出准确的结果。当与众个泛化器一同运用时,堆叠泛化可以被看作是一个交叉验证的繁杂版本,应用比交叉验证更为繁杂的计谋来组合各个泛化器。当与单个泛化器一同运用时,堆叠泛化是一种用于估量(然后改正)泛化器的过失的方法,该泛化器曾经特定进修集上举行了教练并被讯问了特定题目。

优化器技能

优化器基类供应了盘算梯度loss的方法,并可以将梯度运用于变量。优化器里包罗了完成了经典的优化算法,如梯度下降和Adagrad。 优化器是供应了一个可以运用种种优化算法的接口,可以让用户直接调用少许经典的优化算法,如梯度下降法等等。优化器(optimizers)类的基类。这个类定义了教练模子的时分添加一个操作的API。用户基本上不会直接运用这个类,可是你会用到他的子类比如GradientDescentOptimizer, AdagradOptimizer, MomentumOptimizer(tensorflow下的优化器包)等等这些算法。

引荐作品
class SublayerConnection(nn.Module): """ A residual connection followed by a layer norm. Note for code simplicity we apply the norm first as opposed to last. """ def __init__(self, size, dropout): super(SublayerConnection, self).__init__() self.norm = LayerNorm(size) self.dropout = nn.Dropout(dropout) def forward(self, x, sublayer): "Apply residual connection to any sublayer function that maintains the same size." return x + self.dropout(sublayer(self.norm(x))) 着末一行是不是写错了, 因为要返回 Norm(X+sublayer(X)) 这里的写法 返回的是X+sublayer(Norm(X))
和原论文外述不相同,但效果是相同的。按照原论文中的LayerNorm(x+Sublayer(x)),x举措Sublayer()的输入是上一子层颠末LayerNorm的输出。而依据实行中的 x2 = x1 + self.dropout(sublayer(self.norm(x1))),x2举措下一子层的输入照旧需求颠末LayerNorm。LayerNorm(x1)才是Sublayer()的输入,实质是相同的。