一文详解Transformer 细节及代码实现
图1:原论文信息
Attention Is All You Need:https://arxiv.org/pdf/1706.03762.pdf
Introduction
Transformer —— 由 Google 的研究人员在 2017 年的《Attention Is All You Need》[1] 中提出。其首先在 NLP 领域中取得了 SOTA 的表现,之后也逐渐的被运用到 CV 及其他领域里,依旧展露出耀眼的锋芒!网络上已有很多乐于奉献的博主、大佬们通过文章、视频等方式详细解释了 Transformer 的整体架构,对我的学习起到了极大的帮助。本文着重于以下两点:
- Dimensions文章会尽量详细的介绍 Transformer 各个子层输入、输出、权重等的具体维度。
- Implementation在详细了解各层的结构之后趁热打铁利用Pytorch对网络结构代码实现。
Transformer
Transformer 主要由 Encoder 和 Decoder 各重复 N 次构成 (在原论文中重复 6 次),如图:
图 2:Transformer 模型整体结构
Encoder 和 Decoder 的数据流如下图 3 所示[2],前一个 Encoder 的输出作为后一个 Encoder 的输入,最后一个 Encoder 的输出被作为 N 个 Decoder 各自的输入。除了最后一个 Encoder 的输出之外,每个 Decoder 也接收前一个 Decoder 的输出作为输入。就机器翻译的任务而言,第一个 Encoder 的输入是源语言的 token,而最后一个 Decoder 的输出是目标语言的 token。
图 3:Encoder 和 Decoder 各层及相互之间的数据流
接下来,我们深入探讨每一个 Encoder 和 Decoder 的内部,看看它们如何通过接收相同维度 的输入来产生维度为 的输出。需要注意,输入到 Encoder 和 Decoder 的输入数分别为可以不相同。(例如:在机器翻译任务中“你好”英文翻译成“hello”,很明显单词的数量是不一样的,当然这个例子不严谨,因为并不是每个字或者单词就是一个 token)
在每一个 Encoder 和 Decoder 的内部包含有一些子层,如下图 4 所示:
图 4:Encoder 和 Decoder 内部子层结构
这里我们先引入一些必要的库,方便大家在后续代码中直接调用:
import copy
import math
import warnings
import torch
import torch.nn as nn
from torch.nn.functional import log_softmax
warnings.filterwarnings("ignore")
Self-Attention
考虑如下句子:"Jane is going to the cinema to watch a new ____." [3]空格里应该填写什么单词呢?可能的答案有:"movie", "comedy", "a new action movie", "romantic movie"等;且很明显:"book", "cat", "grocery store" 这些单词是不合适的。预测正确单词的关键在于上下文和前面单词所提供的信息。即:下图中箭头所表达的含义:
图 5:现实中单词填空的例子
在句子中,每个单词之间的关联程度是不一样的。例如,"cinema"(电影院)和 "watch"(观看)这两个词在上下文中的关系更密切,这有助于预测空白处与电影相关的词。因此,每个词都要根据上下文和这些词的关联程度,以不同的关注度去关注其他词,这其实就是 Attention 的思想。
在具体计算上,输入的句子会经过 Embedding 层获得每个单词对应的 feature vector (特征向量),如下图的,每个单词向量的维度是,所有的单词向量可以构成维度是的矩阵 。在每个特征向量上,使用 3 个可学习的权重矩阵进行 3 次矩阵乘法,得到,分别代表 Query, Key, Value。查询向量和向量的大小必须一致,表示为,但值向量的大小可以不同,表示为。
图 6:q, k, v 的计算过程
上述对每个单词向量的矩阵操作可以合并,写成如下的矩阵形式:
图 7:q, k, v 的矩阵计算过程
在获得了每个单词的之后,计算某一个单词的与其余单词的的内积,作为两单词之间的关联权重,如下图所示[4]:
图 8:单词间关联权重的计算过程
将得到的权重分别与对应单词的相乘再求和得到:考虑了单词间关联程度的特征向量。
图 9:获得关联程度加权后的单词特征向量的计算过程
Encoder
每一个 Encoder 内部包含两个子层,每个子层后进行 LayerNormalization 和 残差连接:
- Multi-Head Attention (多头注意力)
- Feed Forward (前向传播网络)
图 10:Encoder 单独结构
Multi-Head Attention in Encoder
Multi-Head Attention 是最关键、计算量最大的模块。如下图 5 所示,该模块将个大小各为的向量作为输入(打包成一个大小为的矩阵),并产生一个大小与相同的输出矩阵(由个大小各为的向量打包而成)。
图 11:Multi-Head Attention 计算过程和维度细节
每个 Attention Head 都有自己学习到的线性变换,作用于输入上获得属于自己的 Query, Key, Value。由于这些变换是单独学习的,因此每个 Attention Head 都有可能学会关注不同的事物。在 NLP 任务中,这可能意味着一个 Attention Head 会学习关注语法关系(如主谓一致),而另一个注意头则会学习关注语义关系。每个内部均采取如下的执行流程:
- 输入 Input Embedding + Positional Encoding或 outputs from previous encoder,矩阵大小为,通过将输入与相应的权重矩阵相乘获得 Query (), Key (), Key () 三个大家耳熟能详的矩阵。
图 12:Q, K, V 矩阵的计算过程
- 执行scaled dot-product attention计算获得 attention 矩阵。如下图 12 所示:首先,对如上节图 8 最终获得的注意力分数除以缩放因子得到矩阵’;然后,对’的每一行施加,得到 attention 矩阵。
为什么需要除以缩放因子?[5] 假设 key 向量的维度很大。由于维度增加,查询向量 q 和 k 之间的点积可能会变得不成比例地大。这可能会导致训练过程中出现问题,例如梯度爆炸或梯度消失,从而阻碍学习过程,并使模型难以有效捕捉标记之间的依赖关系。通过这种缩放的方法,可以缩减注意力分数,确保其大小保持在可控范围内。这种缩放可以防止点积变得过大,并有助于稳定学习过程。
图 13:含缩放因子的 dot product attention 计算过程
- 将注意力分数矩阵和矩阵相乘得到加权之后的新的特征向量矩阵,作为的输出。如下图 14 所示:
图 14:对 V 进行注意力加权得到每个 Head 的输出
- 接下来,将所有 head 的输出拼接起来得到,再经过一次线性变换,得到 Multi-Head Attention 的输出:
图 15:拼接并线性变换后得到 Multi-Head Attention 的输出
中每一行的维度为(h 个大小为 的向量连接)。矩阵的维度为,它将 Z' 的每一行从维度投影到。
因此,Encoder 的一个 Multi-Head Attention 子层接收 的输入,然后最终产生相同大小的输出。这也被叫做 Input-Input Attention 或 Encoder Self-Attention (例如:输入序列的每个单词与句子中的所有单词都计算 attention score)。代码实现如下[6]:
该代码的 attention 函数中包含 mask 项,这是由于 Decoder 里 Masked Multi-Head Attention 也会用到该函数,因此,统一设置 mask 参数。但在 Encoder 里不会用到。
def clones(module, N):
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
def attention(query, key, value, mask=None, dropout=None):
"""
Size of q, k, v: [nbatches, nheads, -1, d_k]
"""
d_k = key.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 = scores.softmax(dim=-1)
if dropout is not None:
p_attn = dropout(p_attn)
return torch.matmul(p_attn, value), p_attn
class MultiHeadAttention(nn.Module):
def __init__(self, h, d_model, dropout=0.1):
super().__init__()
assert d_model % h == 0
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(dropout)
def forward(self, query, key, value, mask=None):
if mask is not None:
mask = mask.unsqueeze(1)
nbatches = query.size(0)
query, key, value = [
linear(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
for linear, x in zip(self.linears, (query, key, value))
]
x, self.attn = attention(query, key, value, mask, self.dropout)
x = x.transpose(1, 2).contiguous().view(nbatches, -1, self.h * self.d_k)
# .contigugous() Returns a contiguous in memory tensor containing the same data
del query
del key
del value
return self.linears[-1](x)
Feed Forward Network and Residual Connection
图 16:FFN 网络结构图
前向传播网络采用大小为的输入,通过执行如下函数,得到相同大小的输出:
图 17:FFN 公式内容
class PositionwiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__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_1(self.dropout(self.w_1(x).relu()))
- 在两个子层(Multi-Head Attention 和 Feed Forward)后进行LayerNormalization和Residual Connection。即,子层的输出是 。
- LayerNormalization 是由 Ba et al (2016) 提出的(https://paperswithcode.com/method/layer-normalization),它是众多 norm 技巧中的一种,可以简化模型训练过程,从而提高性能并缩短训练时间。
class LayerNorm(nn.Module):
def __init__(self, features, eps=1e-6):
super().__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
- Residual Connection 的思想是由 He et al (2005)(https://paperswithcode.com/method/resnet) 在 ResNet 模型中提出来的。即:将子层的输入与子层的输出相加。这样可以防止梯度消失的问题。
class SublayerConnection(nn.Module):
def __init__(self, size, dropout):
super().__init__()
self.norm = LayerNorm(size)
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer):
return x + self.dropout(sublayer(self.norm(x))) # LayerNorm before residual
Encoder Implementation
通过上面的步骤我们已经实现了 Multi-Head Attention 和 Feed Forward Network 将他们通过经过 LayerNorm 和残差连接之后组合起来就构成了 1 个 Encoder,然后,把它复制 N 份就实现了 Transformer 左边的结构。
class EncoderLayer(nn.Module):
def __init__(self, size, self_attn, feed_forward, dropout):
super().__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):
x = self.sublayer[0](x, lambda x: self.attn(x, x, x, mask))
return self.sublayer[1](x, self.feed_forward)
class Encoder(nn.Module):
def __init__(self, layer, N):
super().__init__()
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.size)
def forward(self, x, mask):
for layer in self.layers:
x = layer(x, mask)
return self.norm(x) # LayerNorm after residual
Decoder
Decoder 包含三个子层:
- Masked Multi-Head Attention
- Multi-Head Attention
- Feed Forward
图 18:Decoder 单独结构
Masked Multi-Head Attention
Decoder 中的 Multi-Head Attention 也被称为 Output-Output Attention 或 Decoder Self-Attention。它采取个大小的向量作为输入(目标序列的 token 向量)作为输入,然后产生相同大小的输出矩阵 。与 Encoder 中的 Multi-Head Attention 唯一不同的是:Masking。对于查询,它只能计算自己和之间的关联。可以通过将矩阵中时刻之后的 query-key 组合设成来实现,如下图。
通过 mask 可以防止当前位置的 query 与其后的 key 产生关联。例如:在机器翻译任务中,原句子是 "简打算去影院看一部新电影。",目标句子是 "Jane is going to the cinema to watch a new movie.",原句子作为 Encoder 的输入,目标句子作为 Decoder 的输入。假如我们翻译到 "Jane is going to the cinema",此时 cinema 只会和其前面的单词产生关联,而不会与其后还未翻译出来的单词产生联系。因此,需要使用 mask 将 cinema 后面的单词遮住。
图 19:Decoder 中的权重矩阵
Multi-Head Attention in Decoder
Decoder 中的 Multi-Head Attention 也被称作是 Input-Output Attention 或 Encoder-Decoder Attention。与 Encoder 中的 Multi-Head Attention 大致相同,除接收最后一层 Encoder 的输出作为额外输入 () 外。被用于生成矩阵,而 Decoder 的输入用于生成矩阵。
图 20:Decoder 中的 Q, K, V 生成过程
Feed Forward Network in Decoder
该模块与 Encoder 中的 Feed Forward Network 模块相同。
Decoder Implementation
仿照 Encoder 的实现很容易得到 Decoder 的代码实现:
def subsequent_mask(size):
attn_shape = (1, size, size)
subsequent_mask = torch.triu(torch.ones(attn_shape), diagnal=1).type(torch.uint8)
return subsequent_mask == 0
class DecoderLayer(nn.Module):
def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
super().__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):
m = memory # additional input from Encoder-Stack to generate K and V
# while the input within the Decoder side is used to generate Q
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)
class Decoder(nn.Module):
def __init__(self, layer, N):
super().__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)
我们将 Encoder 和 Decoder 组合起来:
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)
Generator
Linear 和 Softmax 层组成了 generator 用于输出一个概率分布。通过从最后一个 Decoder 的输出中获取维度为的输入,并将其转换为与词汇表大小相同的维度,表示下一时刻单词的概率分布。
Transformer 的 Decoder 在 Inference 的时候是 auto-regressive (自回归) 的[7]。即:将上一时刻的输出追加到输入后面组成当前时刻的输入,然后产生当前时刻的输出,如下图所示。
图 21:Auto-Regressive 的例子
图 22:Linear 层和 Softmax层结构
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 log_softmax(self.proj(x), dim=-1)
Embedding
Embedding 包含两步:
- Input and Output Embedding
- Position Encoding
图 23:Embedding 层结构
Embeddings
与其他的序列模型相同,Transformer 使用可学习的 embedding 将 input tokens 和 output tokens 转化成为维度是的向量。
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
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)
Positional Encoding
由于 Transformer 不像 RNN 或者 CNN 那样,不包含 recurrent 和 convolution,因此,为了使模型能利用到序列的顺序信息,在 Encoder 和 Decoder 的 input embedding 基础上额外的添加位置编码,来表示序列中 tokens 的次序关系,论文中使用 cosine 函数来计算:
图 24:Positional Encoding 公式
其中 pos 表示是 token 的位置,i 表示 position encoding 的第 i 维度。即:positional encoding 的每一维对应了一个正弦曲线。
class PositionalEncoding(nn.Module):
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)
# pe will become a persistent parameter of the model
# that can be used and accessed in other methods of the model
def forward(self, x):
x = x + self.pe[:, : x.size(1)].requires_grad_(False)
return self.dropout(x)
目前,所有的子模块均已完成了代码实现,接下来就是完整 Transformer 模型的构建:
def make_model(
src_vocab,
tgt_vocab,
N=6,
d_model=512,
d_ff=2048,
h=8,
dropout=0.1,
):
c = copy.deepcopy
attn = MultiHeadAttention(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),
)
for p in model.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
return model
Appendix
import copy
import math
import warnings
import torch
import torch.nn as nn
from torch.nn.functional import log_softmax
warnings.filterwarnings("ignore")
def clones(module, N):
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
def subsequent_mask(size):
attn_shape = (1, size, size)
subsequent_mask = torch.triu(torch.ones(attn_shape), diagnal=1).type(torch.uint8)
return subsequent_mask == 0
class EncoderDecoder(nn.Module):
def __init__(
self,
encoder,
decoder,
src_embed,
tgt_embed,
generator,
):
super().__init__()
self.encoder = encoder
self.decoder = decoder
self.src_embed = src_embed
self.tgt_embed = tgt_embed
self.generator = generator
def forward(self, src, src_mask, tgt, tgt_mask):
return self.decoder(
self.tgt_embed(tgt),
self.encoder(
self.src_embed(src),
src_mask,
), # memory
src_mask,
tgt_mask,
)
class Generator(nn.Module):
def __init__(self, d_model, vocab):
super().__init__()
self.proj = nn.Linear(d_model, vocab)
def forward(self, x):
return log_softmax(self.proj(x), dim=-1)
def attention(query, key, value, mask=None, dropout=None):
"""
[nbatches, nheads, -1, d_k]
"""
d_k = key.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 = scores.softmax(dim=-1)
if dropout is not None:
p_attn = dropout(p_attn)
return torch.matmul(p_attn, value), p_attn
class MultiHeadAttention(nn.Module):
def __init__(self, h, d_model, dropout=0.1):
super().__init__()
assert d_model % h == 0
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(dropout)
def forward(self, query, key, value, mask=None):
if mask is not None:
mask = mask.unsqueeze(1)
nbatches = query.size(0)
query, key, value = [
linear(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
for linear, x in zip(self.linears, (query, key, value))
]
x, self.attn = attention(query, key, value, mask, self.dropout)
x = x.transpose(1, 2).contiguous().view(nbatches, -1, self.h * self.d_k)
# .contigugous() Returns a contiguous in memory tensor containing the same data
del query
del key
del value
return self.linears[-1](x)
class PositionwiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__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_1(self.dropout(self.w_1(x).relu()))
class LayerNorm(nn.Module):
def __init__(self, features, eps=1e-6):
super().__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
class SublayerConnection(nn.Module):
def __init__(self, size, dropout):
super().__init__()
self.norm = LayerNorm(size)
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer):
return x + self.dropout(sublayer(self.norm(x))) # LayerNorm before residual
class EncoderLayer(nn.Module):
def __init__(self, size, self_attn, feed_forward, dropout):
super().__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):
x = self.sublayer[0](x, lambda x: self.attn(x, x, x, mask))
return self.sublayer[1](x, self.feed_forward)
class Encoder(nn.Module):
def __init__(self, layer, N):
super().__init__()
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.size)
def forward(self, x, mask):
for layer in self.layers:
x = layer(x, mask)
return self.norm(x) # LayerNorm after residual
class Decoder(nn.Module):
def __init__(self, layer, N):
super().__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)
class DecoderLayer(nn.Module):
def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
super().__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):
m = memory # additional input from Encoder-Stack to generate K and V
# while the input within the Decoder side is used to generate Q
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)
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
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)
class PositionalEncoding(nn.Module):
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)
# pe will become a persistent parameter of the model
# that can be used and accessed in other methods of the model
def forward(self, x):
x = x + self.pe[:, : x.size(1)].requires_grad_(False)
return self.dropout(x)
def make_model(
src_vocab,
tgt_vocab,
N=6,
d_model=512,
d_ff=2048,
h=8,
dropout=0.1,
):
c = copy.deepcopy
attn = MultiHeadAttention(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),
)
for p in model.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
return model
本文转自AI生成未来 ,作者:Maitouer