Lecture 05 · 路径 A:分数进步了,为什么这一句仍然错

路径 A:分数进步了,为什么这一句仍然错

课堂接续:T15 · T16 · T17 · T33 · T34。连续读本:第2章:右移训练与生成 · 第3章:真实反转样本与搜索诊断;本主题提供独立定义、例子、代码与来源。

源句为“我昨天给了他一本书。”,选定译文为 I gave him a book yesterday .。当前给书主线的训练与生成可以先用同一张右移表分清:

生成的下一步输入来自刚刚选择的词;训练已有参考译文,可以为每个位置提供正确历史。右移对齐如下:

输入位置 BOS I gave him a book yesterday .
本位置目标 I gave him a book yesterday . EOS

在gave这一输入行预测him时,可用历史只有BOS、I、gave及完整中文源句。him作为标签用于求loss,不能先送入同一预测位置。这就是teacher forcing使用真实历史的含义;所有位置的误差汇总后,通过输出头、decoder、encoder与embedding更新共享参数。

输入gave与标签him分列,正确右移保持合法条件

训练与生成使用同一套条件概率模型,下一步历史的提供者不同。生成若选错一个词,后续条件会随之偏离真实历史;若第一个错误已经发生在两种前缀仍完全相同的位置,就不能将它归咎于更早的自馈错误。完整生成、EOS和任务评价仍须单独检查。

接下来切换到已经保存的六字符反转实验,完整保留原输入、标签、checkpoint、首错位置、beam与EOS。它不是自然语言给书句,也不因课堂改成他/him而改动任一字符或结果。路径A用这个具体失败区分评分、搜索与自馈条件。

阅读本主题 · 返回实验 · 与Agent共读全文

读本: CLS 03 · 生成、视觉条件与上下文 ,查找“路径 A:分数进步了,为什么这一句仍然错”;实验中查找“路径A · 分数进步了,为什么这一句仍然错”。

一条已经失败的序列

这里切换到已经训练的合成序列反转任务。它不是her/him教学翻译;源序列、正确目标、实际输出与checkpoint身份在下表列出。动态组300条整句中仍有39条失败。构造翻译里正确候选得分更高却未返回,支持查搜索;当前真实样本将检验另一种排序情形。

对象 固定选择后的实际内容
源序列 b d a d e
应得(含结束符) e d a d b <EOS>
greedy实际输出 d e d a b <EOS>
第一次分歧(从1计) 1

这里使用DN1在Deepnote训练并保存的 dynamic-500.pt。按存档test顺序取第一条greedy失败的样本,得到从0计的索引25;没有为了让beam修好错误而另挑样本。DN2普通CPU只读重算,把同一对象的teacher forcing、自由生成与beam接起来。

训练/验证/测试3000/300/300,源长3–7,各集合源序列不重复。两组共享BiLSTM encoder与LSTM decoder设计、共同500次更新、各102417训练目标token;固定摘要15577参数,动态读取16601,多1024个query投影参数。没有多种子置信区间。

真实翻译也有条件输入、前缀和搜索,但可能存在多个合格译文、歧义、未知实体与复杂对齐;这里exact match的唯一目标不能替代翻译评价,更不能解释旧六片段诊断句“她→他”的全部原因。当前结果只支持这个受控合成任务上的比较。

同一格里,训练和生成看见什么

旧版独立扩展:源条件不变,右移参考前缀与逐步自馈区别

Teacher forcing:每步接入正确的历史。

步 真前缀 目标 p(目标) argmax
1 <BOS> e 0.327242 d
2 <BOS> e d 0.990110 d
3 <BOS> e d a 0.914952 a
4 <BOS> e d a d 0.992090 d
5 <BOS> e d a d b 0.982063 b
6 <BOS> e d a d b <EOS> 0.962593 <EOS>

自由生成:每步接入自己已经选出的词。按同一步号对照上表,最后一列仍查询标准目标,而非所选词的概率。

步 自馈前缀 选词 p(同位置标准目标)
1 <BOS> d 0.327242
2 <BOS> d e 0.000236
3 <BOS> d e d 0.134230
4 <BOS> d e d a 0.001531
5 <BOS> d e d a b 0.997601
6 <BOS> d e d a b <EOS> 0.855428

同一失败样本:真前缀与自馈前缀下,标准目标概率从第二步开始分离

图中横轴是目标位置;两线都取该位置标准目标的概率,条件前缀分别来自标准答案与实际生成,曲线不是两次新训练。

第一次分歧发生在第1步,此时两种路径都还只有BOS作为decoder输入,所以不能把第一步错误归因于先前自馈错误积累。模型已给d比正确e更高的概率。第2步开始,teacher forcing接回正确e,自由生成接回自己选出的d,两个条件分布不再相同。此后出现错位,并不能只靠观察一句输出判定是状态信息不足、读取错位还是前缀分布变化。

在这个实现中动态读取的初始h为0,query投影无bias,第一步对所有有效源位置均匀读取;encoder状态仍可能携带足够信息,所以“均匀”本身不等于必然失败。它提出一个可检验假设:初始化/读取时序可能影响首词。另一个解释是当前模型对这个排列训练不足。比较同长度样本的首词错误分布、检查首步表示,再做受控初始化训练,才能区分;这些初始化训练仍是待做对照。

指标变好没有承诺每一句都对

模型 TF平均NLL TF token正确率 自由生成exact match
fixed 0.287898 90.9722% 57.67%
dynamic 0.106673 97.7679% 87.00%

NLL和token accuracy在正确前缀下按2016个有效目标token(含EOS、排除PAD)汇总;exact match在自由生成下按300条完整样本算。它们不是同一分母,也不是同一种输入条件。动态组总体更好,却仍有39条整句失败。这条样本让平均进步与局部失败同时变得具体:不需要用“attention更强”来掩盖错误。

正确答案的整句log概率可直接将teacher-forced各目标的log p相加;对候选句也用它自己的前缀逐步计分。它是概率模型的排序,不是教师对忠实性的判分。

beam有没有把正确答案漏掉

宽度 最高分完成序列 累计log p(含EOS) exact match
1 d e d a b <EOS> -0.905535 False
2 d e d a b <EOS> -0.905535 False
4 d e d a b <EOS> -0.905535 False

同一正确序列的teacher-forced累计log p = -1.280045。beam2最后一轮确实保留了这条正确完成项,仍把错误项排在前面。

beam2已经把正确序列保留到了结束,但它的分数低于错误序列,所以加宽到4仍选错。这个观察削弱“只是beam太窄漏掉正确路径”的解释;它没有证明已找到全词表所有可能句子的全局最优。它还表明在这条样本上,搜索更忠实地执行当前分数,不一定让任务更正确。可检查的是模型排序为何偏好错序,而不是继续机械增加beam。

表中数值是DN2本地CPU参考重算;Deepnote同一checkpoint的云端保存输出中,错误完成项为−0.905534469,本地为−0.905535110,相差约6.41×10⁻⁷。环境与浮点运算顺序会造成末位差别;样本、首错与候选排序一致,不能要求逐位相同。

旧扩展概率树是另一种身份:人为规定归一化边概率,用来展示局部选择与整句分数分离;不要把树上的A/B概率当成这次checkpoint输出。真实模型中EOS完成项占beam名额,累计log p含EOS、无长度惩罚;Sutskever原论文的完成集合管理不同,不宣称逐行复刻。

从这里提问,再回到同一个对象

你可以把本主题URL交给Agent,用自己的话描述:“它已经把正确顺序留在候选里了,为什么还选错?”上下文应包含这条源/目标、checkpoint身份和上述分数,而不是只贴87%。Agent能据正文区分搜索、概率和任务正确性;若讨论你的私人运行,需要你另附参数和输出。

回到LAB02的“路径A · 分数进步了,为什么这一句仍然错”,运行下面的只读检查;它重新按固定规则取样,不覆盖参考文件。把beam宽度改为已有1/2/4中的另一值,核对正确路径是否出现、它的累计分数与第一分歧。返回判断应是“这个checkpoint的这个样本不因当前beam加宽而修好”,不是“beam永远没用”或“所有错误都来自exposure bias”。

import learning_paths as paths
A = paths.path_a(ROOT)
print(A['test_index'], A['source'], A['target'], A['greedy'])
display(pd.DataFrame(A['teacher_forced']))
display(pd.DataFrame(A['free_trace']))
print({w:(x['tokens'], x['log_probability']) for w,x in A['beam'].items()})
beam_width = 2  # 可选1、2、4;结果对象的键是字符串
display(pd.DataFrame(A['beam'][str(beam_width)]['trace'][-1]))

复核与实现

复核项 身份
存档test索引(从0计) 25
checkpoint dynamic-500.pt
SHA256 67ffebe93c8072e23b65a03b3ded8c6eabd18bff5701986f36747842d7634b38
结果身份 DN2本地CPU只读重算;云端保存输出的末位差异见beam段

下面是与上述检查同源的诊断函数:包括固定取样、加载权重、两种前缀与beam。它读取参考文件;结果写入由LAB的独立输出目录管理。

@torch.no_grad()
def path_a(root):
    root=Path(root);p=root/'results/cloud/seq2seq'
    models,data,report=seq.load_pair(p)
    rows=data['test'];model=models['dynamic'];pred,_,_=seq.greedy(model,rows)
    # Rule fixed before inspecting the selected answer.
    idx=next(i for i,r in enumerate(rows) if pred[i]!=r['target']);row=rows[idx]
    src,l,di,y=seq.batch([row]);logits,_=model(src,l,di);lp=logits.log_softmax(-1)[0]
    free=pred[idx];first=next(i for i,(a,b) in enumerate(zip(free,row['target'])) if a!=b)
    z,mask=model.encode(src,l);state=model.initial(1);token=torch.tensor([seq.BOS]);trace=[];prefix=[]
    for t in range(max(12,len(row['target'])+2)):
        out,state,weights=model.step(token,state,z,mask);prob=out.softmax(-1)[0];chosen=int(prob.argmax())
        trace.append({'step_one_based':t+1,'input_prefix':[seq.VOCAB[seq.BOS]]+[seq.VOCAB[x] for x in prefix],
                      'chosen':seq.VOCAB[chosen],'p_chosen':float(prob[chosen]),
                      'p_reference_here':float(prob[row['target'][t]]) if t<len(row['target']) else None,
                      'reference':seq.VOCAB[row['target'][t]] if t<len(row['target']) else None,
                      'source_weights':weights[0].tolist()})
        prefix.append(chosen);token=torch.tensor([chosen])
        if chosen==seq.EOS:break
    tf=[{'step_one_based':t+1,'input_prefix':[seq.VOCAB[seq.BOS]]+[seq.VOCAB[x] for x in row['target'][:t]],
         'target':seq.VOCAB[tok],'argmax':seq.VOCAB[int(lp[t].argmax())],
         'p_target':float(lp[t,tok].exp()),'nll':float(-lp[t,tok])} for t,tok in enumerate(row['target'])]
    metrics={}
    for name,m in models.items():
        nll=correct=count=0
        for i in range(0,len(rows),32):
            s,ll,dd,yy=seq.batch(rows[i:i+32]);oo,_=m(s,ll,dd);valid=yy!=seq.PAD
            nll+=float(torch.nn.functional.cross_entropy(oo.flatten(0,1),yy.flatten(),ignore_index=seq.PAD,reduction='sum'))
            correct+=int(((oo.argmax(-1)==yy)&valid).sum());count+=int(valid.sum())
        pp,_,_=seq.greedy(m,rows)
        metrics[name]={'teacher_forced_nll':nll/count,'teacher_forced_token_accuracy':correct/count,
                       'free_generation_exact_match':sum(a==b['target'] for a,b in zip(pp,rows))/len(rows),'valid_tokens':count}
    return {'identity':'DN2 local CPU read-only reevaluation of DN1 cloud checkpoints; no retraining',
            'selection_rule':'first dynamic greedy failure in stored test order, zero-based index',
            'test_index':idx,'source':[seq.VOCAB[x] for x in row['source']],
            'target':[seq.VOCAB[x] for x in row['target']], 'greedy':[seq.VOCAB[x] for x in free],
            'first_divergence_one_based':first+1,'teacher_forced':tf,'free_trace':trace,
            'beam':{str(w):seq.beam(model,row,w) for w in [1,2,4]},'metrics':metrics,
            'config':model.config,'checkpoint_sha256':{n:hashlib.sha256((p/f'{n}-500.pt').read_bytes()).hexdigest() for n in models},
            'data_sha256':hashlib.sha256((p/'data.json').read_bytes()).hexdigest()}

来源:LAB02存档results/cloud/seq2seq的data.json与fixed/dynamic-500.pt;正文结果与哈希同源导出,完整JSON附在主题入口。训练背景见R7读本第二、三章与Seq2Seq2014 v1。模型诊断是DN2只读重算,历史57.67%/87.00%训练身份保留。

相邻知识与复核资源

从2017计算图到2020组合,再看持续状态系统 · 路径 B:换图后分布变了,模型是否看懂了

全部主题 · 关键结果JSON · 只读诊断源码 · Seq2Seq完整实现 · 视觉机制实现 · TinyDecoder与cache实现