假设你请了一位师傅教徒弟写作文,题目是“描写下雨天”。徒弟没法一口气写完,只能先写第一个词,然后根据自己刚写下的内容,再决定第二个词怎么写——每一句都站在前一句的肩膀上。问题来了:如果徒弟第二句就写了一个别扭的词,第三句就会在这个错误基础上继续编,越写越离谱,整篇作文崩盘。聪明的师傅有妙招——“你先别管前面,我把正确的前文写给你,你只管练好'当前这一句'怎么写。”这样徒弟能专注打磨每一步的功力,训练又快又稳。这个技巧,就是深度学习里的 Teacher Forcing(教师强制):训练时把“标准答案”喂给模型当上下文,让它每一步都从正确位置出发。但期末考试(推理)时师傅不在场,模型必须用自己的产出继续写,这时“一步错、步步错”的暴露偏差就出现了。本篇把这个故事讲透:原理、实现、问题、改进与工程实战,带你彻底吃透这个序列模型训练的标准技巧。

岗位关联

机器翻译、文本生成等序列任务都依赖 Teacher Forcing 才能稳定训练;它是 NLP 面试高频考点,也是你从“跑通模型”走向“训好模型”的必修课。

目录
  1. 可视化演示
  2. 序列模型基础
  3. 核心思想
  4. 实现方式
  5. 暴露偏差问题
  6. 计划采样改进
  7. 其他改进方案
  8. 工程实践经验
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Seq2Seq中Teacher Forcing训练方法:编码器处理、GT输入训练、推理暴露偏差与Scheduled Sampling改进,分步交互查看。

本节配有交互动效(进入视口后自动加载,约 3KB)。

编码器处理:输入序列编码为上下文向量 输入token逐个送入Encoder 编码为固定维度的上下文向量C C = Encoder(x_1, x_2, ..., x_n) 上下文向量C作为Decoder的初始输入,驱动逐步生成。

序列模型基础

想象你请了一位师傅教徒弟写作文,题目是“描写下雨天”。徒弟不能一口气写完,只能先写第一个词,然后根据自己写下的内容,再写第二个词……这种“自己写完的话,决定下一句话”的过程,就像滚绣球一样一环扣一环。师傅没法跳过过程直接教结果,只能一个词一个词地陪练。

在深度学习里,“给一句话,生成另一句话”的任务由 Seq2Seq(序列到序列)模型承担。它像一个两人小组:编码器负责把输入的句子“读进脑子里”,压缩成一个隐状态;解码器负责从这个记忆出发,一个词一个词地把答案吐出来。解码器每吐出一个词,这个词又会被当作“已写出的前文”,参与下一个词的预测。这种“用自己的输出续写”的性质,叫作自回归——你可以理解为“自己接自己话”。

数学上,模型对整句输出的概率可以写作逐词条件概率的连乘:给定输入 X,最终输出句子 Y 的概率,等于每个位置在“已知它前面所有词和 X”时的条件概率相乘。

这里藏着整个故事的关键:公式里的“前文”到底由谁提供?如果由师傅(真实标签)提供,就是教师强制;如果由徒弟(模型自己)提供,就是推理时的自回归。一字之差,训练结果天差地别。

P(Y∣X)=∏t=1TP(yt∣y1,y2,…,yt−1,X)\displaystyle P(Y \mid X) = \prod_{t=1}^{T} P(y_t \mid y_1, y_2, \dots, y_{t-1}, X)
这个公式把“生成一整句”拆成“一个词一个词地猜”,是理解 Teacher Forcing 的地基。
  1. 条件概率P(y_t | ……) 表示在给定前面所有词和输入序列的前提下,第 t 个位置是词 y_t 的可能性有多大,也就是“已知前文,猜下一个词”。
    P(yt∣y<t,X)P(y_t \mid y_{<t}, X)
  2. 连乘记号∏_{t=1}^{T} 把每个位置猜对的概率乘起来,只有每个词都对,整句才算对,所以用乘法。
    ∏t=1T\prod_{t=1}^{T}
  3. 前文依赖y_1 到 y_{t-1} 是当前词之前的所有词。注意它们是谁提供的(标准答案还是自己写的),将直接影响训练效果——这正是后面几节的伏笔。
    y1,y2,…,yt−1y_1, y_2, \dots, y_{t-1}

核心思想

如果让徒弟从第一个字就自己写,写错了师傅再去纠正,徒弟容易越错越远:第二句错,第三句就在错的基础上继续错。聪明的师傅换了个策略——“你只管写当前这个词,前面该有什么,我给你标准答案。”训练时,模型每一步的输入都被“强制”替换成真实标注的词(ground truth,即人工标注的标准答案),而不是它自己的预测。这就是 Teacher Forcing,直译“教师强制”。

技术上说,在训练阶段,解码器第 t 步的输入是真实标签序列中的 y_{t-1}^*,而非模型上一步的输出。模型只需要学习“在正确的前文下,下一步该说什么”。因为每一步都站在正确的位置上,误差不会在句子内部滚雪球,梯度信号也清晰,训练更快收敛——这正是它成为机器翻译等任务标配训练技巧的原因。

训练目标因此写成“在真实标签序列上的最大似然”:每个位置,模型给真实标签的概率越大,损失越小。

一句话概括:师傅每时每刻都把“正确的上一句”递到徒弟手边,徒弟只需要练好“写下一句”这一个动作。这正是 Teacher Forcing 能让训练又稳又快的根本原因。

L(θ)=−1T∑t=1Tlog⁡Pθ(yt∗∣y1∗,…,yt−1∗,X)\displaystyle \mathcal{L}(\theta) = -\frac{1}{T} \sum_{t=1}^{T} \log P_\theta\left(y_t^* \mid y_1^*, \dots, y_{t-1}^*, X\right)
带星号的 y^* 是标注数据里的标准答案词。公式说的是:在标准答案前文下,模型越会“接对下一句”,损失越小。
  1. 标准答案标记y_t^* 的上标星号代表这是人工标注的真实词,不是模型自己生成的词。
    yt∗y_t^*
  2. 条件概率 P_θ模型在参数 θ 下,预测“下一个词是标准答案”的概率,概率越大说明模型越懂这个位置。
    Pθ(yt∗∣y<t∗,X)P_\theta\left(y_t^* \mid y_{<t}^*, X\right)
  3. 对数似然对概率取对数再取负,把“最大化概率”变成“最小化损失”,方便梯度下降优化。
    −log⁡Pθ(⋅)-\log P_\theta(\cdot)
  4. 平均损失对所有位置的损失求和再除以 T,得到这条样本的平均损失,方便不同长度句子之间比较。
    −1T∑t=1T-\frac{1}{T} \sum_{t=1}^{T}

实现方式

具体到代码层面,这个“递标准答案”的动作怎么落地?其实就一句话:把解码器每一步的输入,从“模型上一时刻的输出”换成“上一时刻的真实标签”。在 PyTorch 这类框架里,就是在时间步循环里把 decoder_input 直接赋值为 target[:, t](真实标签),而不是 decoder_output。

更形式化地,训练与推理阶段对解码器输入的设定可以写成下面这个分情况公式,它像一道岔路口:左边通往“师傅代笔”,右边通往“自己动笔”。

工程上还有几个细节:序列开头要插入起始符 <sos>,让模型知道“现在开始生成”;训练时每个位置独立计算交叉熵损失再平均;教师强制的“标签”必须是训练语料里的真实词,不能是模型的高置信预测。把这些细节串起来,就是一个标准的 Teacher Forcing 训练循环——师傅逐词示范,徒弟逐词模仿。

inputt={yt−1∗训练阶段(教师强制)y^t−1推理阶段(自回归)\displaystyle \text{input}_t = \begin{cases} y_{t-1}^* & \text{训练阶段(教师强制)} \\[6pt] \hat{y}_{t-1} & \text{推理阶段(自回归)} \end{cases}
训练时用标准答案 y^* 当输入,推理时只能用模型自己的预测 ŷ 当输入。两条路线分岔的地方,就是暴露偏差的源头。
  1. 训练分支input_t = y_{t-1}^*,也就是上一步的标准答案,模型在第 t 步看到的是“标准答案版本”的前文。
    inputt=yt−1∗\text{input}_t = y_{t-1}^*
  2. 推理分支input_t = \hat{y}_{t-1},这是模型上一步自己 argmax 出来的词。推理时没有标准答案,只能自食其果。
    y^t−1=arg⁡max⁡y Pθ(y∣y^<t−1,X)\hat{y}_{t-1} = \underset{y}{\arg\max}\ P_\theta(y \mid \hat{y}_{<t-1}, X)
  3. 分情况讨论花括号把两种模式并列在一起,强调它们唯一的区别就在于“上一步输入由谁提供”。
    {⋯⋯\begin{cases} \cdots \\[4pt] \cdots \end{cases}

暴露偏差问题

期末考试到了,师傅不在场。徒弟在考场上写作文,第一句、第二句都顺利,第三句写错了一个字。接下来怎么办?他只能在自己写错的句子上继续往下编,于是第四句跟着错、第五句错得更远,最后整篇作文面目全非。问题不在徒弟“变笨了”,而在于他从来没有在“自己的错误前文”上练习过续写。

这正是暴露偏差(exposure bias):训练时,模型看到的输入永远来自真实标签分布(师傅代笔的前文);推理时,模型面对的输入却来自自己的输出分布(自己写的前文)。两种分布不一致,模型一旦在推理中途出错,错误就会顺着自回归链路一路传导,像滚雪球一样越滚越大。

把这一点量化,感受会更直观。假设模型每一步出错的概率是 ε,那么生成一个长度为 T 的句子,至少出现一处错误的概率是:

既然错误会滚雪球,那解决办法只有一个方向:让训练阶段也“见到”模型自己的错误输出,提前锻炼它在不完美前文下“爬出来”的能力。怎么做到?请看下一节师傅的放手艺术——计划采样。

P(整句至少一处错)=1−(1−ϵ)T\displaystyle P(\text{整句至少一处错}) = 1 - (1-\epsilon)^T
T 在指数位置上:句子越长,整句出错的概率急剧上升。长句翻译的末尾经常“胡言乱语”,根源就在这里。
  1. 单步正确率1-ε 表示模型某一步生成正确的概率,ε 越小说明单步越可靠。
    1−ϵ1-\epsilon
  2. 连乘累积(1-ε)^T 是所有 T 步全部正确的概率,每一步都对,整句才可能对。
    (1−ϵ)T(1-\epsilon)^T
  3. 互补事件用 1 减去“全对”的概率,得到“至少错一处”的概率。当 T 很大时,这个概率趋近于 1,说明长序列几乎必然出岔子。
    1−(1−ϵ)T1 - (1-\epsilon)^T

计划采样改进

聪明的师傅从来不是“第一天就撒手”,而是扶一段、放一段:前期步步把稳,等徒弟动作熟练了,再故意让他自己走两步,摔一下,学会自我纠偏。这个思想落到训练里,就是 Scheduled Sampling(计划采样,也叫调度采样)。

计划采样的做法很朴素:每一步以一个概率 ε 继续使用真实标签作为输入,以 1-ε 的概率改用模型自己的输出作为输入。也就是说,训练时解码器的输入是两种来源的随机混合,训练早期 ε 接近 1(师傅全程扶着),训练后期 ε 逐渐变小(师傅逐渐放手),模型慢慢适应“用自己写的前文续写”。

ε 怎么随时间变化?常用指数退火:训练轮数(epoch)越大,ε 越小。典型设置如下,先纯教师训练把模型扶稳,再让 ε 缓慢下降。

回看比喻:计划采样就是“循序渐进地放手”。ε 退火太快,徒弟摔得太狠,训练崩溃;太慢,徒弟永远依赖师傅代笔,考试照样翻车。两者之间的分寸,正是调参工程师的功夫。

inputt={yt−1∗以概率 ϵy^t−1以概率 1−ϵ\displaystyle \text{input}_t = \begin{cases} y_{t-1}^* & \text{以概率 } \epsilon \\[6pt] \hat{y}_{t-1} & \text{以概率 } 1-\epsilon \end{cases}
每一步抛一枚“偏心硬币”:偏向真实标签还是模型自输出。这迫使模型在训练时就熟悉推理时的输入分布。
  1. 初始值 ε0训练刚开始时 ε 通常设为 1,即先纯教师强制把模型“扶稳”。
    ϵ0=1\epsilon_0 = 1
  2. 衰减率 kk 是 0 到 1 之间的数,epoch 越大,k^epoch 越小,ε 单调下降。
    kepoch,0<k<1k^{\text{epoch}},\quad 0 < k < 1
  3. 下限 ε_minε 不会衰减到 0,而是稳定在一个小值(如 0.1),保证训练始终带一点“脚手架”,防止完全放飞后训练震荡。
    ϵmin⁡\epsilon_{\min}

其他改进方案

计划采样是“调整训练时的输入”,还有三套思路从不同角度对付暴露偏差:换考试策略、按整篇打分、多练题型。它们可以和计划采样组合使用,像给徒弟配齐了草稿纸、评分表和题库。

第一套是 Beam Search(束搜索)。推理时不再一个个词取最大概率,而是每一步都保留概率最高的 B 条候选路径,到最后整句一起评分。这就像期末考试时同时打几份草稿,最后挑最通顺的一份交上去。它不改变训练,只改变“怎么从模型概率里抽取结果”,能显著缓解单步失误被永久放大的问题。

第二套是强化学习(RL)。Teacher Forcing 优化的是“每一步词对不对”,但用户真正关心的是整句质量(比如 BLEU 分数,一种衡量机器翻译和参考译文相似度的指标)。RL 直接把整句指标作为奖励,目标变成“生成句子的期望奖励最大化”:

第三套是数据增强:通过回译、同义词替换、句式改写,让训练语料覆盖更多样化的“正确前文”,相当于让徒弟多练不同题材的范文。师傅给的标准答案见得越多,模型对前文的适应力越强,暴露偏差自然减轻。四套方案各有侧重:改输入(计划采样)、改解码(Beam Search)、改目标(RL)、改数据(增强),工程里常常组合出牌。

J(θ)=EY∼Pθ(⋅∣X)[R(Y)]\displaystyle J(\theta) = \mathbb{E}_{Y \sim P_\theta(\cdot \mid X)}\left[ R(Y) \right]
模型生成整句 → 按 BLEU 等指标打分 → 分数高的句子路径获得更大梯度。相当于师傅不再逐词纠正,而是直接批改整篇作文。
  1. 期望操作E_{Y ~ P_θ} 表示按模型自己的生成分布采样出句子,再对这些句子的奖励取平均。
    EY∼Pθ(⋅∣X)\mathbb{E}_{Y \sim P_\theta(\cdot \mid X)}
  2. 奖励函数 R(Y)BLEU、ROUGE 等自动指标,相当于给整篇作文打分。奖励越大,这段路径在参数更新中被加强得越多。
    R(Y)R(Y)

工程实践经验

把前六节落到真实项目里,师傅的排课表是这样的:先用纯 Teacher Forcing 把模型训练到初步收敛(比如机器翻译模型的 BLEU 不再明显上涨);再切换计划采样,让 ε 随 epoch 退火,让模型适应自由运行;最后在评估集上以“自由运行”模式计算指标——注意,评估必须模拟推理环境,不能用教师强制,否则指标虚高,上线就露馅。

几个高频技巧值得记住。第一,标签平滑:把 one-hot 硬标签软化,避免模型对训练集过度自信,从而提升泛化能力:

第二,训练早期配合梯度裁剪和 warmup(先把学习率从小往大热起来),防止崩溃;第三,Teacher Forcing 的收敛曲线很漂亮,但别被它骗了——线上指标一定要看“自由运行”的翻译结果;第四,上线后监控长句生成质量,如果末尾出现胡言乱语,优先检查 ε 退火曲线和 beam size。回看整个比喻:训练是师傅扶着走,评估是模拟独自上路,上线才是真正的期末考试。把每一步都安排好,徒弟才能从考场全身而退。

y~=(1−α) y+αV\displaystyle \tilde{y} = (1-\alpha)\, y + \frac{\alpha}{V}
把 y 的一部分概率(α)匀给词表中其他词,模型不再“押注”单一答案,训练更稳,长期生成更不容易走向极端。
  1. 硬标签 y真实标签的 one-hot 向量,正确词位置为 1,其余为 0。
    yy
  2. 平滑系数 α通常取 0.1 左右,α 越大,标签被抹得越平。
    0<α<0.20 < \alpha < 0.2
  3. 均分项 α/V把 α 的概率平均分给 V 个词,相当于给每个词一点点“可能性”,防止模型过度自信。
    αV\frac{\alpha}{V}

AI 应用场景

机器翻译平台训练

阶段:模型训练与优化

解决的问题:电商平台需要把商品描述批量从英文翻译成中文。传统做法是逐词拼接规则翻译,质量差且无法处理长句;改用 Seq2Seq 生成式翻译后,如果训练时不用 Teacher Forcing,模型早期生成的每个词都是垃圾,梯度充满噪声,训练几天都不收敛,甚至直接崩溃。

具体案例:某跨境电商团队用 Transformer 训练英中翻译模型,先以纯 Teacher Forcing 训练 20 个 epoch 让 BLEU 稳定上涨,再切到计划采样,ε 从 1.0 指数退火到 0.1,最后在评估集上用自由运行模式计算 BLEU 并调 beam size。上线后长句翻译的“末尾胡言乱语”明显减少,人工抽检通过率从 71% 提升到 89%。

智能客服话术生成

阶段:对话模型训练与调优

解决的问题:客服机器人的回复如果逐句自由生成,用户提问稍有偏差,模型就会在错误前文上“越描越黑”,输出前后矛盾的答案。Teacher Forcing 让它先学会“在标准话术下接话”,计划采样再让它适应真实对话中的口语化输入。

具体案例:某银行智能客服每天处理数十万次咨询。训练话术生成模型时,工程师把历史优质对话作为真实标签,前 10 个 epoch 全量 Teacher Forcing,之后每轮把教师比例降低 5%,并配合束搜索(beam size=5)解码。上线后回复的语义连贯性打分提升显著,需要人工兜底的比例下降约 40%。

新闻标题自动生成

阶段:文本摘要模型训练与评估

解决的问题:给长篇新闻自动生成标题,输出是一整段浓缩文本,比单句翻译更容易积累错误——开头一个小错,结尾可能完全跑题。单纯依赖 Teacher Forcing 会让模型“没见过自己的错”,摘要质量虚高。

具体案例:某资讯 App 用 BART 做标题生成,训练中采用“教师强制 + 计划采样 + 标签平滑”的组合拳,评估阶段专门用自由运行输出与人工标题对比 ROUGE 分数。经过两轮退火调参,标题可读性明显改善,编辑人工改写率从 55% 降到 32%。

Python 演示

python105 行
import numpy as np V, T, H = 8, 5, 16   # 词表大小、序列长度、隐层维度LR = 0.3  def onehot(ids):    """整数id或id数组转one-hot向量"""    if isinstance(ids, int):        ids = [ids]    out = np.zeros((len(ids), V))    out[np.arange(len(ids)), ids] = 1.0    return out  def make_batch(n=32, seed=0):    """构造“倒序”任务:模型要学会把输入序列倒过来输出"""    rng = np.random.default_rng(seed)    src = rng.integers(1, V, (n, T))    return src, src[:, ::-1]  class ToyRNN:    def __init__(self):        rng = np.random.default_rng(0)        self.Wxh = rng.normal(0, 0.1, (V, H))        self.Whh = rng.normal(0, 0.1, (H, H))        self.Why = rng.normal(0, 0.1, (H, V))        self.bh = np.zeros(H)        self.by = np.zeros(V)     def step(self, x, h):        h = np.tanh(x @ self.Wxh + h @ self.Whh + self.bh)        z = h @ self.Why + self.by        p = np.exp(z - z.max())        return h, p / p.sum()     def train_step(self, src, tgt, ratio, lr=LR):        """一次BPTT更新。ratio=真实标签被选作下一步输入的概率"""        xs = onehot(src)        h, hs, ps, loss = np.zeros(H), [np.zeros(H)], [], 0.0        for t in range(T):            h, p = self.step(xs[t], h)            hs.append(h)            ps.append(p)            loss += -np.log(p[tgt[t]] + 1e-9)            if t < T - 1:                nxt = tgt[t + 1] if np.random.rand() < ratio else int(np.argmax(p))                xs[t + 1] = onehot(nxt)[0]        dWxh = np.zeros_like(self.Wxh)        dWhh = np.zeros_like(self.Whh)        dWhy = np.zeros_like(self.Why)        dbh, dby = np.zeros(H), np.zeros(V)        dh_next = np.zeros(H)        for t in reversed(range(T)):            dz = ps[t].copy()            dz[tgt[t]] -= 1.0            dWhy += np.outer(hs[t + 1], dz)            dby += dz            dh = dz @ self.Why.T + dh_next            dh_raw = (1 - hs[t + 1] ** 2) * dh            dWxh += np.outer(xs[t], dh_raw)            dWhh += np.outer(hs[t], dh_raw)            dbh += dh_raw            dh_next = dh_raw @ self.Whh.T        for w, dw in ((self.Wxh, dWxh), (self.Whh, dWhh),                      (self.Why, dWhy), (self.bh, dbh), (self.by, dby)):            w -= lr * dw        return loss / T     def infer(self, src):        """推理:完全用模型自己的上一步输出作为下一步输入"""        xs = onehot(src)        h, preds = np.zeros(H), []        for t in range(T):            h, p = self.step(xs[t], h)            preds.append(int(np.argmax(p)))            if t < T - 1:                xs[t + 1] = onehot(preds[-1])[0]        return np.array(preds)  def evaluate(model, n=60):    src, tgt = make_batch(n, seed=7)    return float(np.mean([np.mean(model.infer(src[i]) == tgt[i])                          for i in range(n)]))  def train(model, ratio, epochs=150):    for ep in range(epochs):        src, tgt = make_batch(32, seed=ep)        for i in range(32):            model.train_step(src[i], tgt[i], ratio)  def main():    for name, ratio in (('教师强制', 1.0), ('自由运行', 0.0),                        ('计划采样', 0.7)):        model = ToyRNN()        train(model, ratio)        print('%s: 推理准确率 %.2f' % (name, evaluate(model)))  if __name__ == '__main__':    main()
目的:用一个小型 RNN 在“倒序序列”任务上,对比纯教师强制、完全自由运行、固定比例计划采样三种训练策略,最后都用自由运行模式评估推理准确率,直观展示暴露偏差的影响。
  1. nxt = tgt[t + 1] if np.random.rand() < ratio else int(np.argmax(p))教师强制的核心:按概率决定这一步喂真实标签还是模型自己的输出。
  2. xs[t + 1] = onehot(nxt)[0]把选中的 token 编码后注入下一时刻的输入,这就是“前文替换机制”的落地。
  3. dz[tgt[t]] -= 1.0softmax 交叉熵对 logits 的梯度,等于预测概率减去真实标签的 one-hot,是整个反向传播的关键一行。
  4. for w, dw in ((self.Wxh, dWxh), ...)一行循环把 5 组参数全部按梯度更新,等价于优化器的一步。
  5. preds.append(int(np.argmax(p)))推理时用模型自己上一步的输出作为下一步输入,这正是暴露偏差的来源。
  6. print('%s: 推理准确率 %.2f' % (name, evaluate(model)))用自由运行模式评估,三种训练策略高下立判:教师强制训练稳但评估暴露问题,计划采样最接近实用。

常见误区

误区“模型推理时,我也可以把标准答案手动喂进去,这样输出肯定更准。既然训练时是这么干的,推理时也照做不就行了?”

正解推理时根本没有标准答案可用——如果有,就不需要模型了。Teacher Forcing 只能在训练阶段使用,因为它依赖真实标签;推理时模型只能用自己上一步的输出。这就好比期末考试,师傅不会坐到考场里帮徒弟写前文。训练时“扶着走”是为了学本领,考场上“独自走”才是真正的检验。

为何常见:初学者容易把“训练技巧”误当成“通用推理手段”,没有意识到 Teacher Forcing 依赖人工标注数据这个前提。

误区“既然 Teacher Forcing 会造成训练和推理不一致,那干脆完全不用的好,直接用模型自己的输出训练,训练和推理就完全一致了。”

正解完全自由运行的训练在早期几乎无法收敛:模型第一步输出就是垃圾,后面的梯度充满噪声,训练曲线剧烈震荡甚至直接炸掉。Teacher Forcing 的价值是“扶着走”的脚手架,让模型先学会单步能力;真要解决不一致,应该用计划采样逐步放手,而不是从悬崖边直接跳下去。

为何常见:只看到了 Teacher Forcing 的“不一致”缺点,没看到它是让前期训练稳定的必要手段。

误区“Teacher Forcing 名字里带 Teacher,和课程学习一样,都是让模型由易到难学习。”

正解课程学习是“按难度排序训练数据”,Teacher Forcing 是“用真实标签替换模型输出作为输入”,机制完全不同。不过计划采样(Scheduled Sampling)确实有点课程学习的味道:先全真值(简单)、后自输出(困难),但它只是 Teacher Forcing 的一个改进版。名字像,机理不同,别混为一谈。

为何常见:中文语境里“教师”“课程”容易引发联想混淆,缺乏对机制层面的区分。

误区“训练时模型每步都有真实标签,所以训练阶段根本不会犯错,暴露偏差是推理阶段才出现的问题。”

正解暴露偏差恰恰是指“训练和推理的输入分布不一致”:训练时模型从未把自己的错误输出当作输入,推理时却必须这么做。问题出在“训练时没见过这种输入”,而不是“推理时突然变笨”。用计划采样,就是让训练阶段也模拟推理时的输入分布,提前打预防针。

为何常见:把现象(推理崩坏)当成了原因(训练策略导致的分布不一致),没有意识到问题在训练时就已经埋下。

误区“计划采样混合了真实标签和模型输出,那教师比例 ε 一直设成 0.9,模型学得又快又稳,应该是最优解。”

正解ε 恒高等于几乎一直在用 Teacher Forcing,暴露偏差依旧存在。ε 应该是“前期大、后期小”的退火曲线,否则模型永远学不会自我纠错。经验做法是:先全 Teacher Forcing 若干 epoch,再让 ε 指数或线性衰减到 0.1 甚至 0.05,让模型逐步适应自由运行环境。

为何常见:把“训练稳”当成了唯一目标,忽略了最终要适应推理时“没有师傅”的环境。

面试题

Q1. 在 Seq2Seq 训练中使用 Teacher Forcing 时,解码器第 t 步的输入是什么?

单选题 · easy

  • A.模型上一步预测的词
  • B.上一步的真实标签← 正确答案
  • C.编码器的末层隐状态
  • D.随机采样的一个词

答案:B。Teacher Forcing 的核心就是用上一步的真实标签替换模型自己的输出作为输入,让模型每一步都从正确位置出发。就像师傅递给徒弟标准答案前文。模型上一步预测词只在推理时使用。

Q2. 模型在测试(推理)阶段生成下一个词时,输入是什么?

单选题 · easy

  • A.上一步的真实标签
  • B.自己上一步生成的词← 正确答案
  • C.训练集的平均词向量
  • D.不需要任何输入

答案:B。推理时没有真实标签可用,模型只能把自己上一步生成的词当作输入,这是自回归的必然要求,也是暴露偏差产生的根本背景。师傅不在考场,徒弟只能接自己的话。

Q3. Teacher Forcing 带来的最主要好处是?

单选题 · easy

  • A.显著减小模型参数量
  • B.训练更稳定且收敛更快← 正确答案
  • C.推理速度提升数倍
  • D.不再需要人工标注数据

答案:B。Teacher Forcing 让每一步输入都是正确的标准答案前文,误差不会在句子内部滚雪球,梯度信号清晰,所以训练稳定、收敛快。它既不改变模型大小,也不影响推理速度,更不省标注。

Q4. “暴露偏差”的核心原因是?

单选题 · mid

  • A.训练数据被恶意污染
  • B.训练与推理的输入分布不一致← 正确答案
  • C.梯度在反向传播中消失
  • D.词表过大导致显存不足

答案:B。训练时输入来自真实标签分布,推理时输入来自模型自己的输出分布,两种分布不一致导致模型面对陌生输入时错误累积。这是训练策略与推理方式之间的错位,并非数据或硬件问题。

Q5. Scheduled Sampling 中,ε 表示什么?

单选题 · mid

  • A.使用真实标签作为输入的概率← 正确答案
  • B.使用模型输出作为输入的概率
  • C.模型的学习率衰减系数
  • D.词表中被屏蔽词的比例

答案:A。计划采样中 ε 是“教师信号保留概率”:以 ε 概率用真实标签,以 1-ε 概率用模型自输出。ε 大等于多扶着走,ε 小等于多放手,与学习率和词表屏蔽无关。

Q6. 训练初期,计划采样中的 ε 通常设置为?

单选题 · mid

  • A.接近 0
  • B.接近 1← 正确答案
  • C.固定 0.5
  • D.随机波动

答案:B。训练初期模型能力弱,需要师傅多扶,ε 一般取接近 1(甚至直接取 1 纯 Teacher Forcing 若干轮),再随训练推进退火下降。一上来就放手会让训练崩溃。

Q7. 关于 Teacher Forcing 理解正确的有哪些?

多选题 · mid

  • A.训练时用真实标签替代模型上一步输出← 正确答案
  • B.推理阶段也依赖真实标签输入
  • C.能使训练损失曲线更平滑← 正确答案
  • D.可完全消除推理阶段的所有错误

答案:AC。A 是定义本身,C 是稳定收敛的直接表现。B 错误,推理时没有真实标签;D 错误,暴露偏差只是被缓解,无法被完全消除,长句仍可能出错。

Q8. 关于计划采样退火公式 ε = max(ε_min, ε0·k^epoch),下列说法正确的是?

单选题 · hard

  • A.k 越大说明退火越快,模型很快完全自跑
  • B.ε_min 通常取接近 1 的值以保持训练稳定
  • C.ε0 一般初始设为 1,即早期全用真实标签← 正确答案
  • D.退火只在每个 batch 内部进行衰减

答案:C。k 在 0~1 之间,k 越大衰减越慢;ε_min 通常接近 0,只是下限保护;退火按 epoch 或 step 进行,不是只在 batch 内部。ε0 初始为 1 是先扶稳再放手的标准做法。

Q9. 下列哪些策略可以缓解暴露偏差或长序列退化?

多选题 · hard

  • A.Scheduled Sampling 混合真实标签与模型输出← 正确答案
  • B.Beam Search 解码时保留多条候选路径← 正确答案
  • C.用 BLEU 等序列指标构造强化学习目标← 正确答案
  • D.训练时完全禁止真实标签进入解码器

答案:ABC。计划采样让训练适应自身输出分布,束搜索通过多路径降低单步错误被放大的风险,强化学习直接优化整句指标。D 完全禁止真实标签会让训练无法稳定起步,反而更糟。

Q10. Teacher Forcing 训练使用的损失函数本质上是?

单选题 · mid

  • A.模型输出与真实标签的交叉熵← 正确答案
  • B.预测值与标签的均方误差
  • C.编码器与解码器的余弦相似度
  • D.生成样本的对抗判别损失

答案:A。每个位置都在最大化“真实标签词被预测出来的概率”,等价于最小化预测分布与真实 one-hot 标签之间的交叉熵。MSE 不适合离散词表,余弦相似度与对抗损失都不是序列生成的标配。