近年来, Transformer模型 引起了广泛关注,它在近几年的 自然语言处理(NLP) 领域掀起了一场风暴。Transformer是一种利用注意力机制(Attention)显著提升深度学习NLP翻译模型性能的架构。
它最初在 《Attention is All You Need》 这篇论文中被提出,并迅速确立为大多数文本数据应用中的主流架构。
自那之后,包括谷歌的 BERT和OpenAI的GPT 系列在内的众多项目都在此基础上进行了拓展,并发布了远远超越现有最先进基准的性能结果。
所以掌握Transformer模型还是非常有必要的,特别是对LLM感兴趣的人来说,今天这个教程中,我们将带大家使用 PyTorch 从零开始构建一个基础Transformer模型。
构建Transformer模型,我们将遵循以下步骤:
- 导入必要的库和模块
- 定义基本构建模块:多头注意力、位置前馈网络、位置编码
- 构建编码器和解码器层
- 将编码器和解码器层组合以创建完整的Transformer模型
- 准备样本数据
- 训练模型
让我们从导入必要的库和模块开始。
# 导入PyTorch库
import torch # PyTorch的核心库,用于张量操作
import torch.nn as nn # 包含构建神经网络的模块
import torch.optim as optim # 提供优化算法,如SGD、Adam等
import torch.utils.data as data # 数据加载和处理工具
import math # 数学函数库,例如三角函数、对数等
import copy # 深拷贝工具,用于复制模型或其他复杂对象
下面我们将定义Transformer模型的基本构建模块。
多头注意力
多头注意力机制计算序列中每对位置之间的注意力,它由多个“注意力头”组成,这些头捕获输入序列的不同方面。
class MultiHeadAttention(nn.Module):
"""
多头注意力机制的实现类,继承自 PyTorch 的 nn.Module。
"""
def __init__(self, d_model, num_heads):
"""
初始化多头注意力机制。
参数:
- d_model: 输入的特征维度
- num_heads: 注意力头的数量
"""
super(MultiHeadAttention, self).__init__()
# 验证 d_model 是否可以被 num_heads 整除,以便分割为多个头
assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
self.d_model = d_model # 输入的特征维度
self.num_heads = num_heads # 注意力头的数量
self.d_k = d_model // num_heads # 每个注意力头的维度大小
# 定义用于生成查询(Q)、键(K)、值(V)的线性变换
self.W_q = nn.Linear(d_model, d_model) # 线性层,将输入映射到查询
self.W_k = nn.Linear(d_model, d_model) # 线性层,将输入映射到键
self.W_v = nn.Linear(d_model, d_model) # 线性层,将输入映射到值
# 输出线性层,将多头注意力的结果映射回 d_model 的维度
self.W_o = nn.Linear(d_model, d_model)
def scaled_dot_product_attention(self, Q, K, V, mask=None):
"""
缩放点积注意力的实现。
参数:
- Q: 查询张量,形状为 (batch_size, num_heads, seq_length, d_k)
- K: 键张量,形状为 (batch_size, num_heads, seq_length, d_k)
- V: 值张量,形状为 (batch_size, num_heads, seq_length, d_k)
- mask: 可选的掩码张量,用于屏蔽某些位置的注意力
返回:
- output: 注意力输出,形状与 V 相同
"""
# 计算注意力得分矩阵
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# 如果有掩码,屏蔽掩码位置
if mask is not None:
attn_scores = attn_scores.masked_fill(mask == 0, -1e9)
# 使用 softmax 计算注意力权重
attn_probs = torch.softmax(attn_scores, dim=-1)
# 加权求和值,得到注意力输出
output = torch.matmul(attn_probs, V)
return output
def split_heads(self, x):
"""
将输入张量拆分为多个注意力头。
参数:
- x: 输入张量,形状为 (batch_size, seq_length, d_model)
返回:
- 分割后的张量,形状为 (batch_size, num_heads, seq_length, d_k)
"""
batch_size, seq_length, d_model = x.size()
# 拆分成 num_heads 个头,并调整维度顺序
return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)
def combine_heads(self, x):
"""
将多个注意力头的输出合并为单个张量。
参数:
- x: 分割后的张量,形状为 (batch_size, num_heads, seq_length, d_k)
返回:
- 合并后的张量,形状为 (batch_size, seq_length, d_model)
"""
batch_size, _, seq_length, d_k = x.size()
# 调整维度顺序并合并 num_heads 维度
return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)
def forward(self, Q, K, V, mask=None):
"""
前向传播逻辑,实现多头注意力的完整计算流程。
参数:
- Q: 查询张量,形状为 (batch_size, seq_length, d_model)
- K: 键张量,形状为 (batch_size, seq_length, d_model)
- V: 值张量,形状为 (batch_size, seq_length, d_model)
- mask: 可选的掩码张量,用于屏蔽某些位置的注意力
返回:
- output: 多头注意力的输出,形状为 (batch_size, seq_length, d_model)
"""
# 将输入通过线性层,映射到查询、键、值
Q = self.split_heads(self.W_q(Q))
K = self.split_heads(self.W_k(K))
V = self.split_heads(self.W_v(V))
# 计算多头注意力输出
attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
# 合并多个头并通过输出线性层
output = self.W_o(self.combine_heads(attn_output))
return output
MultiHeadAttention代码使用输入参数和线性变换层初始化模块。
它计算注意力分数,将输入张量重塑为多个头,并合并所有头的注意力输出。
forward方法计算多头自注意力,使模型能够关注输入序列的不同方面。
位置前馈网络
class PositionWiseFeedForward(nn.Module):
"""
位置编码前馈网络的实现,用于Transformer的每个位置独立进行非线性变换。
"""
def __init__(self, d_model, d_ff):
"""
初始化前馈网络。
参数:
- d_model: 输入和输出的特征维度
- d_ff: 前馈网络中隐藏层的特征维度(通常大于 d_model)
"""
super(PositionWiseFeedForward, self).__init__()
# 定义前馈网络的第一层,全连接层,将输入映射到高维空间
self.fc1 = nn.Linear(d_model, d_ff)
# 定义前馈网络的第二层,全连接层,将高维特征映射回原始维度
self.fc2 = nn.Linear(d_ff, d_model)
# 激活函数,ReLU 用于引入非线性
self.relu = nn.ReLU()
def forward(self, x):
"""
前向传播逻辑。
参数:
- x: 输入张量,形状为 (batch_size, seq_length, d_model)
返回:
- 输出张量,形状与输入相同
"""
# 第一步:通过第一层全连接层 (d_model -> d_ff)
# 第二步:应用 ReLU 激活函数
# 第三步:通过第二层全连接层 (d_ff -> d_model)
return self.fc2(self.relu(self.fc1(x)))
PositionWiseFeedForward类扩展了PyTorch的nn.Module,并实现了位置前馈网络。
该类使用两个线性变换层和ReLU激活函数进行初始化。
forward方法按顺序应用这些变换和激活函数来计算输出,此过程使模型能够在做出预测时考虑输入元素的位置。
位置编码
位置编码用于向输入序列中每个令牌的位置信息。它使用不同频率的正弦和余弦函数来生成位置编码。
class PositionalEncoding(nn.Module):
"""
位置编码模块,用于为输入序列添加位置信息。
位置编码采用正弦和余弦函数的固定形式编码,帮助模型捕捉序列中元素的相对和绝对位置。
"""
def __init__(self, d_model, max_seq_length):
"""
初始化位置编码模块。
参数:
- d_model: 输入序列特征的维度
- max_seq_length: 序列的最大长度
"""
super(PositionalEncoding, self).__init__()
# 初始化一个用于存储位置编码的张量,形状为 (max_seq_length, d_model)
pe = torch.zeros(max_seq_length, d_model)
# 创建一个张量,表示序列中的位置,形状为 (max_seq_length, 1)
position = torch.arange(0, max_seq_length, dtype=torch.float).unsqueeze(1)
# 计算位置编码中的除数项(指数衰减),形状为 (d_model // 2)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
# 将正弦编码赋值到偶数维度的位置
pe[:, 0::2] = torch.sin(position * div_term)
# 将余弦编码赋值到奇数维度的位置
pe[:, 1::2] = torch.cos(position * div_term)
# 扩展一个批次维度(使其形状为 (1, max_seq_length, d_model)),并将其注册为模型的非参数缓冲区
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
"""
前向传播,将位置编码添加到输入序列中。
参数:
- x: 输入张量,形状为 (batch_size, seq_length, d_model)
返回:
- 输出张量,形状与输入相同,但每个位置上添加了对应的编码
"""
# 将位置编码与输入相加。取位置编码中与输入序列长度匹配的部分
return x + self.pe[:, :x.size(1)]
PositionalEncoding类使用输入参数d_model和max_seq_length进行初始化,创建一个张量来存储位置编码值。
该类分别计算偶数和奇数索引的正弦和余弦值,基于缩放因子div_term。
forward方法通过将存储的位置编码值添加到输入张量来计算位置编码,从而使模型能够捕获输入序列的位置信息。
编码器层
编码器层包含一个多头注意力层、一个位置前馈层和两个层归一化层。
class EncoderLayer(nn.Module):
"""
Transformer编码器层的实现。
一个编码器层包括多头自注意力机制和位置前馈网络,两者之间分别有残差连接和层归一化。
"""
def __init__(self, d_model, num_heads, d_ff, dropout):
"""
初始化编码器层。
参数:
- d_model: 输入特征的维度
- num_heads: 多头注意力机制中的注意力头数
- d_ff: 前馈网络的隐藏层维度
- dropout: Dropout 概率,用于防止过拟合
"""
super(EncoderLayer, self).__init__()
# 多头自注意力模块
self.self_attn = MultiHeadAttention(d_model, num_heads)
# 位置前馈网络模块
self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
# 第一层归一化,用于多头自注意力后的残差连接
self.norm1 = nn.LayerNorm(d_model)
# 第二层归一化,用于前馈网络后的残差连接
self.norm2 = nn.LayerNorm(d_model)
# Dropout 层,用于防止过拟合
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask):
"""
前向传播逻辑。
参数:
- x: 输入张量,形状为 (batch_size, seq_length, d_model)
- mask: 注意力掩码,形状为 (batch_size, seq_length, seq_length)
返回:
- 输出张量,形状为 (batch_size, seq_length, d_model)
"""
# 1. 多头自注意力计算:
# 输入的 Q、K、V 都是 x,通过注意力机制计算自注意力输出。
attn_output = self.self_attn(x, x, x, mask)
# 2. 残差连接和层归一化:
# 将注意力输出加到原始输入上,然后进行层归一化。
x = self.norm1(x + self.dropout(attn_output))
# 3. 位置前馈网络:
# 使用位置前馈网络对每个位置独立处理。
ff_output = self.feed_forward(x)
# 4. 残差连接和层归一化:
# 将前馈网络的输出加到输入上,然后进行层归一化。
x = self.norm2(x + self.dropout(ff_output))
return x
EncoderLayer类使用输入参数和组件进行初始化,包括MultiHeadAttention模块、PositionWiseFeedForward模块、两个层归一化模块和一个丢弃层。
forward方法通过应用自注意力、将注意力输出添加到输入张量并对结果进行归一化来计算编码器层输出。
然后,它计算位置前馈输出,将其与归一化的自注意力输出相结合,并在返回处理后的张量之前对最终结果进行归一化。
解码器层
解码器层包含两个多头注意力层、一个位置前馈层和三个层归一化层。
class DecoderLayer(nn.Module):
"""
Transformer解码器层的实现。
解码器层由三个主要模块组成:自注意力、多头交叉注意力和位置前馈网络,
每个模块后都有残差连接和层归一化。
"""
def __init__(self, d_model, num_heads, d_ff, dropout):
"""
初始化解码器层。
参数:
- d_model: 输入特征的维度
- num_heads: 多头注意力机制中的注意力头数
- d_ff: 前馈网络的隐藏层维度
- dropout: Dropout 概率,用于防止过拟合
"""
super(DecoderLayer, self).__init__()
# 自注意力模块(针对解码器的输入序列)
self.self_attn = MultiHeadAttention(d_model, num_heads)
# 交叉注意力模块(将解码器的输入与编码器的输出结合)
self.cross_attn = MultiHeadAttention(d_model, num_heads)
# 位置前馈网络模块
self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
# 第一层归一化,用于自注意力后的残差连接
self.norm1 = nn.LayerNorm(d_model)
# 第二层归一化,用于交叉注意力后的残差连接
self.norm2 = nn.LayerNorm(d_model)
# 第三层归一化,用于前馈网络后的残差连接
self.norm3 = nn.LayerNorm(d_model)
# Dropout 层,用于防止过拟合
self.dropout = nn.Dropout(dropout)
def forward(self, x, enc_output, src_mask, tgt_mask):
"""
前向传播逻辑。
参数:
- x: 解码器的输入张量,形状为 (batch_size, tgt_seq_length, d_model)
- enc_output: 编码器的输出张量,形状为 (batch_size, src_seq_length, d_model)
- src_mask: 源序列的注意力掩码,用于交叉注意力
- tgt_mask: 目标序列的注意力掩码,用于自注意力
返回:
- 输出张量,形状为 (batch_size, tgt_seq_length, d_model)
"""
# 1. 自注意力机制:
# 对解码器输入序列进行自注意力计算。
attn_output = self.self_attn(x, x, x, tgt_mask)
# 残差连接和层归一化。
x = self.norm1(x + self.dropout(attn_output))
# 2. 交叉注意力机制:
# 将解码器的输出与编码器的输出进行注意力计算。
attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
# 残差连接和层归一化。
x = self.norm2(x + self.dropout(attn_output))
# 3. 位置前馈网络:
# 对序列中每个位置独立处理。
ff_output = self.feed_forward(x)
# 残差连接和层归一化。
x = self.norm3(x + self.dropout(ff_output))
return x
DecoderLayer使用输入参数和组件进行初始化,如用于掩码自注意力和交叉注意力的MultiHeadAttention模块、PositionWiseFeedForward模块、三个层归一化模块和一个丢弃层。
forward方法通过以下步骤计算解码器层输出:
- 计算掩码自注意力输出并将其添加到输入张量,然后进行丢弃和层归一化。
- 计算解码器和编码器输出之间的交叉注意力输出,并将其添加到归一化的掩码自注意力输出,然后进行丢弃和层归一化。
- 计算位置前馈输出并将其与归一化的交叉注意力输出相结合,然后进行丢弃和层归一化。
- 返回处理后的张量。
这些操作使解码器能够根据输入和编码器输出来生成目标序列。
Transformer模型
整合所有部分:
class Transformer(nn.Module):
"""
Transformer模型的实现,包含编码器和解码器两部分。
用于序列到序列的任务(如机器翻译、文本生成等)。
"""
def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):
"""
初始化Transformer模型。
参数:
- src_vocab_size: 源语言的词汇表大小
- tgt_vocab_size: 目标语言的词汇表大小
- d_model: 输入和输出特征的维度
- num_heads: 多头注意力机制中的注意力头数
- num_layers: 编码器和解码器的层数
- d_ff: 前馈网络的隐藏层维度
- max_seq_length: 输入序列的最大长度
- dropout: Dropout 概率,用于防止过拟合
"""
super(Transformer, self).__init__()
# 编码器嵌入层
self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
# 解码器嵌入层
self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
# 位置编码层
self.positional_encoding = PositionalEncoding(d_model, max_seq_length)
# 编码器层:由多个 `EncoderLayer` 组成
self.encoder_layers = nn.ModuleList([
EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)
])
# 解码器层:由多个 `DecoderLayer` 组成
self.decoder_layers = nn.ModuleList([
DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)
])
# 全连接层,用于将解码器的输出映射到目标词汇表的概率分布
self.fc = nn.Linear(d_model, tgt_vocab_size)
# Dropout层,用于防止过拟合
self.dropout = nn.Dropout(dropout)
def generate_mask(self, src, tgt):
"""
生成源序列和目标序列的注意力掩码。
参数:
- src: 源序列张量,形状为 (batch_size, src_seq_length)
- tgt: 目标序列张量,形状为 (batch_size, tgt_seq_length)
返回:
- src_mask: 源序列的掩码,用于屏蔽填充符(padding token)
- tgt_mask: 目标序列的掩码,用于屏蔽未来的时间步和填充符
"""
# 源序列掩码:屏蔽填充符
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
# 目标序列掩码:屏蔽填充符
tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
# 防止信息泄露的掩码:目标序列中屏蔽未来的时间步
seq_length = tgt.size(1)
nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
# 组合目标序列掩码和防止信息泄露的掩码
tgt_mask = tgt_mask & nopeak_mask
return src_mask, tgt_mask
def forward(self, src, tgt):
"""
前向传播逻辑。
参数:
- src: 源序列张量,形状为 (batch_size, src_seq_length)
- tgt: 目标序列张量,形状为 (batch_size, tgt_seq_length)
返回:
- output: 解码器的输出张量,形状为 (batch_size, tgt_seq_length, tgt_vocab_size)
"""
# 生成注意力掩码
src_mask, tgt_mask = self.generate_mask(src, tgt)
# 对源序列嵌入并添加位置编码
src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
# 对目标序列嵌入并添加位置编码
tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
# 编码器前向传播
enc_output = src_embedded
for enc_layer in self.encoder_layers:
enc_output = enc_layer(enc_output, src_mask)
# 解码器前向传播
dec_output = tgt_embedded
for dec_layer in self.decoder_layers:
dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)
# 将解码器的输出映射到目标词汇表的概率分布
output = self.fc(dec_output)
return output
Transformer类将之前定义的模块组合起来,以创建完整的Transformer模型。
在初始化期间,Transformer模块设置输入参数并初始化各种组件,包括用于源序列和目标序列的嵌入层、PositionalEncoding模块、用于创建堆叠层的EncoderLayer和DecoderLayer模块、用于投影解码器输出的线性层以及一个dropout层。
generate_mask方法为源序列和目标序列生成二进制掩码,以忽略填充令牌并防止解码器关注未来的令牌。
forward方法通过以下步骤计算Transformer模型的输出:
- 使用generate_mask方法生成源和目标掩码。
- 计算源和目标嵌入,并应用位置编码和丢弃。
- 通过编码器层处理源序列,更新enc_output张量。
- 使用enc_output和掩码通过解码器层处理目标序列,并更新dec_output张量。
- 对解码器输出应用线性投影层,获得输出逻辑值。
这些步骤使Transformer模型能够处理输入序列并根据其组件的组合功能生成输出序列。
准备样本数据
在本例中,我们将创建一个玩具数据集用于演示,在实际应用中,您会使用更大的数据集,对文本进行预处理,并为源语言和目标语言创建词汇映射。
src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100
dropout = 0.1
# 初始化 Transformer 模型
transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)
# 生成随机输入数据
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
训练模型
现在,我们将使用样本数据训练模型,在实际应用中,我们会使用更大的数据集并将其拆分为训练集和验证集。
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss(ignore_index=0)
# 使用交叉熵损失函数,忽略索引为 0 的位置(通常表示填充 token)
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
# 使用 Adam 优化器,带有推荐的超参数:
# lr: 学习率,设置为 0.0001
# betas: Adam 优化的两个超参数,(0.9, 0.98) 用于加速收敛
# eps: 防止分母为零的小值,设置为 1e-9
# 切换模型到训练模式
transformer.train()
# 开始训练循环
for epoch in range(100):
# 每个 epoch 清空优化器的梯度
optimizer.zero_grad()
# 模型前向传播
# 输入源数据 src_data 和目标数据 tgt_data(去掉最后一个 token 的部分)
output = transformer(src_data, tgt_data[:, :-1])
# 计算损失
# output: 模型的预测输出,形状为 (batch_size, seq_length-1, tgt_vocab_size)
# tgt_data[:, 1:]: 目标数据的真实值,去掉第一个 token(通常为起始标记 <sos>)
# 将预测输出和真实目标数据展平到二维,便于计算交叉熵损失
loss = criterion(
output.contiguous().view(-1, tgt_vocab_size), # 展平预测输出,形状为 (batch_size * seq_length-1, tgt_vocab_size)
tgt_data[:, 1:].contiguous().view(-1) # 展平真实目标数据,形状为 (batch_size * seq_length-1)
)
# 反向传播计算梯度
loss.backward()
# 更新模型参数
optimizer.step()
# 打印当前 epoch 的损失值
我们可以使用这种方法在PyTorch中从零开始构建一个简单的Transformer。
所有大型语言模型都使用这些Transformer编码器或解码器块进行训练。
因此,了解这一切的开端网络至关重要,希望本文可以帮助所有希望深入了解大型语言模型(LLM)的人。
如何学习大模型
现在社会上大模型越来越普及了,已经有很多人都想往这里面扎,但是却找不到适合的方法去学习。
作为一名资深码农,初入大模型时也吃了很多亏,踩了无数坑。现在我想把我的经验和知识分享给你们,帮助你们学习AI大模型,能够解决你们学习中的困难。
下面这些都是我当初辛苦整理和花钱购买的资料,现在我已将重要的AI大模型资料包括市面上AI大模型各大白皮书、AGI大模型系统学习路线、AI大模型视频教程、实战学习,等录播视频免费分享出来
,需要的小伙伴可以扫取。

一、AGI大模型系统学习路线
很多人学习大模型的时候没有方向,东学一点西学一点,像只无头苍蝇乱撞,我下面分享的这个学习路线希望能够帮助到你们学习AI大模型。
二、AI大模型视频教程
三、AI大模型各大学习书籍
四、AI大模型各大场景实战案例
五、结束语
学习AI大模型是当前科技发展的趋势,它不仅能够为我们提供更多的机会和挑战,还能够让我们更好地理解和应用人工智能技术。通过学习AI大模型,我们可以深入了解深度学习、神经网络等核心概念,并将其应用于自然语言处理、计算机视觉、语音识别等领域。同时,掌握AI大模型还能够为我们的职业发展增添竞争力,成为未来技术领域的领导者。
再者,学习AI大模型也能为我们自己创造更多的价值,提供更多的岗位以及副业创收,让自己的生活更上一层楼。
因此,学习AI大模型是一项有前景且值得投入的时间和精力的重要选择。