AI Hero Club

Transformer 实战 · 第 12 章

已自行标记完成0 / 12

仅保存在当前浏览器;完成标记不代表通过项目验收。

本章提纲 6 个小节
全课目录 12 章
完整课程计划 ↗
Transformer 实战 / 课程计划 ↗

CHAPTER 12 / 12建议学习 240 分钟

最终项目:交付一台可以复跑的Transformer

别人只拿到项目包时,怎样独立复跑训练、恢复、评估和生成,并核对同一版本的结果?

本章要解决的 4 个问题
  • 独立组装数据、模型、优化、恢复、评价和生成的完整链
  • 用明确的验收矩阵检查正确性而非只看演示
  • 把参考运行的收益、失败与数据边界一起报告
  • 产出带环境、哈希、原始结果和复跑命令的项目包
课程项目 ZIP ↓

最后交付什么,怎样算是完成

本章不再换一个孤立的小例子。最终交付物就是前十一章持续构建的同一台字符Transformer:从文档编号与编码开始,经自写解码块、全参数训练、保存恢复,到独立评分和缓存生成。你应能从一份新解压的目录重复跑通,解释每个主要张量的含义,并用失败检查界定结果。如果只能展示一段生成文字,或者只能运行别人保存的权重,却无法重建训练与评价,就尚未完成这门实战课的项目。

最终包分两部分:自己的实现与一份实验报告。实现应包含数据、配置、源码、测试和必要说明;报告应包含原始指标、失败样例与对应检查点的身份信息。课程提供完整参考工程,帮助你对照公式和接口,但参考实现能跑不等于你已经理解。建议先按章节逐个补齐自己的模块,再把同一输入交给参考版对照,保存首次失败的中间量,而不是只在最后粘贴一份能通过的结果。

本课默认读者已经会写Python函数、列表、字典和运行本地程序;线性代数、概率与导数的本地用法已在前面展开。项目依赖PyTorch的张量运算和自动求导引擎,但注意力、因果遮罩、解码块与训练组织由我们自己编写,没有使用预训练模型或nn.Transformer。这是完整架构与工程链的小规模实现,不是用几千字语料训练出了预训练级中文模型。实现完整与能力规模是两条不同的边界。

先检查环境,再确认资料从哪里进入系统

解压项目包,进入README所在目录。首先确认解释器版本至少为Python3.10;课程实际验证使用Python3.12.14与PyTorch2.6.0。若系统默认python3版本太旧,改用已有符合要求的解释器创建环境,后续统一使用环境内的解释器。不要一部分命令用系统Python、另一部分用新环境,再把缺包错误误判成模型问题。下列命令不会下载预训练权重,依赖安装完成后,示例数据和全部计算都在本地。

终端命令在解压后的项目目录执行。若版本检查失败,先换用Python3.10或以上解释器。
python3 -c 'import sys; assert sys.version_info >= (3,10), sys.version'
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python -m tests.run_tests --output evidence/my-tests.json

项目示例包含二十四篇为本课程新写的短中文文档,稳定编号从doc001到doc024;标题只作说明,正文才参与训练。划分文件指定十八篇训练、三篇验证、三篇测试,然后只由训练正文构建排序字符词表。不要把“先建立全部词表,再分割文档”当作相同操作。对新语料,先写出来源和可用范围,再固定文档身份、切分与重复检查;文件名不同,并不能排除正文重复。

读取数据后,检查清单中的文档数量、内容哈希、词表与未知字符比例。每篇编码为BOS、真实字符、EOS;文档内分窗,绝不把上一文档的结尾当作下一文档的开头。每个真实字符和每个EOS在完整评价中计分一次,窗口开头的可用上下文可能较短。分窗长度和重置位置属于协议,不能在挑选最好结果后悄悄更换。第八章和第九章的同一约定在最终项目中仍有效。

阶段必须读到的输入必须产出的证据
数据文档、split、编码约定ID隔离、重复检查、词表、未知率
模型冻结Config与参数初始化形状、因果性、参数账本、梯度
训练训练窗口与采样器状态loss记录、更新次数、完整checkpoint
评价保存的词表与未更新文档有效token总量、损失和、评分协议
生成同一checkpoint与提示编号序列、停止规则、cache对照
报告同一版本的上述文件命令、环境、哈希、原始结果与失败
每个阶段的输出是下一阶段的明确输入;缺项时不能靠一张截图补上。

第一道验收:每个部件真的完成了它声称的计算

先运行数值与行为测试,再启动较长训练。注意力测试不仅检查输出形状,还用手算分数核对权重行和、未来权重为零以及值汇总;因果测试改变未来编号,要求前缀输出不变;梯度测试检查每层与每个参数张量都收到有限梯度,并选定参数用float64中心有限差分对照。这里的“收到梯度”只证明接线的一部分,优化器是否真正更新它们还需要额外检查。

有限差分的步长、精度和容差都应记录。步长过大包含明显截断误差,过小则放大相减的舍入影响;看几个步长的误差趋势,比只选一个恰好通过的数更可靠。若前向已经把整个错误目标计算得很一致,自动求导和有限差分也可能一起通过。因此还要把它们接回第二章的正确目标与第三章的手算注意力,不让“两个程序相同”替代“它们都在解决正确的问题”。

缓存测试比较同一前缀的每一步logits,覆盖单词元、多词元块、位置偏移、窗口上限与滑窗重算。保存恢复测试则比较连续训练与中途保存后继续训练:不仅看最终loss,还比较参数、优化器矩、采样排列、采样器位置、torch随机状态和逐步loss。项目额外开启dropout检查恢复,防止只在没有随机分支时偶然相等。这两类测试检验不同的状态机,不应合成一个不说明覆盖范围的绿色勾号。

读测试报告时,确认每个用例实际执行、没有被跳过,并查看失败信息和源码哈希。测试通过只覆盖已列出的输入和性质,不能推导任意数据、设备或精度都正确。一个实用的反驳步骤是故意引入已知故障,例如去掉未来遮罩、重置缓存位置、漏保存采样器状态,确认相应测试会失败,再恢复实现。这能检验测试是否有能力发现它声称检测的错误。

第二道验收:在同一套数据与参数上完成训练

参考命令使用CPU、单线程、宽度三十二、两层、四头、窗口十六、前馈宽度一百二十八、批量八。小配置降低等待成本,但注意力、前馈、嵌入、归一化和输出头的所有参数都参与优化。优化器为AdamW,学习率零点零零三、权重衰减零点零一,梯度范数裁剪到一;没有把固定注意力特征上的线性训练改名为完整Transformer训练。

终端命令完整CPU小规模训练,160是总更新次数。
.venv/bin/python -m hero_transformer.train   --output-dir runs/my-model --steps 160 --batch-size 8   --block-size 16 --d-model 32 --n-heads 4 --n-layers 2 --d-ff 128   --dropout 0 --lr 0.003 --seed 7 --threads 1 --eval-every 40

训练目录会得到checkpoint.pt、metrics.json和run-manifest.json。先对照参数账本:示例词表大小三百八十五时,总参数五万零三百六十八,均可训练。然后查看从初始模型到最终模型的全训练集与验证集分数,而不是只看最后一个随机批次的loss。采样批次的难易程度会变化,所以单步下降或上升都不能直接替代同一固定集合上的前后评价。

本课程保留的一次真实参考运行中,训练集loss从5.962669下降到2.026304,验证集从5.964481下降到5.435705。二者下降幅度差异很大,提示模型对这份小训练资料的拟合远强于未见资料上的改善。这是需要解释的结果,不是应被删掉的“不好看”数字。训练曲线、文档划分和未知率一起看,才能判断下一步该改数据、表示、容量还是优化协议。

第三道验收:独立评分、生成与恢复

终端命令用已保存的同一词表评价;测试集不参与选步。
.venv/bin/python -m hero_transformer.evaluate   --checkpoint runs/my-model/checkpoint.pt --split validation   --batch-size 8 --threads 1 --output runs/my-model/validation.json
.venv/bin/python -m hero_transformer.evaluate   --checkpoint runs/my-model/checkpoint.pt --split test   --batch-size 8 --threads 1 --output runs/my-model/test.json

参考运行的独立测试loss为5.460169,困惑度约235.137101;测试集有二百二十八个原始字符,加三篇EOS,共二百三十一个计分目标。验证未知字符比例约13.14%,测试约17.54%。它们被折叠为UNK,评分并不保留原文全部字符身份;因此不能把这份困惑度拿去与其他分词器或开放词表大模型直接排名。具体数字只指向包内对应检查点与评分协议,不是每台机器每次运行都应得到的通用常数。

终端命令生成和无缓存参考对照;temperature=0走贪心分支。
.venv/bin/python -m hero_transformer.generate   --checkpoint runs/my-model/checkpoint.pt --prompt 小禾   --max-new-tokens 60 --temperature 0 --threads 1   --output runs/my-model/greedy.json
.venv/bin/python -m hero_transformer.generate   --checkpoint runs/my-model/checkpoint.pt --prompt 小禾   --max-new-tokens 60 --temperature 0 --threads 1 --no-cache   --output runs/my-model/greedy-reference.json

这次参考模型对提示“小禾”的贪心输出为“小禾给小禾给小禾检查步。”,随后EOS。它有明显重复,也不够通顺。保留这个样例是项目报告的一部分:模型可以正确执行完整训练与推理算法,同时仍缺少足够数据和能力。把重复样例换成手工写好的流畅句子,会把软件行为和编辑产物混在一起,使前面的验收失去意义。

终端命令检查保存状态,并继续到第164步;不是额外训练164步。
.venv/bin/python -m hero_transformer.checkpoint   --checkpoint runs/my-model/checkpoint.pt   --output runs/my-model/checkpoint-summary.json
.venv/bin/python -m hero_transformer.train   --resume runs/my-model/checkpoint.pt --output-dir runs/my-model-continued   --steps 164 --threads 1 --eval-every 4

要检验这次继续是否精确,在另一独立目录用同一初始设置直接训练到一百六十四步,再比较两边状态。包内保存了这种真实CLI对照,参数最大差为零,优化器、采样器、随机状态和清单一致。这个结论只覆盖相同CPU环境、固定线程和所记录版本,不是跨GPU、跨框架版本逐位相同的承诺。前面一百六十步模型的质量表,与这里一百六十四步的恢复比较,应分别注明检查点,不拼成一份模糊的“最好模型”成绩。

第四道验收:让另一个人知道你究竟跑了什么

报告首页应写清任务、读者可复跑的命令、模型配置、数据来源与划分、环境、检查点身份以及主要结果。随后提供三类材料:正确性检查的原始输出,固定数据上的训练与评价结果,包含失败的生成样例。至少保留一个没有被优化掉的失败案例,并解释它支持什么结论、还不能支持什么。完整报告可以有失败结果;它不能有被隐藏的协议变化。

哈希用于把这些记录绑定到明确文件,不能证明算法正确或素材授权。对配置清单采用固定排序与编码再计算SHA256,可以避免仅因字典键顺序不同就得到另一身份;修改种子、词表、文档内容或架构时则应产生不同身份。模型权重、源码和数据各有自己的哈希,把一张截图的哈希称为“模型收据”没有关联作用。下例只展示清单规范化,不代替对实际项目文件的检查。

独立运行独立检查清单身份:键顺序不改变规范化内容,改变种子则改变内容。
import json,hashlib
def digest(manifest):
    encoded = json.dumps(manifest,sort_keys=True,ensure_ascii=False,separators=(',',':')).encode('utf-8')
    return hashlib.sha256(encoded).hexdigest()
a = {'seed':7,'config':{'width':32,'layers':2},'data_hash':'example-data'}
b = {'data_hash':'example-data','config':{'layers':2,'width':32},'seed':7}
changed = {**a,'seed':8}
assert digest(a)==digest(b)
assert digest(a)!=digest(changed)
print({'manifest':digest(a),'changed_seed':digest(changed)})
验收线必须展示的结果不能替代它的材料
数学与模块手算、梯度差分、因果/形状检查只看输出形状或一段文字
完整训练所有参数接入优化且实际更新仅输出层拟合或预训练权重演示
恢复与缓存同一输入/轨迹的状态与分数对照只看最后文本相似
任务评价冻结split、有效目标、未知率和失败只报训练loss或挑选样例
可复跑交付环境、命令、源码/数据/权重身份没有对应文件的成绩表
这些线分别验收,某一项通过不能抵消另一项缺失。

自评时可以给推导、实现、实验和交付分别打分,但先满足不可补偿的门槛:因果性失败、标签错位、测试资料参与训练、保存恢复不一致、报告与实际检查点不对应,任何一项都应先修复,再讨论总分。课程页面的“已完成”按钮只是你在当前浏览器的阅读记录,不是自动证明通过项目验收,也不授予外部课程机构的学分或证书。

到这里,十二章形成了一条可重构的闭环:任务定义决定标签,标签决定目标,注意力与位置决定信息路径,解码块与反传决定可训练函数,训练协议决定参数轨迹,评价与生成检验最终行为。后续扩大模型或进入从零语言模型、后训练、高效推理等课程时,可以更换一个明确模块,并沿同一链条重新验收,而不必靠新的演示重新相信一遍。

TRY IT YOURSELF

练习一|梯度存在,却没有真正训练

所有参数都得到非零有限梯度,但训练后只有输出头变化。至少给出两种可能原因、每种如何定位,以及为什么“梯度检查通过”还不够。

提示 1
梯度已存在只说明求导链,检查真正更新链还需哪些对象?
提示 2
先在step前后即时比较全部参数,再追踪随后是否被旧状态覆盖;对照优化器参数身份定位漏项。
对照参考解答
第一种是优化器只收到输出头参数:打印优化器参数对象的身份,与model.named_parameters逐一对应,检查是否漏掉嵌入或块。第二种是更新后又从旧状态恢复了部分权重,或原地覆盖了子模块:在optimizer.step前后立即比较每个参数,并追踪后续赋值。还可能有学习率为零、冻结标记或过小更新被精度吞掉。梯度检查只检查损失对当前参数的局部导数,不证明更新器覆盖、更新幅度和后续状态管理正确。

TRY IT YOURSELF

练习二|给复跑失败定位边界

甲连续训练16步,乙训练7步后保存再恢复9步。两边模型与优化器初值相同,最终参数却不同。按由输入到输出的顺序列出应比较的中间状态,不要只建议“换一个种子”。

提示 1
按“数据身份→批次输入→随机状态→前向→梯度→优化器”排列检查点。
提示 2
找到恢复后的第一个分歧步骤,先核对索引、游标与RNG,再比较该步loss和参数,不先换种子。
对照参考解答
先核对源码、配置、数据及split哈希;再比较每步采样窗口编号、补齐目标与有效token数;比较保存时与恢复后的采样排列、cursor、epoch及本地generator状态;随后比较torch RNG,尤其dropout;再比较第一个恢复步骤的前向loss、梯度、优化器矩和更新后参数。第一个出现差异的边界决定故障所在。线程、版本与模式也要一致;换种子会更换实验,不能修复这条恢复等价命题。

TRY IT YOURSELF

练习三|写一段边界正确的最终结论

参考模型训练loss显著下降、独立测试仍高、未知字符比例17.54%、生成重复,但数值与恢复检查通过。写出一段既不隐瞒进展,也不夸大能力的项目结论。

提示 1
把实现链正确与独立测试质量分成两列证据。
提示 2
一句写已验证的训练/恢复/缓存机制,另一句用未知率和重复生成约束能力结论,不删除失败。
对照参考解答
可以写:我们交付并验证了自写字符Transformer的完整数据、全参数训练、恢复、评分与缓存生成链;在固定小语料上观察到训练目标下降,并保留独立测试与错误样例。测试未知率较高,生成存在重复,因此证据只支持实现与小语料拟合机制,尚不支持广泛中文理解或高质量生成。列出同一版本的文件和命令,并把下一步的数据覆盖与评价扩展作为待做实验,而不是已完成能力。

CHECK YOUR UNDERSTANDING

检验理解

先作答,再决定是否展开解析。选择题用于发现理解缺口;独立实现与迁移任务仍需另行检验。

1. 最终项目只展示流畅文字,足以证明完整训练链吗?

查看解析外观样例不能证明数据来源、优化覆盖、评分协议或恢复状态。
2. 参数有非零梯度,是否必然被优化器更新?

查看解析求导图、优化器参数集合和后续状态管理是不同环节。
3. 续训命令的--steps 164表示什么?

查看解析本项目将steps定义为累计更新上限;恢复从保存的step继续。
4. 哪一种结论与参考实验边界一致?

查看解析正确性检查与任务能力分别报告;低训练loss不能抹去未知率和生成失败。

自行标记用于记录学习进度,不代表通过项目验收。