深度学习课程 10:模型评估、调优与部署闭环

· 约 10 分钟 更新于 2026-09-20 ·
本文目录(21 节)

训练损失下降,只能说明优化器在当前数据上找到了更低目标值。它没有回答模型能否泛化、错误是否可接受、导出后输出是否一致、线上数据是否变化。一个可交付的深度学习项目,需要把这些问题放进同一条证据链。

这篇以项目收尾日志的方式整理评估、调优与部署。目标不是给出万能参数表,而是建立可复查的决策流程:先定义业务代价,再固定数据与基线,每次只改有限变量,最后验证制品而不只是训练进程。

文中的具体指标归入“参考运行记录”,用于示范比较方法和停止条件。

1. 先写清成功标准

模型指标应由使用场景反推。垃圾评论过滤更怕正常评论被误杀,疾病筛查可能更怕漏检,推荐系统还要平衡相关性、延迟和多样性。

一份可执行的成功标准至少包含:

  • 主要指标与最低阈值;
  • 关键类别或人群的单独约束;
  • 延迟、内存、模型大小等资源预算;
  • 低置信、输入异常和服务故障时的回退;
  • 测试集使用次数与最终签字条件。

没有业务边界时,“提高准确率”会无限延伸,也无法判断何时停止调优。

2. 数据切分是评估的地基

独立同分布的随机划分只适用于样本确实独立的场景。现实数据常包含用户、设备、患者、文章、视频帧或时间批次。同一实体跨集合会产生泄漏。

常见策略:

  • 按实体分组:同一用户或文档只进入一个集合;
  • 按时间切分:用过去训练、未来评估,模拟上线;
  • 分层切分:维持类别比例,但不能破坏分组约束;
  • 外部测试:使用不同来源数据检验分布迁移。

所有统计变换都只能在训练集拟合,包括归一化均值、词表、缺失值填充、特征选择和过采样。验证和测试只应用已确定的变换。

数据的三种职责

图解:测试集反馈若反复影响方案选择,就已经参与了开发,需要重新考虑最终评估设计。

把每次试验的配置和结果按 run_id 对齐。最终报告不仅写最佳分数,还要写试了多少个方案、如何选择、是否存在同一主体跨集合。部署时保存阈值与预处理,避免离线使用一个工作点、线上使用另一个。

动手核对: 为同一个模型生成离线批量结果与服务返回结果,逐样本比较标签、概率与错误码。性能测试应在输出正确的前提下进行。

3. 准确率之外的指标

二分类混淆矩阵包含 TP、FP、TN、FN。精确率与召回率分别为:

Precision=TPTP+FPPrecision = \frac{TP}{TP+FP} Recall=TPTP+FNRecall = \frac{TP}{TP+FN}

F1 是两者调和平均:

F1=2Precision⋅RecallPrecision+RecallF1 = 2\frac{Precision\cdot Recall}{Precision+Recall}

多分类中,Macro F1 先计算每类 F1 再平均,对少数类更敏感;Micro F1 汇总所有样本计数,容易被多数类主导。还应结合:

  • 每类召回与精确率;
  • 混淆矩阵及高代价方向;
  • ROC-AUC 或 PR-AUC;
  • 概率校准与置信度分桶;
  • 覆盖率:达到自动处理阈值的样本比例。

指标不是越多越专业。选一个主指标、若干安全护栏,再用错误样本解释变化。

4. 建立不能跳过的基线

基线可以是多数类、规则、线性模型、小型 CNN/RNN,或冻结骨干的分类头。它的作用是证明复杂度是否值得。

基线记录应包含:

run_id: image-r0
data_version: 2026-08-20-a
split_version: grouped-v1
model: small-cnn
seed: 42
primary_metric: macro-f1
artifact: artifacts/image-r0/

只记“模型名 + 最终分数”不够。数据版本或切分变化后,数字不能直接横向比较。

5. 先做小批次过拟合测试

正式调参前,取一个很小的固定批次,关闭随机增强,确认模型能把训练损失压到很低。这个测试可以快速暴露:

  • 标签和类别编号错位;
  • 输出维度错误;
  • 参数没有进入优化器;
  • loss 输入格式不匹配;
  • mask 或 padding 逻辑损坏;
  • 图像归一化范围错误。

若连小批次都学不会,扩大数据、增加 epoch 或搜索超参数只会消耗更多时间。

6. 受控调优:一次回答一个问题

每次运行都应对应一个假设。例如:

验证损失在前期下降后反弹,训练损失继续下降,假设模型过拟合。保持数据和优化器不变,只增加权重衰减,观察验证损失和关键类别召回。

建议先按影响层级调试:

  1. 数据与标签正确性;
  2. 切分与泄漏;
  3. 学习率和训练稳定性;
  4. 正则化与增强;
  5. 模型容量;
  6. 更复杂结构或预训练模型。

顺序并非铁律,但数据问题未解决前,更换模型通常没有清晰结论。

7. 学习率、批次和调度器

学习率过高常见震荡或发散,过低则改善缓慢。批次大小会影响梯度噪声、显存和归一化层行为,因此调整批次后可能需要重新验证学习率。

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=3e-4,
    weight_decay=1e-2,
)

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer,
    T_max=num_epochs,
)

调度器不是自动提升按钮。如果总训练轮数、调用时机或初始学习率不合适,曲线只会更难解释。记录每轮真实学习率,并在同一预算下比较。

8. 处理不平衡数据

不平衡不等于一定要过采样。先确认少数类是否真的重要、标签是否可靠、训练与线上比例是否一致。

可选方法包括:

  • 类别权重:提高少数类错误代价;
  • 分层批采样:让训练批次更均衡;
  • 数据增强:只做保持标签语义的变换;
  • 阈值调整:按业务成本控制召回与误报;
  • 两阶段系统:高置信自动处理,其余人工复核。

每种方法都要观察每类指标和概率分布。总体 Macro F1 上升,仍可能伴随关键类别精确率大幅下降。

9. 参考运行记录:如何停止调优

下面是一组图像分类项目的参考运行记录。固定了数据版本、分组切分和测试集,调优阶段只查看验证集;具体数字用于展示决策方式。

运行唯一主要变化验证 Macro F1关键类召回P95 CPU 延迟决策
R0小型 CNN 基线0.7810.70418 ms管线可用
R1迁移学习冻结骨干0.8460.79231 ms明显改善
R2解冻后层、分组学习率0.8610.81731 ms保留
R3更强增强0.8580.82531 ms主指标回落,待看成本
R4更大骨干0.8690.82976 ms超出延迟预算

如果上线预算要求 P95 低于 40 ms,R4 即使验证分数更高也不是合格候选。R2 与 R3 的选择取决于关键类漏判代价;若 R3 的召回收益不值得整体误报增加,就停止在 R2。

停止条件可以写成:连续若干次受控实验未产生超过最小有效改善,或改善违反资源与关键指标护栏。它比“感觉还能再调”更可执行。

10. 错误分析表比单个指标更有用

每次候选模型都导出相同字段:

sample_id,true_label,pred_label,confidence,source,length,run_id

按以下切片检查:

  • 高置信错误:可能是标签错误、泄漏或分布盲区;
  • 两版预测翻转:直接解释改动影响;
  • 关键类别漏判:检查共同视觉或语言模式;
  • 特定来源退化:发现域偏移;
  • 极短、极长或低质量输入:验证边界策略。

模型调优应从错误簇产生假设,再回到训练;不是从超参数列表随机选择下一个数字。

11. 概率校准和拒绝策略

softmax 最大值高,不代表预测必然正确。模型可能对分布外输入过度自信。可以在验证集上做置信度分桶:比较每个区间的平均置信度和实际正确率。

期望校准误差的一种形式为:

ECE=∑m=1M∣Bm∣n∣acc(Bm)−conf(Bm)∣ECE = \sum_{m=1}^{M}\frac{|B_m|}{n} \left|acc(B_m)-conf(B_m)\right|

部署接口可以返回三种状态:自动接受、需要人工确认、输入无效。阈值要和覆盖率一起报告,否则提高阈值虽然减少错误,也可能让几乎所有请求都进入人工流程。

12. 保存完整制品

模型文件只是制品的一部分。建议目录:

artifacts/run-r2/
  model.pt
  model-config.json
  preprocessing.json
  labels.json
  metrics.json
  environment.txt
  regression-samples.jsonl

metrics.json 应包含数据与切分版本、主指标、关键类指标和阈值。environment.txt 记录 Python、框架、CUDA 或 CPU 信息。发布时为目录生成版本号或内容哈希,避免“best-final-v2”式命名。

13. 导出前后的等价性验证

以 PyTorch 为例,先将模型切换到推理模式,再用固定样本保存输出:

model.eval()
with torch.inference_mode():
    reference_logits = model(regression_batch).cpu()

torch.save(
    {
        "model_state": model.state_dict(),
        "config": model_config,
        "labels": class_names,
    },
    "artifacts/run-r2/model.pt",
)

在新进程创建同结构模型、加载权重,再比较固定样本输出:

checkpoint = torch.load("artifacts/run-r2/model.pt", map_location="cpu")
loaded_model = build_model(checkpoint["config"])
loaded_model.load_state_dict(checkpoint["model_state"])
loaded_model.eval()

with torch.inference_mode():
    loaded_logits = loaded_model(regression_batch.cpu())

torch.testing.assert_close(reference_logits, loaded_logits, rtol=1e-4, atol=1e-5)

如果还要转换 ONNX、TensorRT 或端侧格式,每跨一次边界都重复等价性测试,并检查目标设备上的预处理、动态批次和不支持算子。

14. 推理服务的最小接口

from dataclasses import dataclass

@dataclass
class Prediction:
    label: str
    confidence: float
    model_version: str
    needs_review: bool

def predict(prepared_input) -> Prediction:
    with torch.inference_mode():
        logits = model(prepared_input)
        probabilities = logits.softmax(dim=-1)[0]
    class_id = int(probabilities.argmax().item())
    confidence = float(probabilities[class_id].item())
    return Prediction(
        label=class_names[class_id],
        confidence=confidence,
        model_version=MODEL_VERSION,
        needs_review=confidence < REVIEW_THRESHOLD,
    )

接口应对输入大小、类型和超时做限制,错误返回明确状态,日志保留请求标识、模型版本和耗时。涉及隐私的原始输入不要直接写日志。

15. 性能测试要分层

报告延迟时写清从哪里开始计时:模型前向、完整接口和用户看到完整结果的耗时不是一回事。还要记录冷启动、输入长度与并发数。脱离这些条件的 P95 很难用于容量规划,也不能拿来比较不同机器上的模型。

端到端延迟包括解析、预处理、模型推理、后处理和网络开销。只测一次 model(x) 容易低估实际延迟。建议分别记录:

  • 冷启动与预热后延迟;
  • batch=1 与目标批量吞吐;
  • P50、P95、P99,而非只报平均值;
  • CPU/GPU、线程数和内存占用;
  • 并发下错误率和排队时间。

测试输入应覆盖典型尺寸和边界尺寸。GPU 计时时要正确同步,否则测到的可能只是异步提交时间。

16. 上线监控与回滚

线上没有即时标签时,仍可监控输入长度、类别比例、置信度、未知字符率、图像亮度或嵌入统计。这些代理指标只能提示漂移,不能直接证明质量下降。

需要同时记录:

  • 服务可用性、错误率、超时和延迟;
  • 模型版本与配置版本;
  • 输入和预测分布;
  • 人工复核结果或延迟到达的真实标签;
  • 可一键切回的上一稳定制品。

新模型可先影子运行或小流量灰度。回滚触发条件应在发布前写好,而不是事故发生后临时讨论。

17. 个人项目如何形成作品集证据

作品集不必展示庞大训练规模,但要展示完整性:

  1. 一页需求与数据边界;
  2. 可复现的数据切分和基线;
  3. 三到五次有假设的运行记录;
  4. 混淆矩阵与错误样本分析;
  5. 独立加载和导出等价性测试;
  6. 简洁推理接口与失败回退;
  7. 一张架构图或请求流程图。

如果没有亲自运行某个环境,就不要写成已经完成。可以把预期验证列成待办,把公开依据和自己的运行证据分开保存。可信的边界比夸大的数字更能支撑技术判断。

18. 项目复盘模板

### 约束
- 数据来源、规模、隐私、延迟预算

### 基线
- 数据版本、切分、模型、主要指标

### 问题
- 错误簇、曲线、关键类别

### 单一改动
- 假设、配置差异、预期影响

### 证据
- 验证指标、资源、回归样本

### 决策
- 保留、回退或继续验证的原因

### 边界
- 未覆盖分布、人工复核、监控与回滚

这个模板适用于图像、文本、语音和表格任务。不同项目更换的是指标与输入契约,判断方法保持一致。

19. 面试表达

一个成熟回答可以这样展开:

我先按实体分组固定数据切分,用简单模型验证全流程,再依据业务代价选 Macro F1、关键类召回和延迟作为护栏。每次实验只改变一个主要因素,并通过错误样本解释变化。候选模型导出后在新进程做固定样本一致性测试,制品携带预处理、标签和版本。上线采用低置信人工复核、灰度发布和版本回滚,后续监控输入与预测分布。

它回答了“为什么这样做、证据是什么、失败怎么办”,比只列模型结构和最好分数更完整。

20. 系列收束

从训练循环、反向传播和稳定性,到 CNN、迁移学习、RNN、Transformer,再到 Keras 工程化与 Paddle 中文项目,这条主线最终落在同一个原则上:模型只是系统的一部分。

继续学习时,可以从自己的任务选择一个小数据集,先固定输入与评估契约,再替换模型。每次只提出一个可验证问题,留下运行配置、错误样本和决策。这样积累的不是十个互不相干的 Notebook,而是一套能迁移到新项目的工程方法。

系列入口

回到起点复习完整训练流程:深度学习课程 01:从张量到完整训练循环。

💬 评论

这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。

正在连接评论服务,首次访问可能需要几秒…