AI Hero Club

从零训练语言模型 · 第 1 章

已自行标记完成0 / 8

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

本章提纲 6 个小节
全课目录 8 章
完整课程计划 ↗
从零训练语言模型 / 课程计划 ↗

CHAPTER 01 / 8建议学习 120 分钟

先固定语料与问题,再谈模型

同一批短文怎样划分和计分,才能让留出损失回答一个明确的问题?

本章要解决的 3 个问题
  • 把文档、模板组、数据划分和训练目标写成可逐项核验的语料契约
  • 用完整数值例子区分文档平均、token平均与泄漏造成的虚假泛化
  • 从原始字节和分组清单重建项目数据摘要,并拒绝跨集合重复
课程项目 ZIP ↓

任务、读者起点与可交付物

本课要做的是一个能从本地原始文本出发、训练全部 decoder 参数、保存恢复并在留出文本上给出可重算指标的语言模型实验。训练规模故意很小,因此最终交付的是可信的工程证据,而不是“会说话”的能力证明。读者应能读 Python 函数与字典,计算条件概率、对数和矩阵乘法,并已学过 Transformer 实战中的因果注意力与反向传播。本课复用该课自写模型的本地副本,随后集中解决语料、编码、训练与评估的连接问题;项目压缩包不需要访问另一门课的目录。

本章先冻结问题:我们给定一篇短文的前缀,预测它的下一个离散符号。原始短文不是 token 序列;必须先保存文本和边界,再约定编码。本项目的 data/documents.jsonl 包含三十二篇原创模板短文,每条有 id、group、text、source;data/splits.json 指定二十四篇训练、四篇验证和四篇测试。group 是动作模板,而不是作者或唯一文档号。训练六类动作,验证与测试各留出一类。这种留出只考察同一人工模板体系内的变化,不能代表自然语言分布。

把统计对象写清楚

令 dd 表示完整文档,g(d)g(d) 是它的模板组,b(d)b(d) 是 text 经 UTF-8 编码得到的原始字节串。用 Dtr,Dva,Dte\mathcal D_{tr},\mathcal D_{va},\mathcal D_{te} 表示互不相交的三个文档集合。先按组划分,再在训练集合拟合 tokenizer。一个分词器 TT 把文档转换成 [BOS,x1,…,xn,EOS][\mathrm{BOS},x_1,\ldots,x_n,\mathrm{EOS}];BOS 是已给定的开始条件,不是本项目的预测目标,EOS 是被预测的结束事件。特殊编号下一章正式定义,此处只用名称,避免把字符或字节的编号提前混入概率定义。

pθ(x1,…,xn,EOS∣BOS)=∏t=1n+1pθ(xt∣BOS,x1,…,xt−1),xn+1=EOS.p_\theta(x_1,\ldots,x_n,\mathrm{EOS}\mid\mathrm{BOS})=\prod_{t=1}^{n+1}p_\theta(x_t\mid\mathrm{BOS},x_1,\ldots,x_{t-1}),\qquad x_{n+1}=\mathrm{EOS}.
链式法则是恒等式;有限窗口与神经网络条件分布是后续采用的模型选择。

这里的 θ\theta 表示所有可训练参数。概率分解本身没有假定相邻独立;恰好相反,每一步可以依赖整个过去。真正的近似来自工程:模型最多读一个长度为 CC 的窗口,本项目还在窗口边界重置上下文。我们会把这个截断协议记录到指标中。给定观测序列后,最大化各目标概率的乘积等价于最小化负对数和,因为负对数是严格单调递减函数;对数把连乘变成加法,避免很长序列的概率乘积数值下溢。

令一批实际参与评分的目标数为 NN,第 ii 个目标的真实类别是 yiy_i。经验损失是 L=−(1/N)∑ilog⁡pθ(yi∣ci)L=-(1/N)\sum_i\log p_\theta(y_i\mid c_i),其中 cic_i 是本协议允许的上下文。这个平均定义了每个目标相同的权重;它不会自动让每篇文档权重相同。若业务问题是“随机抽一篇短文的平均难度”,则需要先每篇归一化再平均,那是另一个统计问题。训练与评估必须明确选择哪个问题,不能在曲线中悄悄切换。

算例一:从三个概率到一个总账

设短文甲有两个目标,模型给真实目标的概率为 1/21/2 与 1/41/4;短文乙只有一个目标,真实概率为 1/81/8。甲的负对数和是 log⁡2+log⁡4=3log⁡2\log2+\log4=3\log2,乙是 log⁡8=3log⁡2\log8=3\log2。整个集合共有三个目标,总和 6log⁡26\log2,按目标平均为 2log⁡2≈1.3862942\log2\approx1.386294 nats。若先求文档均值,甲是 1.5log⁡21.5\log2,乙是 3log⁡23\log2,再等权平均得到 2.25log⁡2≈1.5595812.25\log2\approx1.559581。两者都算对,但回答的问题不同。

独立检查是把整体概率直接相乘:(1/2)(1/4)(1/8)=1/64(1/2)(1/4)(1/8)=1/64,负对数确实等于 6log⁡26\log2,再除以三个目标恢复上式。若程序只是把两个 batch 的平均 loss 相加除二,便会在 batch 长度不同时实现错误的权重。我们在第七章返回总 NLL、有效目标数、原始字节数与逐文档记录,使这里的总账可以重新计算。保存足够的分子分母,比输出一个看似精确的小数更重要。

切分边界就是泛化假设

随机切 token 常会让同一文档相邻片段进入训练和测试;对模板数据,随机切文档也可能让同一模板的轻微替换两边都有。设一个组内四篇短文只换姓名,其他内容相同。训练见过三篇再测试第四篇,主要考察姓名替换;把整个动作模板留出,则会遇到训练没有直接提供的动作和对象。后者仍有共享句式,因此也不是彻底独立的新领域。切分规则改变了评估难度,分数必须带着规则一起解释。

项目用两道硬检查:同一 group 不能跨集合,完全相同的 UTF-8 文本摘要不能跨集合。id 唯一并不足够:复制文本后换 id 仍然泄漏。精确摘要也不足以发现近重复:改变一个空格就会变摘要。因此规则的证明范围只是“没有被这两类检查捕捉的冲突”;人工定义的模板分组承担近重复隔离的一部分。我们不声称完成开放网络语料的版权审查、语义去重或隐私过滤,因为项目没有实现这些操作。

算例二:一份会获得满分的坏测试集

假设训练文件 id=a 的文本是“甲整理书。”,测试文件 id=z 的文本完全相同,group 还被错误填成另一个值。只核验 id 与 group 会通过。一个记忆整串映射的系统能对这个测试文本得到极低损失,却没有支持“未见文本预测”的证据。加入 text.encode("utf-8") 的 SHA-256 后,两份摘要一致,跨集合登记立刻报错。若测试改成“甲整理书。 ”,精确摘要不同;这提醒我们摘要是字节级工具,不是语义相等判定器。

第二个独立检查是核对集合并集和交集:全部 split 的 id 列表不能重复,其集合必须恰好覆盖 documents 中的唯一 id;否则遗漏文档可能不被评估,重复 id 可能被多次加权。对本项目还应手算三十二等于二十四加四加四,训练组数六、验证组数一、测试组数一。若过滤删除了一篇文档,必须重新生成清单和摘要,不能继续沿用旧 checkpoint 的语料身份。

独立运行标准库独立验证:故障语料被拒绝;两种平均可分别复算。
import hashlib, math
rows=[{'id':'a','group':'g1','text':'甲整理书。','split':'train'},
      {'id':'z','group':'g2','text':'甲整理书。','split':'test'}]
seen={}; conflicts=[]
for r in rows:
    digest=hashlib.sha256(r['text'].encode('utf-8')).hexdigest()
    if digest in seen and seen[digest]!=r['split']:
        conflicts.append(r['id'])
    seen[digest]=r['split']
assert conflicts==['z']
nlls=[-math.log(.5)-math.log(.25),-math.log(.125)]
weighted=sum(nlls)/3
wrong=(nlls[0]/2+nlls[1])/2
assert abs(weighted+math.log(1/64)/3)<1e-12
print('duplicate rejected:',conflicts,'target mean:',round(weighted,6),'document mean:',round(wrong,6))

从研究问题到可重复的数据身份

数据身份包含内容与划分,而非仅文件路径。项目读取两份文件后,用确定的 JSON 表示计算 corpus_sha256;checkpoint 同时保存它和 tokenizer_sha256。恢复时先重读当前语料计算摘要,若不一致就拒绝继续。这个摘要证明所读对象的字节表示一致,不证明数据来源真实,也不代替人工查看 source 字段。source 在本项目如实记录为原创模板短文。真实语料还须保存获取时间、许可与处理日志,不能从这里的实验收据推断已有这些条件。

本章的验收件是固定的 documents、splits 和可拒绝冲突的数据读取器。独立运行的例子只演示重复检测与平均定义,项目级检查则直接读取真实文件。下一章需要这个稳定集合决定“什么被编码”和“原始字节是多少”;第三章只能从训练集合统计 BPE 规则。若现在就把三个集合合起来学习常见片段,即使 decoder 权重没有见过测试标签,表示层的设计也已经受测试分布影响。数据划分必须先于所有需要拟合的处理步骤。

还要区分数据分布与抽样器分布。设训练集两篇文档各产生一个与九个窗口。若先等概率抽文档再等概率抽该文档窗口,短文唯一窗口被抽中的概率是二分之一,长文每个窗口仅十八分之一;若将十个窗口打平后均匀抽样,每个窗口都是十分之一。本项目采用后者,再按批内有效目标平均求梯度,所以每次更新的目标权重仍随批长度组成变化。评估时则穷举目标并按总数精确归一化。把训练抽样近似与评估精确总账分开,能解释为何单个 batch loss 不等于整套训练集损失。

空文档也需要明确处理。它没有正文原始字节,但编码后仍有开始和结束两个边界,一个结束目标;其每目标损失有定义,每原始字节指标因分母为零没有定义。本项目提供的短文均非空,数据读取与指标的适用域据此固定。扩大语料时应先决定是删除空文档、单列结束率还是报告总集合的字节分母,不应在算到除零时临时把分母改成一,那会悄悄改变统计量。

数据选择还决定概率模型优化的目标分布。假设一类动作模板在训练中复制十倍,即使文档内容正确,经验风险也会更偏向该类;这不是优化器自己发现了它更重要,而是采样权重改变。若原始来源有同一文档的多个版本,应在切分前记录它们属于同一来源组,再决定保留版本或整体分配。否则训练测试共享底稿而只改几个字,低损失可能来自版本记忆。本课用显式模板组代替复杂来源关系,保留这种简化的适用范围。

冻结测试并不是永远不能看它。它意味着当前配置选择阶段不把测试结果作为反馈;一旦根据测试失败调整数据规则或超参数,旧测试就参与了研究过程,之后若要给无偏最终评价应另有新留出集合。当前项目先写下固定配置再运行一次测试,规模网格只看验证组。验证组本身也很小,反复调参会过拟合验证;因此本课只展示预定网格,不靠反复搜索生成一个好看的最终分数。

终端命令在下载项目的根目录、已安装固定依赖的Python环境中运行;这是项目命令,不是Python源码。
python -m unittest -v tests.test_pipeline.PipelineTests.test_group_partition tests.test_pipeline.PipelineTests.test_duplicate_rejected

TRY IT YOURSELF

练习一:概率与权重

两篇文档分别有一个和三个目标,每个目标真实概率分别是 1/2 和 1/4。求按目标平均 NLL 与按文档平均 NLL,并说明哪一个与本项目一致。

提示 1
先将每篇文档中每个目标的负对数概率累加,再决定怎样归约。
提示 2
按目标平均时分母是总目标数;按文档平均则先各除本篇目标数,比较两篇权重。
对照参考解答
第一篇总 NLL 为 log⁡2\log2,第二篇为 3log⁡4=6log⁡23\log4=6\log2。全体总和 7log⁡27\log2,四个目标,所以按目标平均为 1.75log⁡2≈1.2130081.75\log2\approx1.213008。按文档平均为 (log⁡2+log⁡4)/2=1.5log⁡2≈1.039721(\log2+\log4)/2=1.5\log2\approx1.039721。本项目选择前者,因此第二篇贡献三个目标权重;不能简单平均两个 batch 的均值。

TRY IT YOURSELF

练习二:构造反例

构造一份 id 不重、文本不完全相同,但仍可能使测试过于容易的切分。给出修复及修复边界。

提示 1
id与字节摘要只能排除哪些重复?试着只改模板中的一个名字。
提示 2
让两条不同文本共享一个生成模板,再按模板group切分;说明这种规则还遗漏哪些改写。
对照参考解答
可令 train 为“小禾记录清晨的云。”,test 为“阿岚记录清晨的云。”,两份 id 和字节摘要均不同,却共享动作模板。把同一“记录清晨的云”模板登记同一 group,再按组整体切分可阻止此例。它只覆盖已知分组:句式改写或跨来源转载仍可能逃过,需要额外近重复规则;不能由这一次修复宣布完全没有泄漏。

TRY IT YOURSELF

练习三:设计验收断言

若 documents 中有 32 个唯一 id,splits 三个列表长度之和也是 32,是否足以证明有效划分?给出完整的额外条件与失败例。

提示 1
长度相等不保证集合相等,先构造“重复一个、遗漏一个”的列表。
提示 2
同时核对拼接id无重复且覆盖全集,再单独检查group与内容摘要不能跨split。
对照参考解答
不够。列表可能把 a 写两次并漏掉 b,总长度仍是 32。应先断言文档 id 唯一,再断言拼接后的 split id 无重复,并且其集合等于全部文档 id;最后验证 group 与精确摘要不跨 split。前两步保证分区,后两步保证特定隔离策略。所有条件均成立才可继续拟合 tokenizer;任何删除、重排语义或分组变更均需更新数据身份。

CHECK YOUR UNDERSTANDING

检验理解

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

1. 本项目 BOS 的角色是什么?

查看解析概率从 BOS 条件下预测首个正文符号,EOS 才属于预测目标。
2. 先把三种 split 合起来拟合 BPE,会发生什么?

查看解析BPE 是需要估计的预处理,合并全体集合破坏训练独立性;这不等于必然记住所有测试内容。
3. 两个 batch 有效目标数不同,应如何汇总 NLL?

查看解析分子分母分别加总,才能保留每个目标相同权重。
4. 跨集合文本摘要不重复能证明什么?

查看解析字节摘要是精确重复检查,不能判定改写或近重复。

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