大模型系列:C-Eval中文大模型评测数据集介绍和实践

关键词:大模型测评C-Eval

前言

C-Eval是目前权威的中文AI大模型评测数据集之一,用于考察大模型的知识和推理能力,本篇对C-Eval数据集做简要介绍,并演示如何使用C-Eval对大模型进行评测。


内容摘要

  • C-Eval整体结构概述
  • C-Eval数据预览
  • C-Eval的Prompt范式
  • Python脚本实现C-Eval评估ChatGLM2-6B

C-Eval整体结构概述

在前文《大模型系列:LLM-Eval大模型评测理论简述》中介绍了大模型需要评测的内容,包括NLP任务知识和逻辑推理安全性对齐性等多个角度,C-Eval数据集主要用于评测大模型的知识和逻辑推理能力,即大模型是否能够认识和理解广泛的世界知识,并类似人类一样对事物进行推理规划。
C-Eval数据集由13948道多选题组成,涉及4个学科大类,52个学科小类,分别对应四个难度等级,如下所示。

C-EVAL的题目构成
  • STEM:科学、技术、工程和数学教育,包含计算机、电气工程、化学、数学、物理等多个学科
  • Social Science:社会科学,包含政治、地理、教育学、经济学、工商管理等多个学科
  • Humanity:人文科学,包含法律、艺术、逻辑学、语文、历史等多个学科
  • Other:其他,其他学科的汇总,包含环境、消防、税务、体育、医学等多个学科

共有四个难度等级,在图示中使用颜色标记区分,分别是初中(蓝色)、高中(绿色)、大学(黄色)和专业(红色),每个学科对应一个难度等级。


C-Eval数据预览

C-Eval的数据形式为4个选项的单选题,包含问题、选项值、答案、解释,形式预览如下。

C-Eval试题举例

C-Eval包含三份数据分别是dev,val和test,其中dev数据有答案并且带有答案解释,目的是用来构建CoT思维链的few-shot提示语,val数据集有答案,而test数据集没有答案,一般的,利用dev的few-shot在val数据做离线测试获得C-Eval评分,而在test数据集上提交答案给C-Eval官网获得最终得分。

数据集 问题+选项 答案 解释说明
dev
val ×
test × ×

具体的数据在HuggingFace官网中Datasets下搜索ceval-exam既可进行预览,选择Subset为college_programming,数据分割为dev,预览5条大学编程试题。

C-Eval的HuggingFace数据预览

C-Eval的Prompt范式

分别有两种Prompt提示语方式来引导模型给出答案,一种是answer-only,一种是chain-of-thought,answer-only指的是不是用思维链,直接输出答案,而chain-of-thought采用思维链的方式生成中间过程再输出答案。每一种又可以采用zero-shot和few-shot两种形式,对于Base模型,由于没有经过指令微调,因此需要结合few-shot给到范例进行提示,而chat模型采用zero-shot直接对话既可。

few-shot思维链的提示方式举例

Python脚本实现C-Eval评估ChatGLM2-6B

本节采用ChatGLM2-6B项目下的evaluate_ceval.py脚本进行演示,目标是评估ChatGLM2-6B在C-Eval的val数据集下,每个学科的答题正确率和总体平均正确率。
数据已经提前处理为JSON格式,数据预览如下

{
    "id":0,
    "inputs_pretokenized":"蓝印花布是一种传统的民间纺织印染工艺品。蓝印花布印制方法始于____。\nA. 汉代\nB. 魏晋时期\nC. 唐代\nD. 宋代",
    "choices_pretokenized":[
        " A",
        " B",
        " C",
        " D"
    ],
    "label":0,
    "targets_pretokenized":[
        "A"
    ]
}

evaluate_ceval.py对val文件夹下所有科目进行遍历,分别对每个科目进行回答,读取试题的代码如下

accuracy_dict, count_dict = {}, {}
with torch.no_grad():
    for entry in glob.glob("./CEval/CEval/val/**/*.jsonl", recursive=True):
        dataset = []
        with open(entry, encoding='utf-8') as file:
            for line in file:
                # {"id": 9, "inputs_pretokenized": "用户冲击负荷引起的系统频率变动一般不得超过____。\nA. ±0.5Hz\nB. ±0.4Hz\nC. ±0.3Hz\nD. ±0.2Hz", "choices_pretokenized": [" A", " B", " C", " D"], "label": 3, "targets_pretokenized": ["D"]}
                dataset.append(json.loads(line))
        correct = 0
        dataloader = torch.utils.data.DataLoader(dataset, batch_size=8)

每个科目下的试题以8个为一个batch进行推理,将试题文本改造为固定的chat模型的Prompt模板

# 模板
def build_prompt(text):
    return "[Round {}]\n\n问:{}\n\n答:".format(1, text)

然后进行分词编码,调用模型的generate方法进行推理,本次推理结果为回答的中间结果,并不直接映射到选项A、B、C、D。

        for batch in tqdm(dataloader):
            texts = batch["inputs_pretokenized"]
            queries = [build_prompt(query) for query in texts]
            inputs = tokenizer(queries, padding=True, return_tensors="pt", truncation=True, max_length=2048).to('cuda')
            # TODO transformers的generate 批量推理
            outputs = model.generate(**inputs, do_sample=False, max_new_tokens=512)
            intermediate_outputs = []
            for idx in range(len(outputs)):
                output = outputs.tolist()[idx][len(inputs["input_ids"][idx]):]
                response = tokenizer.decode(output)
                intermediate_outputs.append(response)

采用贪婪模式最大推理512个token作为中间结果存储在intermediate_outputs中,以一条数据为例,原始问题的Prompt和推理的中间结果如下

试题prompt
大模型输出的中间过程

输出的中间过程表明模型对于问题有思考过程,并且初步给到了答案选D。紧接着我们把中间过程拼接到原始试题后面,并且在末尾加入提示模板extraction_prompt,让大模型基于问题和中间过程,最终输出选项答案。本质上该脚本采用的Prompt方案是zero-shot的CoT思维链。

extraction_prompt = '综上所述,ABCD中正确的选项是:'

answer_texts = [text + intermediate + "\n" + extraction_prompt for text, intermediate in
                            zip(texts, intermediate_outputs)]
最终的Prompt格式

重新分词编码之后再次给大模型推理,本次推理只需要拿到下一个token即可,通过return_last_logit参数拿到每个token的在词表的得分分布,通过下标-1拿到最后一个token

            input_tokens = [build_prompt(answer_text) for answer_text in answer_texts]
            inputs = tokenizer(input_tokens, padding=True, return_tensors="pt", truncation=True, max_length=2048).to('cuda:2')
            # TODO return_last_logit 控制了只取最后一个词
            outputs = model(**inputs, return_last_logit=True)
            # TODO [2, 1, 65024] 取最后一个token
            logits = outputs.logits[:, -1]

然后定位到A、B、C、D四个字符的得分,以下一个token在四个字符的概率分布作为依据,取最大者来获得答案

choices = ["A", "B", "C", "D"]
choice_tokens = [tokenizer.encode(choice, add_special_tokens=False)[0] for choice in choices]

logits = logits[:, choice_tokens]
preds = logits.argmax(dim=-1)

例如在该题目中,A、B、C、D的得分分别是9.4844,10.5469, 7.2500, 11.9375,因此大模型回答为D

>>> logits
tensor([[ 9.4844, 10.5469,  7.2500, 11.9375]], device='cuda:2',
       dtype=torch.float16, grad_fn=<IndexBackward0>)

本质上ChatGLM2-6B的官方测试代码采用条件概率CLP的方式,考察概率的范围仅限于备选项标号所对应的 token,取其中概率最高的token所对应的选项为模型的推理结果,示意图如下

image.png

接下来和样本数据集中的正确选型进行比对即可获得正确率

correct += (preds.cpu() == batch["label"]).sum().item()
accuracy = correct / len(dataset)  # TODO 正确率
accuracy_dict[entry] = accuracy
count_dict[entry] = len(dataset)

在求得每个科目的正确率之后,再统计一次全局的正确率,代码实现如下

acc_total, count_total = 0.0, 0
for key in accuracy_dict:
    acc_total += accuracy_dict[key] * count_dict[key]
    count_total += count_dict[key]
# TODO 平均正确率
print(acc_total / count_total)

在笔者的环境下最红ChatGLM2-6B的全局平均正确率为0.536,运行日志如下

./CEval/CEval/val/Other/clinical_medicine.jsonl 0.6818181818181818
100%|██████████| 7/7 [02:11<00:00, 18.83s/it]
  0%|          | 0/3 [00:00<?, ?it/s]./CEval/CEval/val/Other/tax_accountant.jsonl 0.3877551020408163
100%|██████████| 3/3 [00:41<00:00, 13.95s/it]
  0%|          | 0/7 [00:00<?, ?it/s]./CEval/CEval/val/Other/sports_science.jsonl 0.47368421052631576
100%|██████████| 7/7 [02:11<00:00, 18.73s/it]
./CEval/CEval/val/Other/accountant.jsonl 0.46938775510204084
0.5364041604754829
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容