任务、读者起点与可交付物
本课要做的是一个能从本地原始文本出发、训练全部 decoder 参数、保存恢复并在留出文本上给出可重算指标的语言模型实验。训练规模故意很小,因此最终交付的是可信的工程证据,而不是“会说话”的能力证明。读者应能读 Python 函数与字典,计算条件概率、对数和矩阵乘法,并已学过 Transformer 实战中的因果注意力与反向传播。本课复用该课自写模型的本地副本,随后集中解决语料、编码、训练与评估的连接问题;项目压缩包不需要访问另一门课的目录。
本章先冻结问题:我们给定一篇短文的前缀,预测它的下一个离散符号。原始短文不是 token 序列;必须先保存文本和边界,再约定编码。本项目的 data/documents.jsonl 包含三十二篇原创模板短文,每条有 id、group、text、source;data/splits.json 指定二十四篇训练、四篇验证和四篇测试。group 是动作模板,而不是作者或唯一文档号。训练六类动作,验证与测试各留出一类。这种留出只考察同一人工模板体系内的变化,不能代表自然语言分布。
把统计对象写清楚
令 表示完整文档, 是它的模板组, 是 text 经 UTF-8 编码得到的原始字节串。用 表示互不相交的三个文档集合。先按组划分,再在训练集合拟合 tokenizer。一个分词器 把文档转换成 ;BOS 是已给定的开始条件,不是本项目的预测目标,EOS 是被预测的结束事件。特殊编号下一章正式定义,此处只用名称,避免把字符或字节的编号提前混入概率定义。
这里的 表示所有可训练参数。概率分解本身没有假定相邻独立;恰好相反,每一步可以依赖整个过去。真正的近似来自工程:模型最多读一个长度为 的窗口,本项目还在窗口边界重置上下文。我们会把这个截断协议记录到指标中。给定观测序列后,最大化各目标概率的乘积等价于最小化负对数和,因为负对数是严格单调递减函数;对数把连乘变成加法,避免很长序列的概率乘积数值下溢。
令一批实际参与评分的目标数为 ,第 个目标的真实类别是 。经验损失是 ,其中 是本协议允许的上下文。这个平均定义了每个目标相同的权重;它不会自动让每篇文档权重相同。若业务问题是“随机抽一篇短文的平均难度”,则需要先每篇归一化再平均,那是另一个统计问题。训练与评估必须明确选择哪个问题,不能在曲线中悄悄切换。
算例一:从三个概率到一个总账
设短文甲有两个目标,模型给真实目标的概率为 与 ;短文乙只有一个目标,真实概率为 。甲的负对数和是 ,乙是 。整个集合共有三个目标,总和 ,按目标平均为 nats。若先求文档均值,甲是 ,乙是 ,再等权平均得到 。两者都算对,但回答的问题不同。
独立检查是把整体概率直接相乘:,负对数确实等于 ,再除以三个目标恢复上式。若程序只是把两个 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 -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
对照参考解答
TRY IT YOURSELF
练习二:构造反例
构造一份 id 不重、文本不完全相同,但仍可能使测试过于容易的切分。给出修复及修复边界。
提示 1
提示 2
对照参考解答
TRY IT YOURSELF
练习三:设计验收断言
若 documents 中有 32 个唯一 id,splits 三个列表长度之和也是 32,是否足以证明有效划分?给出完整的额外条件与失败例。