NLP 45、【Attention is all you need】模型代码实现及应用

目录

一、【Attention is all you need】代码实现

1.Modules.py

Ⅰ、类的定义

Ⅱ、初始化方法

Ⅲ、前向传播 

代码运行流程

Ⅳ、完整代码 

2.SubLayers.py

Ⅰ、多头注意力机制 MultiHeadAttention

① 类的定义

② 初始化方法

③ 前向传播

代码运行流程

③ 完整代码

Ⅱ、位置前馈网络 PositionwiseFeedForward

① 类的定义

② 初始化方法

③ 前向传播

代码运行流程

④ 完整代码

Ⅲ、完整代码 

3.Layers.py

Ⅰ、编码器 Encoder

① 类的定义

② 初始化方法

③ 前向传播

代码运行流程

④ 完整代码

Ⅱ、解码器 Decoder

① 类的定义

② 初始化方法

② 前向传播

代码运行流程

③ 完整代码

Ⅲ、完整代码

4.Models.py

Ⅰ、位置编码 PositionalEncoding

① 初始化方法

② 编码序列信息【正弦位置编码】🌙

③ 前向传播

④ 完整代码

Ⅱ、编码器 Encoder

① 类的定义

② 初始化方法

③ 前向传播

代码运行流程

④ 完整代码

Ⅲ、解码器 Decoder

① 类的定义

② 初始化方法

③ 前向传播

代码运行流程

④ 完整代码

Ⅳ、Transformer模型

① 类的定义

② 初始化方法

③ 前向传播

④ 完整代码

Ⅴ、辅助函数

① get_pad_mask 生成填充掩码

② get_subsequent_mask:生成后续位置掩码

Ⅵ、完整代码

二、利用定义的Transformer架构模型完成文章标题生成任务

数据文件

1.配置文件 config.py

2.数据加载文件 loader.py

Ⅰ、代码运行流程

Ⅱ、初始化方法 __init__

Ⅲ、读取数据文件 load

Ⅳ、序列编码 encode_sentence 

Ⅴ、补齐或截断输入序列 padding

Ⅵ、将输入输出转化为序列 prepare_data

Ⅶ、 类内魔术方法

① 获取数据长度

② 根据索引返回值

Ⅷ、加载词表

Ⅸ、封装数据

Ⅹ、完整代码及测试

3.模型效果评估 evaluate.py

Ⅰ、模型初始化

Ⅱ、解码 

Ⅲ、测试模型

Ⅳ、完整代码及测试 

4.模型训练文件 main.py

Ⅰ、代码运行流程

Ⅱ、导入文件

Ⅲ、日志配置

Ⅳ、设置随机数种子

Ⅴ、根据配置文件选择优化器

Ⅵ、主函数main

1.创建模型保存目录

2.记录配置信息并加载模型

3.检查GPU并迁移模型

4.加载优化器

5.加载训练数据 

6.加载评估器

7.加载损失函数

8.模型训练主流程 ⭐

① Epoch循环控制

② 模型设置训练模式 

③ 设备切换

④ 记录日志

⑤ 初始化训练损失列表

⑥ 遍历训练数据批次

⑦ 前向传播、计算损失并记录、反向传播

⑧ 记录损失进行评估

⑨ 完整训练代码

9.模型保存

Ⅶ、调用模型预测


你曾一个人走过的路,都是最勇敢的征途

                                                        —— 25.3.19

一、【Attention is all you need】代码实现

1.Modules.py

Ⅰ、缩放点积注意力机制类 ScaledDotProductAttention

① 类的定义

继承自nn.Module,是PyTorch中定义神经网络模块的标准方式,该类实现了缩放点积注意力机制

class ScaledDotProductAttention(nn.Module):
    ''' Scaled Dot-Product Attention '''

② 初始化方法

temperature:缩放因子,通常为键向量维度dk​的平方根​​,用于缩放点积结果,防止梯度消失或爆炸。

attn_dropout:Dropout概率,用于正则化,防止过拟合。

self.dropout():对 softmax 后的注意力权重进行随机 dropout,防止过拟合。

nn.Dropout():PyTorch中的一个模块,用于在训练神经网络时随机丢弃部分神经元,以防止过拟合。

参数名类型默认值说明
pfloat0.5每个神经元被丢弃的概率,取值范围为[0, 1)
inplaceboolFalse是否在原地修改输入数据。如果为True,会直接修改输入数据以节省内存。
    def __init__(self, temperature, attn_dropout=0.1):
        super().__init__()
        self.temperature = temperature
        self.dropout = nn.Dropout(attn_dropout)

③ 前向传播 
代码运行流程
forward(q, k, v, mask=None)
├── 计算缩放点积
│   ├── q / self.temperature  # 缩放查询向量
│   ├── k.transpose(2, 3)     # 转置键向量
│   └── torch.matmul(q / self.temperature, k.transpose(2, 3))  # 计算点积
├── 应用掩码(如果mask不为None)
│   └── attn.masked_fill(mask == 0, -1e9)  # 将掩码为0的位置置为极小值
├── 计算Softmax并应用Dropout
│   ├── F.softmax(attn, dim=-1)  # 对点积结果进行Softmax归一化
│   └── self.dropout(attn)       # 对Softmax结果应用Dropout
├── 加权求和
│   └── torch.matmul(attn, v)    # 使用注意力权重对值向量加权求和
└── 返回结果
    ├── output  # 加权求和后的输出
    └── attn    # 注意力权重矩阵

q:查询(Query)向量,形状为[batch_size, num_heads, seq_len, d_k]

k:键(Key)向量,形状为[batch_size, num_heads, seq_len, d_k]

v:值(Value)向量,形状为[batch_size, num_heads, seq_len, d_v]

mask:掩码矩阵,用于屏蔽某些位置的注意力得分,形状为[batch_size, seq_len, seq_len]

attn:注意力分数矩阵,通过 q 和 k 的点积计算得到

self.temperature:缩放因子,通常为键向量维度dk​的平方根​​,用于缩放点积结果,防止梯度消失或爆炸。

output: 最终的注意力加权输出,通过 attn 和 v 的矩阵乘法计算

self.dropout():对 softmax 后的注意力权重进行随机 dropout,防止过拟合。

torch.matmul():PyTorch中的矩阵乘法函数,用于计算两个张量的矩阵乘积。它支持广播机制,可以处理不同维度的张量。

参数名类型说明
inputTensor第一个输入张量。
otherTensor第二个输入张量。

transpose():PyTorch中的转置函数,用于交换张量的两个维度。

参数名类型说明
inputTensor输入张量。
dim0int要交换的第一个维度。
dim1int要交换的第二个维度。

masked_fill():根据布尔掩码(mask)将张量中指定位置的元素替换为特定值(value)。常用于屏蔽无效数据(如填充符)或控制注意力范围(如 Transformer 模型中的因果掩码)

参数名类型必填描述示例
mask

BoolTensor 或

ByteTensor

布尔掩码张量,形状需与输入张量一致或可广播。True 表示需要填充的位置。mask = tensor([[False, True], [True, False]])
valuefloat 或 Tensor填充到掩码标记位置的值(如 -1e9 或 0)。value = -1e9

F.softmax(): 将输入张量归一化为概率分布,使每个元素值在 [0, 1] 范围内且所有元素和为 1。广泛用于多分类任务(如语言模型的词汇概率预测)

参数名类型必填描述示例
inputTensor输入张量,通常为模型输出(如未归一化的 logits)input = torch.tensor([1.0, 2.0, 3.0])
dimint计算归一化的维度(如 dim=1 表示对每行计算)dim=1
dtypetorch.dtype输出张量的数据类型,用于防止数值溢出dtype=torch.float32
    def forward(self, q, k, v, mask=None):

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

        if mask is not None:
            attn = attn.masked_fill(mask == 0, -1e9)

        attn = self.dropout(F.softmax(attn, dim=-1))
        output = torch.matmul(attn, v)

        return output, attn

④ 完整代码 
import torch
import torch.nn as nn
import torch.nn.functional as F

__author__ = "Yu-Hsiang Huang"

class ScaledDotProductAttention(nn.Module):
    ''' Scaled Dot-Product Attention '''

    def __init__(self, temperature, attn_dropout=0.1):
        super().__init__()
        self.temperature = temperature
        self.dropout = nn.Dropout(attn_dropout)

    def forward(self, q, k, v, mask=None):

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

        if mask is not None:
            attn = attn.masked_fill(mask == 0, -1e9)

        attn = self.dropout(F.softmax(attn, dim=-1))
        output = torch.matmul(attn, v)

        return output, attn

2.SubLayers.py

Ⅰ、多头注意力机制实现类 MultiHeadAttention

① 类的定义

继承自nn.Module,是PyTorch中定义神经网络模块的标准方式,该类实现了多头注意力机制

class MultiHeadAttention(nn.Module):
    ''' Multi-Head Attention module '''
② 初始化方法

n_head:注意力头的数量

d_model:输入和输出的维度

d_k:每个注意力头中查询(Query)和键(Key)的维度

d_v:每个注意力头中值(Value)的维度

dropout:Dropout概率,用于正则化

self.w_qs:将输入投影到查询的线性变换层

self.w_ks:将输入投影到键的线性变换层

self.w_vs:将输入投影到值的线性变换层

self.fc:将多头注意力的输出投影回原始维度的线性变换层

self.attention:缩放点积注意力机制(ScaledDotProductAttention)的实例

self.dropout:Dropout层,用于正则化

self.layer_norm:层归一化(Layer Normalization),用于稳定训练过程

ScaledDotProductAttention():缩放点积注意力机制类

nn.Linear():全连接层,执行线性变换 y = xW^T + b,用于神经网络的前向传播

参数类型说明默认值
in_featuresint输入特征维度必选
out_featuresint输出特征维度必选
biasbool是否添加偏置项True

nn.Dropout():随机丢弃部分神经元输出(置零),防止过拟合(仅在训练模式下生效)

参数类型说明默认值
pfloat神经元被丢弃的概率(0 ≤ p < 1)必选
inplacebool是否原地修改输入张量False

nn.LayerNorm():层归一化,对输入特征在指定维度进行标准化(均值0、方差1),提升训练稳定性

参数类型说明默认值
normalized_shapeint/list需归一化的维度(如输入最后一维)必选
epsfloat数值稳定性系数(防止除零)1e-5
   def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
        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(temperature=d_k ** 0.5)

        self.dropout = nn.Dropout(dropout)
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
③ 前向传播
代码运行流程
forward(q, k, v, mask=None)
├── 初始化变量
│   ├── d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
│   ├── sz_b, len_q, len_k, len_v = q.size(0), q.size(1), k.size(1), v.size(1)
│   └── residual = q
├── 线性变换并分头
│   ├── q = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
│   ├── k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
│   └── v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)
├── 转置用于注意力计算
│   └── q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
├── 应用掩码(如果mask不为None)
│   └── mask = mask.unsqueeze(1)
├── 计算注意力
│   └── q, attn = self.attention(q, k, v, mask=mask)
├── 转置并合并多头
│   └── q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
├── 线性变换并应用Dropout
│   └── q = self.dropout(self.fc(q))
├── 残差连接
│   └── q += residual
├── 层归一化
│   └── q = self.layer_norm(q)
└── 返回结果
    ├── q  # 多头注意力的输出
    └── attn  # 注意力权重矩阵

q:查询(Query)向量,形状为[batch_size, num_heads, seq_len, d_k],用于生成注意力权重

k:键(Key)向量,形状为[batch_size, num_heads, seq_len, d_k],用于与 q 计算相似度得分。

v:值(Value)向量,形状为[batch_size, num_heads, seq_len, d_v],存储待聚合的语义信息。

mask:掩码矩阵,用于屏蔽某些位置的注意力得分,形状为[batch_size, seq_len, seq_len]

d_k:每个注意力头中查询(Query)和键(Key)的维度

d_v:每个注意力头中值(Value)的维度

n_head:注意力头的数量

sz_b:批次大小(batch_size

len_q:查询序列长度

len_k:键序列长度

len_v:值序列长度

residual:保留残差连接的原始输入,用于后续与注意力输出相加

self.w_qs:将输入投影到查询的线性变换层

self.w_ks:将输入投影到键的线性变换层

self.w_vs:将输入投影到值的线性变换层

attn:注意力权重矩阵,表示输入序列中不同位置之间的相关性强度。它通过计算 query 和 key 的相似度,并经 Softmax 归一化后得到,最终用于对 value 进行加权求和,生成自注意力输出。

self.attention():缩放点积注意力机制(ScaledDotProductAttention)的实例

self.dropout():Dropout层,用于正则化

self.layer_norm():层归一化(Layer Normalization),用于稳定训练过程

transpose():交换张量的两个指定维度(行列转置),常用于调整数据维度顺序

参数类型说明默认值
inputTensor输入张量必选
dim0int要交换的第一个维度索引必选
dim1int要交换的第二个维度索引必选

unsqueeze():在指定位置插入一个大小为1的维度,用于扩展张量维度以适应计算需求

参数类型说明默认值
inputTensor输入张量必选
dimint插入新维度的位置(支持负数)必选

contiguous():确保张量在内存中连续存储,避免因转置等操作导致的非连续性问题(如后续调用 view() 需连续存储)

view():调整张量的形状(元素总数不变),常用于维度变换或展平数据,-1 会根据张量的总元素个数和前两个维度 sz_b 和 len_q 自动计算出第三个维度的大小。

参数类型/可变参数说明默认值
*shapeint目标形状(可用 -1 自动推导)必选
    def forward(self, q, k, v, mask=None):

        d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
        sz_b, 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 = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
        k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
        v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)

        # Transpose for attention dot product: b x n x lq x dv
        q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)

        if mask is not None:
            mask = mask.unsqueeze(1)   # For head axis broadcasting.

        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)
        q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
        q = self.dropout(self.fc(q))
        q += residual

        q = self.layer_norm(q)

        return q, attn
③ 完整代码
class MultiHeadAttention(nn.Module):
    ''' Multi-Head Attention module '''

    def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
        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(temperature=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):

        d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
        sz_b, 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 = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
        k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
        v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)

        # Transpose for attention dot product: b x n x lq x dv
        q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)

        if mask is not None:
            mask = mask.unsqueeze(1)   # For head axis broadcasting.

        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)
        q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
        q = self.dropout(self.fc(q))
        q += residual

        q = self.layer_norm(q)

        return q, attn

Ⅱ、位置前馈网络实现类 PositionwiseFeedForward

① 类的定义

继承自 PyTorch 的 nn.Module,用于实现位置前馈神经网络(Positionwise Feed-Forward Network)

class PositionwiseFeedForward(nn.Module):
    ''' A two-feed-forward-layer module '''
② 初始化方法

d_in:输入特征维度,通常与模型主维度 d_model 一致(例如 512),表示每个词向量的长度

d_hid:隐藏层(中间层)的维度,扩展模型表达能力,通常设置为 d_in 的 4 倍(如 d_hid=2048),通过非线性变换增强特征交互

dropout:随机丢弃神经元的概率(默认 0.1)

self.w_1:将输入从 d_in 维度映射到 d_hid,激活函数(如 ReLU)引入非线性

self.w_2:将隐藏层维度压缩回 d_in,保持输入输出维度一致,便于残差连接

self.layer_norm:对输出进行层归一化(Layer Normalization)

self.dropout:通常在激活函数(如 ReLU)后调用,随机丢弃神经元,防止过拟合

nn.Linear():全连接层,执行线性变换 y = xW^T + b,用于神经网络的前向传播

参数类型说明默认值
in_featuresint输入特征维度必选
out_featuresint输出特征维度必选
biasbool是否添加偏置项True

nn.Dropout():随机丢弃部分神经元输出(置零),防止过拟合(仅在训练模式下生效)

参数类型说明默认值
pfloat神经元被丢弃的概率(0 ≤ p < 1)必选
inplacebool是否原地修改输入张量False

nn.LayerNorm():层归一化,对输入特征在指定维度进行标准化(均值0、方差1),提升训练稳定性

参数类型说明默认值
normalized_shapeint/list需归一化的维度(如输入最后一维)必选
epsfloat数值稳定性系数(防止除零)1e-5
    def __init__(self, d_in, d_hid, dropout=0.1):
        super().__init__()
        self.w_1 = nn.Linear(d_in, d_hid) # position-wise
        self.w_2 = nn.Linear(d_hid, d_in) # position-wise
        self.layer_norm = nn.LayerNorm(d_in, eps=1e-6)
        self.dropout = nn.Dropout(dropout)
③ 前向传播
代码运行流程
forward(x)  
├── 1. 保存残差连接: residual = x  
├── 2. 前馈网络计算:  
│   ├── a. 第一层线性变换: x = self.w_1(x)  
│   ├── b. 激活函数: x = F.relu(x)  
│   ├── c. 第二层线性变换: x = self.w_2(x)  
│   └── d. Dropout 正则化: x = self.dropout(x)  
├── 3. 残差连接: x += residual  
└── 4. 层归一化: x = self.layer_norm(x)  

residual:原始输入 x 的副本,用于与处理后的张量相加。

x:输入到前馈神经网络的张量,通常来自自注意力层或上一层的输出。

self.w_1():将输入从 d_in 维度映射到 d_hid,激活函数(如 ReLU)引入非线性

self.w_2():将隐藏层维度压缩回 d_in,保持输入输出维度一致,便于残差连接

self.dropout():通常在激活函数(如 ReLU)后调用,随机丢弃神经元,防止过拟合

self.layer_norm():对输出进行层归一化(Layer Normalization)

F.relu():修正线性单元(Rectified Linear Unit)激活函数,其核心作用是为神经网络引入非线性特征,解决线性模型的表达能力局限性问题

参数名类型必填描述默认值
inputTensor输入张量,支持任意维度(如 (batch_size, channels, height, width)
inplacebool是否执行原地操作(直接修改输入张量,减少内存占用)
    def forward(self, x):

        residual = x

        x = self.w_2(F.relu(self.w_1(x)))
        x = self.dropout(x)
        x += residual

        x = self.layer_norm(x)

        return x
④ 完整代码
class PositionwiseFeedForward(nn.Module):
    ''' A two-feed-forward-layer module '''

    def __init__(self, d_in, d_hid, dropout=0.1):
        super().__init__()
        self.w_1 = nn.Linear(d_in, d_hid) # position-wise
        self.w_2 = nn.Linear(d_hid, d_in) # position-wise
        self.layer_norm = nn.LayerNorm(d_in, eps=1e-6)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):

        residual = x

        x = self.w_2(F.relu(self.w_1(x)))
        x = self.dropout(x)
        x += residual

        x = self.layer_norm(x)

        return x

Ⅲ、完整代码 

''' Define the sublayers in encoder/decoder layer '''
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
from .Modules import ScaledDotProductAttention

__author__ = "Yu-Hsiang Huang"

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

    def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
        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(temperature=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):

        d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
        sz_b, 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 = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
        k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
        v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)

        # Transpose for attention dot product: b x n x lq x dv
        q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)

        if mask is not None:
            mask = mask.unsqueeze(1)   # For head axis broadcasting.

        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)
        q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
        q = self.dropout(self.fc(q))
        q += residual

        q = self.layer_norm(q)

        return q, attn


class PositionwiseFeedForward(nn.Module):
    ''' A two-feed-forward-layer module '''

    def __init__(self, d_in, d_hid, dropout=0.1):
        super().__init__()
        self.w_1 = nn.Linear(d_in, d_hid) # position-wise
        self.w_2 = nn.Linear(d_hid, d_in) # position-wise
        self.layer_norm = nn.LayerNorm(d_in, eps=1e-6)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):

        residual = x

        x = self.w_2(F.relu(self.w_1(x)))
        x = self.dropout(x)
        x += residual

        x = self.layer_norm(x)

        return x

3.Layers.py

Ⅰ、编码层 EncoderLayer

① 类的定义

继承自 PyTorch 的 nn.Module 类,EncoderLayer 类代表了 Transformer 编码器中的一个基本层,其主要作用是对输入序列进行特征提取和转换,通过多头自注意力机制捕获序列中元素之间的依赖关系,再利用前馈神经网络进行非线性变换,从而增强模型对序列信息的理解和表达能力。

class EncoderLayer(nn.Module):
    ''' Compose with two layers '''
② 初始化方法

d_model:模型的主维度,表示输入/输出向量的统一维度(如词嵌入维度)

d_inner:前馈网络(Feed Forward Network, FFN)的隐藏层维度

n_head:多头注意力机制中的头数

d_k:每个注意力头中查询(Query)和键(Key)的维度

d_v:每个注意力头中值(Value)向量的维度

dropout:随机失活概率,随机屏蔽神经元的概率(默认0.1)

self.slf_attn:实现多头注意力机制,计算输入序列内部的全局依赖关系

self.pos_ffn:实现位置感知的前馈网络,对每个位置的向量独立进行非线性变换

MultiHeadAttention():创建多头注意力机制类实体

PositionwiseFeedForward():创建位置前馈网络类实体

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(EncoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)
③ 前向传播
代码运行流程
forward(enc_input, slf_attn_mask)  
├── 1. 自注意力计算(Multi-Head Self-Attention)  
│   ├── a. 输入参数:  
│   │   ├── query = enc_input  
│   │   ├── key = enc_input  
│   │   ├── value = enc_input  
│   │   └── mask = slf_attn_mask(可选)  
│   ├── b. 多头注意力机制:  
│   │   ├── 拆分头: 将 enc_input 分割为多个头(维度变换)  
│   │   ├── 计算注意力权重: Q·K^T / sqrt(d_k)  
│   │   ├── 应用掩码: 若提供 slf_attn_mask,抑制无效位置  
│   │   ├── Softmax 归一化: 生成注意力概率分布  
│   │   └── 加权求和: 注意力权重与 Value 相乘,合并多头输出  
│   └── c. 输出:  
│       ├── enc_output: 注意力后的特征表示(维度与输入一致)  
│       └── enc_slf_attn: 注意力权重矩阵(用于可视化或分析)  
├── 2. 前馈网络(Positionwise Feed-Forward Network, FFN)  
│   ├── a. 线性变换: 将 enc_output 从 d_model 映射到 d_inner(升维)  
│   ├── b. 激活函数: 通常为 ReLU  
│   ├── c. 线性变换: 从 d_inner 映射回 d_model(降维)  
│   └── d. Dropout: 随机屏蔽部分神经元(若启用)  
└── 3. 最终输出:  
    ├── enc_output: 经过自注意力和前馈网络处理的特征  
    └── enc_slf_attn: 保留的注意力权重  

enc_input:编码器输入,形状为 (batch_size, seq_len, d_model),包含经过词嵌入和位置编码的特征向量

slf_attn_mask:自注意力掩码,形状为 (batch_size, seq_len, seq_len),用于屏蔽无效位置(如填充符或未来信息)

enc_output:编码器输出,形状与 enc_input 相同

enc_slf_attn:自注意力权重矩阵,形状为 (batch_size, n_head, seq_len, seq_len)

self.slf_attn():多头自注意力模块,接受 enc_input 作为 Q/K/V,计算注意力权重并聚合 Value 向量

self.pos_ffn():位置前馈网络,包含两个线性层和 ReLU 激活函数,公式为 FFN(x) = max(0, xW1 + b1)W2 + b2

    def forward(self, enc_input, slf_attn_mask=None):
        enc_output, enc_slf_attn = self.slf_attn(
            enc_input, enc_input, enc_input, mask=slf_attn_mask)
        enc_output = self.pos_ffn(enc_output)
        return enc_output, enc_slf_attn
④ 完整代码
class EncoderLayer(nn.Module):
    ''' Compose with two layers '''

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(EncoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

    def forward(self, enc_input, slf_attn_mask=None):
        enc_output, enc_slf_attn = self.slf_attn(
            enc_input, enc_input, enc_input, mask=slf_attn_mask)
        enc_output = self.pos_ffn(enc_output)
        return enc_output, enc_slf_attn

Ⅱ、解码层 DecoderLayer

① 类的定义

继承自 PyTorch 的 nn.Module 类,DecoderLayer 类代表了 Transformer 解码器中的一个基本层,其主要作用是在生成目标序列时,综合考虑已生成的目标序列信息(通过自注意力层)、编码器输出的源序列信息(通过编码器 - 解码器注意力层),并利用前馈神经网络进行非线性变换,从而逐步生成高质量的目标序列。

class DecoderLayer(nn.Module):
    ''' Compose with three layers '''
② 初始化方法

d_model:模型的主维度,表示输入/输出向量的统一维度(如词嵌入维度)

d_inner:前馈网络(Feed Forward Network, FFN)的隐藏层维度

n_head:多头注意力机制中的头数

d_k:每个注意力头中查询(Query)和键(Key)的维度

d_v:每个注意力头中值(Value)向量的维度

dropout:随机失活概率,随机屏蔽神经元的概率(默认0.1)

self.slf_attn:多头自注意力模块,接受 enc_input 作为 Q/K/V,计算注意力权重并聚合 Value 向量

self.enc_attn:编码器-解码器注意力,使解码器关注编码器输出(即源序列的语义表示

self.pos_ffn:位置前馈网络,包含两个线性层和 ReLU 激活函数,公式为 FFN(x) = max(0, xW1 + b1)W2 + b2

MultiHeadAttention():创建多头注意力机制类实体

PositionwiseFeedForward():创建位置前馈网络类实体

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(DecoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)
② 前向传播
代码运行流程
forward(dec_input, enc_output, slf_attn_mask, dec_enc_attn_mask)
├── 1. 掩码自注意力层(Masked Multi-Head Self-Attention)  
│   ├── a. 输入参数:  
│   │   ├── query = dec_input(目标序列嵌入 + 位置编码)
│   │   ├── key = dec_input  
│   │   ├── value = dec_input  
│   │   └── mask = slf_attn_mask(因果掩码,屏蔽未来位置)
│   ├── b. 处理流程:  
│   │   ├── 拆分多头: 将输入分割为 `n_head` 个并行头(如 8 头)  
│   │   ├── 计算 Q·K^T / √d_k: 点积缩放防止梯度爆炸  
│   │   ├── 应用因果掩码: 将未来位置的注意力得分设为负无穷(Softmax 后权重为 0) 
│   │   ├── Softmax 归一化: 生成概率分布  
│   │   └── 加权求和 Value: 合并多头输出并线性投影 
│   └── c. 输出:  
│       ├── dec_output: 掩码自注意力后的特征(维度不变,如 512)  
│       └── dec_slf_attn: 注意力权重矩阵(形状:batch × n_head × seq_len × seq_len)
├── 2. 交叉注意力层(Encoder-Decoder Multi-Head Attention)  
│   ├── a. 输入参数:  
│   │   ├── query = dec_output(来自步骤1的输出)  
│   │   ├── key = enc_output(编码器的最终输出) 
│   │   ├── value = enc_output  
│   │   └── mask = dec_enc_attn_mask(屏蔽编码器填充符) 
│   ├── b. 处理流程:  
│   │   ├── 计算交叉注意力: Query 来自解码器,Key/Value 来自编码器
│   │   ├── 应用编码器掩码: 过滤编码器输出的无效位置(如填充符)
│   │   └── 权重聚合: 生成对齐编码器语义的特征  
│   └── c. 输出:  
│       ├── dec_output: 对齐编码器后的特征(维度不变)  
│       └── dec_enc_attn: 解码器-编码器注意力权重(用于可视化对齐关系) 
├── 3. 前馈网络层(Position-wise Feed-Forward Network, FFN)  
│   ├── a. 结构:  
│   │   ├── 第一层线性变换: d_model → d_inner(如 512 → 2048)  
│   │   ├── ReLU 激活: 引入非线性  
│   │   ├── 第二层线性变换: d_inner → d_model
│   │   └── Dropout: 随机屏蔽部分神经元(若启用)
│   └── b. 输出:  
│       └── dec_output: 最终解码器层输出(维度保持 d_model)
└── 4. 返回结果:  
    ├── dec_output: 传递给下一解码器层或输出层(如分类器)  
    ├── dec_slf_attn: 目标序列内部依赖关系的可视化数据  
    └── dec_enc_attn: 源-目标序列对齐关系的分析依据

dec_input:解码器输入,目标序列的嵌入表示(含位置编码),形状为 (batch_size, seq_len, d_model)

enc_output:编码器输出,源序列的上下文表示,形状与 dec_input 相同,作为交叉注意力的 Key/Value 来源

slf_attn_mask:自注意力掩码,防止解码器关注未来位置(如生成任务中当前词不可依赖后续词),形状为 (batch_size, seq_len, seq_len)

dec_enc_attn_mask:交叉注意力掩码,屏蔽编码器输出中的无效位置(如填充符),形状与 slf_attn_mask 相同

dec_output:解码器中间输出,依次通过自注意力、交叉注意力和前馈网络处理后的特征表示

dec_slf_attn:自注意力权重矩阵,反映解码器输入序列内部的关联强度,形状为 (batch_size, n_head, seq_len, seq_len),用于可视化模型关注模式

dec_enc_attn:交叉注意力权重矩阵,反映解码器与编码器输出的对齐关系,形状与 dec_slf_attn 相同

self.slf_attn():通过 slf_attn_mask 将未来位置的注意力得分设为极小值(如 -1e9),经 Softmax 后权重趋近于零,确保自回归生成时仅依赖历史信息

self.enc_attn():通过 dec_enc_attn_mask 过滤编码器输出中的无效位置(如填充符),例如机器翻译中忽略源句子的填充符

self.pos_ffn():增强局部非线性表达能力,公式为 FFN(x) = ReLU(xW1 + b1)W2 + b2

    def forward(
            self, dec_input, enc_output,
            slf_attn_mask=None, dec_enc_attn_mask=None):
        dec_output, dec_slf_attn = self.slf_attn(
            dec_input, dec_input, dec_input, mask=slf_attn_mask)
        dec_output, dec_enc_attn = self.enc_attn(
            dec_output, enc_output, enc_output, mask=dec_enc_attn_mask)
        dec_output = self.pos_ffn(dec_output)
        return dec_output, dec_slf_attn, dec_enc_attn
③ 完整代码
class DecoderLayer(nn.Module):
    ''' Compose with three layers '''

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(DecoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

    def forward(
            self, dec_input, enc_output,
            slf_attn_mask=None, dec_enc_attn_mask=None):
        dec_output, dec_slf_attn = self.slf_attn(
            dec_input, dec_input, dec_input, mask=slf_attn_mask)
        dec_output, dec_enc_attn = self.enc_attn(
            dec_output, enc_output, enc_output, mask=dec_enc_attn_mask)
        dec_output = self.pos_ffn(dec_output)
        return dec_output, dec_slf_attn, dec_enc_attn

Ⅲ、完整代码

''' Define the Layers '''
import torch.nn as nn
import torch
from .SubLayers import MultiHeadAttention, PositionwiseFeedForward
# Layers.py

# 后续代码...

__author__ = "Yu-Hsiang Huang"


class EncoderLayer(nn.Module):
    ''' Compose with two layers '''

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(EncoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

    def forward(self, enc_input, slf_attn_mask=None):
        enc_output, enc_slf_attn = self.slf_attn(
            enc_input, enc_input, enc_input, mask=slf_attn_mask)
        enc_output = self.pos_ffn(enc_output)
        return enc_output, enc_slf_attn


class DecoderLayer(nn.Module):
    ''' Compose with three layers '''

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(DecoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

    def forward(
            self, dec_input, enc_output,
            slf_attn_mask=None, dec_enc_attn_mask=None):
        dec_output, dec_slf_attn = self.slf_attn(
            dec_input, dec_input, dec_input, mask=slf_attn_mask)
        dec_output, dec_enc_attn = self.enc_attn(
            dec_output, enc_output, enc_output, mask=dec_enc_attn_mask)
        dec_output = self.pos_ffn(dec_output)
        return dec_output, dec_slf_attn, dec_enc_attn

4.Models.py

Ⅰ、位置编码 PositionalEncoding

 代码运行流程
PositionalEncoding 类
|
|-- __init__ 方法
|   |-- 调用父类构造函数
|   |   |-- super(PositionalEncoding, self).__init__()
|   |-- 生成并注册位置编码表
|       |-- self.register_buffer('pos_table', self._get_sinusoid_encoding_table(n_position, d_hid))
|           |-- _get_sinusoid_encoding_table 方法
|               |-- get_position_angle_vec 函数
|                   |-- 计算每个位置在每个维度上的角度值
|               |-- 生成角度值表
|                   |-- sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
|               |-- 应用正弦和余弦函数
|                   |-- sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])
|                   |-- sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])
|               |-- 转换为 PyTorch 张量并添加维度
|                   |-- return torch.FloatTensor(sinusoid_table).unsqueeze(0)
|
|-- forward 方法
|   |-- 将位置编码添加到输入序列
|       |-- return x + self.pos_table[:, :x.size(1)].clone().detach()
① 类的定义

继承自 PyTorch 的 nn.Module 类,用于为输入序列添加位置编码信息

class PositionalEncoding(nn.Module):
② 初始化方法

self.register_buffer():在 PyTorch 里,模型的参数(nn.Parameter)会被优化器更新,而缓冲区则是一些不需要被优化器更新,但又需要随着模型一起保存和加载的张量。self.register_buffer() 方法可以将一个张量注册为缓冲区,使其成为模块的一部分,这样在保存和加载模型时,缓冲区也会被正确处理

参数名类型描述
namestr缓冲区的名称,是一个字符串。之后能通过 self.name 来访问这个缓冲区。例如,若 name 设为 'pos_table',就可以用 self.pos_table 访问该缓冲区。
tensortorch.Tensor需要注册为缓冲区的张量。此张量不会被优化器更新,但会和模型一同保存与加载。例如在位置编码里,预先计算好的位置编码张量就可作为该参数传入。
persistentbool,可选指示该缓冲区是否会被包含在 state_dict 中并在保存和加载模型时被处理,默认值为 True。当设为 False 时,这个缓冲区不会出现在 state_dict 里,也就不会被保存和加载。不过在模型的前向传播等操作里,仍然可以使用该缓冲区。

self.get_sinusoid_encoding_table():编码序列信息

    def __init__(self, d_hid, n_position=200):
        super(PositionalEncoding, self).__init__()

        # Not a parameter
        self.register_buffer('pos_table', self._get_sinusoid_encoding_table(n_position, d_hid))
③ 编码序列信息【正弦位置编码】_get_sinusoid_encoding_table

n_position:表示序列的最大长度(即位置的数量),例如 512,决定了生成的编码表有多少行(每个位置对应一行)

d_hid:代表隐藏层的维度。在位置编码的上下文中,它决定了每个位置编码向量的长度,即编码表中每一行的列数。

hid_j:一个循环变量,用于遍历隐藏层维度(d_hid)的索引,位置(0 到 d_hid-1)

pos_i:一个循环变量,用于遍历位置(n_position)的索引,位置(0 到 n_position-1

position:当前要计算角度向量的位置。

sinusoud_table:n_position × d_hid 的矩阵,存储所有位置的位置编码

get_position_angle_vec():内部函数,用于计算给定位置 position 在不同维度上的角度值。这些角度值后续会被用于生成正弦和余弦位置编码

self.rigister_buffer():nn.Module 类的一个方法,其作用是在模块里注册一个缓冲区(buffer)。

        缓冲区:缓冲区(buffer)和模型参数(parameter)有所不同。参数是可学习的变量,会在模型训练时被优化器更新;而缓冲区则是一些不需要进行梯度更新的张量,不过在模型的保存和加载时,它们也会被保存和加载。

self.get_sinusoid_encoding_table(): 编码序列信息【正弦位置编码】

np.sin():计算输入数组(弧度制)中每个元素的正弦值,支持数组广播和多种数值类型

参数类型/默认值说明
xarray_like输入数组(角度需转换为弧度)
outndarray (可选)结果存储位置,需与输入广播后的形状一致
wherearray_like (可选)条件过滤,True的位置执行计算
**kwargs-其他关键字参数(如dtype

np.cos():计算输入数组(弧度制)中每个元素的余弦值,功能与np.sin()类似

参数类型/默认值说明
xarray_like输入数组
outndarray (可选)np.sin()
wherearray_like (可选)np.sin()
**kwargs-np.sin()

np.power():对第一个数组的元素进行逐元素幂运算,基数为第一个数组,指数为第二个数组

参数类型/默认值说明
x1array_like基数数组
x2array_like指数数组,需与x1广播兼容
outndarray (可选)结果存储位置
**kwargs-其他参数(如dtype

torch.FloatTensor():创建浮点型张量,支持通过维度或数据初始化

参数类型/默认值说明
datalist/维度值数据或张量维度(如(2,3)
dtype-默认为torch.float32

张量.unsqueeze():在指定维度插入大小为1的新维度,常用于调整张量形状以适应模型输入

类型/默认值说明
dimint插入维度的索引(如0在第0维插入)
    def _get_sinusoid_encoding_table(self, n_position, d_hid):
        ''' Sinusoid position encoding table '''
        # TODO: make it with torch instead of numpy

        def get_position_angle_vec(position):
            return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]

        sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
        sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])  # dim 2i
        sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])  # dim 2i+1

        return torch.FloatTensor(sinusoid_table).unsqueeze(0)
④ 前向传播

将位置编码添加到输入序列:从位置编码表中截取与输入序列长度相同的部分,然后将其与输入序列相加,最后返回结果。通过 clone().detach() 确保位置编码表的数据不会影响梯度计算。

x:输入张量,通常是词嵌入(word embeddings),形状为 [batch_size, seq_len, d_model]

self.pos_table:存储预计算的位置编码表,由 _get_sinusoid_encoding_table(n_position, d_hid) 生成

clone():创建张量的深拷贝副本,保留计算图梯度(与原张量数据独立)

参数类型/默认值说明
inputTensor输入张量
memory_format-内存格式(可选)

size():返回张量或数组的维度大小(如(3,4)表示3行4列)

参数类型/默认值说明
dimint (可选)指定维度索引(如dim=0返回第0维大小)

detach():从计算图中分离张量,阻止梯度传播,断开计算图,防止位置编码参与梯度更新

参数类型/默认值说明
inputTensor输入张量
    def forward(self, x):
        return x + self.pos_table[:, :x.size(1)].clone().detach()
⑤ 完整代码
class PositionalEncoding(nn.Module):

    def __init__(self, d_hid, n_position=200):
        super(PositionalEncoding, self).__init__()

        # Not a parameter
        self.register_buffer('pos_table', self._get_sinusoid_encoding_table(n_position, d_hid))

    def _get_sinusoid_encoding_table(self, n_position, d_hid):
        ''' Sinusoid position encoding table '''
        # TODO: make it with torch instead of numpy

        def get_position_angle_vec(position):
            return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]

        sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
        sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])  # dim 2i
        sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])  # dim 2i+1

        return torch.FloatTensor(sinusoid_table).unsqueeze(0)

    def forward(self, x):
        return x + self.pos_table[:, :x.size(1)].clone().detach()

Ⅱ、编码器 Encoder

① 类的定义

继承自 PyTorch 的 nn.Module 类,构建一个具备自注意力机制的编码器模型,对输入序列进行特征提取与编码,将输入的原始序列转换为包含丰富语义信息的特征表示,而自注意力机制能够让模型捕捉序列中不同位置元素之间的依赖关系。

class Encoder(nn.Module):
    ''' A encoder model with self attention mechanism. '''
② 初始化方法

n_src_vocab:整数,源语言词汇表的大小,即源语言中不同单词的数量。在自然语言处理任务中,通常需要将文本中的单词转换为对应的整数索引,n_src_vocab 表示这些索引的取值范围。

d_word_vec:整数,词向量的维度。词向量是将单词表示为实数向量的一种方式,d_word_vec 表示每个单词对应的向量的长度。

n_layers:整数,编码器层(EncoderLayer)的数量。在 Transformer 架构中,编码器通常由多个相同的编码器层堆叠而成,n_layers 表示堆叠的层数。

n_head:整数,多头注意力机制中的头数。多头注意力机制通过多个注意力头并行处理输入,能够捕捉输入序列中不同位置之间的多种依赖关系,n_head 表示注意力头的数量。

d_k:查询(Query)和键(Key)向量的维度。在

d_v:值(Value)向量的维度。

d_model:模型的隐藏层维度。在 Transformer 架构中,d_model 表示整个模型中各个层的隐藏单元数量,通常与词向量的维度相同。

d_inner:前馈神经网络(Feed-Forward Network)中隐藏层的维度。

pad_idx:填充标记(Padding Token)的索引

dropout:在训练过程中随机丢弃神经元的概率。

n_position:位置编码的最大长度。

scale_emb:是否对嵌入向量进行缩放

self.src_word_emb:源语言单词的嵌入层,在模型的前向传播过程中,将输入的单词索引转换为词向量。

self.position_enc:位置编码层,在模型的前向传播过程中,为输入的词向量添加位置编码。

self.dropout:丢弃层,在模型的前向传播过程中,对输入进行丢弃操作

self.layer_stack:编码器层的堆叠,在模型的前向传播过程中,依次通过每个 EncoderLayer 对输入进行编码

self.layer_norm:层归一化层

self.scale_emb:是否对嵌入向量进行缩放的标志

self.d_model:模型的隐藏层维度

PositionalEncoding():位置编码层,为输入序列中的每个元素添加位置信息,让模型能够学习到序列中元素的相对位置关系。

EncoderLayer():编码器层类,多个 EncoderLayer 实例堆叠在一起,对输入进行多次编码操作,提取输入序列的特征。

nn.Embedding():将离散索引映射为连续向量(如词嵌入),支持词表大小和向量维度定义

参数类型/默认值说明
num_embeddingsint词表大小
embedding_dimint向量维度
padding_idxint (可选)填充索引(不更新梯度)
max_normfloat (可选)向量最大范数限制

nn.Dropout():随机将输入元素置零(概率由p控制),防止过拟合

参数类型/默认值说明
pfloat (0≤p<1)元素置零概率
inplacebool (False)是否原地操作

nn.ModuleList():存储子模块的列表,支持动态增减模块(如循环网络层)

参数类型/默认值说明
modulesiterable (可选)初始模块列表

nn.LayerNorm():对输入进行层标准化(沿指定维度归一化),常用于稳定训练

参数类型/默认值说明
normalized_shapeint/list归一化的维度(如[128]
epsfloat (1e-5)数值稳定项
elementwise_affinebool (True)是否启用可学习的缩放和偏移参数
    def __init__(
            self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
            d_model, d_inner, pad_idx, dropout=0.1, n_position=200, scale_emb=False):

        super().__init__()

        self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx)
        self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
        self.dropout = nn.Dropout(p=dropout)
        self.layer_stack = nn.ModuleList([
            EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.scale_emb = scale_emb
        self.d_model = d_model

③ 前向传播
代码运行流程
forward 方法
|
|-- 初始化自注意力矩阵列表
|   |-- enc_slf_attn_list = []
|
|-- 前向传播步骤
|   |-- 词嵌入
|       |-- enc_output = self.src_word_emb(src_seq)
|   |-- 缩放嵌入(可选)
|       |-- if self.scale_emb:
|           |-- enc_output *= self.d_model ** 0.5
|   |-- 添加位置编码并应用 Dropout
|       |-- enc_output = self.dropout(self.position_enc(enc_output))
|   |-- 层归一化
|       |-- enc_output = self.layer_norm(enc_output)
|
|-- 遍历编码器层
|   |-- for enc_layer in self.layer_stack:
|       |-- 编码器层计算
|           |-- enc_output, enc_slf_attn = enc_layer(enc_output, slf_attn_mask=src_mask)
|       |-- 记录自注意力矩阵(可选)
|           |-- if return_attns:
|               |-- enc_slf_attn_list += [enc_slf_attn]
|
|-- 返回结果
|   |-- if return_attns:
|       |-- return enc_output, enc_slf_attn_list
|   |-- else:
|       |-- return enc_output,

src_seq:源序列的输入,一般是经过分词并转换为词索引后的序列。

src_mask:源序列的掩码,是一个布尔类型的张量,用于指示哪些位置的元素需要被忽略。

return_attns:标志位,用于决定是否返回每层的自注意力矩阵。

enc_slf_attn_list:存储编码器每层的自注意力矩阵的列表

enc_output:编码器在不同阶段的输出结果。在不同的代码行中,它代表不同阶段的中间结果。

enc_slf_attn:编码器每层的自注意力矩阵,反映了该层中每个位置对其他位置的注意力权重分布。

self.src_word_emb():源语言单词的嵌入层,在类的初始化中定义。它将输入的词索引映射为对应的词向量。

self.scale_emb:标志位,用于决定是否对词嵌入的结果进行缩放操作

self.d_model:模型的隐藏层维度,也就是模型中各层的特征维度。

self.dropout():丢弃层,在模型的前向传播过程中,对输入进行丢弃操作

self.layer_norm():层归一化层

self.layer_stack:编码器层的堆叠,在模型的前向传播过程中,依次通过每个 EncoderLayer 对输入进行编码

    def forward(self, src_seq, src_mask, return_attns=False):

        enc_slf_attn_list = []

        # -- Forward
        enc_output = self.src_word_emb(src_seq)
        if self.scale_emb:
            enc_output *= self.d_model ** 0.5
        enc_output = self.dropout(self.position_enc(enc_output))
        enc_output = self.layer_norm(enc_output)

        for enc_layer in self.layer_stack:
            enc_output, enc_slf_attn = enc_layer(enc_output, slf_attn_mask=src_mask)
            enc_slf_attn_list += [enc_slf_attn] if return_attns else []

        if return_attns:
            return enc_output, enc_slf_attn_list
        return enc_output,
④ 完整代码
class Encoder(nn.Module):
    ''' A encoder model with self attention mechanism. '''

    def __init__(
            self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
            d_model, d_inner, pad_idx, dropout=0.1, n_position=200, scale_emb=False):

        super().__init__()

        self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx)
        self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
        self.dropout = nn.Dropout(p=dropout)
        self.layer_stack = nn.ModuleList([
            EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.scale_emb = scale_emb
        self.d_model = d_model

    def forward(self, src_seq, src_mask, return_attns=False):

        enc_slf_attn_list = []

        # -- Forward
        enc_output = self.src_word_emb(src_seq)
        if self.scale_emb:
            enc_output *= self.d_model ** 0.5
        enc_output = self.dropout(self.position_enc(enc_output))
        enc_output = self.layer_norm(enc_output)

        for enc_layer in self.layer_stack:
            enc_output, enc_slf_attn = enc_layer(enc_output, slf_attn_mask=src_mask)
            enc_slf_attn_list += [enc_slf_attn] if return_attns else []

        if return_attns:
            return enc_output, enc_slf_attn_list
        return enc_output,

Ⅲ、解码器 Decoder

① 类的定义

        继承自 PyTorch 的 nn.Module 类,Decoder 类是一个带有自注意力机制的解码器模型。在许多序列到序列(Seq2Seq)的任务中,如机器翻译、文本生成等,通常会使用编码器 - 解码器(Encoder - Decoder)架构。编码器负责对输入序列进行编码,提取特征;而解码器则根据编码器的输出和已经生成的部分目标序列,逐步生成完整的目标序列。自注意力机制能够让解码器在生成过程中关注目标序列内部不同位置之间的关系,以及目标序列与源序列之间的关系。

class Decoder(nn.Module):
    ''' A decoder model with self attention mechanism. '''
② 初始化方法

n_trg_vocab:目标语言词汇表的大小,即目标语言中不同单词的数量。

d_word_vec:词向量的维度。词向量是将单词表示为实数向量的一种方式,该参数表示每个单词对应的向量的长度。

n_layers:解码器层(DecoderLayer)的数量,用于创建 nn.ModuleList,通过多层的堆叠让模型能够学习到更复杂的特征和语义信息。

n_head:多头注意力机制中的头数。多头注意力机制通过多个注意力头并行处理输入,能够捕捉输入序列中不同位置之间的多种依赖关系,该参数表示注意力头的数量。

d_k:查询(Query)和键(Key)向量的维度

d_v:值(Value)向量的维度。

d_model:模型的隐藏层维度

d_inner:前馈神经网络(Feed - Forward Network)中隐藏层的维度

pad_idx:填充标记(Padding Token)的索引。

n_position:位置编码的最大长度。位置编码用于为输入序列中的每个位置提供位置信息,该参数表示位置编码表的最大长度。

dropout:丢弃率,该参数表示在训练过程中随机丢弃神经元的概率。

scale_emb:是否对嵌入向量进行缩放

self.trg_word_emb:目标语言单词的嵌入层。nn.Embedding 是 PyTorch 中用于将整数索引转换为词向量的层,该变量用于将目标语言中的单词索引转换为对应的词向量

self.position_enc:位置编码层。

self.dropout:丢弃层。nn.Dropout 用于在训练过程中随机丢弃神经元,防止模型过拟合

self.layer_norm:解码器层的堆叠。nn.ModuleList 是 PyTorch 中用于存储多个模块的列表,该变量包含了指定数量的 DecoderLayer 实例,用于对输入进行多次解码操作。

self.scale_emb:是否对嵌入向量进行缩放的标志

self.d_model:模型的隐藏层维度

PositionalEncoding():用于生成位置编码表,并将其添加到输入的词向量中,为模型提供位置信息。

DecoderLayer(): 用于对输入进行解码操作

nn.Embedding():PyTorch 中用于将离散的整数索引转换为连续的向量表示(即词嵌入)的层。在自然语言处理任务中,通常会将单词映射为整数索引,而 nn.Embedding 可以将这些索引转换

参数名类型描述
num_embeddingsint嵌入字典的大小,即词汇表中不同单词的数量。
embedding_dimint每个嵌入向量的维度。
padding_idxint,可选填充标记的索引。如果指定了该参数,那么在计算过程中,填充标记对应的嵌入向量将始终为零向量,并且不会参与梯度更新。
max_normfloat,可选嵌入向量的最大范数。如果嵌入向量的范数超过该值,将对其进行归一化处理。
norm_typefloat,可选计算范数时使用的范数类型,默认为 2 范数。
scale_grad_by_freqbool,可选是否根据单词的频率缩放梯度。如果设置为 True,则频率较高的单词的梯度会相应减小。
sparsebool,可选是否使用稀疏梯度。如果设置为 True,则嵌入层的梯度将是稀疏张量,适用于大规模词汇表的情况。

nn.Dropout():正则化技术,用于防止神经网络过拟合。在训练过程中,nn.Dropout 会以一定的概率随机将输入的某些元素置为零,从而迫使模型学习更鲁棒的特征表示。在测试阶段,nn.Dropout 会自动关闭,即不进行元素置零操作。

参数名类型描述
pfloat,可选元素被置为零的概率,取值范围为 [0, 1],默认值为 0.5。
inplacebool,可选是否原地操作。如果设置为 True,则直接在输入张量上进行修改,不创建新的张量,默认值为 False

nn.ModuleList():PyTorch 中用于存储多个 nn.Module 实例的容器。它类似于 Python 的列表,但 nn.ModuleList 会将其中的模块注册为当前模块的子模块,使得这些子模块的参数能够被正确管理

参数名类型描述
modulesiterable,可选一个可迭代对象,包含要存储在 nn.ModuleList 中的 nn.Module 实例。

nn.LayerNorm():归一化技术,用于对输入的特征进行归一化处理

参数名类型描述
normalized_shapeint 或 list 或 torch.Size输入特征的形状,可以是整数、列表或 torch.Size 对象。如果是整数,则表示输入特征的最后一个维度的大小;如果是列表或 torch.Size 对象,则表示需要归一化的维度的形状。
epsfloat,可选为了数值稳定性而添加到分母中的一个小常数,默认值为 1e-5。
elementwise_affinebool,可选是否对归一化后的输出进行仿射变换(即乘以一个可学习的权重并加上一个可学习的偏置),默认值为 True
    def __init__(
            self, n_trg_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
            d_model, d_inner, pad_idx, n_position=200, dropout=0.1, scale_emb=False):

        super().__init__()

        self.trg_word_emb = nn.Embedding(n_trg_vocab, d_word_vec, padding_idx=pad_idx)
        self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
        self.dropout = nn.Dropout(p=dropout)
        self.layer_stack = nn.ModuleList([
            DecoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.scale_emb = scale_emb
        self.d_model = d_model
③ 前向传播
代码运行流程
forward 方法
|
|-- 初始化
|   |-- dec_slf_attn_list = []
|   |-- dec_enc_attn_list = []
|
|-- 前向传播
|   |-- 词嵌入
|   |   |-- dec_output = self.trg_word_emb(trg_seq)
|   |
|   |-- 缩放嵌入(可选)
|   |   |-- if self.scale_emb:
|   |       |-- dec_output *= self.d_model ** 0.5
|   |
|   |-- 添加位置编码并丢弃
|   |   |-- dec_output = self.dropout(self.position_enc(dec_output))
|   |
|   |-- 层归一化
|   |   |-- dec_output = self.layer_norm(dec_output)
|   |
|   |-- 解码器层循环
|   |   |-- for dec_layer in self.layer_stack:
|   |       |-- 调用解码器层
|   |       |   |-- dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
|   |       |       |-- dec_output, 
|   |       |       |-- enc_output, 
|   |       |       |-- slf_attn_mask=trg_mask, 
|   |       |       |-- dec_enc_attn_mask=src_mask)
|   |       |
|   |       |-- 保存注意力矩阵(可选)
|   |       |   |-- if return_attns:
|   |       |       |-- dec_slf_attn_list.append(dec_slf_attn)
|   |       |       |-- dec_enc_attn_list.append(dec_enc_attn)
|
|-- 返回结果
|   |-- if return_attns:
|   |   |-- return dec_output, dec_slf_attn_list, dec_enc_attn_list
|   |-- else:
|       |-- return dec_output,

trg_seq:目标序列的输入,在机器翻译等任务里,就是目标语言的单词索引序列。

trg_mask:目标序列的掩码矩阵。掩码矩阵是一个布尔类型的张量,用于指示哪些位置的元素需要被关注或忽略。

enc_output:编码器(Encoder)的输出结果。编码器对输入的源序列进行编码,提取出序列的特征表示,这个输出会作为解码器的输入之一,帮助解码器生成目标序列。

src_mask:源序列的掩码矩阵。和 trg_mask 类似,用于指示源序列中哪些位置的元素需要被关注或忽略。

return_attns:一个标志位,用于决定是否返回解码器每层的自注意力矩阵和编码器 - 解码器注意力矩阵。

dec_slf_attn_list:列表,存储解码器每层的自注意力矩阵

dec_enc_attn_list:列表,存储解码器每层的编码器 - 解码器注意力矩阵

dec_output:解码器在不同阶段的输出结果

dec_slf_attn:解码器每层的自注意力矩阵

dec_enc_attn:解码器每层的编码器 - 解码器注意力矩阵

self.trg_word_emb():在类的初始化时定义的目标语言词嵌入层

self.dropout():在类初始化时定义的丢弃层

self.layer_norm():在类初始化时定义的层归一化层

self.position_enc():在类初始化时定义的位置编码层

self.layer_stack:类初始化时定义的解码器层堆叠

dec_layer:self.layer_stack 中的每一个 DecoderLayer 实例。DecoderLayer 是解码器的基本组成单元,通常包含自注意力机制、编码器 - 解码器注意力机制和前馈神经网络。

slf_attn_mask:自注意力掩码矩阵,在 dec_layer 调用时传入,即 slf_attn_mask=trg_mask

dec_enc_attn_mask:编码器 - 解码器注意力掩码矩阵,在 dec_layer 调用时传入,即 dec_enc_attn_mask=src_mask

return_attns: 一个标志位,用于决定是否返回解码器每层的自注意力矩阵和编码器 - 解码器注意力矩阵。

    def forward(self, trg_seq, trg_mask, enc_output, src_mask, return_attns=False):

        dec_slf_attn_list, dec_enc_attn_list = [], []

        # -- Forward
        dec_output = self.trg_word_emb(trg_seq)
        if self.scale_emb:
            dec_output *= self.d_model ** 0.5
        dec_output = self.dropout(self.position_enc(dec_output))
        dec_output = self.layer_norm(dec_output)

        for dec_layer in self.layer_stack:
            dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
                dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask)
            dec_slf_attn_list += [dec_slf_attn] if return_attns else []
            dec_enc_attn_list += [dec_enc_attn] if return_attns else []

        if return_attns:
            return dec_output, dec_slf_attn_list, dec_enc_attn_list
        return dec_output,
④ 完整代码
class Decoder(nn.Module):
    ''' A decoder model with self attention mechanism. '''

    def __init__(
            self, n_trg_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
            d_model, d_inner, pad_idx, n_position=200, dropout=0.1, scale_emb=False):

        super().__init__()

        self.trg_word_emb = nn.Embedding(n_trg_vocab, d_word_vec, padding_idx=pad_idx)
        self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
        self.dropout = nn.Dropout(p=dropout)
        self.layer_stack = nn.ModuleList([
            DecoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.scale_emb = scale_emb
        self.d_model = d_model

    def forward(self, trg_seq, trg_mask, enc_output, src_mask, return_attns=False):

        dec_slf_attn_list, dec_enc_attn_list = [], []

        # -- Forward
        dec_output = self.trg_word_emb(trg_seq)
        if self.scale_emb:
            dec_output *= self.d_model ** 0.5
        dec_output = self.dropout(self.position_enc(dec_output))
        dec_output = self.layer_norm(dec_output)

        for dec_layer in self.layer_stack:
            dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
                dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask)
            dec_slf_attn_list += [dec_slf_attn] if return_attns else []
            dec_enc_attn_list += [dec_enc_attn] if return_attns else []

        if return_attns:
            return dec_output, dec_slf_attn_list, dec_enc_attn_list
        return dec_output,

Ⅳ、Transformer模型 Transformer

① 类的定义

        继承自 PyTorch 的 nn.Module 类,处理输入序列到输出序列的转换任务,像机器翻译(将源语言句子转换为目标语言句子)、文本摘要(将长文本转换为短摘要)等。而注意力机制能让模型在处理序列时,动态地关注输入序列的不同部分,从而提升模型性能。

class Transformer(nn.Module):
    ''' A sequence to sequence model with attention mechanism. '''
② 初始化方法

n_src_vocab:源语言词汇表的大小,即源语言中不同单词的数量。

n_trg_vocab:目标语言词汇表的大小,即目标语言中不同单词的数量。

src_pad_idx:源语言中填充标记(padding token)的索引。

trg_pad_idx:目标语言中填充标记的索引

d_word_vec:词向量的维度,即每个单词被映射到的向量空间的维度。

d_model:模型的隐藏层维度,是整个 Transformer 模型中各层的特征维度。

d_inner:前馈神经网络(Feed - Forward Network)中隐藏层的维度。

n_layers:编码器和解码器中层的数量。

n_head:多头注意力机制(Multi - Head Attention)中的头数。

d_k:查询(Query)和键(Key)向量的维度。

d_v:值(Value)向量的维度。

dropout:Dropout 层的丢弃率。

n_position:位置编码的最大长度。位置编码用于为输入序列中的每个位置添加位置信息,使模型能够感知序列中元素的相对位置。

trg_emb_prj_weight_sharing:一个布尔值,指示是否共享目标语言词嵌入层和最终线性投影层的权重。

scale_emb_or_prj:一个字符串,取值为 'emb''prj' 或 'none','emb' 表示对嵌入层的输出乘以 d_model^1/2​​;'prj' 表示对线性投影层的输出乘以 (d_model^1/2​​)^−1;'none' 表示不进行缩放。

self.src_pad_idx:存储源语言填充标记的索引

self.trg_pad_idx:存储目标语言填充标记的索引

scale_emb:个布尔值,根据 scale_emb_or_prj 和 trg_emb_prj_weight_sharing 的值确定是否对嵌入层的输出进行缩放

self.scale_prj:一个布尔值,根据 scale_emb_or_prj 和 trg_emb_prj_weight_sharing 的值确定是否对线性投影层的输出进行缩放

self.d_model:存储模型的隐藏层维度,方便在后续的代码中使用,例如在缩放操作时会用到该值。

self.encoder:编码器实例,用于对输入的源序列进行编码。

self.decoder:解码器实例,根据编码器的输出和目标序列的部分信息生成完整的目标序列。

self.trg_word_prj:一个线性层,将解码器的输出映射到目标词汇表大小,用于预测每个位置的单词。

assert:对某个条件进行测试,如果条件为 True,程序会继续正常执行;如果条件为 False,则会抛出 AssertionError 异常,同时可以附带一个错误信息,方便开发者定位问题。

参数类型描述
condition布尔表达式需要测试的条件,如果为 True,程序继续执行;如果为 False,抛出 AssertionError 异常。
message(可选)字符串当 condition 为 False 时,作为 AssertionError 异常的错误信息输出。

parameters():PyTorch 中 nn.Module 类的一个方法,用于返回一个包含模型所有可训练参数的迭代器。

dim():PyTorch 中 torch.Tensor 类的一个方法,用于返回张量的维度数(即秩)

nn.init.xavier_uniform():PyTorch 中用于初始化张量参数的函数,它实现了 Xavier 均匀初始化方法。

参数类型描述
tensortorch.Tensor需要进行初始化的张量。
gain(可选)浮点数缩放因子,用于调整初始化的范围。默认值为 1.0。

nn.Linear():PyTorch 中实现全连接层(线性层)的模块,其作用是对输入张量进行线性变换

参数名类型默认值说明
in_featuresint输入样本的特征维度(例如,每个样本的特征数量)。
out_featuresint输出样本的特征维度(例如,全连接层的神经元数量)。
biasboolTrue是否添加偏置项。设为 False 时,输出为 y = xA^T
    def __init__(
            self, n_src_vocab, n_trg_vocab, src_pad_idx, trg_pad_idx,
            d_word_vec=512, d_model=512, d_inner=2048,
            n_layers=6, n_head=8, d_k=64, d_v=64, dropout=0.1, n_position=200,
            trg_emb_prj_weight_sharing=True, emb_src_trg_weight_sharing=True,
            scale_emb_or_prj='prj'):

        super().__init__()

        self.src_pad_idx, self.trg_pad_idx = src_pad_idx, trg_pad_idx

        # In section 3.4 of paper "Attention Is All You Need", there is such detail:
        # "In our model, we share the same weight matrix between the two
        # embedding layers and the pre-softmax linear transformation...
        # In the embedding layers, we multiply those weights by \sqrt{d_model}".
        #
        # Options here:
        #   'emb': multiply \sqrt{d_model} to embedding output
        #   'prj': multiply (\sqrt{d_model} ^ -1) to linear projection output
        #   'none': no multiplication

        assert scale_emb_or_prj in ['emb', 'prj', 'none']
        scale_emb = (scale_emb_or_prj == 'emb') if trg_emb_prj_weight_sharing else False
        self.scale_prj = (scale_emb_or_prj == 'prj') if trg_emb_prj_weight_sharing else False
        self.d_model = d_model

        self.encoder = Encoder(
            n_src_vocab=n_src_vocab, n_position=n_position,
            d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner,
            n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v,
            pad_idx=src_pad_idx, dropout=dropout, scale_emb=scale_emb)

        self.decoder = Decoder(
            n_trg_vocab=n_trg_vocab, n_position=n_position,
            d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner,
            n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v,
            pad_idx=trg_pad_idx, dropout=dropout, scale_emb=scale_emb)

        self.trg_word_prj = nn.Linear(d_model, n_trg_vocab, bias=False)

        for p in self.parameters():
            if p.dim() > 1:
                nn.init.xavier_uniform_(p) 

        assert d_model == d_word_vec, \
        'To facilitate the residual connections, \
         the dimensions of all module outputs shall be the same.'

        if trg_emb_prj_weight_sharing:
            # Share the weight between target word embedding & last dense layer
            self.trg_word_prj.weight = self.decoder.trg_word_emb.weight

        if emb_src_trg_weight_sharing:
            self.encoder.src_word_emb.weight = self.decoder.trg_word_emb.weight
③ 前向传播
代码运行流程
forward 方法
|
|-- 输入
|   |-- src_seq: 源序列
|   |-- trg_seq: 目标序列
|
|-- 生成掩码
|   |-- 源序列掩码
|       |-- src_mask = get_pad_mask(src_seq, self.src_pad_idx)
|   |-- 目标序列掩码
|       |-- trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)
|
|-- 编码器处理
|   |-- 调用编码器对源序列进行编码
|       |-- enc_output, *_ = self.encoder(src_seq, src_mask)
|
|-- 解码器处理
|   |-- 调用解码器生成目标序列
|       |-- dec_output, *_ = self.decoder(trg_seq, trg_mask, enc_output, src_mask)
|
|-- 线性投影
|   |-- 将解码器输出映射到词汇表大小
|       |-- seq_logit = self.trg_word_prj(dec_output)
|
|-- 缩放操作(可选)
|   |-- if self.scale_prj:
|       |-- seq_logit *= self.d_model ** -0.5
|
|-- 调整形状并返回结果
|   |-- return seq_logit.view(-1, seq_logit.size(2))

src_seq:输入的源序列,通常是一个 torch.Tensor 类型的张量。它代表源语言句子中各个单词对应的索引序列。

trg_seq:输入的目标序列,同样是 torch.Tensor 类型的张量。它代表目标语言句子中各个单词对应的索引序列。

src_mask:源序列的掩码矩阵.

trg_mask:目标序列的掩码矩阵,由两部分组合而成。

enc_output:编码器的输出结果

dec_output:解码器的输出结果

self.scale_prj:一个布尔类型的标志,在类的初始化方法中定义,标识是否进行缩放操作

seq_logit:将解码器的输出 dec_output 通过 self.trg_word_prj 线性层进行映射后得到的结果。

self.d_model:模型的隐藏层维度,在类的初始化方法中定义。

get_pad_mask():用于生成填充掩码(padding mask)。在处理变长序列时,通常会将序列填充到相同的长度,填充的部分在后续的计算中是没有意义的,需要通过掩码机制将其屏蔽掉。

self.encoder():编码器模块,编码器的主要作用是对输入的源序列进行编码,将输入的源序列转换为一系列的特征表示,这些特征表示包含了源序列的语义信息。

self.decoder():解码器模块,解码器的主要作用是根据编码器的输出和已经生成的部分目标序列,逐步生成完整的目标序列。

self.trg_word_prj():线性层,用于将解码器的输出映射到目标词汇表大小的维度上。

view():PyTorch 中 torch.Tensor 类的一个方法,用于改变张量的形状。它可以在不改变张量数据的情况下,重新调整张量的维度和大小。

参数类型描述
*shape整数或 torch.Size 对象新的形状。可以传入多个整数参数,也可以传入一个 torch.Size 对象来指定新的形状。例如,tensor.view(2, 3) 表示将张量调整为形状为 (2, 3) 的二维张量;tensor.view(torch.Size([2, 3])) 也有相同的效果。

size():PyTorch 中 torch.Tensor 类的一个方法,用于返回张量的形状。它会返回一个 torch.Size 对象,该对象类似于元组,可以通过索引访问每个维度的大小。

参数类型描述
dim(可选)整数指定要返回大小的维度索引。如果不提供该参数,则返回整个张量的形状(即 torch.Size 对象);如果提供了该参数,则返回指定维度的大小。例如,tensor.size() 会返回整个张量的形状,而 tensor.size(1) 会返回张量第二维的大小。
    def forward(self, src_seq, trg_seq):

        src_mask = get_pad_mask(src_seq, self.src_pad_idx)
        trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)

        enc_output, *_ = self.encoder(src_seq, src_mask)
        dec_output, *_ = self.decoder(trg_seq, trg_mask, enc_output, src_mask)
        seq_logit = self.trg_word_prj(dec_output)
        if self.scale_prj:
            seq_logit *= self.d_model ** -0.5

        return seq_logit.view(-1, seq_logit.size(2))
④ 完整代码
class Transformer(nn.Module):
    ''' A sequence to sequence model with attention mechanism. '''

    def __init__(
            self, n_src_vocab, n_trg_vocab, src_pad_idx, trg_pad_idx,
            d_word_vec=512, d_model=512, d_inner=2048,
            n_layers=6, n_head=8, d_k=64, d_v=64, dropout=0.1, n_position=200,
            trg_emb_prj_weight_sharing=True, emb_src_trg_weight_sharing=True,
            scale_emb_or_prj='prj'):

        super().__init__()

        self.src_pad_idx, self.trg_pad_idx = src_pad_idx, trg_pad_idx

        # In section 3.4 of paper "Attention Is All You Need", there is such detail:
        # "In our model, we share the same weight matrix between the two
        # embedding layers and the pre-softmax linear transformation...
        # In the embedding layers, we multiply those weights by \sqrt{d_model}".
        #
        # Options here:
        #   'emb': multiply \sqrt{d_model} to embedding output
        #   'prj': multiply (\sqrt{d_model} ^ -1) to linear projection output
        #   'none': no multiplication

        assert scale_emb_or_prj in ['emb', 'prj', 'none']
        scale_emb = (scale_emb_or_prj == 'emb') if trg_emb_prj_weight_sharing else False
        self.scale_prj = (scale_emb_or_prj == 'prj') if trg_emb_prj_weight_sharing else False
        self.d_model = d_model

        self.encoder = Encoder(
            n_src_vocab=n_src_vocab, n_position=n_position,
            d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner,
            n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v,
            pad_idx=src_pad_idx, dropout=dropout, scale_emb=scale_emb)

        self.decoder = Decoder(
            n_trg_vocab=n_trg_vocab, n_position=n_position,
            d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner,
            n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v,
            pad_idx=trg_pad_idx, dropout=dropout, scale_emb=scale_emb)

        self.trg_word_prj = nn.Linear(d_model, n_trg_vocab, bias=False)

        for p in self.parameters():
            if p.dim() > 1:
                nn.init.xavier_uniform_(p) 

        assert d_model == d_word_vec, \
        'To facilitate the residual connections, \
         the dimensions of all module outputs shall be the same.'

        if trg_emb_prj_weight_sharing:
            # Share the weight between target word embedding & last dense layer
            self.trg_word_prj.weight = self.decoder.trg_word_emb.weight

        if emb_src_trg_weight_sharing:
            self.encoder.src_word_emb.weight = self.decoder.trg_word_emb.weight


    def forward(self, src_seq, trg_seq):

        src_mask = get_pad_mask(src_seq, self.src_pad_idx)
        trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)

        enc_output, *_ = self.encoder(src_seq, src_mask)
        dec_output, *_ = self.decoder(trg_seq, trg_mask, enc_output, src_mask)
        seq_logit = self.trg_word_prj(dec_output)
        if self.scale_prj:
            seq_logit *= self.d_model ** -0.5

        return seq_logit.view(-1, seq_logit.size(2))

Ⅴ、辅助函数

① get_pad_mask 生成填充掩码

seq:输入序列,包含 token 的索引,其中 pad_idx 表示填充位置(如 0 或 [PAD]

pad_idx:填充 token 的索引值,所有等于 pad_idx 的位置会被掩码(标记为 False

张量.unsqueeze():在指定维度插入大小为1的新维度,常用于调整张量形状以适应模型输入

参数类型/默认值说明
dimint插入维度的索引(如0在第0维插入)
def get_pad_mask(seq, pad_idx):
    return (seq != pad_idx).unsqueeze(-2)
② get_subsequent_mask:生成后续位置掩码

seq:输入序列,形状通常为 [batch_size, seq_len]

sz_b:batch size(批大小),即 seq 的第 0 维大小

len_s:序列长度(seq_len),即 seq 的第 1 维大小

subsequent_mask:最终的后续位置掩码,形状为 [1, len_s, len_s],确保解码时第 i 个位置只能关注到前 i 个位置(包括自己),无法看到未来信息

size():返回张量或数组的维度大小(如(3,4)表示3行4列)

参数类型/默认值说明
dimint (可选)指定维度索引(如dim=0返回第0维大小)

torch.triu():返回矩阵的上三角部分(含对角线及以上元素)

参数类型/默认值说明
inputTensor输入张量
diagonalint (0)对角线偏移量(正数上移)

torch.ones():创建全1张量,支持形状和数据类型指定

参数类型/默认值说明
sizetuple/int张量形状(如(2,3)
dtypedtype (可选)数据类型(默认torch.float32

bool():将输入转换为布尔类型(非零/非空为True,否则False

参数类型/默认值说明
x任意类型输入值(如数值、字符串、列表等)
def get_subsequent_mask(seq):
    ''' For masking out the subsequent info. '''
    sz_b, len_s = seq.size()
    subsequent_mask = (1 - torch.triu(
        torch.ones((1, len_s, len_s), device=seq.device), diagonal=1)).bool()
    return subsequent_mask

Ⅵ、完整代码

''' Define the Transformer model '''
import torch
import torch.nn as nn
import numpy as np
from .Layers import EncoderLayer, DecoderLayer


__author__ = "Yu-Hsiang Huang"


def get_pad_mask(seq, pad_idx):
    return (seq != pad_idx).unsqueeze(-2)


def get_subsequent_mask(seq):
    ''' For masking out the subsequent info. '''
    sz_b, len_s = seq.size()
    subsequent_mask = (1 - torch.triu(
        torch.ones((1, len_s, len_s), device=seq.device), diagonal=1)).bool()
    return subsequent_mask


class PositionalEncoding(nn.Module):

    def __init__(self, d_hid, n_position=200):
        super(PositionalEncoding, self).__init__()

        # Not a parameter
        self.register_buffer('pos_table', self._get_sinusoid_encoding_table(n_position, d_hid))

    def _get_sinusoid_encoding_table(self, n_position, d_hid):
        ''' Sinusoid position encoding table '''
        # TODO: make it with torch instead of numpy

        def get_position_angle_vec(position):
            return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]

        sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
        sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])  # dim 2i
        sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])  # dim 2i+1

        return torch.FloatTensor(sinusoid_table).unsqueeze(0)

    def forward(self, x):
        return x + self.pos_table[:, :x.size(1)].clone().detach()


class Encoder(nn.Module):
    ''' A encoder model with self attention mechanism. '''

    def __init__(
            self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
            d_model, d_inner, pad_idx, dropout=0.1, n_position=200, scale_emb=False):

        super().__init__()

        self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx)
        self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
        self.dropout = nn.Dropout(p=dropout)
        self.layer_stack = nn.ModuleList([
            EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.scale_emb = scale_emb
        self.d_model = d_model

    def forward(self, src_seq, src_mask, return_attns=False):

        enc_slf_attn_list = []

        # -- Forward
        enc_output = self.src_word_emb(src_seq)
        if self.scale_emb:
            enc_output *= self.d_model ** 0.5
        enc_output = self.dropout(self.position_enc(enc_output))
        enc_output = self.layer_norm(enc_output)

        for enc_layer in self.layer_stack:
            enc_output, enc_slf_attn = enc_layer(enc_output, slf_attn_mask=src_mask)
            enc_slf_attn_list += [enc_slf_attn] if return_attns else []

        if return_attns:
            return enc_output, enc_slf_attn_list
        return enc_output,


class Decoder(nn.Module):
    ''' A decoder model with self attention mechanism. '''

    def __init__(
            self, n_trg_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
            d_model, d_inner, pad_idx, n_position=200, dropout=0.1, scale_emb=False):

        super().__init__()

        self.trg_word_emb = nn.Embedding(n_trg_vocab, d_word_vec, padding_idx=pad_idx)
        self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
        self.dropout = nn.Dropout(p=dropout)
        self.layer_stack = nn.ModuleList([
            DecoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.scale_emb = scale_emb
        self.d_model = d_model

    def forward(self, trg_seq, trg_mask, enc_output, src_mask, return_attns=False):

        dec_slf_attn_list, dec_enc_attn_list = [], []

        # -- Forward
        dec_output = self.trg_word_emb(trg_seq)
        if self.scale_emb:
            dec_output *= self.d_model ** 0.5
        dec_output = self.dropout(self.position_enc(dec_output))
        dec_output = self.layer_norm(dec_output)

        for dec_layer in self.layer_stack:
            dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
                dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask)
            dec_slf_attn_list += [dec_slf_attn] if return_attns else []
            dec_enc_attn_list += [dec_enc_attn] if return_attns else []

        if return_attns:
            return dec_output, dec_slf_attn_list, dec_enc_attn_list
        return dec_output,


class Transformer(nn.Module):
    ''' A sequence to sequence model with attention mechanism. '''

    def __init__(
            self, n_src_vocab, n_trg_vocab, src_pad_idx, trg_pad_idx,
            d_word_vec=512, d_model=512, d_inner=2048,
            n_layers=6, n_head=8, d_k=64, d_v=64, dropout=0.1, n_position=200,
            trg_emb_prj_weight_sharing=True, emb_src_trg_weight_sharing=True,
            scale_emb_or_prj='prj'):

        super().__init__()

        self.src_pad_idx, self.trg_pad_idx = src_pad_idx, trg_pad_idx

        # In section 3.4 of paper "Attention Is All You Need", there is such detail:
        # "In our model, we share the same weight matrix between the two
        # embedding layers and the pre-softmax linear transformation...
        # In the embedding layers, we multiply those weights by \sqrt{d_model}".
        #
        # Options here:
        #   'emb': multiply \sqrt{d_model} to embedding output
        #   'prj': multiply (\sqrt{d_model} ^ -1) to linear projection output
        #   'none': no multiplication

        assert scale_emb_or_prj in ['emb', 'prj', 'none']
        scale_emb = (scale_emb_or_prj == 'emb') if trg_emb_prj_weight_sharing else False
        self.scale_prj = (scale_emb_or_prj == 'prj') if trg_emb_prj_weight_sharing else False
        self.d_model = d_model

        self.encoder = Encoder(
            n_src_vocab=n_src_vocab, n_position=n_position,
            d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner,
            n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v,
            pad_idx=src_pad_idx, dropout=dropout, scale_emb=scale_emb)

        self.decoder = Decoder(
            n_trg_vocab=n_trg_vocab, n_position=n_position,
            d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner,
            n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v,
            pad_idx=trg_pad_idx, dropout=dropout, scale_emb=scale_emb)

        self.trg_word_prj = nn.Linear(d_model, n_trg_vocab, bias=False)

        for p in self.parameters():
            if p.dim() > 1:
                nn.init.xavier_uniform_(p) 

        assert d_model == d_word_vec, \
        'To facilitate the residual connections, \
         the dimensions of all module outputs shall be the same.'

        if trg_emb_prj_weight_sharing:
            # Share the weight between target word embedding & last dense layer
            self.trg_word_prj.weight = self.decoder.trg_word_emb.weight

        if emb_src_trg_weight_sharing:
            self.encoder.src_word_emb.weight = self.decoder.trg_word_emb.weight


    def forward(self, src_seq, trg_seq):

        src_mask = get_pad_mask(src_seq, self.src_pad_idx)
        trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)

        enc_output, *_ = self.encoder(src_seq, src_mask)
        dec_output, *_ = self.decoder(trg_seq, trg_mask, enc_output, src_mask)
        seq_logit = self.trg_word_prj(dec_output)
        if self.scale_prj:
            seq_logit *= self.d_model ** -0.5

        return seq_logit.view(-1, seq_logit.size(2))

二、利用定义的Transformer架构模型完成文章标题生成任务

数据文件

通过网盘分享的文件:生成文章标题
链接: https://pan.baidu.com/s/1PraH5soeHEZJ2t0ZIJGiwg?pwd=8pwy 提取码: 8pwy 
--来自百度网盘超级会员v3的分享

1.配置文件 config.py

model_path:模型保存的路径,训练完成后模型会存储在该目录下

input_max_length:输入序列的最大长度,输入序列的长度将被截断或填充到最大长度个token

output_max_length:输出序列的最大长度,生成的输出序列长度不超过最大长度个token

epoch:训练的轮数,表示模型将遍历整个数据集的次数

batch_size:每次训练时使用的样本数量,表示每次训练会处理的样本数量

optimizer:优化器的类型

learning_rate:学习率,控制模型参数更新的步长

seed:随机种子,用于确保实验的可重复性

vocab_size:词汇表的大小,表示模型可以识别的不同token的数量

vocab_path:词汇表的文件路径

train_data_path:训练数据集的路径

valid_data_path:验证数据集的路径

beam_size:束搜索(Beam Search)的宽度,用于生成序列时保留的候选序列数量

# -*- coding: utf-8 -*-

"""
配置参数信息
"""
import os
import torch

Config = {
    "model_path": "output",
    "input_max_length": 120,
    "output_max_length": 30,
    "epoch": 200,
    "batch_size": 32,
    "optimizer": "adam",
    "learning_rate":1e-3,
    "seed":42,
    "vocab_size":6219,
    "vocab_path":"vocab.txt",
    "train_data_path": r"sample_data.json",
    "valid_data_path": r"sample_data.json",
    "beam_size":5
    }


2.数据加载文件 loader.py

Ⅰ、代码运行流程

DataLoader运行流程树状图
├─ 1. 初始化配置
│   ├─ a. 加载词汇表 (load_vocab)
│   │   ├─ 来源路径: config["vocab_path"] [1](@ref)
│   │   └─ 构建词到索引的映射字典
│   ├─ b. 配置更新
│   │   ├─ vocab_size: 词表实际长度
│   │   ├─ pad_idx: 填充符索引([PAD])
│   │   ├─ start_idx: 起始符索引([CLS])
│   │   └─ end_idx: 结束符索引([SEP]) [6](@ref)
├─ 2. 数据加载与预处理
│   ├─ a. 读取原始数据文件 (load)
│   │   ├─ 路径: config["train_data_path"]
│   │   ├─ 逐行解析JSON格式的title和content字段
│   │   └─ 调用prepare_data处理每对(title, content)
│   ├─ b. 序列编码 (encode_sentence)
│   │   ├─ 添加特殊标记:[CLS]在头部,[SEP]在尾部 [6](@ref)
│   │   ├─ 文本转索引(未登录词用[UNK]表示)
│   │   └─ 截断/填充至max_length [1](@ref)
│   ├─ c. 张量转换
│   │   ├─ input_seq: 输入序列(无[CLS],用于模型输入)
│   │   ├─ output_seq: 输出序列(带[CLS],用于解码器输入)
│   │   └─ gold: 目标序列(带[SEP],用于计算损失) [6](@ref)
├─ 3. DataLoader封装
│   ├─ a. 创建DataGenerator实例
│   │   ├─ 实现__len__和__getitem__方法 [4,6](@ref)
│   │   └─ 存储数据为LongTensor三元组列表
│   ├─ b. 参数配置
│   │   ├─ batch_size: 批次大小
│   │   ├─ shuffle: 是否打乱顺序(默认True)
│   │   └─ 自动分批机制:按索引获取数据 [4,6](@ref)
├─ 4. 批次生成流程
│   ├─ a. 迭代器启动
│   │   ├─ 调用DataLoader的__iter__方法
│   │   └─ 创建_DataLoaderIter对象 [5](@ref)
│   ├─ b. 批次采样
│   │   ├─ 根据batch_size划分索引
│   │   └─ 若shuffle=True则随机采样 [6](@ref)
│   ├─ c. 数据组装
│   │   ├─ 调用__getitem__获取单个样本
│   │   └─ 自动拼接为(batch_input, batch_output, batch_gold) [4,6](@ref)
└─ 5. 训练循环
    ├─ a. 遍历DataLoader
    │   ├─ 每个iteration获取一个批次
    │   └─ 数据自动迁移至GPU(若可用) [4](@ref)
    └─ b. 数据维度示例
        ├─ input_seq: (batch_size, input_max_length)
        ├─ output_seq: (batch_size, output_max_length)
        └─ gold: (batch_size, output_max_length) [6](@ref)

Ⅱ、初始化方法 __init__

data_path:数据文件路径,指定原始语料的位置(如 data/train.txt),用于后续加载文本数据。

config:全局配置字典,存储模型参数与训练设置(如 batch_sizemax_seq_len),并在初始化时更新词汇相关参数。

logger:日志记录器,输出调试信息(如词汇表加载状态、数据处理进度)。

self.config:更新后的配置字典,添加或覆盖一些键值

self.logger:日志记录器,记录类初始化过程中的关键事件(如词汇表加载成功/失败)

self.path:原始数据路径,指向待处理的文本文件(如新闻语料、对话数据)

self.vocab:词汇表映射字典,通过 load_vocab(config["vocab_path"]) 加载,格式为 { "word": index }

self.config["vocab_size"]:词汇表实际长度

self.config["pad_idx"]:填充符索引(如 0)

self.config["start_idx"]:起始符索引(如 101,对应 [CLS]

self.config["end_idx"]:结束符索引(如 102,对应 [SEP]

load_vocab():词汇表加载,从vocab_path加载词汇表

self.load():文本读取,从 self.path 加载原始语料。

len():返回对象的长度或元素数量,支持字符串、列表、元组、字典等可迭代对象。对于字符串,返回字符数(单字节或 Unicode 字符)

参数类型说明
obj可迭代对象(如 strlistdict必需,需计算长度的对象。
    def __init__(self, data_path, config, logger):
        self.config = config
        self.logger = logger
        self.path = data_path
        self.vocab = load_vocab(config["vocab_path"])
        self.config["vocab_size"] = len(self.vocab)
        self.config["pad_idx"] = self.vocab["[PAD]"]
        self.config["start_idx"] = self.vocab["[CLS]"]
        self.config["end_idx"] = self.vocab["[SEP]"]
        self.load()

Ⅲ、读取数据文件 load

self.data:一个列表,用于存储从文件中读取并处理后的数据

self.path:类的一个实例属性,是一个字符串类型的变量,代表要读取的文件的路径

f:文件对象,是调用 open 函数打开文件后返回的对象

line:在文件读取循环中,line 代表文件中的每一行内容,是一个字符串。

title:从文件中每一行解析出的字典中的 "title" 键对应的值,通常是一个字符串,表示某条数据的标题

content:从文件中每一行解析出的字典中的 "content" 键对应的值,通常是一个字符串,表示某条数据的具体内容。

self.preprare_data():类的一个实例方法,将输入输出转化为序列

open():打开文件并返回文件对象,用于读写操作。支持多种模式(如读取、写入、追加)和编码格式

参数类型/默认值说明
file字符串必需,文件路径(绝对或相对路径)。
mode字符串('r'文件模式:'r'(只读)、'w'(写入覆盖)、'a'(追加)、'b'(二进制模式)等。
encoding字符串(None文本模式下的编码(如 'utf-8'),默认使用系统编码。
errors字符串(None编码错误处理方式(如 'ignore' 忽略错误)。
newline字符串(None控制换行符解析(如 '\n')。

enumerate():将可迭代对象(如列表、字符串)转换为索引-元素对的迭代器,常用于 for 循环中同时获取索引和值

参数类型/默认值说明
iterable可迭代对象(如 liststr必需,需遍历的对象。
start整数(0可选,索引起始值(如 start=1 从 1 开始计数)。

json.loads():将 JSON 格式的字符串解析为 Python 对象(如字典、列表),常用于处理 API 响应或文件中的 JSON 数据

参数类型/默认值说明
json_string字符串必需,需解析的 JSON 字符串(如 '{"name": "Alice"}')。
object_hook函数(None可选,自定义 JSON 对象转换为 Python 对象的方式(如解析为自定义类实例)。
parse_float函数(None可选,自定义浮点数解析逻辑(如转换为 Decimal 类型)。
    def load(self):
        self.data = []
        with open(self.path, encoding="utf8") as f:
            for i, line in enumerate(f):
                line = json.loads(line)
                title = line["title"]
                content = line["content"]
                self.prepare_data(title, content)
        return

Ⅳ、序列编码 encode_sentence 

text:字符串,表示需要进行编码的文本内容

max_length:代表编码后序列的最大长度。如果编码后的 input_id 长度小于 max_length,则会进行填充操作;如果大于 max_length,可能需要进行截断

with_cls_token:布尔类型的参数,默认值为 True。当它为 True 时,会在编码后的序列开头添加 [CLS] 标记对应的索引;当为 False 时,则不添加

with_sep_token:布尔类型的参数,默认值为 True。当它为 True 时,会在编码后的序列结尾添加 [SEP] 标记对应的索引;当为 False 时,则不添加

input_id:列表,用于存储文本编码后得到的索引序列

char:循环变量,在遍历 text 时,char 依次代表 text 中的每个字符

self.padding():补齐或截断输入序列函数

列表.append():向列表末尾添加单个元素,直接修改原列表,无返回值。支持任意数据类型(如整数、字符串、列表)

参数类型说明
x任意类型必需,要添加的元素(如 list.append(42) 或 list.append([1,2]))。
    #文本到对应的index
    #头尾分别加入[cls]和[sep]
    def encode_sentence(self, text, max_length, with_cls_token=True, with_sep_token=True):
        input_id = []
        if with_cls_token:
            input_id.append(self.vocab["[CLS]"])
        for char in text:
            input_id.append(self.vocab.get(char, self.vocab["[UNK]"]))
        if with_sep_token:
            input_id.append(self.vocab["[SEP]"])
        input_id = self.padding(input_id, max_length)
        return input_id

Ⅴ、补齐或截断输入序列 padding

input_id:列表,包含了经过编码后的文本索引序列

length:整数,代表期望的序列长度

self.vocab:全局配置字典,存储模型参数与训练设置(如 batch_sizemax_seq_len),并在初始化时更新词汇相关参数

len():Python 的内置函数,用于返回对象的长度(元素个数)

参数名数据类型是否必需说明
object字符串、列表、元组、字典、集合等可计算长度的对象要计算长度的对象,根据对象类型的不同,len() 函数返回其对应的长度值,如字符串的字符数、列表的元素个数等
    #补齐或截断输入的序列,使其可以在一个batch内运算
    def padding(self, input_id, length):
        input_id = input_id[:length]
        input_id += [self.vocab["[PAD]"]] * (length - len(input_id))
        return input_id

Ⅵ、将输入输出转化为序列 prepare_data

title:字符串参数,代表文本数据的标题

content:字符串参数,代表文本数据的具体内容

input_seq:列表,是调用 self.encode_sentence 方法对 content 进行编码后得到的结果

output_seq:列表,是调用 self.encode_sentence 方法对 title 进行编码后得到的结果

self.encode_sentence():序列编码函数

列表.append():列表对象的一个方法,用于在列表的末尾添加一个新元素

参数名数据类型是否必需说明
object任意 Python 数据类型要添加到列表末尾的元素。

torch.LongTensor():PyTorch 中的一个函数,用于创建一个 torch.LongTensor 类型的张量(Tensor)。torch.LongTensor 是 64 位整数类型的张量,常用于存储索引等整数值。

参数名数据类型是否必需说明
data数值、列表、元组、torch.Tensor 等用于初始化张量的数据。如果传入数值,会创建一个包含该数值的标量张量;传入列表或元组,会根据其元素创建相应形状的张量;传入 torch.Tensor,会尝试将其转换为 torch.LongTensor 类型。如果不提供该参数,则创建一个空张量。
size整数或整数元组用于指定张量的形状。例如,torch.LongTensor(3, 4) 会创建一个形状为 (3, 4) 的张量。如果提供了 data 参数,size 参数通常不需要使用。
outtorch.Tensor可选参数,用于指定输出的张量对象。如果提供了该参数,生成的张量会存储在这个指定的对象中。
    #输入输出转化成序列
    def prepare_data(self, title, content):
        input_seq = self.encode_sentence(content, self.config["input_max_length"], False, False) #输入序列
        output_seq = self.encode_sentence(title, self.config["output_max_length"], True, False) #输出序列

        gold = self.encode_sentence(title, self.config["output_max_length"], False, True) #不进入模型,用于计算loss

        self.data.append([torch.LongTensor(input_seq),
                          torch.LongTensor(output_seq),
                          torch.LongTensor(gold)])
        return

Ⅶ、 类内魔术方法

① 获取数据长度

self.data:类的一个实例属性,通常是一个可迭代对象

len():Python 的内置函数,用于返回对象的长度或元素个数

参数名数据类型是否必需说明
object字符串、列表、元组、字典、集合等可计算长度的对象要计算长度的对象,根据对象类型的不同,len() 函数返回其对应的长度值,如字符数、元素个数、键值对数量等
    def __len__(self):
        return len(self.data)
② 根据索引返回值

index:代表要从 self.data 中获取元素的索引位置

self.data:类的实例属性,通常是一个可迭代对象,像列表、元组或者其他支持索引访问的数据结构

    def __getitem__(self, index):
        return self.data[index]

Ⅷ、加载词表

token_dict:空字典,用于存储词汇表中每个词(token)及其对应的索引

vocab_path:字符串类型的参数,代表词汇表文件的路径。该文件通常是一个文本文件,每行包含一个词。

f:文件对象,是调用 open 函数打开词汇表文件后返回的对象。

index:循环变量,是 enumerate 函数为每一行分配的索引,从 0 开始计数。

line:在文件读取循环中,line 代表词汇表文件中的每一行内容,是一个字符串。

token:字符串,是对 line 进行 strip 处理后得到的词。

open():Python 内置函数,用于打开文件并返回一个文件对象。

参数名数据类型是否必需说明
file字符串要打开的文件的路径,可以是相对路径或绝对路径。
mode字符串指定文件的打开模式,常见模式有:
'r':只读模式(默认),打开文件用于读取。
'w':写入模式,打开文件用于写入,如果文件已存在则覆盖,不存在则创建。
'a':追加模式,打开文件用于追加内容,如果文件不存在则创建。
'b':二进制模式,可与其他模式组合使用,如 'rb' 表示以二进制只读模式打开文件。
'+':更新模式,可与其他模式组合使用,如 'r+' 表示以读写模式打开文件。
buffering整数可选参数,用于设置缓冲策略:
-1:使用系统默认缓冲(默认值)。
0:不使用缓冲(仅适用于二进制模式)。
1:使用行缓冲(仅适用于文本模式)。
- 大于 1 的整数:指定缓冲区大小。
encoding字符串指定文件的编码格式,常见的编码格式有 'utf-8''gbk' 等。在处理文本文件时,需要指定正确的编码格式,否则可能会出现编码错误。
errors字符串指定如何处理编码错误,常见的值有 'strict'(默认,遇到错误抛出异常)、'ignore'(忽略错误)、'replace'(用 ? 替换错误字符)等。
newline字符串控制换行符的处理,常见的值有 None(使用系统默认换行符)、''(不转换换行符)、'\n'(只识别 \n 为换行符)等。

enumerate():Python 内置函数,用于将一个可迭代对象(如列表、元组、字符串等)组合为一个索引序列,同时列出数据和数据的索引。

参数名数据类型是否必需说明
iterable可迭代对象(如列表、元组、字符串等)要进行枚举的可迭代对象。
start整数可选参数,指定索引的起始值,默认值为 0。

字符串.strip():Python 字符串对象的方法,用于移除字符串首尾的指定字符(默认为空格、制表符、换行符等空白字符)。

参数名数据类型是否必需说明
chars字符串可选参数,指定要移除的字符集合。如果不提供该参数,则默认移除字符串首尾的空白字符。
def load_vocab(vocab_path):
    token_dict = {}
    with open(vocab_path, encoding="utf8") as f:
        for index, line in enumerate(f):
            token = line.strip()
            token_dict[token] = index
    return token_dict

Ⅸ、封装数据

data_path:字符串类型的参数,代表数据文件的路径

config:全局配置字典,存储模型参数与训练设置(如 batch_sizemax_seq_len),并在初始化时更新词汇相关参数。

logger:日志记录器对象,用于记录数据加载过程中的各种信息

shuffle:布尔类型的参数,默认值为 True。当 shuffle 为 True 时,DataLoader 在每个 epoch 开始时会对数据进行洗牌,即打乱数据的顺序;当为 False 时,数据将按照原始顺序加载。

dg:DataGenerator 类的一个实例对象。DataGenerator 类通常用于生成数据,它会根据 data_pathconfig 和 logger 对数据进行处理和组织,以便后续使用。

dl: DataLoader 类的一个实例对象。DataLoader 是 PyTorch 中用于批量加载数据的工具,它可以将数据分成多个批次,并且可以进行洗牌、并行加载等操作。

DataGenerator():用于生成数据,它会根据 data_pathconfig 和 logger 对数据进行处理和组织,以便后续使用。

DataLoader():PyTorch 中的一个实用类,用于批量加载数据

参数名数据类型是否必需说明
datasetDataset 类的实例要加载的数据集对象,该对象需要实现 __len__ 和 __getitem__ 方法,以便 DataLoader 可以获取数据集的长度和访问其中的元素。
batch_size整数每个批次加载的数据样本数量,默认值为 1。
shuffle布尔值是否在每个 epoch 开始时对数据进行洗牌(打乱顺序),默认值为 False。设置为 True 有助于提高模型训练的泛化能力。
samplerSampler 类的实例自定义的数据采样器,用于控制数据的采样方式。如果指定了 sampler,则 shuffle 参数将被忽略。
batch_samplerSampler 类的实例自定义的批次采样器,用于生成批次索引。如果指定了 batch_sampler,则 batch_sizeshuffle 和 sampler 参数将被忽略。
num_workers整数用于数据加载的子进程数量,默认值为 0,表示在主进程中进行数据加载。设置为大于 0 的值可以实现并行数据加载,提高数据加载的效率。
collate_fn可调用对象用于将多个样本组合成一个批次的函数。默认情况下,DataLoader 会使用一个简单的函数将样本组合成批次,但对于一些特殊的数据类型或需要自定义处理的情况,可以提供自定义的 collate_fn 函数。
pin_memory布尔值是否将数据加载到 CUDA 固定内存中,默认值为 False。对于使用 GPU 进行训练的情况,设置为 True 可以加快数据从 CPU 到 GPU 的传输速度。
drop_last布尔值是否丢弃最后一个不完整的批次(即样本数量不足 batch_size 的批次),默认值为 False
timeout数值数据加载的超时时间,单位为秒,默认值为 0。如果在指定时间内无法获取数据,则会抛出异常。
worker_init_fn可调用对象每个子进程启动时调用的函数,用于初始化子进程的状态。
multiprocessing_context字符串或 multiprocessing.context.BaseContext 类的实例用于多进程数据加载的上下文,默认值为 None
#用torch自带的DataLoader类封装数据
def load_data(data_path, config, logger, shuffle=True):
    dg = DataGenerator(data_path, config, logger)
    dl = DataLoader(dg, batch_size=config["batch_size"], shuffle=shuffle)
    return dl

Ⅹ、完整代码及测试

# -*- coding: utf-8 -*-

import json
import re
import os
import torch
import numpy as np
from torch.utils.data import Dataset, DataLoader
from collections import defaultdict
"""
数据加载
"""


class DataGenerator:
    def __init__(self, data_path, config, logger):
        self.config = config
        self.logger = logger
        self.path = data_path
        self.vocab = load_vocab(config["vocab_path"])
        self.config["vocab_size"] = len(self.vocab)
        self.config["pad_idx"] = self.vocab["[PAD]"]
        self.config["start_idx"] = self.vocab["[CLS]"]
        self.config["end_idx"] = self.vocab["[SEP]"]
        self.load()

    def load(self):
        self.data = []
        with open(self.path, encoding="utf8") as f:
            for i, line in enumerate(f):
                line = json.loads(line)
                title = line["title"]
                content = line["content"]
                self.prepare_data(title, content)
        return

    #文本到对应的index
    #头尾分别加入[cls]和[sep]
    def encode_sentence(self, text, max_length, with_cls_token=True, with_sep_token=True):
        input_id = []
        if with_cls_token:
            input_id.append(self.vocab["[CLS]"])
        for char in text:
            input_id.append(self.vocab.get(char, self.vocab["[UNK]"]))
        if with_sep_token:
            input_id.append(self.vocab["[SEP]"])
        input_id = self.padding(input_id, max_length)
        return input_id

    #补齐或截断输入的序列,使其可以在一个batch内运算
    def padding(self, input_id, length):
        input_id = input_id[:length]
        input_id += [self.vocab["[PAD]"]] * (length - len(input_id))
        return input_id

    #输入输出转化成序列
    def prepare_data(self, title, content):
        input_seq = self.encode_sentence(content, self.config["input_max_length"], False, False) #输入序列
        output_seq = self.encode_sentence(title, self.config["output_max_length"], True, False) #输出序列

        gold = self.encode_sentence(title, self.config["output_max_length"], False, True) #不进入模型,用于计算loss

        self.data.append([torch.LongTensor(input_seq),
                          torch.LongTensor(output_seq),
                          torch.LongTensor(gold)])
        return


    def __len__(self):
        return len(self.data)

    def __getitem__(self, index):
        return self.data[index]


def load_vocab(vocab_path):
    token_dict = {}
    with open(vocab_path, encoding="utf8") as f:
        for index, line in enumerate(f):
            token = line.strip()
            token_dict[token] = index
    return token_dict

#用torch自带的DataLoader类封装数据
def load_data(data_path, config, logger, shuffle=True):
    dg = DataGenerator(data_path, config, logger)
    dl = DataLoader(dg, batch_size=config["batch_size"], shuffle=shuffle)
    return dl



if __name__ == "__main__":
    from config import Config
    dl = load_data(Config["train_data_path"], Config, 1)
    # print(dl[1])
    batch_index = 0
    for batch_data in dl:
        if batch_index == 1:
            print("第 2 个批次的数据:", batch_data)
            break
        batch_index += 1

3.模型效果评估 evaluate.py

Ⅰ、模型初始化

config:配置对象的字典,包含评估器所需的各种配置信息,如验证数据路径、束搜索大小等

model:模型对象实例

logger:日志记录器对象,用于记录评估过程中的信息

self.config:配置对象的字典

self.model:模型对象实例

self.logger:日志记录器对象

self.valid_data:加载验证数据后得到的 DataLoader 对象,用于提供验证数据的批次

self.reverse_vocab:将词汇表中的索引映射回对应的词的字典,用于解读模型的输出

self.translator:进行序列生成任务的对象,生成预测结果

Translator():实现序列生成任务的类,根据输入序列生成输出序列

dict():创建字典对象,支持多种初始化方式(关键字参数、键值对列表等)

参数类型说明
**kwargs关键字参数如 dict(name="Alice", age=25)
iterable可迭代对象如 dict([("name", "Alice"), ("age", 25)])
mapping映射对象如 dict({"name": "Alice", "age": 25})

items():返回字典的键值对视图(dict_items 对象),用于遍历或转换为列表

class Evaluator:
    def __init__(self, config, model, logger):
        self.config = config
        self.model = model
        self.logger = logger
        self.valid_data = load_data(config["valid_data_path"], config, logger, shuffle=False)
        self.reverse_vocab = dict([(y, x) for x, y in self.valid_data.dataset.vocab.items()])
        self.translator = Translator(self.model,
                                     config["beam_size"],
                                     config["output_max_length"],
                                     config["pad_idx"],
                                     config["pad_idx"],
                                     config["start_idx"],
                                     config["end_idx"])

Ⅱ、解码 

seq:可迭代对象,通常为列表或元组。其中的每个元素代表一个索引值,这些索引值一般是由模型生成的输出序列经过编码后得到的,对应着词汇表中的某个词的索引。

self.reverse_vocab:字典,它的键是词汇表中的索引,值是对应的词。这个字典是通过将词汇表中的键值对反转得到的,用于将索引映射回对应的词。

idx:循环变量,在遍历 seq 的过程中,idx 依次代表 seq 中的每个元素,即每个索引值。

join():将可迭代对象中的元素用指定分隔符连接成字符串

参数类型说明
sep字符串分隔符(如 ", "
iterable可迭代对象需拼接的元素(如 ["a", "b", "c"]

int():将数值或字符串转换为整数,支持指定进制

参数类型/默认值说明
x数值/字符串输入值(如 int("A", 16) 返回 10)
baseint / 10进制(2-36,或 0 表示自动检测前缀如 0x
    def decode_seq(self, seq):
        return "".join([self.reverse_vocab[int(idx)] for idx in seq])

Ⅲ、测试模型

epoch:整数,代表当前进行评估的训练轮数。在深度学习训练中,一个 epoch 表示对整个训练数据集的一次完整遍历。

self.logger:日志记录器对象。它用于记录评估过程中的各种信息

self.model:深度学习模型对象。通常是已经训练好的模型,用于进行评估和推理。

self.stats_dict:defaultdict 对象,初始值为整数类型。它用于存储测试结果,例如可以记录不同评估指标的值。

index:循环变量,在遍历 self.valid_data 的过程中,index 代表当前批次的索引,从 0 开始计数。

batch_data:一个 DataLoader 对象,它会将验证数据分成多个批次,每个批次的数据包含输入序列、目标序列和用于计算损失的 gold 序列。

self.valid_data:DataLoader 对象。它是通过调用 load_data 函数加载验证数据后得到的,用于提供验证数据的批次。

input_seqs:列表,包含当前批次中所有样本的输入序列。

target_seqs:列表,包含当前批次中所有样本的目标序列。目标序列是模型期望生成的结果,用于与模型的预测结果进行比较。

gold:列表,用于计算损失

generate:对输入序列进行翻译或生成后得到的结果,列表,包含模型生成的输出序列。

self.translator.translate_sentence():对输入序列进行翻译或生成任务。它接受一个输入序列作为参数,使用模型和束搜索等算法生成输出序列。

self.decode_seq():将索引序列解码为文本。它接受一个索引序列作为参数,通过 self.reverse_vocab 字典将索引映射回对应的词,然后将这些词拼接成一个字符串。

logger.info():记录 INFO 级别的日志

参数类型说明
msg字符串日志消息(支持 %s 格式化)。
*args可变参数填充格式化占位符的值。

model.eval():设置模型为评估模式(禁用 Dropout 和 BatchNorm 的训练行为)

cpu():将张量或模型从 GPU 迁移到 CPU

defaultdict():创建默认值字典,自动为缺失键生成默认值(需指定工厂函数)

参数类型说明
default_factory工厂函数如 int(默认值 0)、list(默认值空列表)
**kwargs关键字参数初始键值对(如 defaultdict(int, a=1, b=2)

enumerate():返回索引-元素对的迭代器,常用于遍历序列时获取位置信息

参数类型/默认值说明
iterable可迭代对象如列表、元组
startint / 0索引起始值(如 enumerate(["a", "b"], start=1)

张量.unsqueeze():在指定维度增加大小为 1 的维度(扩展张量形状)

参数类型说明
dimint需扩展的维度(如 unsqueeze(0) 在第 0 维扩展)
    def eval(self, epoch):
        self.logger.info("开始测试第%d轮模型效果:" % epoch)
        self.model.eval()
        self.model.cpu()
        self.stats_dict = defaultdict(int)  # 用于存储测试结果
        for index, batch_data in enumerate(self.valid_data):
            input_seqs, target_seqs, gold = batch_data
            for input_seq in input_seqs:
                generate = self.translator.translate_sentence(input_seq.unsqueeze(0))
                print("输入:", self.decode_seq(input_seq))
                print("输出:", self.decode_seq(generate))
                break
        return

Ⅳ、完整代码及测试 

lable:Python 列表,它包含了一系列整数值,代表了某种数据的标签信息

enumerate():返回索引和元素的迭代器

参数类型说明
iterable可迭代对象如列表、元组。
start整数索引起始值(默认 0)。
# -*- coding: utf-8 -*-
import torch
import collections
import io
import json
import six
import sys
import argparse
from loader import load_data
from collections import defaultdict, OrderedDict

from transformer.Translator import Translator

"""
模型效果测试
"""

class Evaluator:
    def __init__(self, config, model, logger):
        self.config = config
        self.model = model
        self.logger = logger
        self.valid_data = load_data(config["valid_data_path"], config, logger, shuffle=False)
        self.reverse_vocab = dict([(y, x) for x, y in self.valid_data.dataset.vocab.items()])
        self.translator = Translator(self.model,
                                     config["beam_size"],
                                     config["output_max_length"],
                                     config["pad_idx"],
                                     config["pad_idx"],
                                     config["start_idx"],
                                     config["end_idx"])

    def eval(self, epoch):
        self.logger.info("开始测试第%d轮模型效果:" % epoch)
        self.model.eval()
        self.model.cpu()
        self.stats_dict = defaultdict(int)  # 用于存储测试结果
        for index, batch_data in enumerate(self.valid_data):
            input_seqs, target_seqs, gold = batch_data
            for input_seq in input_seqs:
                generate = self.translator.translate_sentence(input_seq.unsqueeze(0))
                print("输入:", self.decode_seq(input_seq))
                print("输出:", self.decode_seq(generate))
                break
        return

    def decode_seq(self, seq):
        return "".join([self.reverse_vocab[int(idx)] for idx in seq])

if __name__ == "__main__":
    label = [2, 2, 2, 2, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

    print([(i, l) for i, l in enumerate(label)])

4.模型训练文件 main.py

Ⅰ、代码运行流程

├── 全局设置与导入
│   ├── 导入必要的库
│   │   ├── sys
│   │   ├── torch
│   │   ├── os
│   │   ├── random
│   │   ├── numpy as np
│   │   ├── time
│   │   ├── logging
│   │   ├── json
│   │   ├── Config 从 config 模块导入
│   │   ├── Evaluator 从 evaluate 模块导入
│   │   ├── load_data 从 loader 模块导入
│   │   └── Transformer 从 transformer.Models 导入
│   ├── 日志基础配置
│   │   └── logging.basicConfig(level = logging.INFO, format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
│   ├── 获取日志记录器
│   │   └── logger = logging.getLogger(__name__)
│   └── 设置随机种子
│       ├── seed = Config["seed"]
│       ├── random.seed(seed)
│       ├── np.random.seed(seed)
│       ├── torch.manual_seed(seed)
│       └── torch.cuda.manual_seed_all(seed)
├── 定义优化器选择函数
│   └── choose_optimizer(config, model)
│       ├── 获取优化器类型和学习率
│       │   ├── optimizer = config["optimizer"]
│       │   └── learning_rate = config["learning_rate"]
│       ├── 根据优化器类型返回对应优化器
│       │   ├── 如果 optimizer 是 "adam"
│       │   │   └── return torch.optim.Adam(model.parameters(), lr=learning_rate)
│       │   └── 如果 optimizer 是 "sgd"
│       │       └── return torch.optim.SGD(model.parameters(), lr=learning_rate)
├── 定义主函数
│   └── main(config)
│       ├── 创建保存模型的目录
│       │   └── 如果目录不存在
│       │       └── os.mkdir(config["model_path"])
│       ├── 记录配置信息日志
│       │   └── logger.info(json.dumps(config, ensure_ascii=False, indent=2))
│       ├── 初始化 Transformer 模型
│       │   └── model = Transformer(config["vocab_size"], config["vocab_size"], 0, 0, d_word_vec=128, d_model=128, d_inner=256, n_layers=1, n_head=2, d_k=64, d_v=64)
│       ├── 检查 GPU 可用性并迁移模型
│       │   ├── cuda_flag = torch.cuda.is_available()
│       │   └── 如果 cuda_flag 为真
│       │       ├── logger.info("gpu可以使用,迁移模型至gpu")
│       │       └── model = model.cuda()
│       ├── 选择优化器
│       │   └── optimizer = choose_optimizer(config, model)
│       ├── 加载训练数据
│       │   └── train_data = load_data(config["train_data_path"], config, logger)
│       ├── 加载效果测试类
│       │   └── evaluator = Evaluator(config, model, logger)
│       ├── 加载损失函数
│       │   └── loss_func = torch.nn.CrossEntropyLoss(ignore_index=0)
│       ├── 训练循环
│       │   └── for epoch in range(config["epoch"])
│       │       ├── epoch 计数加 1
│       │       ├── 设置模型为训练模式
│       │       │   └── model.train()
│       │       ├── 如果 cuda_flag 为真
│       │       │   └── model.cuda()
│       │       ├── 记录 epoch 开始日志
│       │       │   └── logger.info("epoch %d begin" % epoch)
│       │       ├── 初始化训练损失列表
│       │       │   └── train_loss = []
│       │       ├── 遍历训练数据批次
│       │       │   └── for index, batch_data in enumerate(train_data)
│       │       │       ├── 如果 cuda_flag 为真
│       │       │       │   └── batch_data = [d.cuda() for d in batch_data]
│       │       │       ├── 解包批次数据
│       │       │       │   ├── input_seq, target_seq, gold = batch_data
│       │       │       ├── 前向传播
│       │       │       │   └── pred = model(input_seq, target_seq)
│       │       │       ├── 计算损失
│       │       │       │   └── loss = loss_func(pred, gold.view(-1))
│       │       │       ├── 记录损失
│       │       │       │   └── train_loss.append(float(loss))
│       │       │       ├── 反向传播
│       │       │       │   ├── loss.backward()
│       │       │       │   ├── optimizer.step()
│       │       │       │   └── optimizer.zero_grad()
│       │       ├── 记录该 epoch 平均损失日志
│       │       │   └── logger.info("epoch average loss: %f" % np.mean(train_loss))
│       │       ├── 进行评估
│       │       │   └── evaluator.eval(epoch)
│       ├── 保存模型
│       │   ├── model_path = os.path.join(config["model_path"], "epoch_%d.pth" % epoch)
│       │   └── torch.save(model.state_dict(), model_path)
└── 程序入口
    └── if __name__ == "__main__":
        └── main(Config)

Ⅱ、导入文件

# -*- coding: utf-8 -*-
import sys
import torch
import os
import random
import os
import numpy as np
import time
import logging
import json
from config import Config
from evaluate import Evaluator
from loader import load_data

#这个transformer是本文件夹下的代码,和我们之前用来调用bert的transformers第三方库是两回事
from transformer.Models import Transformer
from loader import load_data

Ⅲ、日志配置

logger:日志记录器对象,用于记录数据加载过程中的各种信息,比如加载数据的进度、可能出现的错误信息、数据的统计信息(如数据量大小等)。

logging.basicConfig():配置日志系统的基本参数

参数类型说明
level整数/常量日志级别(如 logging.INFO)。
format字符串日志格式(如 '%(asctime)s - %(message)s')。

logging.getLogger():获取日志记录器实例

参数类型说明
name字符串日志记录器名称(默认 __name__)。
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

Ⅳ、设置随机数种子

seed:配置文件中定义的随机数种子

random.seed() 和 np.random.seed():设置随机数生成器的种子,确保可复现性

参数类型说明
seed整数随机种子值(如 42)。

torch.manual_seed() 和 torch.cuda.manual_seed_all():设置 PyTorch 的 CPU 和 GPU 随机种子

参数类型说明
seed整数随机种子值(如 42)。
seed = Config["seed"]
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)

Ⅴ、根据配置文件选择优化器

config:包含各种配置信息的字典

model:深度学习模型对象

optimizer:从 config 中提取出的字符串变量,用于表示优化器的类型

learning_rate:从 config 中提取出的数值变量,用于指定优化器在更新模型参数时的步长大小

torch.optim.Adam():实现 Adam 优化算法(自适应动量估计),结合动量和自适应学习率特性,适用于大多数深度学习任务

参数类型/默认值说明
paramsiterable模型参数(如 model.parameters()
lrfloat / 1e-3学习率
betasTuple / (0.9,0.999)动量衰减系数(一阶和二阶动量)
epsfloat / 1e-8数值稳定性修正项
weight_decayfloat / 0L2 正则化系数
amsgradbool / False是否使用 AMSGrad 变体

torch.optim.SGD():实现随机梯度下降(SGD)算法,支持动量和 Nesterov 加速梯度

参数类型/默认值说明
paramsiterable模型参数
lrfloat / 必填学习率
momentumfloat / 0动量系数(0 表示无动量)
dampeningfloat / 0动量阻尼系数(与 momentum 配合使用)
weight_decayfloat / 0L2 正则化系数
nesterovbool / False是否启用 Nesterov 动量

model.parameters(): 在 PyTorch 中,model.parameters() 是 nn.Module 类的方法,返回模型中所有可学习参数的生成器(如权重、偏置)。这些参数通常用于优化器的初始化(如 optim.Adam(model.parameters(), lr=0.001)

def choose_optimizer(config, model):
    optimizer = config["optimizer"]
    learning_rate = config["learning_rate"]
    if optimizer == "adam":
        return torch.optim.Adam(model.parameters(), lr=learning_rate)
    elif optimizer == "sgd":
        return torch.optim.SGD(model.parameters(), lr=learning_rate)

Ⅵ、主函数main

1.创建模型保存目录

config:配置文件字典

os.path.isdir():拼接多个路径组件,自动处理跨平台路径分隔符(如 Windows 的 \ 和 Linux/macOS 的 /

参数类型说明
*paths可变字符串多个路径组件,如 os.path.join("dir1", "dir2", "file.txt")

os.mkdir():Python 的 os 模块中的一个函数,用于创建一个新的目录(文件夹)。如果目录已经存在,该函数会引发 FileExistsError 异常

参数类型描述默认值是否必需
pathstr要创建的目录的路径
modeint设置目录的权限模式(Unix系统有效)0o777
*-用于强制使用关键字参数--
dir_fdint目录文件描述符None
    #创建保存模型的目录
    if not os.path.isdir(config["model_path"]):
        os.mkdir(config["model_path"])
2.记录配置信息并加载模型

config:包含各种配置信息的字典

d_word_vec:词嵌入的维度

d_model:模型的隐藏层维度

d_inner:前馈神经网络(Feed Forward Network, FNN)内部隐藏层的维度

n_layers:Transformer 模型的层数

n_head:多头注意力机制(Multi-Head Attention)中的头数

d_k:多头注意力机制中,每个头的键(Key)向量和查询(Query)向量的维度

d_v:多头注意力机制中,每个头的值(Value)向量的维度

logging.info():记录 INFO 级别的日志

参数类型说明
msg字符串日志消息(支持 %s 格式化)。
*args可变参数填充格式化占位符的值。

json.dumps():将 Python 对象转换为 JSON 字符串

参数类型描述默认值是否必需
objobject要序列化的 Python 对象
skipkeysbool是否跳过非基本类型键(True=跳过)False
ensure_asciibool是否确保输出 ASCII(True=转义非ASCII字符)True
check_circularbool是否检查循环引用True
allow_nanbool是否允许 NaN, Infinity 等特殊值True
clsclass自定义编码器类None
indentint/str缩进空格数或缩进字符串None
separatorstuple项目分隔符,格式为 (', ', ': ')None
defaultfunction处理不可序列化对象的函数None
sort_keysbool是否按键排序False
    #加载模型
    logger.info(json.dumps(config, ensure_ascii=False, indent=2))
    model = Transformer(config["vocab_size"], config["vocab_size"], 0, 0,
                        d_word_vec=128, d_model=128, d_inner=256,
                        n_layers=1, n_head=2, d_k=64, d_v=64,
                        )
3.检查GPU并迁移模型

cuda_flag:是否使用GPU加速的标志

logger:日志记录器对象,用于记录数据加载过程中的各种信息,比如加载数据的进度、可能出现的错误信息、数据的统计信息(如数据量大小等)。

model:模型的实例

torch.cuda.is_available():检查 GPU 是否可用

logging.info():记录 INFO 级别的日志

参数类型说明
msg字符串日志消息(支持 %s 格式化)。
*args可变参数填充格式化占位符的值。

cuda():将张量/模型迁移到 GPU

参数类型说明
device整数/字符串可选,指定 GPU 设备号(如 0 或 "cuda:0")。
    # 标识是否使用gpu
    cuda_flag = torch.cuda.is_available()
    if cuda_flag:
        logger.info("gpu可以使用,迁移模型至gpu")
        model = model.cuda()
4.加载优化器

optimizer:通过调用 choose_optimizer() 函数后得到的优化器对象

config:包含各种配置信息的字典

model:模型的实例

choose_optimizer(): 根据配置信息选择合适的优化器

    #加载优化器
    optimizer = choose_optimizer(config, model)
5.加载训练数据 

train_data:包含训练数据的可迭代对象

config:包含各种配置信息的字典

logger:日志记录器对象,用于记录数据加载过程中的各种信息,比如加载数据的进度、可能出现的错误信息、数据的统计信息(如数据量大小等)。

load_data():加载并处理训练数据

    # 加载训练数据
    train_data = load_data(config["train_data_path"], config, logger)
6.加载评估器

evaluator:Evaluator 类的一个实例对象,它的主要作用是对模型的性能进行评估

config:包含各种配置信息的字典

model:模型的实例

logger:日志记录器对象,用于记录程序运行过程中的各种信息,包括配置信息、训练过程中的损失值、评估指标等。

Evaluator(): 对模型进行评估。在初始化时,它接收 configmodel 和 logger 作为参数,以便获取必要的配置信息、使用待评估的模型以及记录评估过程中的信息。

    #加载效果测试类
    evaluator = Evaluator(config, model, logger)
7.加载损失函数

torch.nn.CrossEntropyLoss():计算交叉熵损失,常用于分类任务

参数类型说明
ignore_index整数忽略指定类别的损失计算(如填充符 0)。
weight张量类别权重(处理类别不平衡)。
    #加载loss
    loss_func = torch.nn.CrossEntropyLoss(ignore_index=0)
8.模型训练主流程 ⭐
① Epoch循环控制

config:配置文件中定义的配置字典

epoch:当前训练轮次(从1开始计数)

range():生成整数序列

参数类型说明
start整数起始值(默认 0)。
stop整数终止值(不包含)。
step整数步长(默认 1)。
    for epoch in range(config["epoch"]):
        epoch += 1
② 模型设置训练模式 

model.train():设置模型为训练模式(启用 Dropout、BatchNorm)

        model.train()
③ 设备切换

cuda_flag:是否使用GPU加速的标志

batch_data:当前批次的原始数据,通常为 [input_seq, target_seq, gold] 的列表

d:代表 batch_data 中的一个张量(Tensor)

cuda():将张量/模型迁移到 GPU

参数类型说明
device整数/字符串可选,指定 GPU 设备号(如 0 或 "cuda:0")。
            if cuda_flag:
                batch_data = [d.cuda() for d in batch_data]
④ 记录日志

logger:日志记录器,用于输出训练信息

epoch:当前训练轮次(从1开始计数)

logger.info():记录 INFO 级别的日志

参数类型说明
msg字符串日志消息(支持 %s 格式化)。
*args可变参数填充格式化占位符的值。
        logger.info("epoch %d begin" % epoch)
⑤ 初始化训练损失列表

train_loss:列表,存储当前epoch所有批次的损失值

        train_loss = []
⑥ 遍历训练数据批次

index:当前批次的索引号

train_data:训练数据迭代器,每次返回一个批次的数据

batch_data:当前批次的原始数据,通常为 [input_seq, target_seq, gold] 的列表

cuda_flag:是否使用GPU加速的标志

input_seq:输入序列,形状为 [batch_size, seq_len]

target_seq:目标序列(解码器输入),形状同 input_seq

gold:真实标签,形状为 [batch_size, seq_len]

d:代表 batch_data 中的一个张量(Tensor)

enumerate():返回索引和元素的迭代器

参数类型说明
iterable可迭代对象如列表、元组。
start整数索引起始值(默认 0)。

cuda():将张量/模型迁移到 GPU

参数类型说明
device整数/字符串可选,指定 GPU 设备号(如 0 或 "cuda:0")。
        for index, batch_data in enumerate(train_data):
            if cuda_flag:
                batch_data = [d.cuda() for d in batch_data]
            input_seq, target_seq, gold = batch_data
⑦ 前向传播、计算损失并记录、反向传播

pred:模型输出的预测结果,形状通常为 [batch_size * seq_len, vocab_size]

loss:计算得到的损失值(标量)

view():调整张量形状(类似 reshape),不改变数据

参数类型说明
*shape可变整数新形状,可用 -1 自动推断维度(如 view(2, -1))。

列表.append():向列表末尾添加元素

参数类型说明
x任意类型要添加的元素(如 [1, 2].append(3) → [1, 2, 3])。

float():将数值或字符串转换为浮点数

参数类型说明
x数值/字符串可选,默认返回 0.0;支持 "3.14" 或 "inf"(无穷大)。

loss.backward():反向传播计算梯度

参数类型说明
retain_graph布尔值是否保留计算图(默认 False,反向传播后释放)。

optimizer.step():根据梯度更新模型参数(如 SGD、Adam)

optimizer.zero_grad():清空所有参数的梯度,防止梯度累积

            pred = model(input_seq, target_seq)
            loss = loss_func(pred, gold.view(-1))

            train_loss.append(float(loss))
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
⑧ 记录损失进行评估

logger:日志记录器,用于输出训练信息

evaluator:评估器实例

epoch:当前训练轮次(从1开始计数)

train_loss:列表,存储当前epoch所有批次的损失值

logger.info():记录 INFO 级别的日志

参数类型说明
msg字符串日志消息(支持 %s 格式化)。
*args可变参数填充格式化占位符的值。

np.mean():计算数组元素的算术平均值(支持多维数组和指定轴计算)

参数类型描述默认值是否必需
aarray_like输入数组(可以是列表、元组、NumPy数组等)
axisint/tuple沿指定轴计算均值(如 0=列,1=行)None
dtypedata-type计算时使用的数据类型(如 np.float64自动推断
outndarray结果存储的输出数组None
keepdimsbool是否保持原数组维度(True=保留缩减的轴为1)False
wherearray_like条件筛选,仅计算符合条件的元素True

evaluator.eval():将模型切换到评估模式(关闭Dropout/BatchNorm等训练特定层)

参数类型描述默认值是否必需
self对象PyTorch模型或评估器实例是(隐式传递)
        logger.info("epoch average loss: %f" % np.mean(train_loss))
        evaluator.eval(epoch)
⑨ 完整训练代码
    #训练
    for epoch in range(config["epoch"]):
        epoch += 1
        model.train()
        if cuda_flag:
            model.cuda()
        logger.info("epoch %d begin" % epoch)
        train_loss = []
        for index, batch_data in enumerate(train_data):
            if cuda_flag:
                batch_data = [d.cuda() for d in batch_data]
            input_seq, target_seq, gold = batch_data

            pred = model(input_seq, target_seq)
            loss = loss_func(pred, gold.view(-1))

            train_loss.append(float(loss))
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()

        logger.info("epoch average loss: %f" % np.mean(train_loss))
        evaluator.eval(epoch)

9.模型保存

model_path:模型保存路径

epoch:当前训练的 epoch 编号

config:配置文件中定义的配置字典

os.path.join():拼接多个路径组件,自动处理跨平台路径分隔符(如 Windows 的 \ 和 Linux/macOS 的 /

参数类型说明
*paths可变字符串多个路径组件,如 os.path.join("dir1", "dir2", "file.txt")

torch.save():保存 PyTorch 对象(模型、张量等)到文件

参数类型说明
obj对象要保存的对象(如模型、张量、字典)。
f字符串或文件对象保存路径(如 "model.pth")或已打开的文件对象。

model.state_dict():返回模型参数的字典,键为参数名,值为张量

    model_path = os.path.join(config["model_path"], "epoch_%d.pth" % epoch)
    torch.save(model.state_dict(), model_path)
    return

Ⅶ、调用模型预测

Config:配置文件中定义的配置字典

# -*- coding: utf-8 -*-
import sys
import torch
import os
import random
import os
import numpy as np
import time
import logging
import json
from config import Config
from evaluate import Evaluator
from loader import load_data

#这个transformer是本文件夹下的代码,和我们之前用来调用bert的transformers第三方库是两回事
from transformer.Models import Transformer

logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


"""
模型训练主程序
"""

seed = Config["seed"]
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)

def choose_optimizer(config, model):
    optimizer = config["optimizer"]
    learning_rate = config["learning_rate"]
    if optimizer == "adam":
        return torch.optim.Adam(model.parameters(), lr=learning_rate)
    elif optimizer == "sgd":
        return torch.optim.SGD(model.parameters(), lr=learning_rate)


def main(config):
    #创建保存模型的目录
    if not os.path.isdir(config["model_path"]):
        os.mkdir(config["model_path"])
    #加载模型
    logger.info(json.dumps(config, ensure_ascii=False, indent=2))
    model = Transformer(config["vocab_size"], config["vocab_size"], 0, 0,
                        d_word_vec=128, d_model=128, d_inner=256,
                        n_layers=1, n_head=2, d_k=64, d_v=64,
                        )
    # 标识是否使用gpu
    cuda_flag = torch.cuda.is_available()
    if cuda_flag:
        logger.info("gpu可以使用,迁移模型至gpu")
        model = model.cuda()
    #加载优化器
    optimizer = choose_optimizer(config, model)
    # 加载训练数据
    train_data = load_data(config["train_data_path"], config, logger)
    #加载效果测试类
    evaluator = Evaluator(config, model, logger)
    #加载loss
    loss_func = torch.nn.CrossEntropyLoss(ignore_index=0)
    #训练
    for epoch in range(config["epoch"]):
        epoch += 1
        model.train()
        if cuda_flag:
            model.cuda()
        logger.info("epoch %d begin" % epoch)
        train_loss = []
        for index, batch_data in enumerate(train_data):
            if cuda_flag:
                batch_data = [d.cuda() for d in batch_data]
            input_seq, target_seq, gold = batch_data

            pred = model(input_seq, target_seq)
            loss = loss_func(pred, gold.view(-1))

            train_loss.append(float(loss))
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()

        logger.info("epoch average loss: %f" % np.mean(train_loss))
        evaluator.eval(epoch)
    model_path = os.path.join(config["model_path"], "epoch_%d.pth" % epoch)
    torch.save(model.state_dict(), model_path)
    return

if __name__ == "__main__":

    main(Config)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值