AI 工程全景:问题定义、数据闭环与评估基线
AI 工程的难点往往不在“训练一个模型”,而在于让不同角色围绕同一套可验证的事实协作:业务方关心增长与成本,数据团队关心采集与标注,算法团队关心可学习性与泛化,工程团队关心稳定性与可维护性。一个成熟的项目必须把“业务问题 → 可学习任务 → 可度量指标”串成闭环,并在闭环中建立可复用的基线(baseline),否则后续的任何优化都缺乏参照系,最终很难判断“变好”到底来自模型、数据、特征、还是运气。
本章给出一套通用的 AI 工程全景方法:先把业务目标翻译成机器学习任务与指标,再设计数据闭环与数据版本治理,随后建立训练/验证/测试划分以避免信息泄露,最后通过离线评估与在线实验形成可迭代的评估基线,并用一套从零搭建的“模型训练脚手架”把这些原则落到可执行的工程形态中。
---
共同语言:从业务问题到可学习任务
业务目标的三段式定义
把业务需求写成一句话通常是不够的。建议用三段式定义(可直接作为需求评审模板):
1. 决策对象:要对谁/什么做决策?(用户、商品、内容、工单、设备) 2. 动作空间:系统能做什么?(排序、推荐、定价、审核、生成回复、控制阀门) 3. 价值函数:怎样算“更好”?(转化率、损失成本、时延、满意度、能耗)
这三段式的价值在于:它把“想要一个 AI”变成“需要一个在动作空间上优化价值函数的决策系统”。下一步才谈得上任务形式、训练样本与指标。
任务映射:五类常见可学习任务
把业务问题映射到任务类型时,要优先选择最简单且可交付的形式。常见任务可归纳为五类:
- `[[任务:分类]]`:输出离散标签(是否欺诈、是否流失、是否合规)。
- `[[任务:回归]]`:输出连续数值(需求预测、到达时间 ETA、价格)。
- `[[任务:排序]]`:在候选集合上输出顺序(推荐/搜索排序、广告竞价排序)。
- `[[任务:生成]]`:输出序列或结构(文本、代码、图像、SQL、总结)。
- `[[任务:控制]]`:在动态环境下选择动作(强化学习、策略优化、工业控制)。
一个业务场景往往能对应多种任务表述。工程上要选“最少假设、最少依赖、最容易验证”的那一个。例如客服质检可以做 `[[任务:分类]]`(是否违规),也可以做 `[[任务:生成]]`(生成整改建议);如果组织还不具备高质量对话数据与安全护栏,先做分类更稳妥。
“可学习”的必要条件:标签、信号与因果方向
任务能否学习,取决于是否存在稳定的输入—输出关系与足够的监督信号。这里有三条经常被忽略的工程原则:
1. 标签是否可获得且一致:同一规则能否被标注员稳定执行?否则噪声会成为上限。 2. 信号是否可行动:即便预测准确,系统能否据此改变动作空间?否则只是“看得更清楚”。 3. 因果方向是否合理:很多业务数据是“系统已经介入后的结果”,直接拿来训练会引入偏差(见后文 `[[风险:选择偏差]]`)。
> 侧栏(电子版):推荐把“可学习性”在评审时写成清单并打分。可参考 Google 的 ML Test Score 框架:https://github.com/GoogleCloudPlatform/mlops-on-gcp
---
指标基线:把“好”变成可以计算的数
离线指标与在线指标:相关但不等价
离线指标衡量模型在静态数据上的拟合与泛化;在线指标衡量系统在真实环境中对业务的影响。两者差异来自:
- 分布差异:线上流量、候选集、用户意图都在变。
- 反馈回路:模型上线后改变曝光,从而改变未来数据分布。
- 目标错配:离线标签通常是代理目标(proxy),例如点击是转化的代理。
因此工程上要建立两套基线:
- **离线评估基线**:用于快速迭代、回归测试、模型对比。
- **在线实验基线**:用于最终裁决是否发布与规模化。
把离线指标当作“上线许可”是常见误区;更合理的方式是把它当作“上线候选过滤器”。
常见任务的指标选型与陷阱
表 1 给出常用任务到指标的映射,并标注典型风险点。
| 任务类型 | 常见离线指标 | 典型在线指标 | 常见陷阱(`[[风险:...]]`) | |---|---|---|---| | `[[任务:分类]]` | `[[指标:Accuracy]]`、`[[指标:F1]]`、`[[指标:AUC]]` | CTR、转化率、误杀率 | `[[风险:类别不平衡]]`、阈值漂移 | | `[[任务:回归]]` | MAE、RMSE、MAPE | 成本、时延、库存缺货率 | `[[风险:长尾误差]]`、异常值 | | `[[任务:排序]]` | `[[指标:NDCG]]`、MAP、MRR | CTR、GMV、停留时长 | `[[风险:位置偏差]]`、`[[风险:曝光偏差]]` | | `[[任务:生成]]` | `[[指标:BLEU]]`、`[[指标:ROUGE]]`、`[[指标:Perplexity]]` | 满意度、一次解决率、投诉率 | `[[风险:评估失真]]`、安全问题 | | `[[任务:控制]]` | 累积回报、约束违背率 | 能耗、良率、风险事件率 | `[[风险:仿真偏差]]`、安全约束 |
指标不是越多越好。工程上更推荐“一个主指标 + 两个护栏指标”:
- 主指标:优化目标(例如 AUC、NDCG、ROUGE-L)
- 护栏指标:不可恶化的约束(例如误杀率、延迟、合规率)
指标定义要写清“计算口径”
指标在团队协作中经常因为口径不一致而失效。建议每个核心指标都附一个“口径卡片”,包含:
- 样本范围:按天/按会话/按曝光?是否去重?
- 正负例定义:点击窗口、转化归因窗口、多次点击处理方式
- 缺失与异常:是否过滤、如何填充
- 分桶分析:按人群/渠道/地域拆分的必选维度
关键指标公式(便于代码实现与审核)
`[[指标:F1]]`: $$ F1 = \frac{2PR}{P+R},\quad P=\frac{TP}{TP+FP},\quad R=\frac{TP}{TP+FN} $$
`[[指标:AUC]]`(ROC 曲线下的面积,本质是排序概率): $$ AUC = P(\hat{s}(x^+) > \hat{s}(x^-)) $$
`[[指标:NDCG@K]]`(排序质量,强调前排): $$ DCG@K=\sum_{i=1}^{K}\frac{2^{rel_i}-1}{\log_2(i+1)},\quad NDCG@K=\frac{DCG@K}{IDCG@K} $$
`[[指标:Perplexity]]`(语言模型困惑度): $$ PPL=\exp\left(-\frac{1}{N}\sum_{t=1}^{N}\log p(x_t\mid x_{<t})\right) $$
> 引用(纸书排版建议):指标卡片与公式建议放入附录式边栏,便于读者在实现时快速查阅。
---
数据闭环:采集、标注、抽样与版本化
数据闭环的工程定义
数据闭环不是一句口号,而是一条可画出的流程线:线上日志 → 清洗与抽样 → 标注与质检 → 训练与评估 → 上线 → 新日志。其核心是:每次模型迭代都能追溯“用了哪些数据、产生了什么效果、下一轮需要补什么数据”。
图 1 展示典型闭环(可直接用于纸书流程图)。
```mermaid flowchart TD A[业务目标与指标] --> B[日志与事件埋点] B --> C[样本构建与抽样] C --> D[标注/弱监督/规则生成标签] D --> E[数据版本化与特征快照] E --> F[训练-验证-测试与离线评估] F --> G[上线灰度与在线实验] G --> H[监控: 指标/漂移/告警] H --> C ```
采集:埋点要“为学习而设计”
工程上最常见的失败之一是:埋点只为 BI 报表服务,而不为训练样本服务。面向学习的埋点至少包含:
- **曝光日志**:系统给了什么候选、排名、位置、上下文特征(用于解决 `[[风险:曝光偏差]]`)
- **行为日志**:点击、停留、转化、取消、投诉(必须带时间戳与归因)
- **决策日志**:模型版本、特征版本、阈值、策略参数(支持回溯与复现)
如果缺少曝光日志,排序任务只能看到“被展示过的样本”,会导致严重偏差;如果缺少决策日志,线上异常很难定位是数据漂移还是策略变更。
标注:一致性与成本的平衡
标注不是“请人打标签”这么简单。中级读者应把标注当成一个小型生产系统,至少包括:
- 标注规范:正例/负例边界、冲突处理、示例库
- 标注一致性:Kappa、重标抽检、仲裁机制
- 主动学习/困难样本挖掘:把预算花在最能提升模型的样本上
- 弱监督:规则、词典、启发式、模型自训练(但要记录来源与噪声)
这里的关键是:标注质量是模型上限,标注流程质量是团队迭代速度的上限。
抽样:让数据分布服务于目标
数据抽样决定了“模型看到的世界”。常见抽样策略:
- 业务分层抽样:按渠道/地域/人群,保证覆盖
- 时间切片抽样:保留最新数据比例,适应漂移
- 难例挖掘:针对误差分布补样
- 负例下采样:处理 `[[风险:类别不平衡]]`,但要在评估时校正
抽样策略必须写入数据版本说明中,否则同一个“训练集”在不同人嘴里可能是不同分布。
版本化:数据与特征必须可追溯
工程基线要求做到:
- 任何一次训练都能回答:**数据版本是什么?特征快照是什么?代码版本是什么?**
- 任何一次线上异常都能回溯:**当时上线的模型来自哪次训练?训练用的分布与线上是否一致?**
可选工具(电子版链接):
- DVC(Data Version Control):https://dvc.org/
- lakeFS(数据湖版本控制):https://lakefs.io/
- MLflow(实验与模型追踪):https://mlflow.org/
> 侧栏:很多团队只做“代码版本”,不做“数据版本”。这会导致模型不可复现,尤其在数据清洗逻辑变化或上游表结构变化时。
---
划分与泄露:训练-验证-测试的正确姿势
三份数据的职责边界
- 训练集(train):拟合参数
- 验证集(val):调参、早停、模型选择
- 测试集(test):最终评估,只能“看一次”,用于报告与发布门槛
工程纪律是:测试集不能参与任何形式的决策,包括特征选择、阈值选择、错误分析驱动的清洗规则调整。否则测试集会被“吸收”为验证集,评估将系统性乐观。
信息泄露的常见类型(必须在评审中点名)
- `[[风险:时间泄露]]`:使用了未来信息(例如用全量统计特征计算了未来窗口)
- `[[风险:目标泄露]]`:特征直接包含标签的代理(例如“是否退款”预测“是否退货”)
- `[[风险:实体泄露]]`:同一用户/同一商品同时出现在训练与测试,导致过拟合式高分
- `[[风险:处理泄露]]`:用全数据做标准化/缺失填充,再切分(应先切再 fit)
- `[[风险:曝光泄露]]`:只用被系统展示过的样本训练排序,忽略未曝光候选
划分策略:按 IID 切分只是起点
- IID 随机切分:适用于稳定分布、无强时间依赖的任务
- 时间切分:适用于预测未来(需求预测、内容热度),推荐“训练用过去、验证用最近、测试用最新”
- 按实体切分:防止同实体泄露(用户级、商品级、设备级)
- 场景切分:用一个场景做外推测试(例如新城市、新渠道)
工程上建议在报告中同时给出“随机切分得分”和“更接近线上分布的切分得分”,二者差距就是对 `[[风险:分布漂移]]` 的预警。
---
评估与实验:从离线分数到线上结论
离线评估:不仅要分数,还要“可诊断”
一个可用的离线评估报告应包含:
- 总体指标:主指标与护栏指标
- 分桶指标:按关键维度拆解(人群、场景、时间)
- 校准与阈值曲线:尤其是分类(Precision-Recall、ROC)
- 错误分析样本:Top 错误案例与原因分类
很多团队只报一个 AUC 或 Accuracy,导致“分数高但体验差”。原因往往在分桶指标中暴露:某些渠道严重退化但被总体平均掩盖。
在线实验:显著性与可解释性
在线实验(A/B Test)的核心是对“增量效果”做推断,而不是对“均值差”做展示。基础要点:
- **随机分流单位**:用户级通常优于请求级,避免干扰
- **样本量估计**:基于期望提升、方差与显著性水平
- **显著性检验**:CTR/转化率可用两比例 z 检验或卡方;复杂指标建议 bootstrap
- **多重检验控制**:同时看很多指标会引入假阳性(可用 FDR/Bonferroni 或预注册指标)
- **序贯试验风险**:边跑边看容易“看见显著就停”,需要序贯检验或固定时长
两比例 z 检验(展示型,便于工程实现): $$ z=\frac{\hat{p}_1-\hat{p}_2}{\sqrt{\hat{p}(1-\hat{p})(\frac{1}{n_1}+\frac{1}{n_2})}},\quad \hat{p}=\frac{x_1+x_2}{n_1+n_2} $$
> 侧栏:若线上指标分布重尾或受少数大客户影响,均值检验会很脆弱。此时“分位数指标 + bootstrap 置信区间”常更稳健。
线上-线下不一致:把差异变成可行动的假设
当离线提升但线上不涨,别急着否定模型。按经验可优先排查:
1. 数据与特征一致性:线上特征是否与离线一致(时区、缺失、枚举映射) 2. 候选集变化:线上候选是否不同(召回策略、过滤规则) 3. 阈值与策略:离线优化的是排序分数,线上还叠加阈值/规则 4. 反馈回路:曝光改变导致分布偏移,短期与长期效应不同 5. 指标代理偏差:离线标签不是最终业务目标
把每项排查写成可验证假设,并用最小实验验证,比“继续调参”更有效率。
---
可复现性:让结果可被重跑、可被审计
可复现不是学术洁癖,而是工程必需:当线上出现事故或审计要求解释模型来源时,你需要在可控时间内重现训练过程。
建议把可复现性拆成三层:
1. 随机性控制:固定随机种子(Python、NumPy、框架),并记录到日志 2. 环境一致性:Docker 镜像或 Conda 环境导出;硬件差异要记录(GPU 型号、驱动) 3. 依赖锁定:`requirements.txt` 固定版本,或使用 `pip-tools/poetry` 锁文件
另外,任何“外部输入”都要版本化:数据集版本、特征定义版本、训练配置版本。最小可复现单元是:`(code_sha, data_version, config_hash, env_hash) -> metrics + model_artifact`。
---
实践案例:从零搭建可复用的“模型训练脚手架”
这一节用一个小型二分类数据集建立端到端 baseline,目标不是追求 SOTA,而是搭出可扩展的工程骨架:配置化、日志、指标、模型导出、可复现。
这里选用 scikit-learn 的乳腺癌数据集(内置,无需下载),避免外部依赖,使读者能快速跑通流程。你也可以替换为自己的业务数据,只要遵循相同接口即可。
目录结构:把“实验脚本”升级为“可维护项目”
建议结构如下(图 2):
``` ai_scaffold/ configs/ baseline.yaml src/ train.py data.py metrics.py utils.py artifacts/ models/ reports/ logs/ requirements.txt README.md ```
- `configs/`:所有可调参数集中管理(`[[风险:参数散落]]` 的对策)
- `artifacts/`:模型与评估报告输出,便于归档与回滚
- `logs/`:运行日志与结构化指标
配置化:让“复现实验”变成复制配置文件
`configs/baseline.yaml` 示例:
```yaml seed: 42
data: test_size: 0.2 val_size: 0.2 stratify: true
model: name: logistic_regression params: C: 1.0 max_iter: 200 class_weight: balanced
metrics: main: auc guards: [f1, accuracy]
output: model_dir: artifacts/models report_dir: artifacts/reports log_dir: logs ```
配置文件要能被哈希(例如对 YAML 内容做 SHA1),并写入报告,形成“训练身份证”。
代码实现:数据切分、训练、评估与导出
`src/utils.py`(种子与路径工具):
```python import os import json import random from dataclasses import asdict, is_dataclass from datetime import datetime
import numpy as np
def set_seed(seed: int): random.seed(seed) np.random.seed(seed)
def ensure_dir(path: str): os.makedirs(path, exist_ok=True)
def now_id(): return datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
def to_jsonable(obj): if is_dataclass(obj): return asdict(obj) return obj
def dump_json(path: str, payload: dict): with open(path, "w", encoding="utf-8") as f: json.dump(payload, f, ensure_ascii=False, indent=2) ```
`src/data.py`(切分策略:先切分再拟合预处理,避免 `[[风险:处理泄露]]`):
```python import numpy as np from sklearn.data