1. 为什么 Image Caption 评测又成了多模态圈的热点
如果你最近在跑 VLM 相关的实验,可能会发现一个尴尬的现象:模型生成的图像描述越来越长、越来越细,但手头的评测脚本还停留在 MSCOCO 时代那套 BLEU、CIDEr 上。短描述时代这些指标还能凑合看,一旦描述长度从 10 个词涨到 200 个词,n-gram 匹配基本就失效了——模型换一种说法描述同一张图,分数可能直接掉一半,但人看下来两段描述其实都对。
CapArena 这篇工作(南大、港大、上海 AI Lab,ACL 2025 Findings)正是冲着这个痛点来的。它做了三件事:一是搭了一个大规模人工对战评测平台,收集了 6000 多条标注,覆盖 14 个 VLM 和人类专家的对比;二是系统分析了传统指标、专用指标和 VLM-as-a-Judge 三类方法跟人类偏好的一致性;三是发布了 CapArena-Auto,用 600 张图加三基线模型做 pair-wise 对战,单次评测成本约 4 美元,跟人工排名的相关性达到 94.3%。
对做多模态应用的开发者来说,这套体系的价值在于:你不需要自己组织人工标注,也能拿到一个跟人类判断高度一致的详细描述打分流程。本文就带你从零把这条评测链路在本地跑通,包括配置文件怎么写、Key 怎么统一管理、脚本怎么验证。
2. 先把评测链路拆开:CapArena-Auto 到底怎么打分
在动手配环境之前,得先搞清楚 CapArena-Auto 的评测逻辑,不然配置项填错了都不知道错在哪。
它的核心是 pair-wise 对战。具体来说,测试模型生成的描述会分别跟三个基线模型的描述做对比:GPT-4o、CogVLM-19B、MiniCPM-8B。每次对比由 GPT-4o 作为 Judge 来判定胜负,判定时会同时提供人类参考描述作为辅助。胜 +1、负 -1、平 0,600 个样本累加就是最终得分。
这里有几个关键设计值得注意。第一,基线模型覆盖了高、中、低三个性能档位,这样测试模型在不同难度对手面前的表现都能被捕捉到。第二,Judge 用的是 GPT-4o 而不是规则指标,因为论文里的分析显示 GPT-4o-as-a-Judge 的平均偏差只有 4.4%,而 METEOR 是 8.2%,前者的不一致更接近人类标注者的随机波动,而不是对特定模型的系统性偏好。第三,引入参考描述能进一步提升 Judge 的判断准确性,这一点在配置里会体现为 reference-enhanced 选项。
图像来源方面,CapArena-Auto 从 DOCCI 数据集的 149 个聚类中均匀采样 600 张,再用 CLIP 特征过滤掉过于相似的样本,保证测试集的多样性。你本地跑的时候如果不想下载完整数据集,也可以先用官方提供的样本子集验证流程。
理解了这套逻辑,接下来配置文件的每个字段你都能对上号。
3. 用 TaoToken 统一 Key 和 API 通道
跑评测脚本绕不开调模型 API。测试模型要生成描述、Judge 要打分、基线模型要出结果,如果每个模型都去单独申请 Key、单独配 base_url,光是环境变量就能把你搞晕。我试过用 TaoToken 把这条链路统一到一个 Key 上,配置量能少一大半。
TaoToken 的定位是统一的模型 API 接入通道,你可以在一个控制台里管理多个模型的调用凭证,base_url 统一指向https://taotoken.net/api。对于 CapArena-Auto 这种需要同时调多个模型的场景,好处很直接:settings.json 里不用为每个模型写一套鉴权逻辑,换模型只改 model 字段就行。
具体操作上,先去控制台创建一个 API Key:
控制台入口:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
创建完 Key 之后,在 API Keys 页面可以查看和管理已有的 Key:
Key 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite
如果你对某个模型的对话效果没把握,想先手动试几条描述生成的质量,可以用模型对话页面快速验证:
模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite
接入文档里有完整的参数说明和调用示例,配 config.toml 的时候对着看就行:
接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
Key 拿到后,建议用环境变量注入而不是硬编码在配置文件里。下面两节给出完整的配置骨架。
4. 可复制的评测配置骨架
4.1 settings.json:评测任务与模型声明
这个文件定义评测跑哪些模型、用哪个 Judge、样本路径在哪。字段名我按 CapArena-Auto 的逻辑做了对齐,你可以直接改成自己的路径。
{ "eval_name": "caparena_auto_local", "image_dir": "./data/caparena_auto/images", "reference_file": "./data/caparena_auto/references.jsonl", "output_dir": "./results", "num_samples": 600, "judge": { "model": "gpt-4o", "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "temperature": 0.0, "use_reference": true }, "baselines": [ { "name": "gpt-4o", "model": "gpt-4o", "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY" }, { "name": "cogvlm-19b", "model": "cogvlm-19b", "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY" }, { "name": "minicpm-8b", "model": "minicpm-8b", "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY" } ], "candidates": [ { "name": "my-vlm-v1", "model": "your-model-name", "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY" } ], "scoring": { "win": 1, "loss": -1, "tie": 0 } }几个字段说明一下。use_reference设为 true 时会启用 reference-enhanced 的 Judge 模式,论文里这个变体的一致性更高。temperature设 0 是为了让 Judge 的判断可复现,不然同一对描述跑两次可能出不同结果。api_key_env统一指向TAOTOKEN_API_KEY,这样所有模型共用一个环境变量,不用为每个模型单独配。
4.2 config.toml:运行时与并发控制
settings.json 管的是"评什么",config.toml 管的是"怎么跑"。并发数、重试策略、超时这些都在这里。
[run] max_workers = 8 timeout_seconds = 120 max_retries = 3 retry_backoff = 2.0 save_intermediate = true intermediate_every = 50 [api] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" default_headers = { "Content-Type" = "application/json" } [logging] level = "INFO" log_file = "./logs/caparena_eval.log" log_judge_decisions = true [metrics] compute_spearman = true compute_kendall = true save_per_sample = truemax_workers别设太大,Judge 调用本身有速率限制,8 到 16 之间比较稳。save_intermediate建议开着,600 个样本跑一半崩了不至于从头来。log_judge_decisions会把每次判定的输入和输出都记下来,后面排查 Judge 判断异常时很有用。
环境变量这样设:
export TAOTOKEN_API_KEY="你的Key"Windows 下用set TAOTOKEN_API_KEY=你的Key或者直接在 PowerShell 里$env:TAOTOKEN_API_KEY="你的Key"。
5. 跑通验证:从单样本到完整评测
配置写好了别急着跑 600 个样本,先用单样本验证链路通不通。
5.1 单样本冒烟测试
写一个最小脚本,只跑一对比较,确认 API 能调通、Judge 能返回结果。
import json import os import requests API_BASE = "https://taotoken.net/api" API_KEY = os.environ["TAOTOKEN_API_KEY"] def call_model(model, image_path, prompt): with open(image_path, "rb") as f: image_data = f.read() # 实际调用时按接入文档的格式传图 payload = { "model": model, "messages": [ {"role": "user", "content": prompt} ], "temperature": 0.0 } headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } resp = requests.post( f"{API_BASE}/v1/chat/completions", headers=headers, json=payload, timeout=120 ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] if __name__ == "__main__": result = call_model( "gpt-4o", "./data/caparena_auto/images/sample_001.jpg", "请详细描述这张图片的内容。" ) print(result[:200])跑通这一步说明 Key 和 base_url 没问题。如果报 401,检查环境变量有没有生效;如果报 404,检查 base_url 后面有没有多写或少写路径。
5.2 完整评测脚本骨架
单样本通了之后,把对战逻辑补上。核心流程是:候选模型生成描述 → 跟每个基线模型的描述配对 → Judge 判定 → 累加得分。
import json from concurrent.futures import ThreadPoolExecutor, as_completed def load_samples(settings): samples = [] with open(settings["reference_file"], "r", encoding="utf-8") as f: for line in f: samples.append(json.loads(line)) return samples[:settings["num_samples"]] def judge_pair(judge_cfg, image_path, desc_a, desc_b, reference=None): prompt = build_judge_prompt(desc_a, desc_b, reference) verdict = call_model(judge_cfg["model"], image_path, prompt) return parse_verdict(verdict) def evaluate(settings, config): samples = load_samples(settings) scores = {c["name"]: 0 for c in settings["candidates"]} results = [] with ThreadPoolExecutor(max_workers=config["run"]["max_workers"]) as pool: futures = [] for sample in samples: for candidate in settings["candidates"]: for baseline in settings["baselines"]: futures.append(pool.submit( run_single_battle, sample, candidate, baseline, settings )) for fut in as_completed(futures): r = fut.result() scores[r["candidate"]] += r["score"] results.append(r) return scores, resultsbuild_judge_prompt里要把参考描述拼进去,格式大致是"以下是人类参考描述:... 请判断 A 和 B 哪个更好"。parse_verdict负责从 Judge 返回的文本里提取 A/B/Tie 的判定结果,建议用结构化输出或者固定格式约束,不然解析容易出错。
5.3 成功结果长什么样
跑完之后你会拿到每个候选模型的累加得分,以及跟基线的对比明细。正常情况下,如果候选模型跟 GPT-4o 打平的多、赢 CogVLM 和 MiniCPM 的多,得分应该是正的。如果出现候选模型全面输给 MiniCPM-8B 的情况,要么是模型确实弱,要么是描述生成环节的 prompt 有问题,先检查后者。
论文里报告的相关性指标是 Spearman 和 Kendall τ,你的脚本里如果开了compute_spearman,跑完会直接输出这两个值。跟人工排名对比的话,94.3% 是论文的参考值,你本地跑出来的数字受样本子集和 Judge 版本影响,有波动是正常的。
6. 本篇常见错排查
6.1 401 Unauthorized
最常见的原因是环境变量没生效。echo $TAOTOKEN_API_KEY确认一下有没有值。另一个可能是 Key 复制时带了空格,重新从控制台复制一次。如果用的是 settings.json 里的api_key_env字段,确认脚本读取环境变量的逻辑没问题。
6.2 Judge 返回格式解析失败
GPT-4o 有时候会在判定结果前后加解释性文字,导致正则匹配不到。解决办法是在 prompt 里明确要求"只输出 A、B 或 Tie 三个词之一",或者在解析时做容错,先找关键词再兜底。log_judge_decisions开着的话,去日志里看原始返回长什么样,对着调解析逻辑。
6.3 并发跑满导致超时
max_workers设太大时,API 端可能触发速率限制,表现为大量请求超时或 429。把并发降到 4 到 8 再试,同时把max_retries和retry_backoff配上,让失败的请求自动重试而不是直接丢样本。
6.4 基线模型描述生成失败
CogVLM-19B 和 MiniCPM-8B 如果在你用的通道上模型名对不上,会返回 model not found。去接入文档里确认一下模型标识符的准确写法,不同通道的命名可能有差异。实在跑不通的话,可以先用 GPT-4o 和另外两个能调通的模型临时替代,验证流程本身没问题再换回来。
6.5 得分跟预期差距大
先检查参考描述有没有正确加载。use_reference为 true 但 reference 字段是空的话,Judge 的判断会退化。另外确认图像路径有没有对,图片加载失败时模型可能返回"无法看到图片"之类的描述,这种描述拿去对战基本必输,会拉低得分。
如果你在配 Coding Plan 或者想把这条评测链路接到长期的 Agent 工作流里,可以看下 Coding Plan 的说明:
Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite
Claude Code 相关的接入配置也有单独的文档:
Claude Code 接入:https://taotoken.net/doc/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode-anthropic&utm_campaign=rewrite
整套流程跑下来,最花时间的其实是环境配置和 Judge 解析的调试,真正跑 600 个样本反而很快。建议先用 50 个样本的子集把链路跑顺,确认得分趋势合理之后再上全量。