Course 2:ML 测试策略
课程简介
数据验证、模型评估、集成测试与 CI/CD 集成。
🎬 本课程视频:MLOps Production — 机器学习工程生产实践
一、ML 测试概述
1.1 传统软件测试 vs ML 测试
传统软件测试验证代码是否符合预期行为——如果输入是 x,输出应该是 y。测试可以精确断言。
ML 测试面临着独特的挑战:
- 不可确定性:相同代码 + 相同数据可能训练出不同模型
- 阈值设定:准确率达到多少才算"通过"?
- 数据依赖性:模型行为不仅取决于代码,还取决于数据
- 不可解释性:复杂模型的行为难以完全理解
因此,ML 测试需要覆盖数据、模型、基础设施三个层面。
1.2 ML 测试的分层策略
按照 Google 的 ML 测试分级(ML Testing Trophy):
- 数据验证:输入数据质量和一致性检查
- 模型评估:模型性能指标和鲁棒性测试
- 基础设施测试:训练和推理管道的正确性
- 集成测试:ML 系统与其他系统的交互
- 端到端测试:从数据到预测的完整流程
二、数据验证
2.1 数据质量检查
数据输入到训练管道前应验证:
- Schema 验证:特征类型、范围、枚举值是否符合预期
- 完整性检查:必要字段无缺失
- 唯一性约束:主键唯一
- 新鲜度检查:数据更新时间在允许范围内
2.2 数据一致性验证
训练时和推理时的数据应该一致:
- 特征定义相同
- 预处理逻辑相同
- 特征计算逻辑相同
训练-服务偏差(Training-Serving Skew):
这是 ML 系统中最常见的问题之一。训练时提取的特征和在线推理时提取的特征不一致。比如训练时特征计算使用的是精确的用户点击时间,但在线服务时使用的是日志记录时间——这两个时间可能差了几秒,导致特征值不同。
def validate_schema(data, expected_schema):
for col, dtype, constraints in expected_schema:
assert col in data.columns, f"Missing column: {col}"
assert data[col].dtype == dtype, f"Type mismatch: {col}"
if 'min' in constraints:
assert data[col].min() >= constraints['min']
if 'max' in constraints:
assert data[col].max() <= constraints['max']
2.3 数据分布检查
- 统计量监控:均值、方差、分位数是否偏离基线
- 类别分布:类别频率是否发生显著变化
- 相关性变化:特征间相关性是否异常
2.4 数据漂移测试
前面讲过的漂移检测方法应集成到测试框架中:
- 定期运行 KS 检验
- 当检测到显著漂移时测试失败
三、模型评估
3.1 单元测试
模型级别的单元测试:
- 模型输出形状是否符合预期
- 模型输出的值域是否合理(如概率在 [0, 1] 内)
- 模型对零输入或空输入的行为
- 模型对极端输入的行为
def test_model_output_shape():
model = load_model()
X = np.random.randn(10, n_features)
y_pred = model.predict(X)
assert y_pred.shape == (10,), f"Expected shape (10,), got {y_pred.shape}"
def test_model_output_range():
model = load_model()
X = np.random.randn(10, n_features)
y_pred = model.predict_proba(X)[:, 1]
assert np.all((y_pred >= 0) & (y_pred <= 1)), "Probabilities out of range"
3.2 阈值测试
设定模型性能的"及格线":
def test_model_accuracy():
model = load_model()
X_test, y_test = load_test_data()
accuracy = model.score(X_test, y_test)
assert accuracy >= 0.85, f"Accuracy {accuracy} below threshold 0.85"
但单纯的阈值测试有一个问题:准确率 0.849 真的就比 0.850 差很多吗?合理的做法是设置可接受的性能下降范围,并与基线模型对比。
3.3 鲁棒性测试
对抗性测试:
- 对输入添加微小扰动,观察输出是否剧烈变化
- 好的模型应该对小扰动不敏感
切片评估:
- 按不同维度评估模型性能(用户群组、时间段、地域)
- 模型可能在整体指标上好,但在某个子集上表现极差
def test_model_fairness(model, X, y, sensitive_feature):
for group in X[sensitive_feature].unique():
mask = X[sensitive_feature] == group
acc = model.score(X[mask], y[mask])
assert acc >= 0.80, f"Group {group}: accuracy {acc} below threshold"
3.4 可解释性测试
虽然模型本身可能是黑盒,但我们可以通过可解释性工具验证模型行为是否合理:
- SHAP 值分析:特征重要性是否与业务知识一致
- 部分依赖图:特征与预测的关系是否符合预期
- 错误分析:错误预测是否有系统性的模式
四、CI/CD 流水线
4.1 ML CI/CD 的特点
ML 的 CI/CD 比传统软件更复杂:
- 触发条件不只是代码变更,还有数据变更
- 测试不仅是代码测试,还有数据和模型测试
- 部署不只是部署代码,还有模型和配置
4.2 CI 流水线
代码变更触发的 CI 步骤:
1. 代码检查:Lint、类型检查、格式检查
2. 单元测试:数据处理逻辑测试、特征计算测试
3. 数据验证:Schema 检查、完整性检查
4. 模型训练:用少量数据训练快速验证
5. 模型评估:验证集指标检查
# .github/workflows/ci.yml
name: ML CI Pipeline
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run data tests
run: pytest tests/test_data.py -v
- name: Run model tests
run: pytest tests/test_model.py -v
- name: Validating schema
run: python tests/validate_schema.py
4.3 CD 流水线
模型部署触发的 CD 步骤:
1. 模型注册:将训练好的模型注册到模型注册表
2. 预部署测试:在 staging 环境运行完整评估
3. A/B 测试:部署并运行 A/B 测试
4. 渐进式发布:逐步扩大新模型流量
5. 监控告警:监控模型上线后的表现
4.4 模型验证门(Validation Gates)
在 CI/CD 中设置质量门,不满足条件的模型不能进入下一阶段:
- 门 1:数据验证通过
- 门 2:单元测试通过
- 门 3:模型精度 > 阈值
- 门 4:模型无显著偏差
- 门 5:对比基线模型有统计显著的提升
五、实验管理
5.1 实验对比
系统的实验管理应该支持:
- 记录每次实验的超参数和结果
- 方便对比不同实验的差异
- 识别哪些超参数对结果影响最大
5.2 假设检验
在对比两个模型时,需要判断性能差异是否统计显著:
- 配对 t 检验:比较两个模型在同一测试集上的差异
- McNemar 检验:比较分类模型的预测一致性
from scipy import stats
def test_significant_improvement(y_true, y_pred_a, y_pred_b):
# 判断模型 B 是否显著优于模型 A
errors_a = (y_pred_a != y_true).astype(float)
errors_b = (y_pred_b != y_true).astype(float)
t_stat, p_value = stats.ttest_rel(errors_a, errors_b)
return p_value < 0.05 # 95% 置信度
六、总结
- ML 测试比传统软件测试更复杂,需要覆盖数据、模型、基础设施
- 数据验证包括 Schema 检查、一致性验证、分布检查
- 模型评估包括单元测试、阈值测试、鲁棒性测试
- CI/CD 流水线自动执行测试并把关模型质量
- 假设检验确保模型改进是统计显著的
七、持续测试与监控
7.1 生产环境的持续验证
模型部署到生产环境后,测试并没有结束。持续验证包括:
Shadow 模式(影子模式):
- 新模型与生产模型并行运行
- 新模型的预测结果不被使用,只用于评估
- 零风险地收集新模型在生产数据上的表现
A/B 测试:
- 将用户流量随机分配到 A 组和 B 组
- 对比两组关键业务指标
- 用假设检验判断差异是否统计显著
Interleaved 实验:
- 在推荐系统中,将两个推荐模型的结果交错展示给同一用户
- 更高效地比较两个模型的效果
7.2 回归测试
ML 系统的回归测试确保代码和数据的变更不会降低模型性能:
数据集回归测试:
- 使用固定数据集定期评估模型
- 检测模型性能是否出现下降
- 记录每次评估的结果供对比
管道回归测试:
- 使用已知测试数据运行完整管道
- 验证管道输出是否与预期一致
- 检测 ETL 或特征计算逻辑的变更
7.3 模型可重复性
可重复性(Reproducibility)是 ML 测试的基础。实验应该可以被精确复现:
- 固定随机种子
- 记录所有依赖版本
- 版本化训练数据
- 记录超参数和配置
- 保存模型权重和中间结果
7.4 测试自动化成熟度模型
Google 提出了 ML 测试的成熟度模型:
Level 0:没有测试,依赖手动验证
Level 1:有基本的模型评估和单元测试
Level 2:有自动化的 CI/CD 流水线和数据验证
Level 3:有生产环境的持续监控和自动回滚
Level 4:有自动化根因分析和自愈能力
大多数团队处于 Level 1-2 之间。Level 3-4 需要较多基础设施投入,但能显著提升系统的可靠性。
模型验证的统计学框架
在模型上线前,严格的统计验证是必不可少的。模型验证的核心问题有两个:第一,模型是否显著优于基线?第二,模型在不同数据子集上的表现是否一致?对于第一个问题,可以使用配对统计检验(如 McNemar 检验或配对 t 检验)来比较两个模型在同一个测试集上的表现差异是否统计显著。
对于第二个问题,需要进行公平性审计——检查模型在不同人口统计群体(如性别、年龄、地域)上的表现差异。差异的衡量指标包括:人口均等(Demographic Parity)——预测正类的比例在各群体中应接近相等;均等机会(Equal Opportunity)——真正率在各群体中应接近相等;预测均等(Predictive Parity)——精确率在各群体中应接近相等。在实际项目中,这些公平性指标通常需要权衡,无法同时满足。
持续测试的策略
ML 系统的测试不能停留在模型部署前,还需要建立持续测试(Continuous Testing)机制。持续测试包括:定期在保留数据集上重新评估模型性能、监控训练-测试性能差距的变化趋势、以及定期进行数据质量扫描。当检测到模型性能下降或数据质量问题时,系统应自动触发告警或回滚到上一版本的模型。
延伸阅读
- 📺 B 站播放列表:MLOps Production — 机器学习工程生产实践
- 📚 更多学习资源,请访问 deeplearning.ai 官网