跳转至

P5-6.3 学习(learning)与模型执行(inference)

Section ID: P5-6.3 Version: v2026.07.20

在 P5-6.2 里,我们先把学习循环会按 step、batch、epoch 重复这件事绑在一起。走到这里之后,下一个问题就会冒出来。

如果梯度已经算出来了,那么现在这个模型是在学习,还是只是在被使用?

这个问题非常重要。读者很容易觉得模型总是以同一种方式工作,但这里先要划开的边界,并不是某条具体计算规则的小差异,而是:这是不是一个会改变参数的过程,还是这只是一个使用模型已经学到参数的过程

学习(learning)是改变模型参数的阶段,而模型执行(inference)是在不改变参数的前提下,用当前参数计算结果的阶段。

如果后面几节里又开始把学习和模型执行混在一起,更适合回到英文概念词汇表里的 training 条目inference 条目,先重新确认它们的边界。

区分学习与执行的问题

  • 为什么要区分学习和模型执行?
  • 在深度学习语境里,学习阶段包含哪些东西?
  • 模型执行阶段有什么不同?
  • 为什么同一个模型在学习中和在使用中,需要用不同视角去读?

这一节专注于区分真正改变参数的时间不改变参数、只是使用当前模型的时间。也就是说,这里先用有没有接上 update 路径这个标准,来闭合 learning 和 inference 的差别。

同时,这一节不会马上扩大的问题也很明确。在使用当前参数的区间里,到底该采用什么计算规则? 仍然是下一步问题。即便是同一个模型,为什么训练中和评估中会采用不同计算规则,会在下一节 P5-6.4 继续说明。dropout 和 regularization 的更大意义,则会在 P5-8.1、P5-8.2 里再重新接回。

参数改变与预测使用的判断标准

  • 能用参数是否变化来区分学习和模型执行。
  • 能解释学习不只是 forward,还包含 loss 计算、backpropagation 和 update。
  • 能说明模型执行会计算结果,但不会改动参数。
  • 能用一个可运行的 Python 例子确认两种阶段的差别。

为什么这个区分重要

深度学习入门里常见的误解包括下面这些。

  • 只要把数据放进去、结果出来了,那就算学习
  • 模型只要输出过一次结果,就已经学会了
  • 只要不断重复预测,模型就会自己越来越好

但现实并不是这样。

要让模型真的变好,至少还需要下面这些阶段。

  1. 用当前参数做出预测。
  2. 和正确答案比较,算出损失。
  3. 计算梯度。
  4. 让 optimizer 更新参数。

也就是说,仅仅产生了一个结果这件事,本身还不能说明学习已经发生。

算出结果这件事在 inference 里也会发生,但 learning 还包含:利用这个结果把模型内部数字真正改动起来。

这里首先要固定的只有一个问题。

我们眼前看到的流程,会不会继续接到参数更新?还是它只是在用当前参数算输出?

只要这个问题先答出来,learning 和 inference 的第一层边界就抓住了。这里还不会去问:在同一组参数被使用的区间里,dropout 或 batch normalization 的计算规则如何变化。因为那已经不是参数会不会变的问题,而是下一层:同样的参数要按什么规则来使用的问题。

深度学习里的学习(learning)到底包含什么

这里把深度学习语境里的 learning 先理解成下面四个阶段的组合,就足够了。

阶段 角色
forward pass 用当前参数计算预测
loss computation 把预测与答案之间的差距算成数字
backpropagation 计算各参数对应的 gradient
update optimizer 真实地改动参数

只有这四个阶段都出现时,我们才更适合说:学习发生了一次。

也就是说,在深度学习里,learning 并不只是看了很多数据,而是根据损失反复调节参数

模型执行(inference)在做什么

模型执行(inference)是在固定当前参数的前提下,对输入计算结果的阶段。

例如:

  • 用户上传一张照片,系统返回分类结果
  • 输入设备巡检记录,系统返回风险摘要
  • 给模型一段文档,系统生成下一个 token

这时模型当然在计算。但这种计算并不自动意味着参数被更新。

换句话说,inference 是把模型当前已经会的东西拿来产出答案的阶段。

learning 是模型改变自己的时间,inference 是模型不改变自己、只是被拿来使用的时间。

即使都是 forward,意义也不同

这里还有一点必须单独强调。learning 和 inference 都会使用 forward pass,所以读者很容易觉得它们看起来差不多。

但目的不同。

  • 学习中的 forward:为了接上 loss 计算和 update 的中间阶段
  • 执行中的 forward:为了直接得到最终结果的计算

也就是说,看起来好像是同一种计算,但为什么要做这个计算并不一样。本节关心的不是 forward 的细节设置,而是:这个 forward 是否继续接到了 loss -> gradient -> update

如果把它非常简单地画出来,就是下面这样。

flowchart TD
  A["输入"]
  B["前向传播"]
  C["预测"]
  D["损失 + 反向传播 + 更新"]
  E["返回服务结果"]

  A --> B
  B --> C
  C --> D
  C --> E

这张图要确认的结果是:即使预测值看起来都只是算出了结果,在学习阶段里它会继续连到损失与 update;而在执行阶段里,它会直接变成给用户或系统下一步使用的结果。

  • 在 learning 里,预测之后还会接上错了多少的计算,以及真正改变参数的步骤。
  • 在 inference 里,预测会直接成为给用户看的结果,或者下一系统阶段的输入。

为什么只用一个本地化译词去翻 inference 会产生混淆

正如 Part 1 已经看过的,核心问题在于:某一个本地化译词,往往会把多个不同概念位置一起覆盖掉。在韩语里,某个通常被译成推论/推断的词就可能同时和 reasoning、inference、prediction 混在一起听起来;其他语言里,只要一个地方化说法同时覆盖多个标准术语,也会出现同样的问题。所以在这一节里,最好不要把发生了什么思考是不是在执行已学好的模型最终输出了什么这几件事,统统压成一个词来叫。

在深度学习语境里,inference 通常指的是执行已经学好的模型,并用当前参数计算输出值的阶段。也就是说,在这一节里,inference 并不直接等同于深度思考逻辑推理对未来的预测,而是更接近于模型执行过程。

表达 先问的问题 和 P5-6.1 不同的点 本节里更安全的表达
reasoning 是不是在说沿着依据走到结论的思考过程? 它不是模型参数被执行的操作流程本身,而更偏向解释和逻辑展开 reasoning推理过程思考过程
inference 是不是把学好的模型应用到新输入上? 它会用当前参数做 forward,但不会接上 update 模型执行(inference)模型应用
prediction 模型产出的那个输出值到底是什么? 它是 inference 产生的结果,偏向输出本身,而不是整个过程 prediction预测模型输出
generation 是不是在生成文本、token 这类产物? 在 LLM 里 inference 的结果可能看起来像生成文本,但不能把生成行为模型执行阶段混成一个词 generation生成

在这一节里,inference 的基本含义可以先固定成下面三步。

  • 放入输入
  • 用当前参数计算
  • 产出输出

也就是说,这里的关键不是思考得有多深,而是有没有执行当前模型,并算出输出

因此,本节里比起只放一个地方化译词,更安全的做法是保留模型执行(inference)这样的并写。以后即便扩展到其他语言版本,也更容易保持:不要把 reasoninginferencepredictiongeneration 重新混成一团,而是按已学模型的执行阶段这个角色来翻译。

先抓住 6.3 与 6.4 的边界

P5-6.3 和下一节 P5-6.4 都会谈到训练中使用中这样的说法,所以第一次读时很容易黏在一起。因此,这里更适合先把问题拆成两层。

先回答的问题 本节的答案 下一节的答案
眼前这个流程会不会改动模型参数? learning 会改,inference 不会改。 这个问题已经先结束了。
即使参数不变,计算规则也总该完全一样吗? 这一节暂时还不处理。 会说明 training mode 和 evaluation mode 为什么要分开。

也就是说,P5-6.3 是划分有没有 update 路径的一节,而 P5-6.4 则是在这之后,再去区分同样执行模型时,应该处在什么计算状态的一节。

案例与示例

案例. 把同一条告警分成两种执行日志来看

可以想象这样一个场景:操作员输入一条新的设备告警日志,系统给出立即停机现场确认仅记录之类的分类结果。人看到屏幕上立刻出现结果时,很容易觉得:模型是不是已经根据这条日志又学到了一点新东西。但如果要用 learning 和 inference 把这个场景分开读,首先要看的不是是不是处理了新输入,而是有没有接上 update 路径

同样一条告警输入,如果拆成两种执行日志,差异就会变得很清楚。

执行日志 实际接上的阶段 参数变化
服务执行日志 alarm_count -> forward -> predicted_block_score -> 运营界面输出 没有
训练日志 alarm_count -> forward -> 与 target_block_score 比较 -> loss -> gradient -> update

在服务执行日志里,系统只是用当前参数算出风险分数并展示给运营界面。输入变了,predicted_block_score 当然也可能变,但这本身并不代表参数变化。相反,在训练日志里,即使输入形式看起来是同一类告警,也必须继续连到与 target_block_score 的比较、损失、gradient、再到 update,参数才会真的改变。所以这个案例里真正要确认的结果,不是屏幕上的结果有没有变,而是loss -> gradient -> update 有没有真正接上并改动参数。

人最容易先看的标准 重新用 learning/inference 视角读出来的标准
既然处理了新输入,模型应该也同时学到了东西 可能只是做了输出计算,并没有任何参数更新
结果看起来不同,所以模型内部数字应该也跟着变了 输入变化导致输出变化,和参数是否变化是两回事
只要使用得足够多,模型就会自己学会更多 只有损失、gradient、update 这些步骤真的存在时,才叫 learning

这个场景同样可以迁移到缺陷检测演示页、LLM 聊天界面或风险分析系统里。上传一张图片、改写一句问题、再次输入一条告警日志,都可能只是 inference。即使输出每次都变,只要没有接上 update 路径,参数就仍然保持不变。

如果重新把这个案例压缩成 learning 和 inference 两条线,差别不在于有没有出现结果,而在于这个结果有没有继续接到会真正改变参数的计算里去

场景 人最先看到的结果 learning/inference 视角下真正要区分的东西 参数会改变吗
服务执行日志 新告警结果立刻出现了 先看是不是只做了当前参数下的 forward 不改变
训练日志 比较了告警样本和目标值 先看 loss、gradient、update 是否真的接上 会改变
检测图像、LLM 聊天等服务输入 输入一变,输出也跟着变了 要把输入变化导致的输出变化和实时再训练区分开 一般服务使用中不会改变

这张表里最先要抓住的结果是:区分 learning 和 inference 的核心,不是输出有没有变化,而是损失和更新有没有真正接上并改变参数

如果把这个案例再压缩一次,最先该读的流程就是下面这样。

flowchart TD
    A[新输入到达] --> B[重新计算输出]
    B --> C[没有损失也没有更新]
    C --> D[参数保持不变]

    E[训练 batch 到达] --> F[预测与目标比较]
    F --> G[接上损失、梯度与更新]
    G --> H[参数发生变化]

这张图不是为了把服务日志和训练日志重新讲一遍,而是为了把处理新输入、导致输出改变因为损失-更新路径接上而导致参数改变这两件事,再次一口气分开。

练习与例子

这一节例子的目标,是用同一个小型风险分数模型确认:它在看训练 batch时会改动多个参数,而在看服务输入时不会改动这些参数。这里的代码不是只打印一组数字,而是让我们直接实验:同样是输入进模型,是否接上 update 路径,会不会真的让参数发生变化。

输入:

  • 4 条训练用告警样本
  • 初始参数 alarm_weightdelay_weightbias
  • 学习率 learning_rate

输出:

  • 每个 step 的风险分数预测、损失、参数变化
  • 学习完成后的 inference 结果
  • inference 前后的参数比较
  • 用同一组风险权重处理多条服务输入时,确认是否只有输出变化

问题场景:

  • learning 和 inference 即使使用同一套公式,目的也不同,所以必须分开看:哪一段会更新权重,哪一段只是固定权重
  • 即使连续输入很多次服务请求,只要没有 update,参数也应该保持不变

需要确认的概念:

  • 学习阶段会为了减小损失不断改动权重
  • 推理阶段会固定这些学好的权重,只负责算结果
  • 输入变化导致的输出变化,并不等于参数发生了变化

输入(input):

训练 batch 里,我们会把 alarm_countrestart_delay_hours 变成 predicted_block_score,再与目标值 target_block_score 比较,从而更新 alarm_weightdelay_weightbias。而在服务阶段,我们只确认:新输入进来时,这组参数是不是仍然保持不变。

在看代码之前,先预想一下:哪些区间里权重会变化,会更容易读出差别。

区间 先猜测一下会看到什么比较 猜测理由
train step 1~4 alarm_weightdelay_weightbias 很可能会逐步改变 因为这里会利用损失和 gradient 做真实更新
service input 1 输出会算出来,但参数很可能保持不变 因为 inference 只是使用当前参数
service input 2 输出可能和前一个不同,但参数仍然很可能一样 因为输入变化与参数变化本来就是两回事

这张表的目的,是在看代码前,先把输出变化参数变化分开读。

这个例子需要直接比较下面两条路径来读,实验作用才会更清楚。

要比较的路径 先观察什么输出 想解释的问题
run_inference prediction 会被算出,但 parameters_changed=False 有结果出来但没有 update 路径时,参数是否保持不变?
run_learning_step loss 会被计算,且 parameter_delta 是否不是 0 同一类输入只要接上 loss 和 update,参数是否会改变?
用两条路径比较同一个服务输入 对比 service input 1same input with update 参数变化是否由 update 路径决定,而不是由输入本身决定?
# 这个例子比较同一个服务输入在 inference 路径和 learning step 路径中参数是否会改变。
train_alarm_data = [
    {"alarm_count": 1.0, "restart_delay_hours": 2.0, "target_block_score": 4.0},
    {"alarm_count": 2.0, "restart_delay_hours": 1.0, "target_block_score": 5.0},
    {"alarm_count": 3.0, "restart_delay_hours": 2.0, "target_block_score": 8.0},
    {"alarm_count": 4.0, "restart_delay_hours": 3.0, "target_block_score": 11.0},
]

initial_parameters = {
    "alarm_weight": 0.4,
    "delay_weight": 0.2,
    "bias": 0.0,
}
learning_rate = 0.03
service_inputs = [
    {"label": "service input 1", "alarm_count": 4.0, "restart_delay_hours": 1.0},
    {"label": "service input 2", "alarm_count": 5.0, "restart_delay_hours": 3.0},
]
service_input_with_target = {
    "label": "same input with update",
    "alarm_count": 4.0,
    "restart_delay_hours": 1.0,
    "target_block_score": 10.0,
}

def predict_block_score(row, parameters):
    return (
        row["alarm_count"] * parameters["alarm_weight"]
        + row["restart_delay_hours"] * parameters["delay_weight"]
        + parameters["bias"]
    )

def rounded_parameters(parameters):
    return {name: round(value, 3) for name, value in parameters.items()}

def parameter_delta(before, after):
    return {name: round(after[name] - before[name], 3) for name in before}

def did_parameters_change(before, after):
    return any(abs(after[name] - before[name]) > 1e-12 for name in before)

def run_inference(row, parameters):
    before = parameters.copy()
    prediction = predict_block_score(row, before)
    after = before.copy()
    return {
        "prediction": prediction,
        "loss": None,
        "parameters_before": before,
        "parameters_after": after,
    }

def run_train_step(sample, parameters, learning_rate):
    before = parameters.copy()
    prediction = predict_block_score(sample, before)
    target = sample["target_block_score"]
    error = prediction - target
    loss = error ** 2
    gradients = {
        "alarm_weight": 2 * error * sample["alarm_count"],
        "delay_weight": 2 * error * sample["restart_delay_hours"],
        "bias": 2 * error,
    }
    after = {
        name: value - learning_rate * gradients[name]
        for name, value in before.items()
    }
    return {
        "prediction": prediction,
        "loss": loss,
        "parameters_before": before,
        "parameters_after": after,
    }

def print_result(name, result):
    before = result["parameters_before"]
    after = result["parameters_after"]
    loss_text = "-" if result["loss"] is None else f"{result['loss']:.3f}"
    print(
        f"{name}: prediction={result['prediction']:.3f}, "
        f"loss={loss_text}, "
        f"parameters_changed={did_parameters_change(before, after)}, "
        f"parameter_delta={parameter_delta(before, after)}"
    )

parameters = initial_parameters.copy()
print("initial_parameters =", rounded_parameters(parameters))

for step, sample in enumerate(train_alarm_data, start=1):
    result = run_train_step(sample, parameters, learning_rate)
    print_result(f"train step {step}", result)
    parameters = result["parameters_after"]

parameters_after_training = parameters.copy()
print("parameters_after_training =", rounded_parameters(parameters_after_training))

for row in service_inputs:
    result = run_inference(row, parameters)
    print_result(row["label"], result)
    parameters = result["parameters_after"]

print("parameters_after_all_service_inputs =", rounded_parameters(parameters))

update_result = run_train_step(
    sample=service_input_with_target,
    parameters=parameters,
    learning_rate=learning_rate,
)
print_result(service_input_with_target["label"], update_result)

在输出里,先看 parameters_changedparameter_delta 就可以了。prediction 即使变了,只要 parameter_delta 全部是 0,就是 inference;如果接上了 loss 和 update,且 parameter_delta 不是 0,就是 learning。

initial_parameters = {'alarm_weight': 0.4, 'delay_weight': 0.2, 'bias': 0.0}
train step 1: prediction=0.800, loss=10.240, parameters_changed=True, parameter_delta={'alarm_weight': 0.192, 'delay_weight': 0.384, 'bias': 0.192}
train step 2: prediction=1.960, loss=9.242, parameters_changed=True, parameter_delta={'alarm_weight': 0.365, 'delay_weight': 0.182, 'bias': 0.182}
train step 3: prediction=4.778, loss=10.384, parameters_changed=True, parameter_delta={'alarm_weight': 0.58, 'delay_weight': 0.387, 'bias': 0.193}
train step 4: prediction=10.174, loss=0.682, parameters_changed=True, parameter_delta={'alarm_weight': 0.198, 'delay_weight': 0.149, 'bias': 0.05}
parameters_after_training = {'alarm_weight': 1.735, 'delay_weight': 1.302, 'bias': 0.617}
service input 1: prediction=8.859, loss=-, parameters_changed=False, parameter_delta={'alarm_weight': 0.0, 'delay_weight': 0.0, 'bias': 0.0}
service input 2: prediction=13.197, loss=-, parameters_changed=False, parameter_delta={'alarm_weight': 0.0, 'delay_weight': 0.0, 'bias': 0.0}
parameters_after_all_service_inputs = {'alarm_weight': 1.735, 'delay_weight': 1.302, 'bias': 0.617}
same input with update: prediction=8.859, loss=1.302, parameters_changed=True, parameter_delta={'alarm_weight': 0.274, 'delay_weight': 0.068, 'bias': 0.068}

这里首先要确认的是:在学习 step 里会反复出现 parameters_changed=True,而在 inference 里即使来了新输入,也会保持 parameters_changed=False。最后的 same input with update 行,是用和 service input 1 相同的输入值,但额外接上目标值和 update 路径的对比场景。

  • 学习 step 里会计算 loss,且 parameter_delta 不是 0,所以参数真实地改变了
  • inference 里即使输入不同导致 prediction 变化,parameter_delta 也全都是 0
  • same input with update 里,即便输入类型相似,只要接上损失和 gradient,parameter_delta 就会不是 0
  • 也就是说,不是服务输入用得多了就会自动再训练,只有 update 路径真正接上时,参数才会变

如果再用图来看,流程差异会更明显。在学习流程里,每个 step 之后 alarm_weightdelay_weightbias 都会发生变化,而 update 后的值会继续成为下一个 step 的起点。

学习流程里多个参数随 step 变化的图

在模型执行流程里,虽然服务输入变化会让 predicted_block_score 跟着变化,但同一段时间里的参数线会像水平线一样保持不动。这里真正要看的,不是输出线发生了变化,而是参数线没有动。

模型执行流程里 prediction 变化但参数固定的图

区间 现在要读出的核心
train step 1~4 因为预测之后接上了损失与 update,所以参数会持续改变
inference input 1 即使处理新输入,使用的仍然是当前固定好的参数
inference input 2 输出虽然会变,但变化的是输入,不是参数

如果再把这些结果按输出变化参数变化重新绑一遍,差别会更清楚。

执行结果里看到的差别 只看结果时容易留下的解读 重新用 learning/inference 视角读出来的解读
train step 1~4 里的 prediction 一直在变 好像只是不断看了更多告警样本 因为损失和 update 接上了,所以参数本身正在变化
两个 inference 输入的 prediction 不同 输出不同,好像模型内部也同时变了 只是输入不同,而 parameter_delta 是 0
parameters_after_all_service_inputs 和学习刚结束后的参数相同 因为也有结果,所以似乎也可能发生了学习 inference 只是计算了结果,参数仍然固定
same input with update 里的 parameter_delta 不是 0 同样的输入本来应该只得到相同输出 真正决定参数是否变化的,不是输入种类,而是有没有接上损失-更新路径

把这张表也读完以后,就会更明确:learning 和 inference 的核心,不是两边都会用 forward,而是什么时候真的接上 update

在传统统计模型和机器学习教育里,区分训练用数据(training data)预测阶段(prediction stage)本来就很重要。到了深度学习里,又多了 backpropagation、optimizer、mode 切换等因素,所以这条边界只会变得更重要。

从课程结构看,这一节之所以必须放在这里,也很明确。刚在 P5-5.1、P5-5.2 看过 gradient 是怎样算出来的,现在就必须继续分清:算出来了之后,什么时候会真的触发更新,什么时候只是单纯执行模型。

  • 刚学完反向传播时,很容易把所有计算都感觉成学习
  • 模型执行又很容易被低估成只是做个简单 forward
  • 而如果后面要理解 dropout、batch normalization、evaluation mode,就必须先弄清:什么时候会更新,什么时候不会更新

也就是说,这一节是开始从运行视角去读深度学习学习过程的第一节。

什么时候需要先把 learning 和 inference 分开读

模型会给出结果这类说法已经不足以说明参数什么时候会变、什么时候不会变时,就需要把这一节重新拿出来看。

先出现的问题场景 为什么 learning/inference 区分会先有用 紧接着该去看的问题
只要出现结果,就觉得模型已经学习了 可以先按参数是否变化,把学习与执行明确分开 接下来还要看:同一个模型为什么 mode 还会不同
眼前只看到 forward,损失、反向传播、update 全都糊成一团 能先闭合:learning 是包含 update 的整条流程 下一步要接着看 training/eval mode 下计算规则如何变化
聊天、分类演示、服务返回看起来像实时再训练 可以先确认:inference 是使用当前参数,而不是自动再训练 后面还要再看 optimizer 究竟什么时候负责真实更新
训练数据处理和用户请求处理看起来像同一件事 能分开:学习计算和服务执行计算的目标本来不同 下一节会继续看哪些层对 mode 差异特别敏感

检查清单

  • 能解释学习(learning)和模型执行(inference)是按什么标准分开的吗?
  • 能区分改变参数的阶段与使用固定参数的阶段吗?
  • 能说明 learning 是改变参数的阶段,而 inference 是不改变参数、只是使用参数的阶段吗?
  • 能说明深度学习中的学习包含 forward、loss 计算、backpropagation、update 吗?
  • 能解释 inference 里也会做 forward,但目的和后续步骤不同吗?
  • 能指出:仅仅有结果产出,并不等于学习已经发生了吗?
  • 当别人把服务处理和学习过程混成一件事时,能先想到 learning/inference 这条边界吗?
  • 能知道:这一节之后还要继续单独看 training mode 和 evaluation mode 的计算差异吗?

出处与参考资料