evalscope_v0.17.0/evalscope.0.17.0/docs/zh/advanced_guides/add_benchmark.md

17 KiB
Raw Blame History

👍 贡献基准评测

EvalScope作为ModelScope的官方评测工具其基准评测功能正在持续优化中我们诚邀您参考本教程轻松添加自己的评测基准并与广大社区成员分享您的贡献。一起助力EvalScope的成长让我们的工具更加出色

下面以MMLU-Pro为例,介绍如何添加基准评测,主要包含上传数据集、注册数据集、编写评测任务三个步骤。

1. 准备基准评测数据集

您有两种方式准备基准评测数据集:

  1. 上传到ModelScope推荐将数据集上传到ModelScope平台这样其他用户可以一键加载您的数据集使用更加便捷也能让更多用户受益于您的贡献。

  2. 本地使用:您也可以直接使用本地数据集进行评测,适合数据集尚在开发阶段或含有敏感信息的情况。

如需上传到ModelScope可参考[modelscope/MMLU-Pro](https://modelscope.cn/datasets/modelscope/MMLU-Pro/summary)示例,以及[数据集上传教程](https://www.modelscope.cn/docs/datasets/create)。

无论选择哪种方式请确保数据的格式正确且可被加载。如使用ModelScope可通过以下代码测试

from modelscope import MsDataset

dataset = MsDataset.load("modelscope/MMLU-Pro")  # 替换为你的数据集

若使用本地数据集,需要在后续注册基准评测时相应调整dataset_id参数和重写load_from_disk方法。

2. 注册基准评测

在EvalScope中添加基准评测。

创建文件结构

首先Fork EvalScope 仓库即创建一个自己的EvalScope仓库副本将其clone到本地。

然后,在evalscope/benchmarks/目录下添加基准评测,结构如下:

evalscope/benchmarks/
├── benchmark_name
│   ├── __init__.py
│   ├── benchmark_name_adapter.py
│   └── ...

具体到MMLU-Pro,结构如下:

evalscope/benchmarks/
├── mmlu_pro
│   ├── __init__.py
│   ├── mmlu_pro_adapter.py
│   └── ...

注册Benchmark

我们需要在benchmark_name_adapter.py中注册Benchmark使得EvalScope能够加载我们添加的基准测试。以MMLU-Pro为例,主要包含以下内容:

  • 导入BenchmarkDataAdapter
  • 注册Benchmark,指定:
    • name:基准测试名称
    • pretty_name:基准测试的可读名称
    • tags:基准测试标签,用于分类和搜索
    • description基准测试描述可以使用Markdown格式建议使用英文
    • dataset_id基准测试数据集ID用于加载基准测试数据集
    • model_adapter:基准测试模型默认适配器。支持两种:
      • OutputType.GENERATION通用文本生成模型评测通过输入prompt返回模型生成的文本
      • OutputType.MULTIPLE_CHOICE多选题评测通过logits来计算选项的概率返回最大概率选项
    • output_types:基准测试输出类型,支持多选:
      • OutputType.GENERATION:通用文本生成模型评测
      • OutputType.MULTIPLE_CHOICE多选题评测输出logits
    • subset_list:基准测试数据集的子数据集
    • metric_list:基准测试评估指标
    • few_shot_num评测的In Context Learning样本数量
    • train_split基准测试训练集用于采样ICL样例
    • eval_split:基准测试评估集
    • prompt_template:基准测试提示模板
  • 创建MMLUProAdapter类,继承自DataAdapter
默认`subset_list`, `train_split`, `eval_split` 可以从数据集预览中获取,例如[MMLU-Pro预览](https://modelscope.cn/datasets/modelscope/MMLU-Pro/dataPeview)

![MMLU-Pro预览](./images/mmlu_pro_preview.png)

代码示例如下:

from evalscope.benchmarks import Benchmark, DataAdapter
from evalscope.constants import EvalType, OutputType

SUBSET_LIST = [
    'computer science', 'math', 'chemistry', 'engineering', 'law', 'biology', 'health', 'physics', 'business',
    'philosophy', 'economics', 'other', 'psychology', 'history'
]  # 自定义的子数据集列表

@Benchmark.register(
    name='mmlu_pro',
    pretty_name='MMLU-Pro',
    tags=['MCQ', 'Knowledge'],
    description=
    'MMLU-Pro is a benchmark for evaluating language models on multiple-choice questions across various subjects. It includes questions from different domains, where the model must select the correct answer from given options.',  # noqa: E501
    dataset_id='modelscope/MMLU-Pro',
    model_adapter=OutputType.GENERATION,
    output_types=[OutputType.MULTIPLE_CHOICE, OutputType.GENERATION],
    subset_list=SUBSET_LIST,
    metric_list=['AverageAccuracy'],
    few_shot_num=5,
    train_split='validation',
    eval_split='test',
    prompt_template=
    'The following are multiple choice questions (with answers) about {subset_name}. Think step by step and then finish your answer with \"the answer is (X)\" where X is the correct letter choice.\n{query}',  # noqa: E501
)
class MMLUProAdapter(DataAdapter):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

3. 编写评测逻辑

在完成Benchmark注册后,接下来需要编写DataAdapter类中的核心方法,以实现评测功能。这些方法控制着数据的加载、处理以及评分流程。

评测流程概述

EvalScope评测流程主要包含以下步骤

  1. 数据加载:通过load方法加载数据集
  2. 生成提示:通过gen_prompts调用gen_prompt生成模型输入
  3. 模型推理通过模型adapter的predict方法对生成的提示进行推理
  4. 答案解析:通过parse_pred_result解析模型输出
  5. 答案评分:通过matchllm_match方法评估预测结果
  6. 指标计算:通过compute_metric计算评估指标
  7. 报告生成:通过gen_reportpost_process_report生成评估报告

必须实现的核心方法

以下是必须实现的核心方法,每个方法都有明确的功能和作用:

  1. gen_prompt:将数据集样本转换为模型可接受的输入格式

    • 处理few-shot示例
    • 格式化问题和选项
    • 应用提示模板
  2. get_gold_answer:从数据集样本中提取标准答案

    • 通常返回数据集中的答案字段
  3. parse_pred_result:解析模型输出,提取有效答案

    • 对于文本生成模型,通常需要提取答案选项
    • 对于多选题直接输出,可以直接返回结果
  4. match:比较预测答案与标准答案,计算得分

    • 通常使用精确匹配exact_match等方法

可选实现的方法

除了必须实现的方法外,还可以根据需要实现或重写以下方法:

  1. llm_match:使用大语言模型作为评判器评估答案质量

    • 适用于开放式问题或需要复杂理解的评测任务
    • 需要在配置中指定评判模型
    • 相比简单规则匹配,能更好地评估答案质量
    def llm_match(self, gold: Any, pred: Any, judge: Optional[LLMJudge] = None, **kwargs) -> float:
        """
        使用LLM作为评判器评估预测答案
    
        Args:
            gold: 标准答案
            pred: 预测答案
            judge: LLM评判器实例
    
        Returns:
            评分结果通常为0到1之间的浮点数
        """
        # 默认评判器处理
        if judge is None:
            return 0
    
        # 构建评判提示并获取评分
        prompt = judge.build_prompt(pred, gold, kwargs.get('raw_input', {}).get('question'))
        score = judge(prompt)
        return judge.get_score(score)
    
  2. post_process_report:处理评测报告,添加自定义分析或可视化

  3. load:重写数据加载流程,适用于需要自定义数据加载逻辑的场景

    • 适合处理特殊格式的数据集
    • 可以实现自定义的子集划分逻辑
    • 可以添加数据预处理或过滤步骤
    def load(self, dataset_name_or_path: str = None, subset_list: list = None, work_dir: Optional[str] = DEFAULT_DATASET_CACHE_DIR, **kwargs) -> dict:
        """
        自定义数据集加载逻辑
    
        Args:
            dataset_name_or_path: 数据集路径或名称
            subset_list: 子集列表
            work_dir: 工作目录
    
        Returns:
            数据字典,格式为: {'subset_name': {'train': train_dataset, 'test': test_dataset}}
        """
        # 可以在这里实现自定义的数据加载和处理逻辑
        # 例如:从本地文件加载数据、过滤数据、重新组织数据结构等
    
        # 调用父类方法加载基础数据
        data_dict = super().load(dataset_name_or_path, subset_list, work_dir, **kwargs)
    
        # 进行自定义处理,如根据特定字段进行子集划分
        return self.reformat_subset(data_dict, subset_key='your_category_field')
    
  4. load_from_disk:专门用于从本地磁盘加载数据集

    • 当使用本地数据集而非ModelScope托管数据集时需要重写此方法
    • 可以处理自定义格式的本地数据文件
    def load_from_disk(self, dataset_path, subset_list, work_dir, **kwargs) -> dict:
        """
        从本地磁盘加载数据集
    
        Args:
            dataset_path: 本地数据集路径
            subset_list: 子集列表
            work_dir: 工作目录
    
        Returns:
            数据字典,格式为: {'subset_name': {'train': train_dataset, 'test': test_dataset}}
        """
        # 示例从本地JSON文件加载数据
        import json
        import os
    
        data_dict = {}
        for subset in subset_list:
            data_dict[subset] = {}
    
            # 加载训练集few-shot示例
            if self.train_split:
                train_path = os.path.join(dataset_path, f"{subset}_{self.train_split}.json")
                if os.path.exists(train_path):
                    with open(train_path, 'r', encoding='utf-8') as f:
                        data_dict[subset][self.train_split] = json.load(f)
    
            # 加载测试集
            if self.eval_split:
                test_path = os.path.join(dataset_path, f"{subset}_{self.eval_split}.json")
                if os.path.exists(test_path):
                    with open(test_path, 'r', encoding='utf-8') as f:
                        data_dict[subset][self.eval_split] = json.load(f)
    
        return data_dict
    

代码示例与解释

下面是MMLU-Pro适配器的完整实现包含详细注释

class MMLUProAdapter(DataAdapter):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 定义选项标识符,用于构建选项
        self.choices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
    
    def load(self, **kwargs):
        """
        重写加载方法,实现自定义的数据集加载和子集划分逻辑
        
        在这个例子中,我们根据'category'字段对数据集进行子集划分
        """
        # 先使用默认方式加载所有数据
        kwargs['subset_list'] = ['default']
        data_dict = super().load(**kwargs)
        # 使用'category'字段作为子集键值进行重新格式化
        return self.reformat_subset(data_dict, subset_key='category')
    
    def gen_prompt(self, input_d: Dict, subset_name: str, few_shot_list: list, **kwargs) -> Any:
        """
        生成模型输入的提示文本
        
        Args:
            input_d: 当前样本数据
            subset_name: 子集名称,用于模板填充
            few_shot_list: few-shot示例列表
        
        Returns:
            格式化后的提示文本
        """
        # 处理few-shot示例
        if self.few_shot_num > 0:
            prefix = self.format_fewshot_examples(few_shot_list)
        else:
            prefix = ''
        
        # 构建当前问题文本
        query = prefix + 'Q: ' + input_d['question'] + '\n' + \
            self.__form_options(input_d['options']) + '\n'

        # 应用提示模板
        full_prompt = self.prompt_template.format(subset_name=subset_name, query=query)
        return self.gen_prompt_data(full_prompt)
    
    def format_fewshot_examples(self, few_shot_list):
        """
        格式化few-shot示例
        
        将每个示例格式化为一致的格式,包括问题、选项和思考过程
        """
        prompts = ''
        for index, d in enumerate(few_shot_list):
            prompts += 'Q: ' + d['question'] + '\n' + \
                self.__form_options(d['options']) + '\n' + \
                d['cot_content'] + '\n\n'  # 包含思考过程
        return prompts
    
    def __form_options(self, options: list):
        """
        格式化选项列表
        
        将选项数组转换为格式化的文本,每个选项前添加标识符(A)、(B)等
        """
        option_str = 'Options are:\n'
        for opt, choice in zip(options, self.choices):
            option_str += f'({choice}): {opt}' + '\n'
        return option_str
    
    def get_gold_answer(self, input_d: dict) -> str:
        """
        提取标准答案
        
        从数据样本中提取正确答案,通常是'A'、'B'、'C'、'D'等选项标识符
        
        Args:
            input_d: 输入数据样本
        
        Returns:
            标准答案字符串
        """
        return input_d['answer']  # 直接返回数据集中的answer字段

    def parse_pred_result(self, result: str, raw_input_d: dict = None, eval_type: str = EvalType.CHECKPOINT) -> str:
        """
        解析模型预测结果
        
        根据模型类型不同,使用不同的解析方法:
        - 对于直接输出选项的模型,直接返回结果
        - 对于生成文本的模型,需要从文本中提取选项
        
        Args:
            result: 模型预测结果
            raw_input_d: 原始输入数据
            eval_type: 评测类型
        
        Returns:
            解析后的答案选项
        """
        if self.model_adapter == OutputType.MULTIPLE_CHOICE:
            # 多选题直接输出模式,直接返回结果
            return result
        else:
            # 文本生成模式,从文本中提取首个选项字母
            return ResponseParser.parse_first_option(result)

    def match(self, gold: str, pred: str) -> float:
        """
        比较预测答案与标准答案
        
        Args:
            gold: 标准答案,如'A'
            pred: 预测答案,如'B'
        
        Returns:
            匹配得分正确为1.0错误为0.0
        """
        return exact_match(gold=gold, pred=pred)  # 使用精确匹配

提示和最佳实践

  • 在设计few-shot示例时确保格式一致且包含足够信息
  • 仔细设计提示模板,确保模型能理解任务要求
  • 对于复杂任务,考虑在match方法中实现更灵活的评分逻辑
  • 添加足够的注释和文档,方便其他开发者理解和维护代码
  • 考虑使用llm_match方法进行更复杂的答案评估,尤其是对于开放式问题

4. 运行评测

调试代码,看看是否能正常运行。

from evalscope import run_task, TaskConfig
task_cfg = TaskConfig(
    model='Qwen/Qwen2.5-0.5B-Instruct',
    datasets=['mmlu_pro'],
    limit=10,
    dataset_args={'mmlu_pro': {'subset_list': ['computer science', 'math']}},
    debug=True
)
run_task(task_cfg=task_cfg)

输出如下:

+-----------------------+-----------+-----------------+------------------+-------+---------+---------+
| Model                 | Dataset   | Metric          | Subset           |   Num |   Score | Cat.0   |
+=======================+===========+=================+==================+=======+=========+=========+
| Qwen2.5-0.5B-Instruct | mmlu_pro  | AverageAccuracy | computer science |     10 |       0.1 | default |
+-----------------------+-----------+-----------------+------------------+-------+---------+---------+
| Qwen2.5-0.5B-Instruct | mmlu_pro  | AverageAccuracy | math             |     10 |       0.1 | default |
+-----------------------+-----------+-----------------+------------------+-------+---------+---------+ 

5. 基准评测文档生成

完成基准评测实现后您可以使用EvalScope提供的工具生成标准文档。这将确保您的基准评测有一致的文档格式并能够被其他用户轻松理解和使用。

要生成中英文文档,请运行以下命令,将根据注册信息生成文档:

# 进入evalscope根目录
cd /path/to/evalscope

# 生成基准评测文档
python docs/generate_dataset_md.py

完成这些方法的实现和文档生成后,您的基准评测就准备就绪了!可以提交PR我们将尽快合并你的贡献让更多用户来使用你贡献的基准评测。如果你不知道如何提交PR可以查看我们的指南,快来试一试吧🚀