AI 学习手记一个普通学习者的 AI 知识笔记
首页/实践记录/自己攒一套模型评测:40 道题、两个脚本、一个 CSV

实践记录

自己攒一套模型评测:40 道题、两个脚本、一个 CSV

不抄别人的排行榜。用一晚上攒一套属于自己的评测,之后每次换模型都能省掉半天争论。

选模型这件事,我一开始完全靠"看别人评测 + 自己聊两句"。前者是别人的任务分布,后者是我的印象分,两个都不牢靠。后来一晚上攒了套最小评测,之后每次选型都靠它。

一、为什么公开的排行榜帮不上忙

三个原因,我每个都实际吃过亏:

  1. 题目泄漏。公开基准的题目很可能在训练数据里。分数高,可能只说明它见过。
  2. 任务分布不同。通用基准里数学和代码占了不少比重,而我实际 80% 的调用是从中文材料里抽取结构化字段。这两件事的能力相关性没我想象中高。
  3. 判分方式不同。同一份输出,按"包含关键词"判和按"结构合法"判,得分可以差出十几分。

结论不是"评测没用",而是你自己的 40 道题,比任何排行榜都更接近你的真实场景。

二、评测集怎么攒

我用了 40 道题,分四类,每类 10 道:

  • 抽取:给一段文本,输出规定的 JSON 字段。判分客观——结构合法、字段值逐条比对。
  • 归纳:给一段长材料,回答三个指定问题。判分要点清单:每个必答点是否出现。
  • 改写:给定风格约束重写一段文字。判分只能人工,但约束违反项可以自动查(比如"每句不超过 25 字")。
  • 拒答:给材料里没有答案的问题。判分客观——正确行为是回答"材料未提及",凡是给出了具体数值的都算错。

前三类大家都会做,第四类我强烈建议加上。它测的是编造倾向,而编造是我实际工作里代价最高的失败模式。

题目从我的真实历史任务里挑,不做修饰。每道题旁边写"标准答案 + 判定依据",全部存进一个 CSV,进 git。这一步是最有价值的资产——半年后我换模型,直接重跑同一份题。

三、脚本:两个文件,加起来不到 200 行

run.mjs 负责打题、落盘原始响应;score.mjs 负责判分。核心是"原始响应必须留档"——不然你事后发现判分逻辑有 bug,题就得重跑,钱和配额都白花。

// run.mjs —— 逐题请求,原始响应按题号落盘
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';

const rows = readFileSync('set.csv', 'utf8').trim().split('\n');
const head = rows.shift().split(',');
const items = rows.map((r) => Object.fromEntries(head.map((k, i) => [k, r[i]])));

mkdirSync('raw', { recursive: true });
for (const it of items) {
  const body = {
    model: 'qwen2.5-7b-instruct',
    messages: [{ role: 'user', content: it.prompt }],
    temperature: 0,
    max_tokens: 800,
    stream: false,
  };
  const t0 = Date.now();
  const res = await fetch('http://127.0.0.1:11434/v1/chat/completions', {
    method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
  });
  const j = await res.json();
  const ms = Date.now() - t0;
  writeFileSync(`raw/${it.id}.json`, JSON.stringify({
    ms, usage: j.usage, content: j.choices?.[0]?.message?.content ?? null,
  }, null, 2));
  console.log(it.id, ms + 'ms');
}

温度钉死为 0,是为了让失败可归因。温度不为 0 时同一道题时时对时错,判分就变成了测运气。想评估稳定性,就把同一道题跑 5 次看一致率,那是另一项指标,不要混在准确率里。

四、判分规则要能复述

score.mjs 里我坚持一条:任何一条打分规则,我都要能用一句话说清它为什么这样判。说不清的,规则本身就有问题。

抽取类的判分是:响应必须是合法 JSON、键集合与期望完全一致、每个值等于期望值或包含期望值(按题目标注的匹配模式)。这里有个坑值得单独说——"包含"这个宽松规则,会让"答案里同时出现了正确值和错误值"也算对。我最初的版本就是这么宽松,后来加了"不得含冲突值"这条,得分掉了 6 个点。掉分不是评测变差了,是它终于开始测真实行为了。

五、这套东西帮我拦下过什么

一次具体经历:某个 14B 模型在公开对话质量上口碑很好,我换上去之后,抽取类的"输出合法 JSON"这一项从 98% 掉到 71%,主要是多输出一段解释文字、以及把数字字段写成了带单位的字符串。这个差异,靠"我拿它聊两句"是完全感觉不到的——它的对话质量确实更好。

另一次:把提示词从"仅输出 JSON"改成"输出 JSON,不要任何解释",合法率反而降了两三个点。这类"改着改着变差了"的情况,只有在有回归测试时才会被发现。

所以这套评测真正的价值不在"选出最强模型",而在把变更变成可回滚的判断。

六、我知道它的局限

  • 40 道题的统计噪声不小。两个模型差三五个百分点,我不会下结论,会加测。
  • 人工判分的改写类,我的标准本身在漂移,隔几个月重判同一份输出,结论可能变。
  • 题库会泄漏。我自己的题不会进训练集,但方法传开了,别人用同类题就会有这个问题。
  • 只测了单轮。我的真实业务里多轮和长上下文才是难点,这部分我目前只有那篇长上下文的探针法,还没有系统化。

内容标识:本文正文、评测集与统计脚本均为本人自己编写,未使用 AI 生成;文中数据为我本机跑出的结果,只用于说明方法。

本文出处:http://honeysss.cn/post/model-eval-on-a-budget.html 转载请注明出处。