手搓Transformer:Conbination Transformer Layers
《Attention Is All You Need》里的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 操作之前。这个流程是:
- 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]
- 除以根号 d 进行缩放操作
- 接下来在注意力分数矩阵上应用掩码,因此 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
- 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