当前位置: 首页 > news >正文

中英翻译器之04 Transformer 翻译模型

中英翻译器之04 Transformer 翻译模型超通俗详解笔记

一、这个脚本到底是干嘛的?

一句话说清楚:这就是 AI 翻译官的大脑!

前面我们把中文和英文都变成了数字,又打包成了标准的批次。现在这个脚本定义了一个完整的 Transformer 模型,它能:

  1. 看懂输入的中文数字序列
  2. 理解中文句子的意思
  3. 一个词一个词地生成对应的英文数字序列

这是整个机器翻译项目最核心、最复杂的部分,也是 Transformer 架构的经典实现。


二、代码逐行大白话解释

2.1 开头导入工具

# 导入数学库:用来计算正弦余弦函数(位置编码要用)importmath# 导入PyTorch核心库importtorch# 导入神经网络模块:所有层都从这里来fromtorchimportnn# 导入配置文件:里面存了模型维度、头数、层数等超参数fromconfigimport*

2.2 主模型类 TranslationModel

这是整个模型的外壳,采用了经典的 ** 编码器 - 解码器 (Encoder-Decoder)** 架构。

超级形象的比喻

  • 编码器 = 翻译官的耳朵和大脑:负责听懂中文,理解句子的意思
  • 解码器 = 翻译官的嘴巴:负责把理解到的意思用英文说出来
  • 整个过程:中文句子 → 编码器理解 → 解码器生成英文句子
# 自定义翻译模型类,继承自PyTorch的nn.Module(所有神经网络都必须继承它)classTranslationModel(nn.Module):# 初始化方法:定义模型的所有零件def__init__(self,src_vocab_size,tgt_vocab_size,src_padding_idx=0,tgt_padding_idx=0):# 调用父类的构造函数(PyTorch规定必须写)super().__init__()# 保存填充标记的ID(就是0),后面用来生成掩码self.src_padding_idx=src_padding_idx self.tgt_padding_idx=tgt_padding_idx
零件 1:词嵌入层 (Embedding)
# ============ 1. 词嵌入层 ============# 中文词嵌入层:把中文数字ID变成有意义的向量self.src_embedding=nn.Embedding(num_embeddings=src_vocab_size,# 中文词表有多大embedding_dim=DIM_MODEL,# 每个词变成多少维的向量padding_idx=src_padding_idx# 告诉模型ID=0是填充,不用管它)# 英文词嵌入层:和中文一样,把英文数字ID变成向量self.tgt_embedding=nn.Embedding(num_embeddings=tgt_vocab_size,embedding_dim=DIM_MODEL,padding_idx=tgt_padding_idx)

大白话解释

  • 之前我们把 “我” 变成了数字 4,但数字 4 本身没有任何意义
  • 词嵌入层就是把每个数字变成一个有意义的长向量
  • 比如:"我"→[0.1, 0.5, -0.3, …],"你"→[0.2, 0.4, -0.2, …]
  • 意思相近的词,它们的向量也会很接近
  • padding_idx=0:告诉模型 ID=0 是我们用来填充的,不用学习它的向量,永远是 0
零件 2:位置编码层 (PositionEncoding)
# ============ 2. 位置编码层 ============# 给每个位置加上位置信息,告诉模型哪个词在前,哪个词在后self.pos_encoding=PositionEncoding(MAX_SEQ_LEN,DIM_MODEL)

超级重要的大白话解释

  • Transformer 有个天生的缺陷:它不知道词的顺序!
  • 对它来说,“我爱你” 和 “你爱我” 是完全一样的,因为它只看词,不看位置
  • 所以我们必须手动给每个位置加上一个独特的 “位置标签”
  • 位置编码层就是干这个的:给第 1 个词加标签 1,第 2 个词加标签 2,以此类推
  • 这样模型就能区分不同位置的词了
零件 3:Transformer 主体
# ============ 3. Transformer主体 ============# 使用PyTorch官方写好的Transformer模型self.transformer=nn.Transformer(d_model=DIM_MODEL,# 模型的特征维度(和词嵌入维度一样)nhead=NUM_HEADS,# 多头注意力的头数(一般是8)num_encoder_layers=NUM_ENCODER_LAYERS,# 编码器有几层(一般是6)num_decoder_layers=NUM_DECODER_LAYERS,# 解码器有几层(一般是6)batch_first=True# 输入的第一个维度是批次大小(必须加!))

大白话解释

  • 这就是 Transformer 的核心,里面包含了所有的注意力机制
  • 多头注意力:让模型同时从多个角度关注句子中的词
  • 比如翻译 “我吃苹果”,模型在翻译 “吃” 的时候,会同时关注 “我”(谁吃)和 “苹果”(吃什么)
  • batch_first=True:和之前 DataLoader 里的一样,保证输入形状是(批次大小, 序列长度)
零件 4:输出线性层
# ============ 4. 输出线性层 ============# 把模型的输出变成词表大小的向量,每个位置对应一个词的概率self.linear=nn.Linear(in_features=DIM_MODEL,out_features=tgt_vocab_size)

大白话解释

  • 解码器输出的是一个 512 维的向量,但我们需要知道下一个词是什么
  • 线性层就是把这个 512 维的向量,变成一个和英文词表一样大的向量
  • 比如英文词表有 10000 个词,就输出一个 10000 维的向量
  • 向量中最大的那个值对应的词,就是模型预测的下一个词

前向传播方法:数据在模型里的旅行路线
# 前向传播方法:定义数据从输入到输出的完整流程defforward(self,src_seq,tgt_seq,src_padding_mask,tgt_mask,tgt_padding_mask):# 第一步:中文句子经过编码器,变成上下文向量(理解了中文的意思)memory=self.encode(src_seq,src_padding_mask)# 第二步:根据上下文向量和已经生成的英文,预测下一个词output=self.decode(tgt_seq,memory,tgt_mask,tgt_padding_mask,src_padding_mask)returnoutput

编码过程:听懂中文

# 编码过程:把中文句子变成上下文向量defencode(self,src_seq,src_padding_mask):# 1. 中文数字ID → 词嵌入向量embed=self.src_embedding(src_seq)# 2. 加上位置编码,告诉模型词的顺序src=self.pos_encoding(embed)# 3. 经过Transformer编码器,得到上下文向量# src_padding_mask告诉编码器哪些位置是填充的,不用管memory=self.transformer.encoder(src=src,src_key_padding_mask=src_padding_mask)returnmemory

解码过程:说出英文

# 解码过程:根据上下文向量生成英文defdecode(self,tgt_seq,memory,tgt_mask,tgt_padding_mask,memory_padding_mask):# 1. 英文数字ID → 词嵌入向量embed=self.tgt_embedding(tgt_seq)# 2. 加上位置编码tgt=self.pos_encoding(embed)# 3. 经过Transformer解码器output=self.transformer.decoder(tgt=tgt,memory=memory,# 编码器输出的上下文向量tgt_mask=tgt_mask,# 因果掩码:不能看未来的词tgt_key_padding_mask=tgt_padding_mask,# 英文填充掩码memory_key_padding_mask=memory_padding_mask# 中文填充掩码)# 4. 经过线性层,变成词表大小的概率向量output=self.linear(output)returnoutput

⚠️ 这里有个新手最容易困惑的点:tgt_seq是什么?

  • 这是 ** 教师强制训练 (Teacher Forcing)** 的概念
  • 训练的时候,我们不把模型上一步预测的词作为下一步的输入
  • 而是直接把正确的英文句子作为输入,让模型学习
  • 比如要翻译 “我爱你” 成 “I love you”:
    • 输入给解码器的tgt_seq是:[<sos>, I, love, you]
    • 模型需要输出的是:[I, love, you, <eos>]
  • 这样训练更快,更稳定

2.3 位置编码层 PositionEncoding

这是整个模型中最 “玄学” 也最巧妙的部分。

# 自定义位置编码层:给每个位置生成独特的位置标签classPositionEncoding(nn.Module):def__init__(self,max_len,d_model):super().__init__()# 创建一个空矩阵,形状是(最大序列长度, 模型维度)pe=torch.zeros(max_len,d_model)# 遍历每个位置forposinrange(max_len):# 遍历每个维度for_2iinrange(0,d_model,2):# 偶数维度用正弦函数pe[pos,_2i]=math.sin(pos/(10000**(_2i/d_model)))# 奇数维度用余弦函数pe[pos,_2i+1]=math.cos(pos/(10000**(_2i/d_model)))# 把这个矩阵注册为模型的缓冲区(不是可训练参数)self.register_buffer('pe',pe)defforward(self,x):# 截取和输入序列一样长的位置编码,加到词向量上returnx+self.pe[0:x.shape[1]]

大白话解释

  • 为什么用正弦余弦函数?因为它有个神奇的性质:

    • 任意两个位置 k 和 k+d 的位置编码,都可以通过线性变换得到
    • 这样模型就能很容易地学习到词之间的相对位置关系
  • register_buffer是什么意思?

    • 这个位置编码是我们提前算好的,固定不变的,不需要模型学习
    • 注册为缓冲区后,它会跟着模型一起保存和移动(CPU/GPU)
    • 但不会被优化器更新

2.4 测试代码:验证模型能跑通

if__name__=='__main__':# 假设中文词表1000个词,英文词表1500个词src_vocab_size=1000tgt_vocab_size=1500# 创建模型实例model=TranslationModel(src_vocab_size,tgt_vocab_size)# 随机生成一个批次的中文数据:32个句子,每个句子20个词src_seq=torch.randint(src_vocab_size,(BATCH_SIZE,20))# 随机生成一个批次的英文数据:32个句子,每个句子17个词tgt_seq=torch.randint(tgt_vocab_size,(BATCH_SIZE,17))# 生成填充掩码:标记哪些位置是0(填充)src_padding_mask=(src_seq==0)tgt_padding_mask=(tgt_seq==0)# 生成因果掩码:防止解码器看到未来的词# 这是一个上三角矩阵,对角线以上都是True,表示这些位置被屏蔽tgt_mask=model.transformer.generate_square_subsequent_mask(tgt_seq.shape[1]).bool()# 执行前向传播output=model(src_seq,tgt_seq,src_padding_mask,tgt_mask,tgt_padding_mask)# 打印输出形状:应该是(32, 17, 1500)# 32个句子,每个句子17个词,每个词对应1500个词的概率print(output.shape)

因果掩码的形象解释

  • 就像考试的时候,你只能看已经做过的题,不能看后面的题
  • 模型在预测第 3 个词的时候,只能看第 1 和第 2 个词,不能看第 4、5… 个词
  • 因果掩码就是一个上三角矩阵,把未来的位置都挡住了

三、特别重要的说明

  1. 整个模型的完整流程

    中文数字序列 → 中文词嵌入 → 加位置编码 → 编码器 → 上下文向量 ↓ 英文数字序列 → 英文词嵌入 → 加位置编码 → 解码器 → 线性层 → 英文概率序列
  2. 三个掩码的作用

    掩码作用
    src_padding_mask告诉编码器哪些位置是填充的,不用关注
    tgt_padding_mask告诉解码器哪些位置是填充的,不用关注
    tgt_mask告诉解码器不能看未来的词,只能看已经生成的词
  3. 新手最容易踩的坑

    • 忘了加batch_first=True:这会导致形状不匹配,非常难排查
    • 因果掩码的形状不对:必须是(T, T),T 是目标序列长度
    • 填充掩码的类型不对:必须是布尔类型,True 表示被屏蔽
    • 位置编码的维度和词嵌入维度不一样:必须完全相同
  4. 为什么输出是 logits 而不是概率?

    • 线性层输出的是 logits(原始得分),还没有经过 softmax
    • 因为 PyTorch 的交叉熵损失函数内部已经包含了 softmax
    • 这样做数值更稳定,计算更快
http://www.cnnetsun.cn/news/2578339.html

相关文章:

  • 智能解锁B站缓存:m4s-converter完整恢复指南
  • 避坑指南:用Springer的sn-basic模板投稿时,如何快速搞定参考文献的序号排序问题?
  • 如何3分钟完成iOS应用签名:终极图形化解决方案指南
  • 《学Unity的猫》——第十六章:Unity粒子系统ParticleSystem,打造一场浪漫樱花雨
  • 终极英雄联盟自动化工具指南:5分钟掌握League Akari核心功能
  • CAD与3D打印电子集成:多工艺自动化设计制造实践
  • 基于深度信念网络的软件缺陷预测:从原理到工程实践
  • 模型驱动工程与领域特定建模:提升软件开发效率的核心实践
  • 别再纠结了!给电子新人的EDA软件选择指南:AD、PADS、Allegro到底怎么选?
  • 终极指南:使用ROFL-Player深度解析英雄联盟回放文件
  • PICT:成对组合测试的终极解决方案与架构革命
  • 从LED到数字钟:AVR动态扫描与BASCOM定时器编程实战
  • 数据科学家必备的时序信号处理实战指南
  • 从稀疏到清晰:K-SVD字典学习在医学图像降噪中的实战解析
  • ChanlunX缠论插件:快速掌握通达信自动缠论分析的终极指南
  • CANoe FDX协议实战:手把手教你用Wireshark抓包调试UDP通信(避坑指南)
  • 国产多模态大模型:如何成为元宇宙的“创世引擎”?
  • 3步学会缠论自动化:用ChanlunX插件告别手动画线烦恼
  • 【Lovable保险系统开发实战指南】:20年架构师亲授高可用、高合规、高体验的3大核心设计法则
  • 设备端DNN训练加速器设计:攻克数据流、内存墙与计算能效挑战
  • 从FreeSRP开源项目看AD936x接口设计:如何为你的SDR项目选CMOS还是LVDS?
  • CVCL网络:轻量级跨域语义匹配系统,6%参数量实现96%大模型性能
  • 用户身份链接技术:从特征工程到图神经网络的应用与挑战
  • LGTV Companion终极指南:如何让LG电视智能同步电脑开关机
  • Excel TRIM函数实战指南:清除空格与隐形字符
  • LLM在渗透测试中的应用与PentestGPT创新实践
  • 开源MES系统架构解析:基于ISA88/ISA95标准的制造业数字化转型技术实现
  • Struts2 OGNL表达式执行漏洞原理与三重防御体系
  • A64架构中TLBI RVALE1IS指令详解与性能优化
  • Soul聊天协议逆向实战:Protobuf解析与TLS绕过技术