一文详解Transformer 细节及代码实现

发布于 2024-4-1 15:43
浏览
0收藏

一文详解Transformer 细节及代码实现-AI.x社区

图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 次),如图:

一文详解Transformer 细节及代码实现-AI.x社区

图 2:Transformer 模型整体结构



Encoder 和 Decoder 的数据流如下图 3 所示[2],前一个 Encoder 的输出作为后一个 Encoder 的输入,最后一个 Encoder 的输出被作为 N 个 Decoder 各自的输入。除了最后一个 Encoder 的输出之外,每个 Decoder 也接收前一个 Decoder 的输出作为输入。就机器翻译的任务而言,第一个 Encoder 的输入是源语言的 token,而最后一个 Decoder 的输出是目标语言的 token。

一文详解Transformer 细节及代码实现-AI.x社区

图 3:Encoder 和 Decoder 各层及相互之间的数据流



接下来,我们深入探讨每一个 Encoder 和 Decoder 的内部,看看它们如何通过接收相同维度  的输入来产生维度为  的输出。需要注意,输入到 Encoder 和 Decoder 的输入数分别为可以不相同。(例如:在机器翻译任务中“你好”英文翻译成“hello”,很明显单词的数量是不一样的,当然这个例子不严谨,因为并不是每个字或者单词就是一个 token)

在每一个 Encoder 和 Decoder 的内部包含有一些子层,如下图 4 所示:

一文详解Transformer 细节及代码实现-AI.x社区

图 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" 这些单词是不合适的。预测正确单词的关键在于上下文和前面单词所提供的信息。即:下图中箭头所表达的含义:

一文详解Transformer 细节及代码实现-AI.x社区

图 5:现实中单词填空的例子

在句子中,每个单词之间的关联程度是不一样的。例如,"cinema"(电影院)和 "watch"(观看)这两个词在上下文中的关系更密切,这有助于预测空白处与电影相关的词。因此,每个词都要根据上下文和这些词的关联程度,以不同的关注度去关注其他词,这其实就是 Attention 的思想。


在具体计算上,输入的句子会经过 Embedding 层获得每个单词对应的 feature vector (特征向量),如下图的,每个单词向量的维度是,所有的单词向量可以构成维度是的矩阵 。在每个特征向量上,使用 3 个可学习的权重矩阵进行 3 次矩阵乘法,得到,分别代表 Query, Key, Value。查询向量和向量的大小必须一致,表示为,但值向量的大小可以不同,表示为。

一文详解Transformer 细节及代码实现-AI.x社区

图 6:q, k, v 的计算过程



上述对每个单词向量的矩阵操作可以合并,写成如下的矩阵形式:

一文详解Transformer 细节及代码实现-AI.x社区

图 7:q, k, v 的矩阵计算过程



在获得了每个单词的之后,计算某一个单词的与其余单词的的内积,作为两单词之间的关联权重,如下图所示[4]:

一文详解Transformer 细节及代码实现-AI.x社区

图 8:单词间关联权重的计算过程



将得到的权重分别与对应单词的相乘再求和得到:考虑了单词间关联程度的特征向量。

一文详解Transformer 细节及代码实现-AI.x社区

图 9:获得关联程度加权后的单词特征向量的计算过程



Encoder

每一个 Encoder 内部包含两个子层,每个子层后进行 LayerNormalization 和 残差连接:

  • Multi-Head Attention (多头注意力)
  • Feed Forward (前向传播网络)

一文详解Transformer 细节及代码实现-AI.x社区

图 10:Encoder 单独结构



Multi-Head Attention in Encoder

Multi-Head Attention 是最关键、计算量最大的模块。如下图 5 所示,该模块将个大小各为的向量作为输入(打包成一个大小为的矩阵),并产生一个大小与相同的输出矩阵(由个大小各为的向量打包而成)。

一文详解Transformer 细节及代码实现-AI.x社区

图 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 () 三个大家耳熟能详的矩阵。

一文详解Transformer 细节及代码实现-AI.x社区

图 12:Q, K, V 矩阵的计算过程



  • 执行scaled dot-product attention计算获得 attention 矩阵。如下图 12 所示:首先,对如上节图 8 最终获得的注意力分数除以缩放因子得到矩阵’;然后,对’的每一行施加,得到 attention 矩阵。

为什么需要除以缩放因子?[5] 假设 key 向量的维度很大。由于维度增加,查询向量 q 和 k 之间的点积可能会变得不成比例地大。这可能会导致训练过程中出现问题,例如梯度爆炸或梯度消失,从而阻碍学习过程,并使模型难以有效捕捉标记之间的依赖关系。通过这种缩放的方法,可以缩减注意力分数,确保其大小保持在可控范围内。这种缩放可以防止点积变得过大,并有助于稳定学习过程。

一文详解Transformer 细节及代码实现-AI.x社区

图 13:含缩放因子的 dot product attention 计算过程

  • 将注意力分数矩阵和矩阵相乘得到加权之后的新的特征向量矩阵,作为的输出。如下图 14 所示:

一文详解Transformer 细节及代码实现-AI.x社区

图 14:对 V 进行注意力加权得到每个 Head 的输出

  • 接下来,将所有 head 的输出拼接起来得到,再经过一次线性变换,得到 Multi-Head Attention 的输出:

一文详解Transformer 细节及代码实现-AI.x社区

图 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

一文详解Transformer 细节及代码实现-AI.x社区

图 16:FFN 网络结构图

前向传播网络采用大小为的输入,通过执行如下函数,得到相同大小的输出:

一文详解Transformer 细节及代码实现-AI.x社区

图 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)后进行LayerNormalizationResidual 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

一文详解Transformer 细节及代码实现-AI.x社区

图 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 后面的单词遮住。

一文详解Transformer 细节及代码实现-AI.x社区

图 19:Decoder 中的权重矩阵

Multi-Head Attention in Decoder

Decoder 中的 Multi-Head Attention 也被称作是 Input-Output Attention 或 Encoder-Decoder Attention。与 Encoder 中的 Multi-Head Attention 大致相同,除接收最后一层 Encoder 的输出作为额外输入 () 外。被用于生成矩阵,而 Decoder 的输入用于生成矩阵。

一文详解Transformer 细节及代码实现-AI.x社区

图 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]。即:将上一时刻的输出追加到输入后面组成当前时刻的输入,然后产生当前时刻的输出,如下图所示。

一文详解Transformer 细节及代码实现-AI.x社区

图 21:Auto-Regressive 的例子

一文详解Transformer 细节及代码实现-AI.x社区

图 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

一文详解Transformer 细节及代码实现-AI.x社区

图 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 函数来计算:

一文详解Transformer 细节及代码实现-AI.x社区

图 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


原文链接:​​https://mp.weixin.qq.com/s/s-n5I3EpOSgpbV40DbVOMg​

已于2024-4-1 15:45:29修改
收藏
回复
举报
回复
相关推荐