等价类划分法:减少测试用例数量

做参数调试,最怕什么?最怕用例太多,跑一次要等半天。我见过不少团队,参数组合一多就直接上全量遍历,结果CI流水线从20分钟变成2小时。其实没必要。你想想看,很多参数值其实是“等价”的——它们的表现模式一样,测一个就能代表一类。

这就是等价类划分法的核心思想。说白了,就是把无穷无尽的输入域,压缩成几个有代表性的类。每个类里挑一个值,就够了。

核心思想:用最少用例覆盖最多场景

等价类划分法不是什么新鲜概念,但在exhaustiveness参数调试中特别好用。我个人的习惯是,拿到一个参数后,先问自己三个问题:

  • 这个参数有哪些合法的输入值?
  • 哪些值的行为是“相同”的?
  • 边界上的值有没有特殊表现?

回答了这三个问题,等价类基本就划分出来了。举个例子,假设你的模型有个参数叫temperature,取值范围是0.0到2.0。你不需要测0.01、0.02、0.03……所有值。它们属于同一个等价类——低温确定性区。取一个0.1就够了。

关键原则:同一个等价类的值,系统应该表现出相同的行为。如果发现某个值的行为和其他同类不一致,那就说明你划分错了,或者这个参数有隐藏的非线性特性。

一个小技巧:我通常先用--help或文档确认参数的合法范围,然后手动枚举出"明显不一样"的几类。而不是一上来就写脚本去穷举。

一个真实案例:AI对话系统的temperature参数

之前在调一个对话模型时,遇到了temperature参数。官方文档说取值范围是[0.0, 2.0],但实际测试发现,0.0到0.3之间的回答几乎一模一样,0.7到1.5之间开始出现多样性,1.5以上就完全放飞了。你猜怎么着?其实只需要三个等价类:

等价类 取值范围 代表值 预期行为
低温确定区 0.0 ~ 0.3 0.1 输出稳定,几乎每次都一样
中温平衡区 0.4 ~ 1.2 0.8 有一定随机性,但内容可控
高温发散区 1.3 ~ 2.0 1.8 输出变化大,可能偏离主题

你看,三个用例就覆盖了整个温度范围。如果按穷举法,哪怕步长0.1,也得跑21次。代价差了一个数量级。

不过要注意一点:等价类划分不是一刀切的。我曾经犯过一个错误——把0.4和0.9都划到“中温区”,结果0.4附近出现了随机性断层。后来才发现,模型在这个参数空间里有一个隐藏的模式切换点。所以,划分等价类后,建议在边界附近补一两个用例。

再举一个例子:top_k 和 top_p 的组合

很多采样参数是配合使用的。比如top_k(整数范围1~100)和top_p(浮点范围0.0~1.0)。这时候等价类划分法依然管用,但要考虑“组合等价类”。

我的做法是:先把每个参数单独划分,然后组合时只取“有意义的搭配”。比如:

  • top_k 的等价类:极小值(1)、中等值(50)、极大值(100)
  • top_p 的等价类:极小值(0.1)、中等值(0.5)、极大值(0.9)

组合起来有3×3=9种。但实际上,当top_k=1时,top_p的值基本不影响结果。所以可以砍掉(1, 0.5)和(1, 0.9)这两个组合。最终剩下7个。少了2个,但覆盖度没降。

注意陷阱:等价类划分法的最大风险是“类内不一致”。两个看起来差不多的值,内部实现可能走了不同的分支。所以,初次划分后一定要用一小批随机值做验证。如果发现异常,就说明等价类需要拆得更细。

等价类划分法的通用步骤

我梳理了一下,在实际项目中可以这样操作:

  1. 明确输入域:从文档、API定义或源码里确认参数的合法范围。
  2. 识别关键特征:比如整数/浮点、正/负、边界、空值等。
  3. 初步划分:按直觉划分出3~5个等价类。
  4. 边界值验证:每个类的边界附近取2~3个值,验证类内一致性。
  5. 优化合并:如果相邻两个类的行为完全一致,就合并它们。
  6. 生成测试用例:每个类选一个代表值,边界也加上。

这套流程我用了好几年,基本没出过大问题。关键就是第4步——验证类内一致性。很多人跳过这步,结果漏掉了隐藏的模式切换点。

SVG:等价类划分流程

等价类划分法核心流程 1. 识别输入域 2. 分析关键特征 3. 初步划分等价类 4. 验证类内一致性 5. 优化与合并 6. 生成测试用例 不一致则拆分

进阶用法:多参数联合等价类

刚才是单参数,现实场景中往往是多个参数联动的。比如frequency_penalty和presence_penalty,它们都是[0.0, 2.0]的浮点数,但作用机制不同。我遇到过一种情况:两个参数都取大值时,模型输出会出现重复抑制导致的短回复。这个现象在单个参数测试时完全没暴露出来。

处理多参数等价类,我建议用“正交等价类矩阵”。先把每个参数划分出3~5个等价类,然后只取正交中心点。举个例子:

参数A等价类: [0.1, 1.0, 1.9]
参数B等价类: [0.2, 0.8, 1.5]

正交组合:
(0.1, 0.2)  -- 双低
(0.1, 1.5)  -- A低B高
(1.0, 0.8)  -- 双中
(1.9, 0.2)  -- A高B低
(1.9, 1.5)  -- 双高

一共5组,就能覆盖大多数交互模式。比全量9组少了近一半。

实战建议:对每个正交组合,建议再加上“边界扰动”——比如A取边界值0.0和2.0,看是否出现异常。我在做LLM采样参数调试时,就靠这招抓到了两个参数在边界附近的负交互bug。

避坑指南:我踩过的三个坑

  • 坑一:以为所有参数都适合划分等价类。有些参数的行为不是单调的,而是有多个模式切换点。比如top_k从1到100,你在每个数量级的行为都不同。这时候不能只划3个类,得按对数尺度划。
  • 坑二:忽视无效等价类。参数范围外的值(比如temperature=-1或top_p=1.5)也需要测。我曾经漏掉负值,结果API返回的是模糊的错误信息,导致线上误报。
  • 坑三:过早优化。刚开始调一个新模型时,先用全量小规模采样跑一遍,发现模式后再做等价类划分。直接跳过探索阶段容易错过重要的非线性特征。

提醒一句:等价类划分法不是为了省事而省事。它的本质是“用对测试设计的理解,换取测试效率的提升”。如果对参数行为不够了解,就不要强行压缩用例。先跑一轮探索性测试,再回头做划分,会安全得多。

写在最后

等价类划分法是我做参数调试时最常用的武器之一。它不复杂,但非常实用。关键是培养一种直觉——看到参数范围,就能立刻判断哪些值是“同一类”。这种直觉,靠的是大量实践和复盘。多踩几次坑,自然就懂了。