概述
经历过一段时间的LLM
评测调研,对LLM
的评测有了不一样的体会,现将其做一个记录。
此次调研主要关注了LLM-Benchmark
评测集、测评工具、测评方式。具体来讲,是调研评测工具对评测集的评测方式。
对于评测方式,我们通常希望关注:评测思路、评测具体实施。
一些评测集的评测思路容易也难以理解,但也需要关注这些问题:让模型直接输出答案?还是让模型通过CoT
方式输出答案?使用loglikehood
方式?还是Generative
方式?等等
评测具体实施方法不禁要问:评测Prompt
怎么写?参数怎么设置?是否需要用json_schema
来规范输出格式?还是使用extract
关键信息并match
方式?等等
因为我们的出发点是评测集,所以下面的小节内容安排也将按照上面的逻辑来进行。
MCQA
MCQA,Multiple Choices Question & Answers
,是大部分数据集构建的方式。如,MMLU
、MMLU_PRO
、GPQA
等等。这种构建数据集方式的好处在于给予模型固定的答案选项,不让模型过度发挥。主要关注模型解决问题的能力。
那么评测MCQA的思路是什么呢?
通常,我们的第一直接告诉我们:直接把问题和答案告诉模型,并让它直接输出答案,我们只需要从中提取关键选项就行了。
然而,evaluation-guidebook建议我们使用loglikehood
方式来评测MCQA
任务。
那么什么又是loglikehood
?
简单来说,loglikehood
的评测方式是通过返回预测tokens
的概率,而不是tokens
本身。概率P
越大,P
约接近于1,loglikehood
的值约接近于0。
loglikehood
评测方式是否又存在细微的差别?
当然,这篇文章介绍了三种针对MMLU数据集的loglikehood
评测方式。简单总结一下:
Default
:仅关注预测tokens
中的最大概率选项
HELM
:仅关注预测tokens
中的第一个最大概率token
Harness
:不仅关注选项正确,也关注答案正确
那么loglikehood
具体怎么实现呢?
MMLU
官方给出了一份评测脚本,这段代码详细的描述了如何使用loglikehood
进行MCQA
的评测。
然而这段代码有点老旧了,我们简单对它进行修改,就能够得到适用于目前OpenAI API
的评测脚本:
def eval(args, subject, dev_df, test_df): cors = [] all_probs = [] answers = choices[:test_df.shape[1]-2] for i in range(test_df.shape[0]): # get prompt and make sure it fits k = args.ntrain prompt_end = format_example(test_df, i, include_answer=False) train_prompt = gen_prompt(dev_df, subject, k) prompt = train_prompt + prompt_end while crop(prompt) != prompt: k -= 1 train_prompt = gen_prompt(dev_df, subject, k) prompt = train_prompt + prompt_end label = test_df.iloc[i, test_df.shape[1]-1] try: response = client.chat.completions.create( model=args.model_name, messages=[ {"role": "system", "content": prompt}, ], max_tokens=1, logprobs=True, top_logprobs=20, temperature=0 ) except Exception as e: raise e top_logprobs = response.choices[0].logprobs.content[-1].top_logprobs lprobs = [] for ans in answers: found = False for top_logprob in top_logprobs: if top_logprob.token == ans: lprobs.append(top_logprob.logprob) found = True break if not found: print("Warning: {} not found. Artificially adding log prob of -100.".format(ans)) lprobs.append(-100) pred = {0: "A", 1: "B", 2: "C", 3: "D"}[np.argmax(lprobs)] probs = softmax(np.array(lprobs)) cor = pred == label cors.append(cor) all_probs.append(probs) acc = np.mean(cors) cors = np.array(cors) all_probs = np.array(all_probs) print("Average accuracy {:.3f} - {}".format(acc, subject)) return cors, acc, all_probs
那么使用loglikehood
请求方式,response
如下:
----------------------------prompt-------------------------- The following are multiple choice questions (with answers) about abstract algebra. Find all c in Z_3 such that Z_3[x]/(x^2 + c) is a field. A. 0 B. 1 C. 2 D. 3 Answer: B Statement 1 | If aH is an element of a factor group, then |aH| divides |a|. Statement 2 | If H and K are subgroups of G then HK is a subgroup of G. A. True, True B. False, False C. True, False D. False, True Answer: B Statement 1 | Every element of a group generates a cyclic subgroup of the group. Statement 2 | The symmetric group S_10 has 10 elements. A. True, True B. False, False C. True, False D. False, True Answer: C Statement 1| Every function from a finite set onto itself must be one to one. Statement 2 | Every subgroup of an abelian group is abelian. A. True, True B. False, False C. True, False D. False, True Answer: A Find the characteristic of the ring 2Z. A. 0 B. 3 C. 12 D. 30 Answer: A Statement 1 | If H is a subgroup of G and a belongs to G then |aH| = |Ha|. Statement 2 | If H is a subgroup of G and a and b belong to G, then aH and Hb are identical or disjoint. A. True, True B. False, False C. True, False D. False, True Answer: --------------------------response------------------------ ChatCompletion(id='chat-3e3ef7af5257479094af5b9bb61daf39', choices=[Choice(finish_reason='length', index=0, logprobs=ChoiceLogprobs(content=[ChatCompletionTokenLogprob(token='A', bytes=[65], logprob=-0.41949766874313354, top_logprobs=[TopLogprob(token='A', bytes=[65], logprob=-0.41949766874313354), TopLogprob(token='Let', bytes=[76, 101, 116], logprob=-2.0444977283477783), TopLogprob(token='The', bytes=[84, 104, 101], logprob=-2.1694977283477783), TopLogprob(token='To', bytes=[84, 111], logprob=-3.5444977283477783), TopLogprob(token='**', bytes=[42, 42], logprob=-3.6694977283477783), TopLogprob(token='Statement', bytes=[83, 116, 97, 116, 101, 109, 101, 110, 116], logprob=-3.7944977283477783), TopLogprob(token='Answer', bytes=[65, 110, 115, 119, 101, 114], logprob=-4.419497489929199), TopLogprob(token='###', bytes=[35, 35, 35], logprob=-5.669497489929199), TopLogprob(token='We', bytes=[87, 101], logprob=-6.419497489929199), TopLogprob(token='C', bytes=[67], logprob=-6.544497489929199), TopLogprob(token='B', bytes=[66], logprob=-6.919497489929199), TopLogprob(token='Correct', bytes=[67, 111, 114, 114, 101, 99, 116], logprob=-7.294497489929199), TopLogprob(token='D', bytes=[68], logprob=-7.669497489929199), TopLogprob(token='E', bytes=[69], logprob=-7.794497489929199), TopLogprob(token='First', bytes=[70, 105, 114, 115, 116], logprob=-8.2944974899292), TopLogprob(token='Based', bytes=[66, 97, 115, 101, 100], logprob=-8.5444974899292), TopLogprob(token='For', bytes=[70, 111, 114], logprob=-8.9194974899292), TopLogprob(token='Both', bytes=[66, 111, 116, 104], logprob=-9.0444974899292), TopLogprob(token='This', bytes=[84, 104, 105, 115], logprob=-9.2944974899292), TopLogprob(token=' Statement', bytes=[32, 83, 116, 97, 116, 101, 109, 101, 110, 116], logprob=-10.7944974899292)])], refusal=None), message=ChatCompletionMessage(content='A', refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[]), stop_reason=None)], created=1736836251, model='Qwen/Qwen2.5-7B-Instruct', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=1, prompt_tokens=385, total_tokens=386, completion_tokens_details=None, prompt_tokens_details=None), prompt_logprobs=None)
从上面的response
可以看到,官方在prompt
中使用了few-shot
,那么为什么要用few-shot
呢?答案是为了保持格式一致。那为什么不在prompt
中加一句,“请直接输出答案,不要解释”呢?我的理解是为了保持评测的纯净性——尽量避免Prompt
中出现与评测任务无关的描述,减少冗余prompt
对生成结果的影响。
那么是否MCQA
任务就一定要用loglikehood
方式呢?
根据我调研到的结果,GPQA在他们的代码中注释掉了loglikehood
方法。这是为什么呢?这就引出了另外一个问题:
对于MCQA
任务,什么情况使用loglikehood
方式,什么情况使用generative + extract_match
方式呢?
我的理解是:简单的MCQA
问题可以使用loglikehood
方式;困难的MCQA
问题使用generative + extract_match
方式。
原因:困难问题使用loglikehood
方法会导致模型能力不能完全发挥。之前的CoT
和最近的omni
模型都是通过增加模型上下文,让模型具有思考能力,来使模型表达更强。如果用loglikehood
方式,限制模型只输出选项答案,这限制了模型的思考能力,固然结果会变差。
Generative
我对Generative
评测集的定义是:无选择性的生成式评测任务。像gsm8k
(数学)、HumanEval
(代码)、chinese_sampleqa
(通识)、CLUEWSC2020
(理解)、Arena-Hard-Auto
(指令调优),都属于Generative
任务。
那么这类任务的评测方式如何呢?
因为是生成式任务,所以我们通常只需要prompt
和parameters
就足够了,然后从中进行extract
。我们更多需要关注的是metric
。
那么上述任务的metric有什么不同呢?
-
gsm8k:acc
-
HumanEval:pass@k
-
chinese_sampleqa:llm as judge
-
CLUEWSC2020:acc
-
Arena-Hard-Auto:llm as judge
那么chinese_sampleqa
和Arena-Hard-Auto
都使用llm as judge
方式,他们有什么区别呢?
区别在于,chinese_sampleqa
使用的是一个Judge-LLM
+一个Task-LLM
,而Arena-Hard-Auto
使用的是一个Judge-LLM
+两个Task-LLM
。
为什么有这样的区别呢?
我们首先来看一下两个评测集的样例:
chinese_sampleqa
:
{ "primary_category": "中华文化", "secondary_category": "道教", "question": "《三教源流搜神大全》中的记载”终南山人,头戴铁冠,手执铁鞭,面如黑炭,胡须四张”是形容的道教中的哪位神明?", "answer": "赵公明" }
Arena-Hard-Auto
:
{ "question_id": "328c149ed45a41c0b9d6f14659e63599", "category": "arena-hard-v0.1", "cluster": "ABC Sequence Puzzles & Groups", "turns": [{"content": "Use ABC notation to write a melody in the style of a folk tune."}] }
可以看到,chinese_sampleqa
是有参考答案,而Arena-Hard-Auto
没有。
那么答案就呼之欲出了:因为缺少参考答案,所以需要对比的Task-LLM
来生成参考答案进行对比。
小结
从上面的内容可以看到,生成式任务评测方式也并不是想象中的简单。在没有llm as judge
之前,NLP
通常使用perplexity
的方式,如ROUGE, BLEU, N-Gram
的计算方式。但这对于大模型时代较为落后了,因为语义是无法被计算的,只能被理解。