基于 GRU 的 Seq2Seq 中英机器翻译:从 Tatoeba 语料到 BLEU 0.195

基于 GRU 的 Seq2Seq 中英机器翻译:从 Tatoeba 语料到 BLEU 0.195

项目地址(Gitee):https://gitee.com/Touari/seq2seq_zh_en_translation.git

本文整合自项目《构建文档》与《技术文档》,既讲复现步骤,也讲设计原理。


1. 项目简介

中文 → 英文(中译英)的机器翻译模型 ,采用基于 GRU 的 Seq2Seq(Encoder-Decoder)架构,无注意力机制。

复制代码
输入:你好。
输出:Hi.
  • 数据:Tatoeba cmn-eng 平行语料(cmn.txt,29,155 句对)
  • 技术栈:PyTorch + GRU 循环网络 + Teacher Forcing 训练 + 贪心解码(Greedy Decoding)
  • 实测成绩:训练 loss 0.0642 (50 epoch),测试集 BLEU 0.1952(优于课程原版 0.1823)

#mermaid-svg-HnZPNABefPJIr24N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HnZPNABefPJIr24N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HnZPNABefPJIr24N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HnZPNABefPJIr24N .error-icon{fill:#552222;}#mermaid-svg-HnZPNABefPJIr24N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HnZPNABefPJIr24N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HnZPNABefPJIr24N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HnZPNABefPJIr24N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HnZPNABefPJIr24N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HnZPNABefPJIr24N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HnZPNABefPJIr24N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HnZPNABefPJIr24N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HnZPNABefPJIr24N .marker.cross{stroke:#333333;}#mermaid-svg-HnZPNABefPJIr24N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HnZPNABefPJIr24N p{margin:0;}#mermaid-svg-HnZPNABefPJIr24N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HnZPNABefPJIr24N .cluster-label text{fill:#333;}#mermaid-svg-HnZPNABefPJIr24N .cluster-label span{color:#333;}#mermaid-svg-HnZPNABefPJIr24N .cluster-label span p{background-color:transparent;}#mermaid-svg-HnZPNABefPJIr24N .label text,#mermaid-svg-HnZPNABefPJIr24N span{fill:#333;color:#333;}#mermaid-svg-HnZPNABefPJIr24N .node rect,#mermaid-svg-HnZPNABefPJIr24N .node circle,#mermaid-svg-HnZPNABefPJIr24N .node ellipse,#mermaid-svg-HnZPNABefPJIr24N .node polygon,#mermaid-svg-HnZPNABefPJIr24N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HnZPNABefPJIr24N .rough-node .label text,#mermaid-svg-HnZPNABefPJIr24N .node .label text,#mermaid-svg-HnZPNABefPJIr24N .image-shape .label,#mermaid-svg-HnZPNABefPJIr24N .icon-shape .label{text-anchor:middle;}#mermaid-svg-HnZPNABefPJIr24N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HnZPNABefPJIr24N .rough-node .label,#mermaid-svg-HnZPNABefPJIr24N .node .label,#mermaid-svg-HnZPNABefPJIr24N .image-shape .label,#mermaid-svg-HnZPNABefPJIr24N .icon-shape .label{text-align:center;}#mermaid-svg-HnZPNABefPJIr24N .node.clickable{cursor:pointer;}#mermaid-svg-HnZPNABefPJIr24N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HnZPNABefPJIr24N .arrowheadPath{fill:#333333;}#mermaid-svg-HnZPNABefPJIr24N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HnZPNABefPJIr24N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HnZPNABefPJIr24N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HnZPNABefPJIr24N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HnZPNABefPJIr24N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HnZPNABefPJIr24N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HnZPNABefPJIr24N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HnZPNABefPJIr24N .cluster text{fill:#333;}#mermaid-svg-HnZPNABefPJIr24N .cluster span{color:#333;}#mermaid-svg-HnZPNABefPJIr24N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HnZPNABefPJIr24N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HnZPNABefPJIr24N rect.text{fill:none;stroke-width:0;}#mermaid-svg-HnZPNABefPJIr24N .icon-shape,#mermaid-svg-HnZPNABefPJIr24N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HnZPNABefPJIr24N .icon-shape p,#mermaid-svg-HnZPNABefPJIr24N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HnZPNABefPJIr24N .icon-shape .label rect,#mermaid-svg-HnZPNABefPJIr24N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HnZPNABefPJIr24N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HnZPNABefPJIr24N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HnZPNABefPJIr24N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} process.py
train.py
evaluate.py
predict.py
cmn.txt

29,155 句对
train.jsonl

23,324 条
best.pt

loss 0.0642
BLEU 0.1952
交互翻译

与前两个项目对比(学习路线的质变)

RNN_TextInput(项目1) LSTM_Sentiment_2class(项目2) Seq2Seq_Translation(本项目)
任务 前 5 词预测下一词 整句判定正/负情感 中译英翻译
任务本质 多分类(21,138 类) 二分类(2 类) 序列生成(逐词产出整句)
网络形态 单 RNN 编码 单 LSTM 编码 双网络:Encoder + Decoder
循环层 nn.RNN nn.LSTM nn.GRU(比 LSTM 更轻量)
输出 一个词(下一词) 一个 logit 一串词(直到 ``)
训练信号 交叉熵 BCE 交叉熵(teacher forcing
评估 top-1/top-5 准确率 准确率 BLEU(n-gram 匹配)

核心跨越:前两个项目是「看一句话 → 出一个 结论」(判别式);本项目是「看一句话 → 生成一整句 」(生成式)。生成式的关键是自回归:逐个词地生成,每个新词都依赖之前生成的词。


2. 整体架构与数据流

#mermaid-svg-aklzAJlDqTWHIHWI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aklzAJlDqTWHIHWI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aklzAJlDqTWHIHWI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aklzAJlDqTWHIHWI .error-icon{fill:#552222;}#mermaid-svg-aklzAJlDqTWHIHWI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aklzAJlDqTWHIHWI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aklzAJlDqTWHIHWI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aklzAJlDqTWHIHWI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aklzAJlDqTWHIHWI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aklzAJlDqTWHIHWI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aklzAJlDqTWHIHWI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aklzAJlDqTWHIHWI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aklzAJlDqTWHIHWI .marker.cross{stroke:#333333;}#mermaid-svg-aklzAJlDqTWHIHWI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aklzAJlDqTWHIHWI p{margin:0;}#mermaid-svg-aklzAJlDqTWHIHWI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aklzAJlDqTWHIHWI .cluster-label text{fill:#333;}#mermaid-svg-aklzAJlDqTWHIHWI .cluster-label span{color:#333;}#mermaid-svg-aklzAJlDqTWHIHWI .cluster-label span p{background-color:transparent;}#mermaid-svg-aklzAJlDqTWHIHWI .label text,#mermaid-svg-aklzAJlDqTWHIHWI span{fill:#333;color:#333;}#mermaid-svg-aklzAJlDqTWHIHWI .node rect,#mermaid-svg-aklzAJlDqTWHIHWI .node circle,#mermaid-svg-aklzAJlDqTWHIHWI .node ellipse,#mermaid-svg-aklzAJlDqTWHIHWI .node polygon,#mermaid-svg-aklzAJlDqTWHIHWI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aklzAJlDqTWHIHWI .rough-node .label text,#mermaid-svg-aklzAJlDqTWHIHWI .node .label text,#mermaid-svg-aklzAJlDqTWHIHWI .image-shape .label,#mermaid-svg-aklzAJlDqTWHIHWI .icon-shape .label{text-anchor:middle;}#mermaid-svg-aklzAJlDqTWHIHWI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aklzAJlDqTWHIHWI .rough-node .label,#mermaid-svg-aklzAJlDqTWHIHWI .node .label,#mermaid-svg-aklzAJlDqTWHIHWI .image-shape .label,#mermaid-svg-aklzAJlDqTWHIHWI .icon-shape .label{text-align:center;}#mermaid-svg-aklzAJlDqTWHIHWI .node.clickable{cursor:pointer;}#mermaid-svg-aklzAJlDqTWHIHWI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aklzAJlDqTWHIHWI .arrowheadPath{fill:#333333;}#mermaid-svg-aklzAJlDqTWHIHWI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aklzAJlDqTWHIHWI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aklzAJlDqTWHIHWI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aklzAJlDqTWHIHWI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aklzAJlDqTWHIHWI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aklzAJlDqTWHIHWI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aklzAJlDqTWHIHWI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aklzAJlDqTWHIHWI .cluster text{fill:#333;}#mermaid-svg-aklzAJlDqTWHIHWI .cluster span{color:#333;}#mermaid-svg-aklzAJlDqTWHIHWI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aklzAJlDqTWHIHWI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aklzAJlDqTWHIHWI rect.text{fill:none;stroke-width:0;}#mermaid-svg-aklzAJlDqTWHIHWI .icon-shape,#mermaid-svg-aklzAJlDqTWHIHWI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aklzAJlDqTWHIHWI .icon-shape p,#mermaid-svg-aklzAJlDqTWHIHWI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aklzAJlDqTWHIHWI .icon-shape .label rect,#mermaid-svg-aklzAJlDqTWHIHWI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aklzAJlDqTWHIHWI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aklzAJlDqTWHIHWI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aklzAJlDqTWHIHWI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据预处理
dataset.py

TranslationDataset + collate_fn

pad_sequence 按 batch 最长补齐
train.py

GRU Encoder-Decoder

teacher forcing + CrossEntropyLoss(ignore pad)
predict.py

greedy 自回归解码
evaluate.py

corpus_bleu
process.py

读取 → 8:2 划分 → 双语建词表 → 编码 → 存 jsonl
data/raw/cmn.txt

29,155 句对(Tatoeba cmn-eng)
train.jsonl 23,324 条
test.jsonl 5,831 条
DataLoader

batch_size=64, shuffle
models/best.pt

50 epoch, loss 0.0642

参数 3,797,619
英文翻译
BLEU = 0.1952

核心思想:把「翻译」转化为「条件语言模型」 ------模型学习条件概率 P(英文词序列∣中文词序列)P(英文词序列 | 中文词序列)P(英文词序列∣中文词序列)。Encoder 把中文「压缩」成一个向量(context vector),Decoder 在这个向量的引导下逐词生成英文。


3. 数据处理详解

3.1 原始数据

cmn.txt 来自 Tatoebatatoeba.org,开源平行语料库),每行三列(制表符分隔):

复制代码
Hi.	嗨。	CC-BY 2.0 (France) Attribution: tatoeba.org #538123 ...
Run.	你用跑的。	CC-BY 2.0 (France) Attribution: tatoeba.org #4008918 ...
  • 29,155 句对,全部是短句(日常会话级)------训练快、效果好、适合入门的关键原因
  • usecols=[0, 1] 只取英文和中文两列,忽略出处

3.2 划分数据集

train_test_split(test_size=0.2, random_state=42):训练 23,324 句 / 测试 5,831 句。random_state=42 是本项目相对课程的改进:划分可复现(课程原版未固定随机种子)。

3.3 分词与词表(双语策略)

两侧分词策略完全不同(第一个设计决策):

分词方式 词表大小(实测) 原因
中文(Encoder 输入) 字符级 list(text) 2,746 中文分词(jieba)错误会传播成翻译错误;字符级零误差、词表小
英文(Decoder 输出) NLTK Treebank 词级 7,411 英文天然空格分词;Treebank 处理标点与缩写(don'tdo n't

词表结构:['', '', '', ''] + 语料 token

特殊符 索引 含义
`` 0 填充占位(padding_idx,向量恒 0、不参与梯度)
`` 1 未登录词统一映射(OOV 兜底)
`` 2 序列开始(decoder 的启动信号)
`` 3 序列结束(decoder 的停止信号)

只使用训练集建词表 ------ 防止测试集信息泄漏。

3.4 编码契约:两侧不对称(最容易踩的坑)

encode(text, add_sos_eos=False),由调用方显式决定是否加边界:

python 复制代码
train_df['zh'] = train_df['zh'].apply(lambda x: zh_tokenizer.encode(x, add_sos_eos=False))  # 中文:不加
train_df['en'] = train_df['en'].apply(lambda x: en_tokenizer.encode(x, add_sos_eos=True))   # 英文:加

#mermaid-svg-va2bpNp91FZuZj5N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-va2bpNp91FZuZj5N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-va2bpNp91FZuZj5N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-va2bpNp91FZuZj5N .error-icon{fill:#552222;}#mermaid-svg-va2bpNp91FZuZj5N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-va2bpNp91FZuZj5N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-va2bpNp91FZuZj5N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-va2bpNp91FZuZj5N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-va2bpNp91FZuZj5N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-va2bpNp91FZuZj5N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-va2bpNp91FZuZj5N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-va2bpNp91FZuZj5N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-va2bpNp91FZuZj5N .marker.cross{stroke:#333333;}#mermaid-svg-va2bpNp91FZuZj5N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-va2bpNp91FZuZj5N p{margin:0;}#mermaid-svg-va2bpNp91FZuZj5N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-va2bpNp91FZuZj5N .cluster-label text{fill:#333;}#mermaid-svg-va2bpNp91FZuZj5N .cluster-label span{color:#333;}#mermaid-svg-va2bpNp91FZuZj5N .cluster-label span p{background-color:transparent;}#mermaid-svg-va2bpNp91FZuZj5N .label text,#mermaid-svg-va2bpNp91FZuZj5N span{fill:#333;color:#333;}#mermaid-svg-va2bpNp91FZuZj5N .node rect,#mermaid-svg-va2bpNp91FZuZj5N .node circle,#mermaid-svg-va2bpNp91FZuZj5N .node ellipse,#mermaid-svg-va2bpNp91FZuZj5N .node polygon,#mermaid-svg-va2bpNp91FZuZj5N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-va2bpNp91FZuZj5N .rough-node .label text,#mermaid-svg-va2bpNp91FZuZj5N .node .label text,#mermaid-svg-va2bpNp91FZuZj5N .image-shape .label,#mermaid-svg-va2bpNp91FZuZj5N .icon-shape .label{text-anchor:middle;}#mermaid-svg-va2bpNp91FZuZj5N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-va2bpNp91FZuZj5N .rough-node .label,#mermaid-svg-va2bpNp91FZuZj5N .node .label,#mermaid-svg-va2bpNp91FZuZj5N .image-shape .label,#mermaid-svg-va2bpNp91FZuZj5N .icon-shape .label{text-align:center;}#mermaid-svg-va2bpNp91FZuZj5N .node.clickable{cursor:pointer;}#mermaid-svg-va2bpNp91FZuZj5N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-va2bpNp91FZuZj5N .arrowheadPath{fill:#333333;}#mermaid-svg-va2bpNp91FZuZj5N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-va2bpNp91FZuZj5N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-va2bpNp91FZuZj5N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-va2bpNp91FZuZj5N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-va2bpNp91FZuZj5N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-va2bpNp91FZuZj5N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-va2bpNp91FZuZj5N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-va2bpNp91FZuZj5N .cluster text{fill:#333;}#mermaid-svg-va2bpNp91FZuZj5N .cluster span{color:#333;}#mermaid-svg-va2bpNp91FZuZj5N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-va2bpNp91FZuZj5N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-va2bpNp91FZuZj5N rect.text{fill:none;stroke-width:0;}#mermaid-svg-va2bpNp91FZuZj5N .icon-shape,#mermaid-svg-va2bpNp91FZuZj5N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-va2bpNp91FZuZj5N .icon-shape p,#mermaid-svg-va2bpNp91FZuZj5N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-va2bpNp91FZuZj5N .icon-shape .label rect,#mermaid-svg-va2bpNp91FZuZj5N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-va2bpNp91FZuZj5N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-va2bpNp91FZuZj5N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-va2bpNp91FZuZj5N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 英文侧 - Decoder 输入/输出
It's a nice day.

2, 3922, ..., 3

sos + 5 词 + eos,共 7 个 id
中文侧 - Encoder 输入
今天天气很好。

320, 2171, 1094, 852, 2239

5 个字 id,无边界

  • 中文不加:Encoder 只看句子本身,边界标记对它无意义
  • 英文加 :Decoder 需要 作为「开始生成」的启动词、 作为「停止生成」的目标词
  • 若中文也误加边界 → 训练与推断时 Encoder 输入分布不一致,翻译质量悄悄下降(开发中踩过的坑)

3.5 句长分布(实测统计)

max min 均值 超过 MAX_SEQ_LEN=128
中文(不含边界) 44 2 9.7 0 条
英文(含 sos/eos) 38 4 9.5 0 条

全部是短句 → MAX_SEQ_LEN=128 是安全的生成上限;collate_fnpad_sequence 按 batch 内最长句动态补齐(不是全数据集 pad 到 128,省显存省算力)。


4. 模型结构详解

4.1 网络结构

#mermaid-svg-MutXmlyIogv7NGty{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MutXmlyIogv7NGty .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MutXmlyIogv7NGty .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MutXmlyIogv7NGty .error-icon{fill:#552222;}#mermaid-svg-MutXmlyIogv7NGty .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MutXmlyIogv7NGty .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MutXmlyIogv7NGty .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MutXmlyIogv7NGty .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MutXmlyIogv7NGty .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MutXmlyIogv7NGty .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MutXmlyIogv7NGty .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MutXmlyIogv7NGty .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MutXmlyIogv7NGty .marker.cross{stroke:#333333;}#mermaid-svg-MutXmlyIogv7NGty svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MutXmlyIogv7NGty p{margin:0;}#mermaid-svg-MutXmlyIogv7NGty .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MutXmlyIogv7NGty .cluster-label text{fill:#333;}#mermaid-svg-MutXmlyIogv7NGty .cluster-label span{color:#333;}#mermaid-svg-MutXmlyIogv7NGty .cluster-label span p{background-color:transparent;}#mermaid-svg-MutXmlyIogv7NGty .label text,#mermaid-svg-MutXmlyIogv7NGty span{fill:#333;color:#333;}#mermaid-svg-MutXmlyIogv7NGty .node rect,#mermaid-svg-MutXmlyIogv7NGty .node circle,#mermaid-svg-MutXmlyIogv7NGty .node ellipse,#mermaid-svg-MutXmlyIogv7NGty .node polygon,#mermaid-svg-MutXmlyIogv7NGty .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MutXmlyIogv7NGty .rough-node .label text,#mermaid-svg-MutXmlyIogv7NGty .node .label text,#mermaid-svg-MutXmlyIogv7NGty .image-shape .label,#mermaid-svg-MutXmlyIogv7NGty .icon-shape .label{text-anchor:middle;}#mermaid-svg-MutXmlyIogv7NGty .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MutXmlyIogv7NGty .rough-node .label,#mermaid-svg-MutXmlyIogv7NGty .node .label,#mermaid-svg-MutXmlyIogv7NGty .image-shape .label,#mermaid-svg-MutXmlyIogv7NGty .icon-shape .label{text-align:center;}#mermaid-svg-MutXmlyIogv7NGty .node.clickable{cursor:pointer;}#mermaid-svg-MutXmlyIogv7NGty .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MutXmlyIogv7NGty .arrowheadPath{fill:#333333;}#mermaid-svg-MutXmlyIogv7NGty .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MutXmlyIogv7NGty .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MutXmlyIogv7NGty .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MutXmlyIogv7NGty .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MutXmlyIogv7NGty .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MutXmlyIogv7NGty .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MutXmlyIogv7NGty .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MutXmlyIogv7NGty .cluster text{fill:#333;}#mermaid-svg-MutXmlyIogv7NGty .cluster span{color:#333;}#mermaid-svg-MutXmlyIogv7NGty div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MutXmlyIogv7NGty .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MutXmlyIogv7NGty rect.text{fill:none;stroke-width:0;}#mermaid-svg-MutXmlyIogv7NGty .icon-shape,#mermaid-svg-MutXmlyIogv7NGty .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MutXmlyIogv7NGty .icon-shape p,#mermaid-svg-MutXmlyIogv7NGty .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MutXmlyIogv7NGty .icon-shape .label rect,#mermaid-svg-MutXmlyIogv7NGty .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MutXmlyIogv7NGty .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MutXmlyIogv7NGty .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MutXmlyIogv7NGty :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} TranslationDecoder(英文侧)
TranslationEncoder(中文侧)
context vector

batch, 256

→ unsqueeze(0) 作 decoder 初始 hidden
nn.Embedding

2746 × 128

padding_idx=0
nn.GRU

input=128, hidden=256

单层, batch_first
取最后有效时刻

outputarange, lengths-1
nn.Embedding

7411 × 128

padding_idx=0
nn.GRU

input=128, hidden=256
nn.Linear

256 → 7411
中文 id 序列

batch, seq

decoder 输入

batch, 1 单步
词表分布 logits

batch, 1, 7411

python 复制代码
TranslationModel:
  encoder: Embedding(2746 → 128, padding_idx=0) + GRU(128 → 256)
  decoder: Embedding(7411 → 128, padding_idx=0) + GRU(128 → 256) + Linear(256 → 7411)

4.2 前向传播形状流转

复制代码
Encoder:
  输入 x:               [batch, seq_len]        # 中文 id 序列(pad 后等长)
    → Embedding:        [batch, seq_len, 128]
    → GRU output:       [batch, seq_len, 256]   # 每个时刻的隐状态
    → 取最后有效时刻:     [batch, 256]            # context vector

Decoder(单步):
  输入 x:               [batch, 1]              # 当前步的词 id
  输入 hidden_0:        [1, batch, 256]         # 初始 = context vector unsqueeze(0)
    → Embedding:        [batch, 1, 128]
    → GRU:              [batch, 1, 256]
    → Linear:           [batch, 1, 7411]        # 词表分布 logits
  返回:                 (logits, hidden_n)      # hidden_n 传给下一步

4.3 关键设计点(含金量最高的三处)

① 取「最后一个有效时刻」的隐状态,而不是最后一步

python 复制代码
lengths = (x != self.embedding.padding_idx).sum(dim=1)  # 每句真实长度
last_hidden_state = output[torch.arange(output.shape[0]), lengths - 1]

由于 pad 在尾部,output[:, -1] 取到的是 pad 位置的隐状态(无意义)。GRU 返回的 h_n 同理位于 pad 位置,不可直接使用。(实测验证:out[0] == GRU 输出在时刻 lengths-1 的 hidden 为 True。)

padding_idx=0 双保险

  • Embedding 侧:nn.Embedding(..., padding_idx=0) → pad 词向量恒为 0,不参与梯度更新
  • 损失侧:CrossEntropyLoss(ignore_index=0) → pad 位置不计算损失
  • 漏掉第二个是踩过的坑:pad 位置占 batch 内大量 token,模型被迫学「预测 pad」,稀释真实词梯度。修复后同样 50 epoch,loss 降到 0.0642

③ Decoder 是「单步」组件

forward(x, hidden_0) 每次只吃 [batch, 1](一个词),输出一个词的分布和新的 hidden------没有内部循环。循环在训练/推断脚本里显式控制,这正是「自回归」的体现:网络本身是「一步」的,序列的推进由外部循环完成。

4.4 参数量计算(3,797,619 的分解)

计算 参数量
Encoder Embedding 2,746 × 128 351,488
Encoder GRU (128×256 + 256×256) × 3 + 256×3×2 296,448
Encoder 小计 647,936
Decoder Embedding 7,411 × 128 948,608
Decoder GRU 同 Encoder 296,448
Decoder Linear 7,411 × 256 + 7,411 1,904,627
Decoder 小计 3,149,683
总计 3,797,619 ≈ 380 万

要点:

  • GRU 有 3 组门控权重(重置门/更新门/候选隐状态),故公式为 (input×hidden + hidden×hidden) × 3 + 偏置
  • 参数大头在 Decoder 输出层 (Linear 190 万,占 50%)------英文词表 7,411 类,256 → 7411 的矩阵本身就大
  • FP32 下 best.pt 约 15.2 MB,与磁盘实测 15,196,017 字节吻合

4.5 为什么用 GRU(相对 LSTM 的选择)

GRU 是 LSTM 的简化版:把 LSTM 的遗忘门/输入门合并为更新门,去掉细胞状态(c_t),只保留隐状态。效果与 LSTM 相当但参数更少、训练更快。对翻译这种「中等长度短句」任务,GRU 是性价比选择。


5. 训练机制详解(Teacher Forcing)

5.1 核心切片

python 复制代码
decoder_inputs  = targets[:, :-1]   # 去掉最后一个 eos:作为 decoder 每一步的输入
decoder_targets = targets[:, 1:]    # 去掉开头的 sos:作为每一步的预测目标

为什么训练时喂真实词而不是模型自己的预测?

  • 模型每步都「站在正确的位置」学习:即使上一步预测错了,这一步还是学到正确的条件分布
  • 收敛快、训练稳定
  • 代价:推断时没有真实词可用,模型只能喂自己的预测------一旦某步错,误差会累积(exposure bias),这是 seq2seq 的经典痛点

5.2 训练配置

说明
损失函数 CrossEntropyLoss(ignore_index=en_pad) 只在真实词上算损失
优化器 Adam(lr=0.001) 默认参数
设备 cuda(RTX 3060)
batch 64 × 365 步/epoch 23,324 / 64
保存策略 loss 优于历史最优则覆盖 best.pt state_dict 格式
日志 TensorBoard logs/<时间戳>/ 每 epoch 记录 loss

5.3 实测训练曲线(50 epochs)

复制代码
4.3721 → 1.7293 → 0.8337 → 0.4182 → 0.2278 → 0.1431 → 0.1106 → 0.1024 → 0.0786 → 0.0939 → 0.0642
(epoch 1, 6, 11, 16, 21, 26, 31, 36, 41, 46, 50)
  • 初始 loss ≈ 4.37:大词表(7,411 类)随机猜测的理论交叉熵为 ln(7411) ≈ 8.9,实际 4.37 说明模型立刻学会了高频词
  • epoch 45-46 出现小幅回升(0.0939)后继续下降:学习率固定下的正常波动
  • 最终 0.0642 ,优于课程原版同配置的 0.0841(差距主要来自 ignore_index 修复 + 可复现划分)

6. 推断机制详解(Greedy 自回归解码)

复制代码
Encoder 编码中文 → context vector(初始 hidden)
→ decoder_input = [sos](batch 内全填 sos)
→ 循环直到全部样本生成 eos 或 i >= MAX_SEQ_LEN=128:
    单步解码 → logits [batch, 1, 7411]
    argmax 取最高概率词(greedy)
    把预测词喂回 decoder_input(自回归)
    is_finished 记录已出 eos 的样本,全结束则提前 break
→ 截断到第一个 eos 之前 → decode → 英文文本

与训练的本质区别

训练(teacher forcing) 推断(greedy)
decoder 输入 真实目标词 上一步自己的预测
出错恢复 无影响(下步仍喂真词) 误差累积(一步错步步错)
停止条件 序列长度固定 生成 `` 或到上限

为什么贪心不一定最优:每步取局部最高概率词,可能错过全局更好的组合(如「a nice day」vs「nice day a」)。改进方向:beam search(每步保留 top-K 候选路径)。


7. BLEU 评估详解

7.1 概念

BLEU(Bilingual Evaluation Understudy)衡量机器译文与参考译文的 n-gram 重合度

  • 分别统计 1~4-gram 在预测与参考中的匹配情况(modified precision)
  • 乘上长度惩罚(译文过短会扣分)
  • 输出 0~1 的分数
python 复制代码
corpus_bleu(list_of_references, predictions)
# list_of_references: [[参考1], [参考2], ...](每句可有多个参考,嵌套一层)
# predictions: [预测1, 预测2, ...]

7.2 实测结果(测试集 5,831 条全量)

模型 训练 loss(50 epoch) 测试集 BLEU
本项目(random_state=42 + ignore_index) 0.0642 0.1952
课程原版 best.pt 0.0841 0.1823

注:两者测试集划分不同(本项目固定 seed,课程随机),并非严格同题对比,但量级相同,结论可信。

7.3 怎么理解 0.1952(≈19.5%)

  • 无注意力的基础 GRU Seq2Seq + 短句语料,10~25 区间是正常水平,本项目处于中上
  • BLEU 不会接近 100%:n-gram 精确匹配很苛刻,且 Tatoeba 中一句中文常有多译(「你好。」→ Hi./Hello./How are you?),参考只有一个,天然扣分
  • 课程同架构跑出 0.1823 ------ 本项目的三个小改进(固定 seed、ignore_index、词表略异)合计带来 +1.3 点

8. 快速开始(复现指南)

8.1 环境准备

  • Python 3.12.0,GPU:RTX 3060(6GB),训练全程约 8~9 分钟(50 epoch)
版本 用途
torch 2.7.1+cu118 深度学习框架(CUDA 11.8 版)
pandas 3.0.2 数据读取与 jsonl 存储
scikit-learn 1.8.0 训练/测试集划分
nltk 3.10.2 英文 Treebank 分词 / 反分词 / BLEU 评估
tqdm 4.68.2 进度条
tensorboard 2.21.0 训练曲线可视化

命令行运行注意:① 所有脚本需 cd src 后运行(import config 平级导入)② 建议 python -E 防止 PYTHONPATH 污染 ③ 终端中文乱码是 GBK 显示问题,不影响运行

8.2 目录结构

复制代码
Seq2Seq_Translation/
├── data/
│   ├── raw/cmn.txt                      原始数据(29,155 句对,Tatoeba)
│   └── processed/train.jsonl, test.jsonl  预处理产物(token id 序列)
├── logs/                                 训练日志(TensorBoard)
├── models/
│   ├── zh_vocab.txt                     中文词表(2,746 项)
│   ├── en_vocab.txt                     英文词表(7,411 项)
│   └── best.pt                          最优模型权重(约 15.2 MB)
├── src/
│   ├── config.py                        路径与超参唯一来源
│   ├── process.py                       数据预处理流水线
│   ├── tokenizer.py                     双语分词器(中文按字符、英文 Treebank)
│   ├── dataset.py                       Dataset + collate_fn(pad_sequence 动态补齐)
│   ├── model.py                         Encoder-Decoder 模型(GRU,无注意力)
│   ├── train.py                         训练循环(Teacher Forcing)
│   ├── evaluate.py                      测试集 BLEU 评估
│   └── predict.py                       交互式翻译(贪心解码)
└── test/test-bleu.py                    corpus_bleu 用法迷你测试

8.3 全流程四步

#mermaid-svg-Ohh6MQU1LsT0Bx7c{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .error-icon{fill:#552222;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .marker.cross{stroke:#333333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c p{margin:0;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .cluster-label text{fill:#333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .cluster-label span{color:#333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .cluster-label span p{background-color:transparent;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .label text,#mermaid-svg-Ohh6MQU1LsT0Bx7c span{fill:#333;color:#333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .node rect,#mermaid-svg-Ohh6MQU1LsT0Bx7c .node circle,#mermaid-svg-Ohh6MQU1LsT0Bx7c .node ellipse,#mermaid-svg-Ohh6MQU1LsT0Bx7c .node polygon,#mermaid-svg-Ohh6MQU1LsT0Bx7c .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .rough-node .label text,#mermaid-svg-Ohh6MQU1LsT0Bx7c .node .label text,#mermaid-svg-Ohh6MQU1LsT0Bx7c .image-shape .label,#mermaid-svg-Ohh6MQU1LsT0Bx7c .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .rough-node .label,#mermaid-svg-Ohh6MQU1LsT0Bx7c .node .label,#mermaid-svg-Ohh6MQU1LsT0Bx7c .image-shape .label,#mermaid-svg-Ohh6MQU1LsT0Bx7c .icon-shape .label{text-align:center;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .node.clickable{cursor:pointer;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .arrowheadPath{fill:#333333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ohh6MQU1LsT0Bx7c .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ohh6MQU1LsT0Bx7c .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ohh6MQU1LsT0Bx7c .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .cluster text{fill:#333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .cluster span{color:#333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ohh6MQU1LsT0Bx7c rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .icon-shape,#mermaid-svg-Ohh6MQU1LsT0Bx7c .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .icon-shape p,#mermaid-svg-Ohh6MQU1LsT0Bx7c .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .icon-shape .label rect,#mermaid-svg-Ohh6MQU1LsT0Bx7c .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ohh6MQU1LsT0Bx7c .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ohh6MQU1LsT0Bx7c .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ohh6MQU1LsT0Bx7c :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生成词表+jsonl
生成 best.pt
第1步 process.py
第2步 train.py
第3步 evaluate.py
第4步 predict.py

第 1 步:数据预处理

bash 复制代码
cd src
python -E process.py

产物:train.jsonl(23,324 条)、test.jsonl(5,831 条)、en_vocab.txt(7,411 项)、zh_vocab.txt(2,746 项)。数据来源:Tatoeba 网站的 cmn-eng 句子对(英文\t中文\t出处 三列制表符分隔)。

第 2 步:训练

bash 复制代码
python -E train.py

每轮打印 ========== Epoch N ==========Loss: x.xxxx,若 loss 优于历史最优则覆盖 models/best.pt。RTX 3060 上 50 epochs 约 8~9 分钟。

第 3 步:评估(BLEU)

bash 复制代码
python -E evaluate.py

预期输出:bleu: 0.1952(5,831 条全量;课程原版同流程实测 0.1823)。

第 4 步:交互式翻译

bash 复制代码
python -E predict.py
复制代码
欢迎使用中英翻译模型(输入q或者quit退出)
中文:你好。
英文:Hi.
中文:我喜欢读书。
英文:I like reading a book.
中文:q
欢迎下次再来

8.4 常见问题(FAQ)

  • Q1:为什么必须 cd src 模块间 import config 平级导入,Python 只把「当前目录」加入 sys.path
  • Q2:为什么建议 python -E 忽略所有 PYTHON* 环境变量,避免 PYTHONPATH 污染导入错误的 numpy/torch
  • Q3:终端中文乱码? Windows 控制台默认 GBK 的显示问题,数据本身没损坏
  • Q4:重跑 process.py 后需要重新训练吗? 需要。词表顺序基于 set 迭代,token id 会变,必须「词表 + 数据」成对重建后重训
  • Q5:git 推送失败 Failed to connect 127.0.0.1:7897 全局 git 代理指向 Clash,代理没开时推送失败。绕过:git -c http.proxy= -c https.proxy= push
  • Q6:MAX_SEQ_LEN=128 够用吗? 够。实测中文句长 max=44、英文 max=38。但 dataset.py 没有截断逻辑------换成长句数据需加截断或过滤

9. 实验结果汇总

9.1 指标总览

指标 数值 基线参考
训练 loss(50 epoch) 0.0642 课程原版 0.0841
测试集 BLEU 0.1952 课程原版 0.1823;随机猜测 ≈ 0
模型参数量 3,797,619(≈380 万)
训练时长 约 8~9 分钟(RTX 3060)

9.2 实测翻译样例

中文输入 模型输出 参考
你好。 Hi.
谢谢。 Thank you.
我爱你。 I love you.
我喜欢读书。 I like reading a book. ✅(组合生成,非背诵)
今天天气很好。 It's a nice day.
你是谁? Who are you?

10. 已知边界与改进方向

边界/痛点 说明 改进方向
无注意力 所有信息压进一个 context vector,长句信息丢失 注意力机制(Attention):decoder 每步回看 encoder 全部时刻,预期 BLEU 25~35+
贪心解码 每步局部最优 ≠ 全局最优 beam search(保留 top-K 候选路径)
teacher forcing 暴露偏差 训练喂真词、推断喂自己,误差累积 scheduled sampling(训练时按概率逐渐切换喂预测词)
中文字符级 词表小但丢失词义信息 中文 BPE/词级分词(jieba + 子词)
数据量小 2.9 万短句,覆盖有限 更大语料(AI Challenger / WMT)
单层 GRU 表达力有限 多层堆叠 / 双向 Encoder
评估单一 BLEU 与人工感知不完全一致 补充 chrF、人工评测

11. 小结

本项目用基于 GRU 的 Encoder-Decoder 完成了中译英机器翻译,实现了学习路线的质变:从「判别式」(出结论)跨越到「生成式」(出整句)。三个含金量最高的设计点:取最后有效时刻隐状态padding_idx 双保险(Embedding + Loss)Decoder 单步组件设计;两个相对课程的改进(固定随机种子、ignore_index 修复)合计带来 BLEU +1.3 点(0.1823 → 0.1952)。下一步自然是加注意力机制------那正是 Transformer 的起点。

完整代码与数据见项目仓库:https://gitee.com/Touari/seq2seq_zh_en_translation.git

相关推荐
今天AI了吗3 小时前
从 LLM 到 Agent Skill:把 AI 底层概念串起来
数据库·人工智能·sql·深度学习·神经网络·算法·机器学习
网络工程小王3 小时前
【HCIE-AI】4.NLP 核心任务与技术演进学习笔记
人工智能·深度学习·自然语言处理·nlp·transformer
大模型码小白4 小时前
AI安全前沿:AI大模型安全防护的前沿技术
java·网络·人工智能·python·深度学习·学习·安全
hans汉斯14 小时前
《软件工程与应用》期刊推荐&10月版面征稿中
图像处理·人工智能·深度学习·算法·音视频·软件工程
雨晨源码(同名B站)1 天前
基于深度学习YoloV11农业病害虫害检测系统 智慧农业信息化综合管理平台 (附源码+lw文档+ppt)
数据库·人工智能·深度学习·yolo·信息可视化
liuyunshengsir1 天前
从 TVM 到 TileLang:一文读懂深度学习编译器为什么走向 Tile 化
人工智能·深度学习·tvm·tilelang
DM今天肝到几点?1 天前
英伟达发布 Nemotron 3.5 Lightning:31.6B 参数的“闪电”开源模型,本地智能体的新引擎
人工智能·深度学习·语言模型·开源·知识图谱
云和数据.ChenGuang1 天前
fastapi项目拆分实战数据模型
java·服务器·数据库·人工智能·深度学习·fastapi·强化学习
Lee_jerome1 天前
python神经网络编程入门(三十五)——Transformer 整体架构——一张图看懂全貌
nlp·transformer·encoder·注意力机制·decoder·交叉注意力·self-attention