一文详解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")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

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()))
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 在两个子层(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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

我们将 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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

目前,所有的子模块均已完成了代码实现,接下来就是完整 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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.


本文转自AI生成未来 ,作者:Maitouer


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

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