模型测试方法论

专栏:人工智能训练师(三级)备考全攻略
模块:卷三·知识体系 — 第四部分·模型测试与评估
难度:⭐⭐⭐☆☆
考试权重:中高频(选择+简答)


一、模型测试 vs 传统软件测试

传统软件测试              模型测试
┌──────────────────┐    ┌──────────────────────┐
│ 确定性输出         │    │ 概率性输出(不确定)    │
│ 输入→固定输出      │    │ 输入→概率分布→输出     │
│ 规则明确           │    │ 行为难以穷举           │
│ Bug=偏离规范       │    │ Bug=偏离预期分布       │
│ 回归测试可预期      │    │ 数据变化→性能漂移      │
└──────────────────┘    └──────────────────────┘

核心区别:模型测试关注的不是"对/错",
         而是"在多大概率下表现符合预期"。

1.1 对比表

维度 传统软件测试 模型测试
输出确定性 确定性(同一输入=同一输出) 概率性(同一输入可能不同输出)
正确性标准 与预期输出完全一致 在可接受范围内(准确率≥95%等)
测试重点 功能正确性、性能、安全 准确率、鲁棒性、公平性、安全性
回归触发 代码变更 数据变更、模型更新、环境变化
测试数据 手工设计用例 大量真实数据+边界样本

二、模型测试的五大维度

模型测试金字塔(从基础到高级)

           ┌─────────────┐
           │  安全性测试  │  ← 模型是否产生有害内容
           ├─────────────┤
           │  公平性测试  │  ← 模型是否对特定群体有偏见
           ├─────────────┤
           │  鲁棒性测试  │  ← 模型对异常输入的抵抗能力
           ├─────────────┤
           │  性能测试    │  ← 响应速度、吞吐量、资源消耗
           ├─────────────┤
           │  功能正确性  │  ← 模型输出是否准确(基础)
           └─────────────┘
测试维度 目标 常用方法
功能正确性 输出结果准确 单元测试、基准数据集对比
性能测试 满足延迟/吞吐要求 压力测试、负载测试
鲁棒性测试 异常输入不崩溃/不误判 对抗样本、噪声注入
公平性测试 无歧视偏见 子群体分析、公平指标
安全性测试 无有害输出 Prompt注入、越狱测试

三、测试分层策略

3.1 模型测试分层

┌─────────────────────────────────────┐
│        E2E 测试(端到端)            │  完整业务场景
│  用户输入 → 模型推理 → 后处理 → 输出  │
├─────────────────────────────────────┤
│        集成测试                      │  模型+前后处理Pipeline
│  预处理 → 模型 → 后处理 → 格式校验    │
├─────────────────────────────────────┤
│        模型单元测试                   │  模型本身的输入输出
│  单条样本 → 模型 → 结果校验          │
├─────────────────────────────────────┤
│        数据验证                      │  数据质量、格式、分布
│  数据schema校验、特征分布检查         │
└─────────────────────────────────────┘

3.2 各层测试详解

import numpy as np
import pytest

# =========================================
# 第1层:数据验证测试
# =========================================
def test_data_schema():
    """验证输入数据格式正确"""
    features, labels = load_test_data()
    assert features.shape[0] == labels.shape[0], "特征与标签数量不匹配"
    assert features.shape[1] == 768, f"特征维度应为768,实际{features.shape[1]}"
    assert not np.any(np.isnan(features)), "特征中存在NaN"
    assert not np.any(np.isinf(features)), "特征中存在Inf"

def test_data_distribution():
    """验证测试集分布与训练集一致"""
    train_data = load_train_data()
    test_data = load_test_data()
    
    # 类别分布差异不超过5%
    train_ratio = np.bincount(train_data.labels) / len(train_data.labels)
    test_ratio = np.bincount(test_data.labels) / len(test_data.labels)
    max_diff = np.max(np.abs(train_ratio - test_ratio))
    assert max_diff < 0.05, f"训练/测试集分布差异{max_diff:.3f}超过阈值"

# =========================================
# 第2层:模型单元测试
# =========================================
def test_model_output_shape():
    """验证模型输出维度正确"""
    model = load_model()
    dummy_input = np.random.randn(1, 768).astype(np.float32)
    output = model.predict(dummy_input)
    assert output.shape == (1, 10), f"输出维度应为(1,10),实际{output.shape}"

def test_model_output_range():
    """验证分类模型输出概率在[0,1]之间"""
    model = load_model()
    test_inputs = load_test_data().features[:100]
    outputs = model.predict(test_inputs)
    assert np.all(outputs >= 0), "输出中存在负值"
    assert np.all(outputs <= 1), "输出中存在大于1的值"
    # 概率之和=1
    row_sums = outputs.sum(axis=1)
    assert np.allclose(row_sums, 1.0, atol=1e-5), "输出概率之和不等于1"

def test_model_accuracy_on_golden_set():
    """在Golden Set(标注的标准测试集)上验证准确率"""
    model = load_model()
    golden_data = load_golden_set()
    
    predictions = model.predict(golden_data.features)
    accuracy = (predictions.argmax(axis=1) == golden_data.labels).mean()
    
    assert accuracy >= 0.92, f"Golden Set准确率{accuracy:.4f}低于阈值0.92"

# =========================================
# 第3层:集成测试(Pipeline测试)
# =========================================
def test_full_pipeline():
    """端到端Pipeline测试"""
    from pipeline import TextClassificationPipeline
    
    pipeline = TextClassificationPipeline()
    test_cases = [
        ("这个产品很好用", "正面"),
        ("质量太差了,退款", "负面"),
        ("今天天气不错", "中性"),
    ]
    
    for text, expected_label in test_cases:
        result = pipeline.predict(text)
        assert result['label'] == expected_label, \
            f"输入'{text}',期望'{expected_label}',得到'{result['label']}'"

# =========================================
# 第4层:鲁棒性测试
# =========================================
def test_typo_robustness():
    """验证模型对错别字的鲁棒性"""
    model = load_model()
    
    clean_text = "这部电影非常精彩"
    typo_texts = [
        "这部电影非常精采",   # 同音错字
        "这部电影非常精彩!",  # 多余标点
        "这 部 电 影 非 常 精 彩",  # 多余空格
        "这部电影非常精彩。。。。",  # 重复标点
    ]
    
    clean_result = model.predict(clean_text)
    for typo_text in typo_texts:
        typo_result = model.predict(typo_text)
        assert typo_result == clean_result, \
            f"错别字输入'{typo_text}'导致结果不一致"

四、测试用例设计方法

4.1 边界案例(Edge Case)设计

测试用例设计四象限:

              高频场景
              ┌──────────────────────┐
              │  ① 正常高频用例        │
              │  日常输入,覆盖主要分布  │
              │  例:"好评"、"退款申请"  │
    高影响    ├──────────┬───────────┤  低影响
              │  ② 关键边界用例        │
              │  影响业务的核心场景      │
              │  例:金融风控判"否"     │
              ├──────────┴───────────┤
              │  ③ 异常/对抗用例        │
              │  极端输入、攻击文本      │
              │  例:超长文本、空输入    │
              │  例:Prompt注入         │
              └──────────────────────┘
              低频场景

4.2 测试集构建原则

原则 说明 示例
代表性 覆盖真实数据的各种分布 训练集的主要类别+长尾类别
多样性 避免同质化样本 每类至少20个不同表述
难度分层 简单/中等/困难样本各占比例 简单60%、中等30%、困难10%
独立性 测试集与训练集无重叠 去重+相似度检查
可维护 标注清晰,版本管理 每条用例标注预期输出+难度

五、测试指标体系

5.1 模型测试KPI矩阵

┌──────────────────────────────────────────────────┐
│                  测试指标体系                      │
├──────────┬───────────────────────────────────────┤
│ 功能维度  │ 准确率 ≥ 95%                          │
│          │ 召回率 ≥ 90%                          │
│          │ F1 ≥ 0.92                            │
├──────────┼───────────────────────────────────────┤
│ 性能维度  │ 单次推理延迟 < 200ms                  │
│          │ QPS ≥ 100                            │
│          │ 显存占用 < 4GB                        │
├──────────┼───────────────────────────────────────┤
│ 稳定维度  │ 连续运行72h无崩溃                     │
│          │ 数据漂移检测精度下降 < 3%             │
│          │ 回归测试通过率 = 100%                  │
├──────────┼───────────────────────────────────────┤
│ 安全维度  │ Prompt注入拦截率 ≥ 99%               │
│          │ 有害内容过滤率 ≥ 99.5%                │
│          │ 敏感信息泄露 = 0                      │
└──────────┴───────────────────────────────────────┘

5.2 回归测试流程

回归测试触发条件
┌────────────────────────────────────────────────┐
│  ✓ 模型版本更新                                 │
│  ✓ 训练数据更新                                  │
│  ✓ 推理引擎升级(如PyTorch版本变更)              │
│  ✓ 部署环境变更(OS、GPU驱动)                    │
│  ✓ 上游服务接口变更                               │
└────────────────────────────────────────────────┘

回归测试流程:
  1. 运行全量测试套件
  2. 对比当前结果与基线(Baseline)
  3. 生成差异报告(精度下降/新增失败用例)
  4. 根据阈值决定是否通过
  5. 未通过 → 定位原因 → 修复 → 重新测试

六、考试重点总结

6.1 核心概念辨析

概念 关键点
模型测试 vs 软件测试 模型输出是概率性的,关注"分布正确"而非"完全一致"
Golden Set 经过人工验证的标准测试集,作为模型质量的"真理标准"
回归测试 每次模型/数据变更后重新运行,确保没有退化
Edge Case 边界案例,覆盖极端/异常/对抗性输入

6.2 高频选择题

Q: 模型测试与传统软件测试最大的区别是?
A: 模型输出具有概率性,同一输入可能产生不同输出 ✅

Q: 以下哪项不属于模型测试的五大维度?
A: 代码覆盖率(属于传统软件测试指标)✅

Q: Golden Set的作用是?
A: 作为标准测试集,验证模型输出质量的基准 ✅

Q: 模型回归测试应在什么时候触发?
A: 模型更新、数据更新、环境变更时都应触发 ✅

七、思维导图

模型测试方法论

测试vs传统软件

概率性输出

关注分布正确性

数据变化影响

五大测试维度

功能正确性

性能测试

鲁棒性测试

公平性测试

安全性测试

测试分层策略

数据验证

模型单元测试

集成Pipeline测试

端到端测试

测试用例设计

正常高频用例

关键边界用例

异常对抗用例

难度分层

指标体系

功能KPI

性能KPI

稳定性KPI

安全性KPI

回归测试

触发条件

基线对比

退化检测


📌 备考贴士:模型测试方法论的重点是理解"概率性输出"这一核心区别。记住五大测试维度(功能/性能/鲁棒/公平/安全)和四层测试分层(数据/单元/集成/E2E),选择题基本覆盖这些点。回归测试的触发条件也是常考点。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐