teacher-forcing-and-generation.md · 可读全文

这里保留原文的中文、公式标记、代码和链接。可直接交给 Agent 读取,也可选取下方原文复制。此入口返回 HTML;原始 .md / .py 文件仍保留,完整候选包同时包含 UTF-8 原件。

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

课堂接续:[T15](https://codingai-lec05.pages.dev/classroom/T15.html?step=b0) · [T16](https://codingai-lec05.pages.dev/classroom/T16.html?step=b0) · [T17](https://codingai-lec05.pages.dev/classroom/T17.html?step=b0) · [T33](https://codingai-lec05.pages.dev/classroom/T33.html?step=b0) · [T34](https://codingai-lec05.pages.dev/classroom/T34.html?step=b0)。连续读本:[第2章:右移训练与生成](https://codingai-lec05.pages.dev/lecture.html#chapter-2) · [第3章:真实反转样本与搜索诊断](https://codingai-lec05.pages.dev/lecture.html#chapter-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分列,正确右移保持合法条件](https://codingai-lec05.pages.dev/assets/r72/classroom/T16-b1.svg)

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

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


[阅读本主题](https://codingai-lec05.pages.dev/course/knowledge/teacher-forcing-and-generation.html) · [返回实验](https://deepnote.com/workspace/ise3309jingao-5ba66b0a-4283-4f5a-ae06-3f68d3201e99/project/Fall-26-Lecture-05-841ec8e7-e352-4840-b628-d3742b03d6d0/notebook/LAB-02-bba1d024d8e64a589c290b09381de692?utm_content=bba1d024d8e64a589c290b09381de692&utm_source=openai&utm_medium=mcp&utm_campaign=openaimcp&utm_term=get_notebook) · [与Agent共读全文](https://codingai-lec05.pages.dev/course/knowledge/teacher-forcing-and-generation.md.read.html)

读本:[ CLS 03 · 生成、视觉条件与上下文 ](https://deepnote.com/workspace/ise3309jingao-5ba66b0a-4283-4f5a-ae06-3f68d3201e99/project/Fall-26-Lecture-05-841ec8e7-e352-4840-b628-d3742b03d6d0/notebook/CLS-03-719b25c7c5b64687998ba9c19d2f6776?utm_content=719b25c7c5b64687998ba9c19d2f6776&utm_source=openai&utm_medium=mcp&utm_campaign=openaimcp&utm_term=get_notebook),查找“路径 A:分数进步了,为什么这一句仍然错”;实验中查找“路径A · 分数进步了,为什么这一句仍然错”。


<span id="teacher-forcing-and-generation-s01"></span>

## 一条已经失败的序列

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

| 对象 | 固定选择后的实际内容 |
|---|---|
| 源序列 | b d a d e |
| 应得(含结束符) | e d a d b &lt;EOS&gt; |
| greedy实际输出 | d e d a b &lt;EOS&gt; |
| 第一次分歧(从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的唯一目标不能替代翻译评价,更不能解释旧六片段诊断句“她→他”的全部原因。当前结果只支持这个受控合成任务上的比较。

<span id="teacher-forcing-and-generation-s02"></span>

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

![旧版独立扩展:源条件不变,右移参考前缀与逐步自馈区别](https://codingai-lec05.pages.dev/assets/r71/classroom/S09-b1.svg)

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

| 步 | 真前缀 | 目标 | p(目标) | argmax |
|---|---|---|---|---|
| 1 | &lt;BOS&gt; | e | 0.327242 | d |
| 2 | &lt;BOS&gt; e | d | 0.990110 | d |
| 3 | &lt;BOS&gt; e d | a | 0.914952 | a |
| 4 | &lt;BOS&gt; e d a | d | 0.992090 | d |
| 5 | &lt;BOS&gt; e d a d | b | 0.982063 | b |
| 6 | &lt;BOS&gt; e d a d b | &lt;EOS&gt; | 0.962593 | &lt;EOS&gt; |

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

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

![同一失败样本:真前缀与自馈前缀下,标准目标概率从第二步开始分离](https://codingai-lec05.pages.dev/assets/knowledge/path-a.png)

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

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

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

<span id="teacher-forcing-and-generation-s03"></span>

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

| 模型 | 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相加;对候选句也用它自己的前缀逐步计分。它是概率模型的排序,不是教师对忠实性的判分。

<span id="teacher-forcing-and-generation-s04"></span>

## beam有没有把正确答案漏掉

| 宽度 | 最高分完成序列 | 累计log p(含EOS) | exact match |
|---|---|---|---|
| 1 | d e d a b &lt;EOS&gt; | -0.905535 | False |
| 2 | d e d a b &lt;EOS&gt; | -0.905535 | False |
| 4 | d e d a b &lt;EOS&gt; | -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原论文的完成集合管理不同,不宣称逐行复刻。

<span id="teacher-forcing-and-generation-s05"></span>

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

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

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

```python
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]))
```

<span id="teacher-forcing-and-generation-s06"></span>

## 复核与实现

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

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

```python
@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](https://arxiv.org/html/1409.3215v1)。模型诊断是DN2只读重算,历史57.67%/87.00%训练身份保留。


<span id="teacher-forcing-and-generation-s07"></span>

## 相邻知识与复核资源

[从2017计算图到2020组合,再看持续状态系统](https://codingai-lec05.pages.dev/course/knowledge/architecture-choices-and-state.html) · [路径 B:换图后分布变了,模型是否看懂了](https://codingai-lec05.pages.dev/course/knowledge/visual-condition-use.html)

[全部主题](https://codingai-lec05.pages.dev/course/knowledge/index.html) · [关键结果JSON](https://codingai-lec05.pages.dev/course/knowledge/data/learning-paths.json) · [只读诊断源码](https://codingai-lec05.pages.dev/course/knowledge/code/learning_paths.py.read.html) · [Seq2Seq完整实现](https://codingai-lec05.pages.dev/course/knowledge/code/seq2seq.py.read.html) · [视觉机制实现](https://codingai-lec05.pages.dev/course/knowledge/code/mechanisms.py.read.html) · [TinyDecoder与cache实现](https://codingai-lec05.pages.dev/course/knowledge/code/legacy_numpy.py.read.html)