AB
AiBoss站
教程

C-Eval

教程

用 C-Eval 评测脚本给中文 LLaMA / Alpaca 模型打分

C-Eval 用 12.3K 道选择题、52 个学科衡量中文大模型的学科知识水平。本文讲清如何准备数据集、配置 eval.py 的各项参数、理解 constrained_decoding 与 few-shot 的取舍,以及如何读懂 submission.json 与 summary.json 里的分数,并说明测试集为何需要另行提交。

C-Eval 是一套面向中文大模型的多学科选择题评测集,测试集包含 12.3K 道选择题,覆盖 52 个学科。它要回答的问题很直接:一个中文模型在计算机、数学、物理、法律、医学、马克思主义理论等学科的基础知识上,究竟能答对多少。对于自己训练或微调过中文 LLaMA / Alpaca 系列模型的开发者来说,C-Eval 提供了一条可复现的打分路径——把模型指向评测脚本,脚本会逐题生成答案、抽取选项、统计准确率,最后产出一份符合官方提交规范的答案文件。

本文围绕这套评测脚本展开,讲的是从下载数据集到读懂结果文件的完整流程。如果你只是想先了解这类基准测试的定位与常见用法,可以先看 C-Eval 的工具页;如果你手上已经有一个合并好 LoRA 的 Hugging Face 格式模型,想拿到它在 valid 集上的准确率,或者想生成 submission.json 去官方榜单上查测试集成绩,那么下面的步骤可以直接照做。

需要先说明一点:C-Eval 的评测脚本本身不训练模型、不修改权重,它只做推理与统计。因此评测效果的上限由模型决定,脚本负责的是把「模型答对了多少题」这件事算准、算得可复现。参数配置上的每一个选择——是否用 few-shot、是否用思维链、是否启用约束解码——都会改变最终分数,所以理解每个参数的含义比记住命令更重要。

准备工作

模型与环境

评测脚本要求待评测模型已经是以 Hugging Face 格式存放的本地目录,并且是合并 LoRA 之后的完整模型。也就是说,如果你手上只有基座模型加一个 LoRA 适配器,需要先完成合并,得到可以直接加载推理的权重目录,再把这个目录路径传给脚本。脚本不会替你合并权重。

运行环境方面,脚本以 Python 编写,依赖 PyTorch 与 Transformers 生态来加载模型并做前向推理,因此需要一块能装下目标模型的 GPU。模型规模越大,显存需求越高;33B 级别的模型与 7B 级别的模型对硬件的要求差别很大,具体需要多少显存取决于模型精度与推理框架,请以实际加载情况为准。

获取评测数据集

评测数据需要从 C-Eval 官方指定的路径下载。下载完成后解压,得到一个 data 文件夹,里面按学科组织好了题目。解压命令形如:

wget <C-Eval 官方数据集地址>
unzip ceval-exam.zip -d data

解压出来的 data 文件夹必须放到项目的 scripts/ceval 目录下。这一步是硬性要求:脚本按相对路径读取数据,放错位置会直接导致找不到题目文件。放置完成后的结构大致是 scripts/ceval/data/ 下包含各学科的数据文件。

确认脚本位置

评测入口是 scripts/ceval/eval.py。所有命令都在 scripts/ceval 目录下执行,因为脚本内部对数据路径、输出路径的解析都基于这个工作目录。如果你在仓库根目录直接运行,很可能因为路径不对而报错。

操作步骤

第一步:进入脚本目录并设定路径变量

先把两个路径变量定义好,后续命令直接引用,避免每次手写长路径出错:

model_path=path/to/chinese_llama_or_alpaca
output_path=path/to/your_output_dir
cd scripts/ceval

model_path 指向合并后的 HF 格式模型目录,output_path 指向你希望存放评测结果的目录。输出目录不需要提前创建,脚本会自行生成子目录。

第二步:运行预测脚本

一条完整的调用命令如下,这也是最常用的默认配置组合:

python eval.py \
    --model_path ${model_path} \
    --cot False \
    --few_shot False \
    --with_prompt True \
    --constrained_decoding True \
    --temperature 0.2 \
    --n_times 1 \
    --ntrain 5 \
    --do_save_csv False \
    --do_test False \
    --output_dir ${output_path} \

这条命令的含义是:在 valid 集上做 zero-shot 评测,输入套用 Alpaca 的指令模板,用约束解码从 A/B/C/D 四个选项中挑概率最高的一个,解码温度 0.2,只跑一遍,不额外保存 CSV。下面逐个拆解参数,因为改任何一个都会影响分数。

第三步:按需调整参数

model_path:待评测模型所在目录,必须是合并 LoRA 后的 Hugging Face 格式模型。这是唯一必填且没有默认值可依赖的参数。

cot:是否使用思维链(chain-of-thought)。开启后模型会先输出推理过程再给答案,通常用于观察模型的推理能力,但会显著增加生成长度与耗时。

few_shot:是否使用 few-shot 示例。设为 True 时,脚本会在每道题前拼接若干示例;设为 False 时是 zero-shot 评测。

ntrain:仅在 few_shot=True 时生效,指定 few-shot 示例的数量。做 5-shot 评测就设 ntrain=5。当 few_shot=False 时,这个参数不起任何作用,写多少都不影响结果。

with_prompt:模型输入是否包含针对 Alpaca 模型的指令模板。对指令微调过的 Alpaca 系列模型,通常保持 True,让输入格式与训练时一致;对纯基座模型,是否套模板需要根据模型本身的训练方式判断。

constrained_decoding:这是 C-Eval 评测里最关键的参数之一。C-Eval 的标准答案格式是单个选项字母 'A' / 'B' / 'C' / 'D',而模型自由生成的内容未必是干净的单个字母,因此脚本提供两种抽取答案的方案:

  • 当 constrained_decoding=True 时,脚本计算模型生成的第一个 token 分别为 'A'、'B'、'C'、'D' 的概率,取概率最大的那个作为答案。这种方式不依赖模型输出格式,稳定性最好。
  • 当 constrained_decoding=False 时,脚本改用正则表达式从模型生成内容中提取答案。这种方式更接近模型自由作答的真实表现,但如果模型输出格式不规范,可能提取失败。

temperature:解码温度。温度越高,采样越随机,同一道题多次运行的结果可能不同;温度越低越接近确定性输出。评测追求可复现时通常用较低温度。

n_times:评测的重复次数。脚本会在输出目录下生成对应次数的结果文件夹,用于观察多次采样下的分数波动。

do_save_csv:是否把模型生成结果、抽取出的答案等内容保存成 CSV。设为 True 时,会额外产出 52 个 CSV 文件,每个学科一份。想逐题排查模型答错原因时,这个开关很有用。

do_test:决定在哪个集合上评测。do_test=False 时在 valid 集上测试;do_test=True 时在 test 集上测试。两者的区别见下文注意事项。

output_dir:评测结果的输出路径。

第四步:读取输出目录

预测完成后,输出目录下会生成 outputs/take* 形式的文件夹,其中 * 是数字,范围从 0 到 n_times-1,分别对应每一次解码的结果。也就是说,n_times=1 时只有一个 take0,n_times=3 时会有 take0、take1、take2。

每个 outputs/take* 目录下包含两个 JSON 文件:submission.json 与 summary.json。如果开启了 do_save_csv=True,还会多出 52 个 CSV 文件,分别保存各学科的模型生成结果与抽取出的答案。

一个完整示例

假设你刚把一个 13B 的 Chinese-Alpaca-Plus 模型合并完成,权重放在 /models/chinese-alpaca-plus-13b-hf,想在 valid 集上做一次 5-shot 评测,并把逐题结果保存下来方便排查。完整流程如下。

先准备数据:

wget <C-Eval 官方数据集地址>
unzip ceval-exam.zip -d data
# 将 data 文件夹移动到 scripts/ceval 目录下

再进入脚本目录并定义路径:

model_path=/models/chinese-alpaca-plus-13b-hf
output_path=/results/ceval-13b-5shot
cd scripts/ceval

然后运行 5-shot 评测,开启 CSV 保存:

python eval.py \
    --model_path ${model_path} \
    --cot False \
    --few_shot True \
    --with_prompt True \
    --constrained_decoding True \
    --temperature 0.2 \
    --n_times 1 \
    --ntrain 5 \
    --do_save_csv True \
    --do_test False \
    --output_dir ${output_path} \

注意这里与默认命令的差别只有两处:few_shot 改为 True,do_save_csv 改为 True。因为开启了 few-shot,ntrain=5 才真正生效,构成 5-shot 设置。

运行结束后,进入 /results/ceval-13b-5shot/outputs/take0/,会看到 submission.json、summary.json 以及 52 个 CSV 文件。

submission.json 按官方提交规范组织,结构是学科名映射到题号到答案字母,形如:

{
  "computer_network": {
    "0": "A",
    "1": "B",
    ...
  },
  "marxism": {
    "0": "B",
    "1": "A",
    ...
  },
  ...
}

summary.json 则给出模型在 52 个学科下、4 个大类下以及总体平均的评测结果。文件末尾的 All 字段就是总体平均效果,形如:

"All": {
  "score": 0.36701337295690933,
  "num": 1346,
  "correct": 494.0
}

三个字段的含义分别是:score 为准确率,num 为测试的总样本条数,correct 为答对的题数。上例表示在 1346 道题中答对 494 道,准确率约 36.7%。

如果想看某个具体学科的表现,在 summary.json 里找到对应学科名即可,结构与 All 一致。想定位具体哪道题答错,就打开对应学科的 CSV,对照模型生成内容与抽取出的答案。

注意事项

测试集上没有标签,分数为 0 属正常

这是最容易让人误判的一点。当 do_test=True 在测试集上预测时,因为测试集不提供标签,summary.json 里的 score 和 correct 都会是 0。这不是脚本出错,也不是模型全错,而是设计如此。测试集的真实成绩必须把 submission.json 提交到 C-Eval 官方,按官方提交流程获取。因此本地能自测的只有 valid 集,test 集只能生成答案文件后送交官方。

few_shot 与 ntrain 的联动

ntrain 只在 few_shot=True 时起作用。如果设了 few_shot=False 却把 ntrain 写成 5,评测仍然是 zero-shot,不会因为参数里写了 5 就变成 5-shot。反过来,设了 few_shot=True 但忘记调 ntrain,则会按默认值走,得到的 shot 数与预期不符。

zero-shot 与 few-shot 的分数不一定单调

从公开的部分模型评测结果可以看到,few-shot 并不总是比 zero-shot 分数高。例如 Chinese-Alpaca-33B 在 valid 集上 zero-shot 为 43.3、5-shot 为 42.6,test 集上 zero-shot 为 41.6、5-shot 为 40.4;Chinese-Alpaca-Plus-13B 在 valid 集上 zero-shot 为 43.3、5-shot 为 42.4。而基座模型 Chinese-LLaMA-33B 则相反,valid 集 zero-shot 34.9、5-shot 38.4,test 集 zero-shot 34.6、5-shot 39.5。这说明 shot 设置对指令微调模型与基座模型的影响方向可能不同,报告成绩时必须写清用的是哪种设置,不能混为一谈。

constrained_decoding 会改变评测口径

开启约束解码时,脚本只看第一个 token 在 A/B/C/D 上的概率分布,相当于把模型当作一个四选一的分类器;关闭时则依赖正则从自由生成文本中抽取答案。两种口径得到的分数不可直接比较。做横向对比时,所有模型应使用同一套参数。

temperature 与 n_times 影响可复现性

温度大于 0 时解码带随机性,同一模型同一数据跑两次可能得到不同分数。需要稳定复现时,降低温度;需要观察波动范围时,把 n_times 调大,然后比较各个 take* 目录下的 summary.json。注意每个 take 目录是独立的一次完整评测,不要把它们混在一起统计。

数据目录位置不能错

data 文件夹必须位于 scripts/ceval 之下,且命令必须在 scripts/ceval 目录内执行。这两条同时满足,脚本才能正确找到 52 个学科的数据文件。

模型必须是合并后的完整权重

脚本加载的是标准 Hugging Face 格式模型目录。只提供 LoRA 适配器目录、或提供未合并的基座加适配器组合,都无法直接评测。合并步骤需要在评测之前单独完成。

易变信息以官方为准

数据集下载地址、官方提交流程、榜单口径以及脚本参数都可能随版本更新而变化,本文涉及的路径与参数请以 C-Eval 官方当前发布的信息为准。评测分数本身也依赖于具体的模型版本与推理环境,不同环境下复现出的数值可能存在差异。