【学习】手搓Transformer:Conbination


手搓Transformer:Conbination Transformer Layers


《Attention Is All You Need》里的Transformer架构

Transformer

之前已经写好了 transformer 的各个模块,这里最后合并一下各个组件,组合成最后的 transformer layer!

__init__() 参数说明

  • src_pad_idx:源序列表示 padding 的 idx
  • tgt_pad_idx:目标序列表示 padding 的 idx
  • src_voc_size:源语言词汇表大小
  • tgt_voc_size:目标语言词汇表大小
  • max_len:序列最大长度
  • d_model:词嵌入维度
  • hidden_dim:前馈神经网络隐藏层维度
  • num_heads:多头注意力的头数
  • num_layers:编码器和解码器的层数
  • dropout=0.1:dropout比率
  • device=None:使用设备

掩码mask预处理

我们知道,在 Encoder 和 Decoder 里,mask 操作是必须的一环。mask 种类有两种,一种是处理填充序列的 padding mask,一种是处理因果序列的 casual mask,其中 padding mask 在编码器-解码器的 self-attention 和 cross-attention 里都会用到,而 casual mask 则用于解码器的 self-attention 部分。

casual mask 的定义:对于每个序列,我们通过定义一个对应的下三角矩阵作为 mask。在计算序列第n个词的 attention score 的时候,只有前 n-1 个 token 参与计算。

padding mask 的定义:二值矩阵,等于 pad_idx 的部分记为 0,不等于的部分记为 1.

需要注意的是,mask 的掩码操作在注意力矩阵应用 softmax 操作之前。这个流程是:

  1. Q 查询矩阵与 K 键矩阵转置相乘,[batch_size, num_heads, seq_q, d_k] 与 [batch_size, num_heads, d_k, seq_k] 相乘,得到的矩阵形状为 [batch_size, num_heads, seq_q, seq_k]
  2. 除以根号 d 进行缩放操作
  3. 接下来在注意力分数矩阵上应用掩码,因此 mask 的形状须是一个四维矩阵。在生成 pad_mask 的时候,我们首先用源序列和目标序列创建基础 mask,q_mask = (q != pad_idx),k_mask = (k != pad_idx),shape 为 [batch_size, seq_q / seq_k],接下来拓展维度使其与查询矩阵与键矩阵相匹配,最后将 q_mask & k_mask,得到的最终 mask 为 [batch_size, 1, seq_q, seq_k],最后应用掩码时通过广播机制匹配到所有注意力头上。对于 casual_mask,创建 [seq_q, seq_k] 形状的下三角矩阵,然后拓展维度与 pad_mask 按元素乘,得到最终的 mask
  4. softmax 操作

forward

Encoder 只需要传递源序列,Decoder 需要传递 encoder 计算的结果与目的序列(做 cross-attention),在前向过程中,先通过 encoder 计算出每一层 Encoder Layer 对于源序列的输出,然后将每一层的编码器输出传递给每一层的解码器,解码器在进行计算,得到最终的解码输出。

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import torch.nn.functional as F
import numpy as np
import random
import math
from torch import Tensor
import os
import multi_attention
import layernorm
import embedding
import encoder
import decoder

class Transformer(nn.Module):
    # src_pad_idx: 源序列的填充索引
    # tgt_pad_idx: 目标序列的填充索引
    # 填充索引是单个数值,在词表中用来表示填充符号padding token的索引值,通常设为0或-1
    # src_voc_size: 源序列的词汇表大小
    # tgt_voc_size: 目标序列的词汇表大小
    # max_len: 序列的最大长度
    # d_model: 词嵌入的维度

    def __init__(self, src_pad_idx, tgt_pad_idx, src_voc_size, tgt_voc_size, max_len, d_model, hidden_dim, num_heads, num_layers, dropout=0.1, device=None):
        super(Transformer, self).__init__()
        self.encoder = encoder.Encoder(src_voc_size, max_len, d_model, hidden_dim, num_heads, num_layers, dropout, device)
        self.decoder = decoder.Decoder(tgt_voc_size, max_len, d_model, hidden_dim, num_heads, num_layers, dropout, device)
        self.src_pad_idx = src_pad_idx
        self.tgt_pad_idx = tgt_pad_idx
        self.device = device

    def make_pad_mask(self, q, k, pad_idx_q, pad_idx_k):
        len_q, len_k = q.size(1), k.size(1)
        # 先用ne()操作转换为二值矩阵,然后通过unsqueeze()操作在q的维度上扩展维度
        # 最后通过repeat()操作将q的维度扩展为(batch_size, 1, len_q, len_k)
        # 这个操作是为了将mask矩阵的维度和score分数矩阵的维度对齐
        q = q.ne(pad_idx_q).unsqueeze(1).unsqueeze(3)
        q = q.repeat(1, 1, 1, len_k)
        k = k.ne(pad_idx_k).unsqueeze(1).unsqueeze(2)
        k = k.repeat(1, 1, len_q, 1)
        mask = q & k
        return mask
    
    def make_casual_mask(self, q, k):
        mask = torch.tril(torch.ones(q.size(1), k.size(1))).type(torch.BoolTensor).to(self.device)
        return mask
    
    def forward(self, src, tgt):
        src_mask = self.make_pad_mask(src, src, self.src_pad_idx, self.src_pad_idx)
        # 注意*是按元素乘,相当于把两种mask的作用效果叠加了
        tgt_mask = self.make_pad_mask(tgt, tgt, self.tgt_pad_idx, self.tgt_pad_idx)*self.make_casual_mask(tgt, tgt)
        enc = self.encoder(src, src_mask)
        dec = self.decoder(tgt, enc, tgt_mask, src_mask)
        return dec

文章作者: Cyan.
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cyan. !
  目录