【超详细含面试题】Transformer模型解析

一些细节的理解And面试题:

纯多头注意力输出的维度和原始输入是不一样的,需要一个线性变换进行维度转换

这样,多头注意力模块输出就和原输入一样了,然后经过一个MLP,这个MPL一般包含两个线形层,第一个将d_model扩展为4*d_model,第二个将维度降回去,用于进一步提取特征。

为何使用多头注意力,不是仅仅单头

多个头时,每个头有自己独立的权重矩阵,可以用来捕获不同的特征,分别求出结果之后经过一个Linear融合,达到更好的效果

三个Attention都起到了什么作用?

Encoder的Attention得分矩阵是每一个token对其他token的重视程度,乘以V之后得到一个对原始输入的加强表示。

解释一下Masked-Self-Attention如何起作用的

mask是一个三角矩阵,进行掩码操作之后,QK 相乘之后我们不想让decoder看到的部分就都是0,进行softMax计算注意力得分的时候这一部分就不会参与计算

Transformer为什么Q和K使用不同的权重矩阵生成,为何不能使用同一个值进行自身的点乘?

Q(查询)和K(键)使用不同的权重矩阵生成,是为了在计算注意力得分时能够捕捉到输入序列中不同的特征。如果使用同一个值进行自身的点乘,模型无法有效区分查询向量和键向量的不同特征,导致注意力机制失去灵活性和区分能力。因此,通过不同的权重矩阵生成Q和K,可以增强模型的表达能力,确保注意力机制能够更好地识别和利用输入序列中的信息。

QKV的深入理解:Q是查询,KV是待查询信息所以交叉注意力的时候Q来自解码器,旨在从Encoder过来的全局信息中提取一些有用信息。

Transformer局限性

decoder解码的时候依赖的是之前自回归生成的信息,所以用于补全文本的能力就会稍微差一点,以及Attention组件计算消耗非常大,所以后来也出现了一批用于简化attention的方法

整体架构

Transformer是一个黑盒模型,经典的应用是输入一串文字序列,转换出另外的文字序列,比如说翻译任务

本质上这个黑盒就是一个Encoder-Decoder架构,先来看一个概览图:

架构图如下图所示

transformer的结构可以分为 输入层、编码层、解码层、输出层,我们接下来逐一进行分析

输入层

输入层为将自然语言输入进行词嵌入后和位置嵌入相加得到的结果

词嵌入

以翻译任务为例子,我们的直接输入是“我是学生”,然而,机器无法直接解读汉字字符,需要进行向量表达,我们常见的表达方式包括:

  • 稀疏表达:one-hot编码,对每一个word都用进行独热编码标识,缺点很多,首先占用空间很大,其次含义相近的字很难在向量空间相似
  • 稠密表达:pytorch中调用的nn.embedding()函数就是一种稠密表达,可以在训练过程中找到合适参数,使得输入(有格式要求)变为稠密表达,同时含义相近的词在向量空间上距离也会很近。

Extension:

  • nn.embedding() 在forward中,输入的x应该是 输入的编号序列,假如我们有字典映射:[0:我,1:是,2:学,3:生],那么输入序列则会被映射为[0,1,2,3];nn.embedding(),的输入正是这种序列,所以进行数据处理的时候需要自行构建字典映射。
  • 在《机器学习方法》(李航著)  中,作者提到Transformer的输入层模块编码方式为one-hot向量乘以一个线性变换矩阵,这种实现方法和 nn.embedding()有区别,但是殊途同归得到了稠密向量,相比之下nn.embedding()更为高效。

代码实现

class Embeddings(nn.Module):
    """
    类的初始化
    :param d_model: 词向量维度,512
    :param vocab: 当前语言的词表大小
    """
    def __init__(self, d_model, vocab):

        super(Embeddings, self).__init__()
        # 调用nn.Embedding预定义层,获得实例化词嵌入对象self.lut
        self.lut = nn.Embedding(vocab, d_model)
        self.d_model = d_model  #表示词向量维度

    def forward(self, x):
        """
        Embedding层的前向传播
        参数x:输入给模型的单词文本通过此表映射后的one-hot向量
        x传给self.lut,得到形状为(batch_size, sequence_length, d_model)的张量,与self.d_model相乘,
        以保持不同维度间的方差一致性,及在训练过程中稳定梯度
        """
        return self.lut(x) * math.sqrt(self.d_model)

位置嵌入(position embedding)

目前而言,位置嵌入和Transformer架构强绑定,其他架构虽然零星使用,但是未能成为主流方案。【位置编码领域存在较大改进空间,需要在之后的深入学习之后了解其原理】

Transformer完全依赖自注意力机制(Self-Attention)处理输入序列,而自注意力对输入元素的排列是位置无关的。例如:

  • 输入序列 [A, B, C] 和 [B, A, C] 经过自注意力计算后,输出结果可能无法体现顺序差异;
  • 句子 I like the movie, but hate the ending 和 I hate the movie, but like the ending 的单词相同但顺序不同,语义截然相反,模型必须依赖位置信息区分两者

代码实现

class PositionalEncoding(nn.Module):
    """实现Positional Encoding功能"""
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        """
        位置编码器的初始化函数
        :param d_model: 词向量的维度,与输入序列的特征维度相同,512
        :param dropout: 置零比率
        :param max_len: 句子最大长度,5000
        """
        super(PositionalEncoding, self).__init__()
        # 初始化一个nn.Dropout层,设置给定的dropout比例
        self.dropout = nn.Dropout(p=dropout)

        # 初始化一个位置编码矩阵
        # (5000,512)矩阵,保持每个位置的位置编码,一共5000个位置,每个位置用一个512维度向量来表示其位置编码
        pe = torch.zeros(max_len, d_model)
        # 偶数和奇数在公式上有一个共同部分,使用log函数把次方拿下来,方便计算
        # position表示的是字词在句子中的索引,如max_len是128,那么索引就是从0,1,2,...,127
        # 论文中d_model是512,2i符号中i从0取到255,那么2i对应取值就是0,2,4...510
        # (5000) -> (5000,1)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        # 计算用于控制正余弦的系数,确保不同频率成分在d_model维空间内均匀分布
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        # 根据位置和div_term计算正弦和余弦值,分别赋值给pe的偶数列和奇数列
        pe[:, 0::2] = torch.sin(position * div_term)   # 从0开始到最后面,补长为2,其实代表的就是偶数位置
        pe[:, 1::2] = torch.cos(position * div_term)   # 从1开始到最后面,补长为2,其实代表的就是奇数位置
        # 上面代码获取之后得到的pe:[max_len * d_model]
        # 下面这个代码之后得到的pe形状是:[1 * max_len * d_model]
        # 多增加1维,是为了适应batch_size
        # (5000, 512) -> (1, 5000, 512)
        pe = pe.unsqueeze(0)
        # 将计算好的位置编码矩阵注册为模块缓冲区(buffer),这意味着它将成为模块的一部分并随模型保存与加载,但不会被视为模型参数参与反向传播
        self.register_buffer('pe', pe)

    def forward(self, x):
        """
        x: [seq_len, batch_size, d_model]  经过词向量的输入
        """
        x = x + self.pe[:, :x.size(1)].clone().detach()   # 经过词向量的输入与位置编码相加
        # Dropout层会按照设定的比例随机“丢弃”(置零)一部分位置编码与词向量相加后的元素,
        # 以此引入正则化效果,防止模型过拟合
        return self.dropout(x)

编码层

注意力机制

多头注意力显而易见的是可以提高并行运算速度,但是部分文献会提到多头注意力可以“捕捉不同模式”,想来其实有点困难,为什么是这样呢?我们动笔算一下就知道,多头注意力的时候每一个注意力头出来的Q点乘V都和单头注意力的Q点乘V矩阵形状一样,然后这n个头的结果会concat在一起最后再过一个线性变换变成单头Q点乘V的形状。

最后得到的结果是一个和原始向量形状一样的增强表达【所以我们说transformer的encoder实际上就是一个强有力的嵌入信息增强表示器】

  1.  每一个输入的词向量都乘以矩阵得到一组q k v
  2. 拿第一个词向量为例,用气q 乘以 所有词向量的k转置,得到seq_len个常数序列,用这个常数序列套一个softMax归一化,再和所有的v相乘,即可得到一个加权的信息序列,换句话就是第一个词向量和所有词向量的信息序列;
  3. 实际计算的时候不会一个个算,而是整体进行矩阵运算

最后,用注意力分数矩阵乘以矩阵得到输出矩阵,其中,

即为注意力分数矩阵与矩阵的点积,也是加权和。以上就是注意力机制计算的完整过程。

代码实现

class ScaledDotProductAttention(nn.Module):
    """ Scaled Dot-Product Attention """
    def __init__(self, scale_factor, dropout=0.0):
        super().__init__()
        self.scale_factor = scale_factor
        #dropout用于防止过拟合,在前向传播的过程中,让某个神经元的激活值以一定的概率停止工作
        self.dropout = nn.Dropout(dropout)

    def forward(self, q, k, v, mask=None):
        # batch_size: 批量大小
        # len_q,len_k,len_v: 序列长度 在这里他们都相等
        # n_head: 多头注意力,论文中默认为8
        # d_k,d_v: k v 的dim(维度) 默认都是64
        # 此时q的shape为(batch_size, n_head, len_q, d_k) (batch_size, 8, len_q, 64)
        # 此时k的shape为(batch_size, n_head, len_k, d_k) (batch_size, 8, len_k, 64)
        # 此时v的shape为(batch_size, n_head, len_k, d_v) (batch_size, 8, len_k, 64)
        # q先除以self.scale_factor,再乘以k的转置(交换最后两个维度(这样才可以进行矩阵相乘))。
        # attn的shape为(batch_size, n_head, len_q, len_k)

        attn = torch.matmul(q / self.scale_factor, k.transpose(2, 3))

        if mask is not None:
            """
            用-1e9代替0 -1e9是一个很大的负数 经过softmax之后接近0
            # 其一:去除掉各种padding在训练过程中的影响
            # 其二,将输入进行遮盖,避免decoder看到后面要预测的东西。(只用在decoder中)
            """
            scores = scores.masked_fill(mask == 0, -1e9)
        # 先在attn的最后一个维度做softmax 再dropout 得到注意力分数
        attn = self.dropout(torch.softmax(attn, dim=-1))
        # 最后attn与v矩阵相乘
        # output的shape为(batch_size, 8, len_q, 64)
        output = torch.matmul(attn, v)
        # 返回 output和注意力分数
        return output, attn

多头注意力机制(Multi-Head Attention )

多头注意力机制即就是把上述的、、三个矩阵从特征维度(词向量长度)上拆分为形状相同的小矩阵,如下图所示,拆分为2个形状相同的小矩阵,即为二头注意力。本例中,句子长度为4,词向量维度是4,小矩阵维度即为[4,4/2=2]。接下来以上述方式计算2个b矩阵,再将每个Head Attention计算出来的b矩阵拼接,即为最终的注意力矩阵。

注:论文中句子长度为5,词向量维度是512,将、、三个矩阵拆分成了8个形状相同的小矩阵,也就是8头注意力,小矩阵维度为[5,512/8=64]。

代码实现

class MultiHeadAttention(nn.Module):
    """ Multi-Head Attention module """

    def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
        # 论文中这里的n_head, d_model, d_k, d_v分别默认为8, 512, 64, 64
        '''
        # q k v先经过不同的线性层,再用ScaledDotProductAttention,最后再经过一个线性层
        '''
        super().__init__()

        self.n_head = n_head
        self.d_k = d_k
        self.d_v = d_v

        self.w_qs = nn.Linear(d_model, n_head * d_k, bias=False)
        self.w_ks = nn.Linear(d_model, n_head * d_k, bias=False)
        self.w_vs = nn.Linear(d_model, n_head * d_v, bias=False)
        self.fc = nn.Linear(n_head * d_v, d_model, bias=False)

        self.attention = ScaledDotProductAttention(scale_factor=d_k ** 0.5)

        self.dropout = nn.Dropout(dropout)
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)  # 默认对最后一个维度初始化

    def forward(self, q, k, v, mask=None):
        # q, k, v初次输入为含位置信息的嵌入矩阵X,由于要堆叠N次,后面的输入则是上个多头的输出
        # q, k, v:batch_size * seq_num * d_model
        d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
        # len_q, len_k, len_v 为输入的序列长度
        # batch_size为batch_size
        batch_size, len_q, len_k, len_v = q.size(0), q.size(1), k.size(1), v.size(1)
        # 用作残差连接
        residual = q
        # Pass through the pre-attention projection: b x lq x (n*dv)
        # Separate different heads: b x lq x n x dv
        # q k v 分别经过一个线性层再改变维度
        # 由(batch_size, len_q, n_head*d_k) => (batch_size, len_q, n_head, d_k)
        # (batch_size, len_q, 8*64) => (batch_size, len_q, 8, 64)
        q = self.layer_norm(q)
        k = self.layer_norm(k)
        v = self.layer_norm(v)

        # 与q,k,v相关矩阵相乘,得到相应的q,k,v向量,d_model=n_head * d_k
        q = self.w_qs(q).view(batch_size, len_q, n_head, d_k)
        k = self.w_ks(k).view(batch_size, len_k, n_head, d_k)
        v = self.w_vs(v).view(batch_size, len_v, n_head, d_v)

        # Transpose for attention dot product: b x n x lq x dv
        # 交换维度做attention
        # 由(batch_size, len_q, n_head, d_k) => (batch_size, n_head, len_q, d_k)
        # (batch_size, len_q, 8, 64) => (batch_size, 8, len_q, 64)
        q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)

        if mask is not None:
            # 为head增加一个维度
            mask = mask.unsqueeze(1)   # For head axis broadcasting.
        # 输出的q为Softmax(QK/d + (1-S)σ)V, attn 为QK/D
        q, attn = self.attention(q, k, v, mask=mask)

        # Transpose to move the head dimension back: b x lq x n x dv
        # Combine the last two dimensions to concatenate all the heads together: b x lq x (n*dv)
        # (batch_size, 8, len_k, 64) => (batch_size, len_k, 8, 64) => (batch_size, len_k, 512)
        q = q.transpose(1, 2).contiguous().view(batch_size, len_q, -1)
        # 经过fc和dropout
        q = self.dropout(self.fc(q))
        # 残差连接 论文中的Add & Norm中的Add
        q += residual
        # 论文中的Add & Norm中的Norm
        q = self.layer_norm(q)
        # q的shape为(batch_size, len_q, 512)
        # attn的shape为(batch_size, n_head, len_q, len_k)
        return q, attn

残差与归一化模组

multi-head attention的output直接与输入相加,然后做层归一化

代码实现

class LayerNorm(nn.Module):
    def __init__(self, d_model, eps=1e-12):
        super().__init__()
        # 初始化尺度参数gamma
        self.gamma = nn.Parameter(torch.ones(d_model))
        # 初始化偏差参数beta
        self.beta = nn.Parameter(torch.zeros(d_model))
        # 设置一个小常数,防止除0
        self.eps = eps

    def forward(self, x):
        # 计算均值
        mean = x.mean(-1, keepdim=True)
        # 计算方差,unbiased=False时,方差的计算使用n而不是n-1做分母
        var = x.var(-1, unbiased=False, keepdim=True)

        # 归一化计算
        out = (x - mean) / torch.sqrt(var + self.eps)
        out = self.gamma * out + self.beta
        return out

前馈神经网络

感觉第二个线性层之后是可以激活的,但是这里没有这么做

代码实现

class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        self.fc = nn.Sequential(
            nn.Linear(d_model, d_ff, bias=False),
            nn.ReLU(),
            nn.Linear(d_ff, d_model, bias=False))

    def forward(self, inputs):                             # inputs: [batch_size, seq_len, d_model]
        residual = inputs
        output = self.fc(inputs)
        return nn.LayerNorm(d_model).cuda()(output + residual)   # [batch_size, seq_len, d_model]

Mask掉停用词

在一个句子中有空白的占位符,也会有没有达到seq_len的部分,需要进行mask

mask的具体操作:

把这个调成极小和原向量相加,然后在softMax里边就会趋于零

代码实现

# seq_q: [batch_size, seq_len] ,seq_k: [batch_size, seq_len]
def get_attn_pad_mask(seq_q, seq_k): 
    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    # eq(zero) is PAD token
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)  # batch_size x 1 x len_k(=len_q), one is masking
    # 扩展成多维度
    return pad_attn_mask.expand(batch_size, len_q, len_k)  # batch_size x len_q x len_k

EncoderLayer实现

一个EncoderLayer包括了一个多头注意力和一个前馈神经网络层

代码实现

class EncoderLayer(nn.Module):
    def __init__(self):
        super(EncoderLayer, self).__init__()
        self.enc_self_attn = MultiHeadAttention()                                     # 多头注意力机制
        self.pos_ffn = PoswiseFeedForwardNet()                                        # 前馈神经网络

    def forward(self, enc_inputs, enc_self_attn_mask):                                # enc_inputs: [batch_size, src_len, d_model]
        #输入3个enc_inputs分别与W_q、W_k、W_v相乘得到Q、K、V                          # enc_self_attn_mask: [batch_size, src_len, src_len]
        enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs,    # enc_outputs: [batch_size, src_len, d_model], 
                                               enc_self_attn_mask)                    # attn: [batch_size, n_heads, src_len, src_len]
        enc_outputs = self.pos_ffn(enc_outputs)                                       # enc_outputs: [batch_size, src_len, d_model]
        return enc_outputs, attn

Transformer的Encoder

一般是6个EncoderLayer叠层

代码实现

"""
编码器
"""
class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()

        self.src_emb = nn.Embedding(src_vocab_size, d_model)                     # 把字转换字向量
        self.pos_emb = PositionalEncoding(d_model)                               # 加入位置信息
        self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)])

    def forward(self, enc_inputs):                                               # enc_inputs: [batch_size, src_len]
        # 1. 中文字索引进行Embedding,转换成512维度的字向量
        enc_outputs = self.src_emb(enc_inputs)                                   # enc_outputs: [batch_size, src_len, d_model]
        # 2. 在字向量上面加上位置信息
        enc_outputs = self.pos_emb(enc_outputs)                                  # enc_outputs: [batch_size, src_len, d_model]
        # 3. Mask掉句子中的占位符号
        enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)           # enc_self_attn_mask: [batch_size, src_len, src_len]
        enc_self_attns = []
        # 4. 通过6层的encoder(上一层的输出作为下一层的输入)
        for layer in self.layers:
            enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)  # enc_outputs :   [batch_size, src_len, d_model], 
                                                                                 # enc_self_attn : [batch_size, n_heads, src_len, src_len]
            enc_self_attns.append(enc_self_attn)
        return enc_outputs, enc_self_attns

解码层

先说明解码的过程,比如使用"我是学生"进行翻译任务,首先会将"我是学生"整个句子输入到Encoder中得到最后一个EncoderBlock的输出后,将在Decoder中输入"S I am a student",S表示开始夫。注意这里"S I am a student"不会一并输入,而是在T0时刻先进S,后来逐个亮出之后的单词。

这里需要生成一个上三角矩阵用于掩码

def get_attn_subsequence_mask(seq):                               # seq: [batch_size, tgt_len]
    attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
    subsequence_mask = np.triu(np.ones(attn_shape), k=1)          # 生成上三角矩阵,[batch_size, tgt_len, tgt_len]
    subsequence_mask = torch.from_numpy(subsequence_mask).byte()  #  [batch_size, tgt_len, tgt_len]
    return subsequence_mask

Decoder的Masked Multi-Head Attention

Masked Multi-Head Attention与Multi-Head Attention类似,只是采用了Mask上三角矩阵,掩盖Decoder的输入。如上所述

Decoder的Multi-Head Attention

Decoder的Multi-Head Attention同样和Encoder的Multi-Head Attention结构一样,只是Decoder的Multi-Head Attention中,K、V矩阵来自Encoder的输出,而Q矩阵来自Masked Multi-Head Attention 的输出。

Decoder的输出预测

decoderBlock的输出格式为(batch_size,seq_len,d_model)

DecoderLayer

代码实现

class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.dec_self_attn = MultiHeadAttention()
        self.dec_enc_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask): # dec_inputs: [batch_size, tgt_len, d_model]
                                                                                       # enc_outputs: [batch_size, src_len, d_model]
                                                                                       # dec_self_attn_mask: [batch_size, tgt_len, tgt_len]
                                                                                       # dec_enc_attn_mask: [batch_size, tgt_len, src_len]
        dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, 
                                                 dec_inputs, dec_self_attn_mask)   # dec_outputs: [batch_size, tgt_len, d_model]
                                                                                   # dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len]
        dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, 
                                                enc_outputs, dec_enc_attn_mask)    # dec_outputs: [batch_size, tgt_len, d_model]
                                                                                   # dec_enc_attn: [batch_size, h_heads, tgt_len, src_len]
        dec_outputs = self.pos_ffn(dec_outputs)                                    # dec_outputs: [batch_size, tgt_len, d_model]
        return dec_outputs, dec_self_attn, dec_enc_attn

TransformerDecoder

代码实现

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)
        self.pos_emb = nn.Embedding.from_pretrained(get_sinusoid_encoding_table(tgt_len+1, d_model),freeze=True)
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])

    def forward(self, dec_inputs, enc_inputs, enc_outputs): # dec_inputs : [batch_size x target_len]
        # 1. 英文字索引进行Embedding,转换成512维度的字向量,并在字向量上加上位置信息
        dec_outputs = self.tgt_emb(dec_inputs) + self.pos_emb(torch.LongTensor([[5,1,2,3,4]]))
        # 2. Mask掉句子中的占位符号
        dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)
        dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs)
        dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequent_mask), 0)

        dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)

        dec_self_attns, dec_enc_attns = [], []
        # 3. 通过6层的decoder(上一层的输出作为下一层的输入)
        for layer in self.layers:
            dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)
            dec_self_attns.append(dec_self_attn)
            dec_enc_attns.append(dec_enc_attn)
        return dec_outputs, dec_self_attns, dec_enc_attns

Transformer

代码实现

class Transformer(nn.Module):
    def __init__(self):
        super(Transformer, self).__init__()
        # 编码器
        self.encoder = Encoder()
        # 解码器
        self.decoder = Decoder()
        # 解码器最后的分类器,分类器的输入d_model是解码层每个token的输出维度大小,需要将其转为词表大小,再计算softmax;计算哪个词出现的概率最大
        self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False)

    def forward(self, enc_inputs, dec_inputs):
        #  Transformer的两个输入,一个是编码器的输入(源序列),一个是解码器的输入(目标序列)
        # 其中,enc_inputs的大小应该是 [batch_size, src_len] ;  dec_inputs的大小应该是 [batch_size, dec_inputs]

        """
        源数据输入到encoder之后得到 enc_outputs, enc_self_attns;
        enc_outputs是需要传给decoder的矩阵,表示源数据的表示特征
        enc_self_attns表示单词之间的相关性矩阵
        """
        enc_outputs, enc_self_attns = self.encoder(enc_inputs)

        """
        decoder的输入数据包括三部分:
        1. encoder得到的表示特征enc_outputs、
        2. 解码器的输入dec_inputs(目标序列)、
        3. 以及enc_inputs
        """
        dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)

        """
        将decoder的输出映射到词表大小,最后进行softmax输出即可
        """
        dec_logits = self.projection(dec_outputs) # dec_logits : [batch_size x src_vocab_size x tgt_vocab_size]
        return dec_logits.view(-1, dec_logits.size(-1)), enc_self_attns, dec_self_attns, dec_enc_attns

### Transformer模型练习题与学习资料 #### Scaled Dot-Product Attention机制解析Transformer模型中,Scaled Dot-Product Attention的计算方法涉及查询(Query)、键(Key)和值(Value)三个矩阵的操作。具体来说,Attention函数会将查询和所有的键进行比较,得到一个注意力分布,再利用该分布加权求和对应的值向量作为最终输出[^1]。 #### XLM模型介绍及其优势 XLM模型由Facebook AI Research开发,是一种能够处理多语言任务的预训练模型。此模型不仅继承了Transformer的强大性能,还特别针对跨语言场景进行了优化。通过联合训练来自不同语种的数据集,使得模型具备更好的迁移能力和适应性,在面对未曾见过的语言时也能表现出色[^2]。 #### 推荐深入理解Transformers的方式 对于想要深入了解Transformers的人来说,《Attention is All You Need》这篇论文无疑是最好的起点之一;此外还有许多优质的书籍如《Deep Learning with Python》,它们都提供了详尽的技术细节讲解以及实际应用案例分析。当然,实践同样重要,参与开源项目或是完成一些针对性强的小型实验都能帮助巩固理论知识[^3]。 #### 实战题目推荐 为了更好地掌握Transformer相关技能并准备可能遇到的大规模模型面试环节,“2024最全Transformer面试题汇总”这份文档集合了大量的经典考题和技术要点覆盖范围广泛,非常适合用来检验自己对该领域知识点的理解程度及查漏补缺[^4]。 ```python # 示例代码:实现简单的自定义attention层 import torch.nn as nn class SimpleAttention(nn.Module): def __init__(self, dim_qkv=512): super(SimpleAttention, self).__init__() def forward(self, Q, K, V): # 计算QK转置后的乘积除以根号下dim_k scores = torch.matmul(Q, K.transpose(-2,-1)) / (K.size()[-1])**0.5 # 应用softmax获得权重 weights = F.softmax(scores,dim=-1) # 加权求和V得到输出 output = torch.matmul(weights,V) return output ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值