PyTorch深度学习实战 |手动计算 Transformer和完整的代码实现

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文介绍了基于PyTorch实现Transformer模型的完整过程。主要内容包括:1)Transformer架构的核心组件实现,如多头注意力机制、位置前馈网络、位置编码等;2)模型构建步骤,包括词嵌入层、编码器/解码器块和输出层的实现;3)完整的训练流程,包含数据处理、损失计算和参数优化;4)评估方法验证模型性能。文章通过代码示例详细展示了如何从零开始构建Transformer,并应用于机器翻译任务,同时对模型各层的输入输出维度进行了说明。该实现可作为深度学习实践者学习Transformer架构的实用指南

 输出层

现在我们来假想一个英译汉的机器翻译任务。先把 Transformer 模型看作一个整体:假设待翻

译的英文数据是 “Are you OK?”,经过词嵌入和位置编码之后,是一个4*512的张量。通过前面博

客的讲解,我们知道经过解码器之后,输出仍然是一个4*512的张量。我们以推理模型来简单介

绍,输出层的设计。下面我们来看一下啊,transformer中是如何通过线性层和Softmax层实现预测

的?

基于此前已生成的结果,持续推理后续的输出内容。模型逐步生成输出序列的过程中,自回归的

推理模型如图所示。

image.gif

假设我们的中文词汇表包含 10 个词(对应 10 个索引)整个预测流程如下:

(1)解码器在当前循环输出 1 个 512 维的特征(对应 “下一个要预测的词” 的抽象特征);

(2)这个 512 维特征传入线性层,被映射成 1×10 的张量 —— 张量里的 10 个数值,分别是当前

位置与中文词汇表中 10 个词的 “匹配分数”(也叫 logit);

(3)再将这 1×10 的分数张量传入 Softmax 函数,把分数转换成概率分布(10 个概率的和为 1);

(4)从这 10 个概率中找出数值最大的那个,对应的索引就是当前循环的预测索引;

(5)最后根据这个索引,到中文词汇表里查找对应的汉字,这个字就是当前循环的最终输出。

之后,会把这个已生成的字拼接到解码器的输入中,进入下一次循环,预测下一个词,直到生成结

束符(<end>)为止

image.gif

import torch
import torch.nn as nn
import torch.nn.functional as F
# -------------------------- 1. 实现 Transformer 最后两层(线性层 + Softmax)--------------------------
class TransformerFinalLayers(nn.Module):
    def __init__(self, d_model: int, vocab_size: int):
        """
        Args:
            d_model: 解码器输出的特征维度(固定512)
            vocab_size: 中文词汇表大小(这里设为10)
        """
        super().__init__()
        # 线性层:512维特征 → 10维词汇表分数
        self.linear = nn.Linear(d_model, vocab_size)
        # 参数初始化(保证预测稳定)
        nn.init.xavier_normal_(self.linear.weight, gain=0.02)
        nn.init.zeros_(self.linear.bias)
    def forward(self, decoder_feature: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, int, str]:
        """
        前向传播(单个循环,仅输出1个词)
        Args:
            decoder_feature: 解码器当前循环的特征 (1, 512) → 1个样本,512维特征
        Returns:
            scores: 线性层输出的分数 (1, 10)
            probs: Softmax后的概率 (1, 10)
            pred_idx: 最大概率对应的词索引(整数)
            pred_word: 最终预测的汉字(字符串)
        """
        # 1. 线性层:高维特征 → 词汇表分数
        scores = self.linear(decoder_feature)  # (1, 10)
        
        # 2. Softmax:分数 → 概率(按最后一维归一化,和为1)
        probs = F.softmax(scores, dim=-1)  # (1, 10)
        
        # 3. 取最大概率的索引
        pred_idx = torch.argmax(probs, dim=-1).item()  # 从张量转成整数索引
        
        return scores, probs, pred_idx
# -------------------------- 2. 具体例子验证(自回归单循环)--------------------------
if __name__ == "__main__":
    # -------------- 步骤1:设置关键参数和词汇表 --------------
    d_model = 512  # 解码器特征维度
    vocab_size = 10  # 中文词汇表大小
    # 中文词汇表:索引→汉字映射(简化版)
    chinese_vocab = {
        0: "<PAD>",  # 填充词
        1: "你",     # 目标预测词(对应英文Are)
        2: "好",     # 后续循环预测词
        3: "吗",     # 后续循环预测词
        4: "?",     # 后续循环预测词
        5: "我",     # 干扰项
        6: "他",     # 干扰项
        7: "是",     # 干扰项
        8: "在",     # 干扰项
        9: "吃"      # 干扰项
    }
    # -------------- 步骤2:模拟解码器输入特征(单个循环)--------------
    # 自回归某一次循环:解码器输出 (1, 512) 特征(融合了历史生成词+原文信息)
    torch.manual_seed(42)  # 固定种子,结果可复现
    decoder_feature = torch.randn(1, d_model)  # (1, 512) → 1个样本,512维特征
    print("="*60)
    print("【自回归单循环 - 输入信息】")
    print(f"解码器当前循环输出特征形状:{decoder_feature.shape} → (1个样本, 512维特征)")
    print("词汇表:", chinese_vocab)
    print("="*60)
    # -------------- 步骤3:初始化最后两层并执行预测 --------------
    final_layers = TransformerFinalLayers(d_model=d_model, vocab_size=vocab_size)
    scores, probs, pred_idx = final_layers(decoder_feature)
    pred_word = chinese_vocab[pred_idx]  # 索引→汉字
    # -------------- 步骤4:打印结果(验证流程)--------------
    print("\n【预测流程验证】")
    # 1. 线性层输出(分数)
    print(f"1. 线性层输出(10个词的匹配分数):")
    scores_rounded = (torch.round(scores * 10000) / 10000).squeeze().tolist()  # 保留4位小数
    for idx, score in enumerate(scores_rounded):
        print(f"   索引{idx}(词:{chinese_vocab[idx]})→ 分数:{score}")
    
    # 2. Softmax输出(概率)
    print(f"\n2. Softmax输出(10个词的概率,和为1):")
    probs_rounded = (torch.round(probs * 10000) / 10000).squeeze().tolist()  # 保留4位小数
    total_prob = sum(probs_rounded)  # 验证概率和为1
    for idx, prob in enumerate(probs_rounded):
        print(f"   索引{idx}(词:{chinese_vocab[idx]})→ 概率:{prob}")
    print(f"   概率总和:{round(total_prob, 4)}(理论应为1)")
    
    # 3. 最终预测结果
    print(f"\n3. 预测结果:")
    print(f"   最大概率对应的索引:{pred_idx}")
    print(f"   索引{pred_idx}对应的汉字:{pred_word}")
    print(f"\n结论:当前循环仅输出1个词 → {pred_word}")
    print("="*60)

image.gif

image.gif


集成化的实现

下面这个是Transformer的整体结构图,我们再来看一下:整体结构还是相当复杂的吧,看着感觉

就很复杂。

但是如果我们可以把编码器和解码器分装起来,整体结构是不是就很简单了,我们把分装起来之后

的模块叫做Transformer 模块。

这样我们就可以把整个Transformer 模型简化这几个模块:

(1)词嵌入层

(2)位置编码

(3)Transformer 模块

(4)全连接层

下面我将会详细介绍每一层的作用:

词嵌入层

首先我们来看一下词嵌入层

把 “只能看懂数字的模型” 能理解的词索引(离散整数),转换成包含语义信息的高维向量(连续

数值)

import torch
import torch.nn as nn
# 1. 定义词嵌入层:词汇表大小=3(3个词),每个词转成2维向量(极简维度)
embedding = nn.Embedding(num_embeddings=3, embedding_dim=2)
# 输入3个词的索引(0、1、2)
word_indices = torch.tensor([0, 1, 2], dtype=torch.long)
vecs = embedding(word_indices)
print(vecs)

image.gif

image.gif

一个简单的例子

  然后让我们开始写一个简答的例子模拟每一层的输出和输出:

假设我只有一个样本,这个样本是一句话,包括3个词,所以我输入大小应该是(1*3)

经过词嵌入层,输出(1*3*2)的张量

经过位置编码,输出(1*3*2)的张量

经过Transformer层,输出(1*3*2)的张量

经过全连接层,输出(1*3*3)的张量

import torch
import torch.nn as nn
# 定义完整的简化版 Transformer(含指定结构)
class SimpleTransformer(nn.Module):
    def __init__(self, vocab_size=3, model_dim=2, max_seq_len=1000):
        super().__init__()
        # 1. 词嵌入层
        self.embedding = nn.Embedding(num_embeddings=vocab_size, embedding_dim=model_dim)
        
        # 2. 位置编码(指定参数化方式)
        self.positional_encoding = nn.Parameter(torch.zeros(1, max_seq_len, model_dim))
        
        # 3. Transformer 模块(batch_first=True)
        self.transformer = nn.Transformer(
            d_model=model_dim,
            nhead=1,  # model_dim=2 仅支持1个注意力头(2÷1=2,整除)
            num_encoder_layers=1,
            num_decoder_layers=1,  # 显式指定解码器层数(默认和编码器一致)
            batch_first=True
        )
        # 4. 全连接层
        self.fc = nn.Linear(model_dim, vocab_size)
    def forward(self, x):
        # 输入 x: (batch_size, seq_len) = (1, 3)
        batch_size, seq_len = x.shape
        # -------------------------- 层1:词嵌入层 --------------------------
        embed_out = self.embedding(x)  # (1, 3, 2)
        # -------------------------- 层2:位置编码层(叠加) --------------------------
        # 只取前 seq_len 个位置的编码(避免超出序列长度)
        pos_enc = self.positional_encoding[:, :seq_len, :]  # (1, 3, 2)
        pos_embed_out = embed_out + pos_enc  # (1, 3, 2)
        # -------------------------- 层3:Transformer 模块 --------------------------
        # 生成解码器掩码(屏蔽未来词)
        tgt_mask = self.transformer.generate_square_subsequent_mask(seq_len).to(x.device)
        # Transformer 前向传播(编码器+解码器)
        transformer_out = self.transformer(
            src=pos_embed_out,  # 编码器输入:带位置的嵌入向量
            tgt=pos_embed_out,  # 解码器输入:带位置的嵌入向量(自回归场景)
            tgt_mask=tgt_mask    # 解码器掩码
        )  # (1, 3, 2)
        # -------------------------- 层4:全连接层 --------------------------
        fc_out = self.fc(transformer_out)  # (1, 3, 3)
        # 返回每一层的输出(用于打印)
        return {
            "输入": x,
            "词嵌入层输出": embed_out,
            "位置编码叠加后输出": pos_embed_out,
            "Transformer模块输出": transformer_out,
            "全连接层输出": fc_out
        }
# -------------------------- 运行示例:打印每一层完整输出 --------------------------
if __name__ == "__main__":
    # 输入:1个样本,3个词的索引(shape: (1, 3))
    input_tensor = torch.tensor([[0, 1, 2]], dtype=torch.long) 
    # 初始化模型
    model = SimpleTransformer(vocab_size=3, model_dim=2, max_seq_len=1000)
    # 前向传播,获取所有层输出
    all_outputs = model(input_tensor)
    # 打印每一层的输出(保留4位小数,修复round报错)
    print("="*80)
    for layer_name, output in all_outputs.items():
        print(f"\n【{layer_name}】")
        print(f"形状:{output.shape}")
        print("具体数值:")
        # 保留4位小数(兼容所有PyTorch版本)
        output_rounded = (torch.round(output * 10000) / 10000)
        print(output_rounded)
    print("\n" + "="*80)

image.gif

================================================================================


【输入】

形状:torch.Size([1, 3])

具体数值:

tensor([[0., 1., 2.]])


【词嵌入层输出】

形状:torch.Size([1, 3, 2])

具体数值:

tensor([[[-0.9721, -1.0100],

        [ 1.6261, -1.0117],

        [-0.3243,  1.1878]]], grad_fn=<DivBackward0>)


【位置编码叠加后输出】

形状:torch.Size([1, 3, 2])

具体数值:

tensor([[[-0.9721, -1.0100],

        [ 1.6261, -1.0117],

        [-0.3243,  1.1878]]], grad_fn=<DivBackward0>)


【Transformer模块输出】

形状:torch.Size([1, 3, 2])

具体数值:

tensor([[[ 1., -1.],

        [ 1., -1.],

        [-1.,  1.]]], grad_fn=<DivBackward0>)


【全连接层输出】

形状:torch.Size([1, 3, 3])

具体数值:

tensor([[[ 0.9120,  0.3824, -0.5490],

        [ 0.9120,  0.3824, -0.5490],

        [ 0.0754,  0.6252, -0.2340]]], grad_fn=<DivBackward0>)


================================================================================

完整代码

import torch
import torch.nn as nn
import torch.optim as optim
class TransformerModel(nn.Module):
    def __init__(self, input_dim, model_dim, num_heads, num_layers, output_dim):
        super(TransformerModel, self).__init__()
        self.embedding = nn.Embedding(input_dim, model_dim)
        self.positional_encoding = nn.Parameter(torch.zeros(1, 1000, model_dim))  # 最大序列长度1000
        # 关键修改:开启 batch_first=True,消除警告(输入输出维度变为 (batch_size, seq_len, model_dim))
        self.transformer = nn.Transformer(
            d_model=model_dim, 
            nhead=num_heads, 
            num_encoder_layers=num_layers,
            batch_first=True  # 适配 (batch_size, seq_len, dim) 格式
        )
        self.fc = nn.Linear(model_dim, output_dim)
    def forward(self, src, tgt):
        # 修正后输入维度:(batch_size, seq_len) → 因为 batch_first=True
        batch_size, src_seq_length = src.size(0), src.size(1)
        batch_size, tgt_seq_length = tgt.size(0), tgt.size(1)
        # 词嵌入 + 位置编码
        src = self.embedding(src) + self.positional_encoding[:, :src_seq_length, :]  # (batch, src_len, model_dim)
        tgt = self.embedding(tgt) + self.positional_encoding[:, :tgt_seq_length, :]  # (batch, tgt_len, model_dim)
        # 生成解码器的掩码(避免看未来词,必须加!否则训练无效)
        tgt_mask = self.transformer.generate_square_subsequent_mask(tgt_seq_length).to(src.device)
        # 前向传播(传入 tgt_mask 屏蔽未来信息)
        transformer_output = self.transformer(
            src=src, 
            tgt=tgt, 
            tgt_mask=tgt_mask  # 关键:自回归训练必须加掩码
        )
        output = self.fc(transformer_output)  # (batch, tgt_len, output_dim)
        return output
# 超参数
input_dim = 10000  # 词汇表大小
model_dim = 512    # 模型维度
num_heads = 8      # 多头注意力头数(需能被 model_dim 整除:512÷8=64,符合要求)
num_layers = 6     # 编码器/解码器层数
output_dim = 10000 # 输出维度(与词汇表大小一致)
# 初始化模型、损失函数和优化器
model = TransformerModel(input_dim, model_dim, num_heads, num_layers, output_dim)
criterion = nn.CrossEntropyLoss()  # 适合分类任务(词预测)
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 修正输入数据维度:(batch_size, seq_len) → 因为 batch_first=True(原代码是 (seq_len, batch_size),会报错)
batch_size = 32
src_seq_len = 10  # 源序列长度(比如英文句子长度)
tgt_seq_len = 20  # 目标序列长度(比如中文翻译长度)
src = torch.randint(0, input_dim, (batch_size, src_seq_len))  # (32, 10):32个样本,每个10个词索引
tgt = torch.randint(0, input_dim, (batch_size, tgt_seq_len))  # (32, 20):32个样本,每个20个词索引
# 训练步骤
model.train()  # 开启训练模式
optimizer.zero_grad()  # 清空梯度
output = model(src, tgt)  # 前向传播:(32, 20, 10000)
# 计算损失:CrossEntropyLoss 要求输入 (batch*seq_len, output_dim),标签 (batch*seq_len)
loss = criterion(output.view(-1, output_dim), tgt.view(-1))
# 反向传播 + 参数更新
loss.backward()
optimizer.step()
print(f"初始训练损失:{loss.item():.4f}")  # 输出约 9.3 左右,正常!

image.gif

详细的输出:

================================================================================

【Transformer 每层输出详情】

================================================================================


1. 原始输入-src:

 形状:torch.Size([32, 10])

 维度解读:(batch_size, src_seq_len) = (32, 10)

 数据类型:torch.int64


1. 原始输入-tgt:

 形状:torch.Size([32, 20])

 维度解读:(batch_size, tgt_seq_len) = (32, 20)

 数据类型:torch.int64


2. 词嵌入层-src输出:

 形状:torch.Size([32, 10, 512])

 维度解读:(batch_size, src_seq_len, model_dim) = (32, 10, 512)

 含义:每个英文词索引→512维语义向量


2. 词嵌入层-tgt输出:

 形状:torch.Size([32, 20, 512])

 维度解读:(batch_size, tgt_seq_len, model_dim) = (32, 20, 512)

 含义:每个中文词索引→512维语义向量


3. 位置编码叠加后-src输出:

 形状:torch.Size([32, 10, 512])

 维度解读:(32, 10, 512)

 含义:语义向量 + 位置信息(保留语义+添加词序)


3. 位置编码叠加后-tgt输出:

 形状:torch.Size([32, 20, 512])

 维度解读:(32, 20, 512)

 含义:语义向量 + 位置信息(保留语义+添加词序)


4. 解码器掩码:

 形状:torch.Size([20, 20])

 维度解读:(tgt_seq_len, tgt_seq_len) = (20, 20)

 含义:下三角掩码,屏蔽未来词(避免解码器作弊)


5. Transformer模块输出:

 形状:torch.Size([32, 20, 512])

 维度解读:(batch_size, tgt_seq_len, model_dim) = (32, 20, 512)

 含义:融合英文全局特征+中文历史特征的512维抽象特征


6. 全连接层输出:

 形状:torch.Size([32, 20, 10000])

 维度解读:(batch_size, tgt_seq_len, output_dim) = (32, 20, 10000)

 含义:每个中文位置→10000个词的匹配分数(logits)


7. 损失计算-输入调整后:

 输入形状:torch.Size([640, 10000])

 标签形状:torch.Size([640])

 维度解读:输入(32×20, 10000)=(640,10000),标签(640)

 含义:适配CrossEntropyLoss的输入格式

================================================================================


初始训练损失:9.4007

说明:损失约9.3左右为正常(词汇表10000时,随机猜测损失≈ln(10000)≈9.21)

================================================================================



从0开始手搓Transformer

多头注意力机制

image.gif

首先假设我的数据大小是32*512的,32代表我的样本数,512是词向量的维度(d_model ),这里我

们使用8个自注意力(n_head)。每个注意力头的维度:512÷8=64(n_d),然后开始计算Q,K,

V。

image.gif

分割多头后-Q/K/V

image.gif

Q与K的点积,再除以√n_d(缩放,避免分数过大)

应用掩码后-注意力分数

屏蔽未来词或填充词(分数设为-10000,softmax后权重≈0),之后注意力分数的大小仍然是(8*3*3)

计算注意力权重

softmax归一化(每行和为1,代表关注程度),转换成概率分布,输出的大小是(8*3*3)

加权求和

注意力权重 × V向量(获取关注后的特征)

合并多头

线形层

最后再通过一个线性层,完成最后的输出,最后的输出的大小是,3*512

完整代码

import torch
from torch import nn
x=torch.rand(128,32,512)
d_model=512
n_head=8
import math
class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, n_head):
        super(MultiHeadAttention, self).__init__()
        self.n_head = n_head
        self.d_model = d_model
        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)
        self.w_combine = nn.Linear(d_model, d_model)
        self.softmax = nn.Softmax(dim=-1)
    def forward(self, q, k, v, mask=None):
        batch, time, dimension = q.shape
        n_d = self.d_model // self.n_head
        q, k, v = self.w_q(q), self.w_k(k), self.w_v(v)
        q = q.view(batch, time, self.n_head, n_d).permute(0, 2, 1, 3)
        k = k.view(batch, time, self.n_head, n_d).permute(0, 2, 1, 3)
        v = v.view(batch, time, self.n_head, n_d).permute(0, 2, 1, 3)
        score = q @ k.transpose(2, 3) / math.sqrt(n_d)
        if mask is not None:
            score = score.masked_fill(mask == 0, -10000)
        score = self.softmax(score) @ v
        score = score.permute(0, 2, 1, 3).contiguous().view(batch, time, dimension)
        out = self.w_combine(score)
        return out
# 定义模型的维度和头数
d_model = 512
n_head = 8
# 创建多头注意力实例
attention = MultiHeadAttention(d_model, n_head)
out=attention(x,x,x)
print(out.shape)

image.gif

位置前馈网络(Position-wise Feed-Forward Network)

由两个全连接层和一个 ReLU 激活函数组成,用于进一步处理注意力机制的输出。

class PositionWiseFeedForward(nn.Module):
    def __init__(self, d_model, d_ff):
        super(PositionWiseFeedForward, self).__init__()
        self.fc1 = nn.Linear(d_model, d_ff)  # 第一层全连接
        self.fc2 = nn.Linear(d_ff, d_model)  # 第二层全连接
        self.relu = nn.ReLU()  # 激活函数
    def forward(self, x):
        # 前馈网络的计算
        return self.fc2(self.relu(self.fc1(x)))

image.gif

输入形状:torch.Size([3, 512])    输出形状:torch.Size([3, 512]),这个网络的特征是为了提取特

征,不改变输入的大小。

位置编码

位置编码用于注入输入序列中每个 token 的位置信息。

使用不同频率的正弦和余弦函数来生成位置编码。也是不改变输入的形状。

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_seq_length):
        super(PositionalEncoding, self).__init__()
        pe = torch.zeros(max_seq_length, d_model)  # 初始化位置编码矩阵
        position = torch.arange(0, max_seq_length, dtype=torch.float).unsqueeze(1)
        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)  # 奇数位置使用余弦函数
        self.register_buffer('pe', pe.unsqueeze(0))  # 注册为缓冲区
        
    def forward(self, x):
        # 将位置编码添加到输入中
        return x + self.pe[:, :x.size(1)]

image.gif

构建编码器块(Encoder Layer)


包含一个自注意力机制和一个前馈网络,每个子层后接残差连接和层归一化。

class EncoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, 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)
        self.dropout = nn.Dropout(dropout)  # Dropout
        
    def forward(self, x, mask):
        # 自注意力机制
        attn_output = self.self_attn(x, x, x, mask)
        x = self.norm1(x + self.dropout(attn_output))  # 残差连接和层归一化
        
        # 前馈网络
        ff_output = self.feed_forward(x)
        x = self.norm2(x + self.dropout(ff_output))  # 残差连接和层归一化
        return x

image.gif

构建解码器模块

image.gif

包含一个自注意力机制、一个交叉注意力机制和一个前馈网络,每个子层后接残差连接和层归一化。

class DecoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, 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)
        self.dropout = nn.Dropout(dropout)  # Dropout
        
    def forward(self, x, enc_output, src_mask, tgt_mask):
        # 自注意力机制
        attn_output = self.self_attn(x, x, x, tgt_mask)
        x = self.norm1(x + self.dropout(attn_output))  # 残差连接和层归一化
        
        # 交叉注意力机制
        attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
        x = self.norm2(x + self.dropout(attn_output))  # 残差连接和层归一化
        
        # 前馈网络
        ff_output = self.feed_forward(x)
        x = self.norm3(x + self.dropout(ff_output))  # 残差连接和层归一化
        return x

image.gif

构建完整的 Transformer 模型

image.gif

使用随机数据训练模型,计算损失并更新参数。

# 超参数
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  # Dropout 概率
# 初始化模型
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))  # 源序列
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length))  # 目标序列
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss(ignore_index=0)  # 忽略填充部分的损失
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
# 训练循环
transformer.train()
for epoch in range(100):
    optimizer.zero_grad()  # 清空梯度,防止累积
    
    # 输入目标序列时去掉最后一个词(用于预测下一个词)
    output = transformer(src_data, tgt_data[:, :-1])  
    
    # 计算损失时,目标序列从第二个词开始(即预测下一个词)
    # output形状: (batch_size, seq_length-1, tgt_vocab_size)
    # 目标形状: (batch_size, seq_length-1)
    loss = criterion(
        output.contiguous().view(-1, tgt_vocab_size), 
        tgt_data[:, 1:].contiguous().view(-1)
    )
    
    loss.backward()        # 反向传播
    optimizer.step()       # 更新参数
    print(f"Epoch: {epoch+1}, Loss: {loss.item()}")

image.gif

模型评估

评估过程:在验证数据上计算损失,评估模型性能。

transformer.eval()
# 生成验证数据
val_src_data = torch.randint(1, src_vocab_size, (64, max_seq_length))
val_tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length))
# 假设输入为一批英文和对应的中文翻译(已转换为索引)
# 示例数据:
# src_data: [[3, 14, 25, ..., 0, 0], ...]  # 英文句子(0为填充符)
# tgt_data: [[5, 20, 36, ..., 0, 0], ...]  # 中文翻译(0为填充符)
# 注意:实际应用中需对文本进行分词、编码、填充等预处理
with torch.no_grad():
    val_output = transformer(val_src_data, val_tgt_data[:, :-1])
    val_loss = criterion(val_output.contiguous().view(-1, tgt_vocab_size), val_tgt_data[:, 1:].contiguous().view(-1))
    print(f"Validation Loss: {val_loss.item()}")

image.gif

参考文献:

[1]PyTorch 构建 Transformer 模型 | 菜鸟教程

目录
相关文章
|
16天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
5871 30
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
1天前
|
数据采集 人工智能 前端开发
让 Coding Agent 从黑盒到透明:阿里云 Agent 观测审计数据采集实践
AI Agent 规模化落地带来执行黑盒、行为难追溯、成本难度量三大难题。阿里云基于 OTel 标准,面向 Coding Agent、个人通用助理和框架型 Agent,推出 LoongSuite Pilot、插件及探针等无侵入采集方案,让 Agent 实现可看见、可分析、可审计、可治理。
561 134
|
10天前
|
存储 定位技术 数据库
CodeGraph 如何让 Claude Code减少 7 成工具调用?
CodeGraph 为 Coding Agent 提供本地代码知识图谱,把函数、类、调用链和框架路由提前整理成“项目地图”,减少盲目搜索和文件读取。它不是新 Agent,而是上下文基础设施,让 Agent 更快找到正确代码路径,平均减少 7 成工具调用。
1177 2
|
8天前
|
人工智能 安全 定位技术
CodeGraph深度解析 让Claude Code工具调用直降七成的核心原理与实操教程
如今以Claude Code为代表的AI编程智能体已经成为开发者日常编码、项目重构、漏洞修复的必备工具。但在长期使用过程中,几乎所有开发者都会遇到同一个明显痛点:AI虽然具备强大的代码生成与分析能力,却常常陷入盲目探索的循环中。
959 1
|
17天前
|
人工智能 自然语言处理 供应链
|
8天前
|
人工智能 弹性计算 安全
阿里云618活动时间、活动入口、优惠活动详细解读
2026年阿里云618创新加速季已全面开启,作为年度力度最大的云产品促销活动,本次大促覆盖轻量应用服务器、ECS云服务器、GPU云服务器、数据库、AI算力、安全服务、CDN等全品类产品,推出5亿元算力补贴、新用户限时秒杀、普惠满减、企业专享、免费试用、云大使返佣等多重福利,个人开发者、中小企业、AI团队均可享受专属低价。本文将系统梳理2026年阿里云618活动的完整时间节点、官方参与入口、各类优惠细则、使用规则、热门产品推荐及实操代码,帮助用户精准参与、高效省钱,以最低成本完成上云部署。
764 4
|
8天前
|
运维
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
1432 0