大模型评测、质量保证、datasets数据集、LmEval工具

文章目录

又一课题,必须要有套路,以及能举例。

思路理解:

1、定义一个测评类ModelEvaluator。

2、在该配里面配置基座模型、分词器、量化配置、run_dataset()方法是自定义的评测方法

3、运行时,传入数据集名称gsm8k,任务类型math,表示测评这个维度的这类题目

原生模型-示例代码

python 复制代码
import torch
import re
from tqdm import tqdm
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from peft import PeftModel


class ModelEvaluator:
    def __init__(self, model_path, adapter_path=None, use_4bit=True):
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        print(f"🚀 正在加载模型: {model_path} ...")

        # 1. 量化配置
        bnb_config = None
        if use_4bit and self.device == "cuda":
            bnb_config = BitsAndBytesConfig(
                load_in_4bit=True,
                bnb_4bit_compute_dtype=torch.float16,
                bnb_4bit_use_double_quant=True,
                bnb_4bit_quant_type="nf4"
            )

        # 2. 加载 Tokenizer
        self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

        # 【关键修复 1】设置 pad_token,防止 Attention Mask 报错
        self.tokenizer.pad_token = self.tokenizer.eos_token
        self.tokenizer.pad_token_id = self.tokenizer.eos_token_id

        # 3. 加载基座模型
        self.base_model = AutoModelForCausalLM.from_pretrained(
            model_path,
            quantization_config=bnb_config,
            device_map="auto",
            torch_dtype=torch.float16,
            trust_remote_code=True,
            # 【关键修复 2】显式传入 pad_token_id,彻底解决 mask 问题
            pad_token_id=self.tokenizer.pad_token_id
        )

        # 4. 加载 LoRA 适配器
        if adapter_path:
            print(f"🔧 正在加载 LoRA 适配器: {adapter_path} ...")
            self.model = PeftModel.from_pretrained(self.base_model, adapter_path)
        else:
            self.model = self.base_model

        self.model.eval()

    def generate(self, prompt: str, max_new_tokens=512) -> str:
        # 【关键修复 3】消除警告:强制覆盖模型配置中的采样参数
        self.model.generation_config.do_sample = False
        self.model.generation_config.top_p = None
        self.model.generation_config.top_k = 0

        inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)

        with torch.no_grad():
            generate_ids = self.model.generate(
                inputs.input_ids,
                max_new_tokens=max_new_tokens,
                do_sample=False,
                pad_token_id=self.tokenizer.eos_token_id
            )

        # 解码并去除输入部分
        output = self.tokenizer.batch_decode(generate_ids, skip_special_tokens=True)[0]
        return output.replace(prompt, "").strip()

    def run_dataset(self, dataset_name, task_type="math"):
        print(f"📚 正在加载数据集: {dataset_name} ...")
        # 加载数据集,指定 'main' 配置
        dataset = load_dataset(dataset_name, 'main', split="test")

        results = []
        score = 0

        # 限制数量用于测试,实际评测可去掉 .select(range(10))
        for item in tqdm(dataset.select(range(10)), desc="评测中"):
            # 构造 Prompt:强制要求模型输出特定格式
            prompt = f"请回答下面的数学问题,并在最后一行输出"答案是 [数字]"。\n\n问题:{item['question']}\n"

            # 推理
            raw_output = self.generate(prompt)

            # 阅卷
            is_correct = False
            ground_truth = item['answer']  # GSM8K 的正确答案在 'answer' 字段

            if task_type == "math":
                # 提取模型回答中的数字
                pred = self.parse_math_answer(raw_output)
                # 提取标准答案中的数字
                label = self.parse_math_answer(ground_truth)

                # 比对数字
                if pred and label and pred == label:
                    is_correct = True

            results.append({
                "question": item['question'],
                "prediction": raw_output,
                "ground_truth": ground_truth,
                "correct": is_correct
            })
            if is_correct:
                score += 1

        # 统计结果
        accuracy = score / len(results)
        print(f"✅ 评测完成。准确率: {accuracy:.2%}")
        return results, accuracy

    def parse_math_answer(self, text):
        # 针对数学题的正则:提取 "答案是 " 后面的数字
        # 支持整数和小数
        match = re.search(r"答案是\s*([-+]?\d+\.?\d*)", text)
        if match:
            return match.group(1)

        # 兜底策略:尝试提取最后一个出现的数字
        numbers = re.findall(r"[-+]?\d+\.?\d*", text)
        if numbers:
            return numbers[-1]
        return None


# --- 使用示例 ---
if __name__ == "__main__":
    # 配置模型路径
    MODEL_PATH = "Qwen/Qwen2.5-0.5B-Instruct"
    ADAPTER_PATH = None

    # 初始化评测器
    evaluator = ModelEvaluator(MODEL_PATH, adapter_path=ADAPTER_PATH, use_4bit=True)

    # 运行评测
    try:
        results, acc = evaluator.run_dataset("gsm8k", task_type="math")
    except Exception as e:
        print(f"评测出错: {e}")
        import traceback

        traceback.print_exc()

输出结果:

python 复制代码
🚀 正在加载模型: Qwen/Qwen2.5-0.5B-Instruct ...
📚 正在加载数据集: gsm8k ...
评测中:   0%|          | 0/10 [00:00<?, ?it/s]D:\PycharmProjects\transformer_demo\.venv\Lib\site-packages\transformers\generation\configuration_utils.py:590: UserWarning: `do_sample` is set to `False`. However, `temperature` is set to `0.7` -- this flag is only used in sample-based generation modes. You should set `do_sample=True` or unset `temperature`.
  warnings.warn(
D:\PycharmProjects\transformer_demo\.venv\Lib\site-packages\transformers\generation\configuration_utils.py:612: UserWarning: `do_sample` is set to `False`. However, `top_k` is set to `0` -- this flag is only used in sample-based generation modes. You should set `do_sample=True` or unset `top_k`.
  warnings.warn(
The attention mask is not set and cannot be inferred from input because pad token is same as eos token. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.
评测中: 100%|██████████| 10/10 [04:16<00:00, 25.65s/it]
✅ 评测完成。准确率: 0.00%

解读:

准确率是0.00%,这大概率和模型是千问0.5b有关,先不管它了,这里主要看思路。

LmEval-示例代码

LmEval是一款评测框架,之前原生为了说明机制。如果要快速上手,用框架肯定高效方便。

1、安装依赖

python 复制代码
pip install lm_eval # 注:是lm_eval,不是lmeval,中间有中划线

2、新建python文件lmEval.py,代码:

python 复制代码
import lm_eval
import torch
import os


def run_lm_eval_ultimate():
    task_name = "boolq"

    # 【双重保险 1/2】设置环境变量加速下载
    # 这是解决 ReadTimeout 的关键
    os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'

    # 【双重保险 2/2】关于 dtype 的警告处理
    # 既然 lm_eval 频繁报 dtype 错误,我们直接不传 dtype
    # 并且忽略 HalfTensor 的弃用警告,改用 set_default_dtype (如果环境支持)
    try:
        torch.set_default_dtype(torch.float16)
        print("已设置默认精度为 float16 (兼容新版本 PyTorch)")
    except:
        # 如果 set_default_dtype 失败(比如版本太低),尝试旧方法或直接跳过
        print("当前 PyTorch 版本可能较低,依赖模型自动处理精度...")

    model_args = {
        "pretrained": "Qwen/Qwen2.5-0.5B",

        # 🔥 核心策略:绝对不要写 "dtype": ...
        # 让 HuggingFace 模型加载器自己决定用什么精度
        # lm_eval 有时候会强制注入参数,不写反而最安全

        "device": "cuda:1",  # 优先使用你的第二张显卡
        "trust_remote_code": True  # Qwen 必须
    }

    print(f"正在加载模型 (不指定 dtype 以避免报错) ...")

    try:
        results = lm_eval.simple_evaluate(
            model="hf",
            model_args=model_args,
            tasks=[task_name],
            num_fewshot=0,
            batch_size=1,
            limit=10
        )

        print(f"--- {task_name} 评测结果 ---")
        # print(f"准确率 (acc): {results['results'][task_name]['acc']}")

        # 【临时调试代码】:打印完整结果
        print("\n=== 完整的 Results 字典结构如下 ===")
        import pprint
        pprint.pprint(results)  # 这会把所有结果原封不动打出来

    except Exception as e:
        print(f"发生错误: {e}")
        print("💡 如果依然报 dtype 错误,请尝试手动下载模型(见下方注释)")


if __name__ == "__main__":
    run_lm_eval_ultimate()

输出结果:

内容太多了,900多行,重点看这里:

bash 复制代码
 'results': {'boolq': {'acc,none': 0.6,
                       'acc_stderr,none': 0.16329931618554522,
                       'alias': 'boolq'}},

acc是accuracy(准确度)的意思,寿命准确度是60%。

LmEval投喂自定义数据

说明:

(1)创建jsonl文件,存放多行json数据。

(2)lm_evaltask目录下创建yaml配置文件(必须放在这里)

(3)创建python文件运行代码
python中的tasks的任务名要和yaml中的task任务名一致。

1、创建jsonl文件my_custom_boolq.jsonl,内容:

json 复制代码
{"text": "Passage: 苹果是一种水果。 Question: 苹果是水果吗?", "answer": "yes"}
{"text": "Passage: 太阳从西边升起。 Question: 太阳从东边升起吗?", "answer": "no"}

2、必须在lm_eval的task目录下创建配置文件my_boolq.yaml ,这样会自动识别,不支持通过路径的方式配置yaml地址。

符合规范的路径地址如:
D:/PycharmProjects/transformer_demo/.venv/Lib/site-packages/lm_eval/tasks/my_boolq.yaml,内容:

yml 复制代码
task: my_custom_boolq  # 任务名称,运行命令时用这个
dataset_path: json     # 使用 json 加载器
dataset_kwargs:
  data_files:
    test: "D:/PycharmProjects/transformer_demo/other_demo/my_custom_boolq.jsonl"  # 这里填你上面那个 jsonl 文件的绝对路径
output_type: multiple_choice # 输出类型:多项选择
test_split: test       # 指定测试集切片的名称(对应上面的 test)
doc_to_text: "{{text}}" # 输入文本的字段名
doc_to_target: answer   # 答案所在的字段名 (对应 jsonl 里的 "answer")
# 定义选项列表,顺序必须对应。lm_eval 会计算 "yes" 和 "no" 的概率
doc_to_choice: ["yes", "no"]
metric_list:
  - metric: acc        # 评估指标:准确率
    aggregation: mean
    higher_is_better: true

3、创建python文件custom_lmEval_demo.py,代码:

tasks里面的名称要和yaml里面的名称对应,见代码。

python 复制代码
import lm_eval
import os

os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'

def run_custom_eval():
    model_args = {
        "pretrained": "Qwen/Qwen2.5-0.5B",
        "device": "cuda:1",
        "trust_remote_code": True
    }

    # 核心修改在这里
    results = lm_eval.simple_evaluate(
        model="hf",
        model_args=model_args,
        tasks=["my_custom_boolq"],  # 这里填 yaml 里的 task 名称
        # include_path=".",  # 填 yaml 文件所在的文件夹路径
        num_fewshot=0,
        batch_size=1
    )
    print(results['results']['my_custom_boolq']['acc,none'])
if __name__ == "__main__":
    run_custom_eval()

输出结果:

python 复制代码
Generating test split: 2 examples [00:00, 37.59 examples/s]
Overwriting default num_fewshot of my_custom_boolq from None to 0
100%|██████████| 2/2 [00:00<00:00, 558.38it/s]
Running loglikelihood requests: 100%|██████████| 4/4 [00:05<00:00,  1.39s/it]
fatal: Not a git repository (or any of the parent directories): .git
0.5
进阶玩法

示例太基础,想往深里走可以研究下。

如何喂自定义数据?

大量数据如何统计?

如何和任意模型接入?

lmEval支持的扩展有哪些?

这些都是提前预定义的,只有这几个:

bash 复制代码
m_eval[api]            # 调用在线大模型
lm_eval[multilingual]   # 多语言(中文必须)
lm_eval[vllm]          # 加速推理
lm_eval[hf]             # HuggingFace 模型
lm_eval[all]            # 一次性装全部
LmEval文档

LmEval官网git地址:
https://github.com/EleutherAI/lm-evaluation-harness

LmEval官网git文档地址:
https://github.com/EleutherAI/lm-evaluation-harness/blob/main/docs/config_files.md

datasets

datasets和自建考题哪个好?

一个天上一个地下,datasets完胜。

除非极个别情况,而且要基于你对场景,边界极其了解的情况下,自建考题可能会在某方面有优势。

所以,一般来说datasets是不二之选。

常见的数据集有哪些?

需要先按照大类分一下。然后每个大类下有几种不同的产品。

数据集-1. 数学与逻辑推理类 (你的主战场)

这类数据集主要考察模型的"智商",看它能不能像人一样进行多步思考(Chain-of-Thought)。

数据集 难度/特点 适用场景
GSM8K 入门级 (小学水平) 必测基准。考察多步推理能力。如果模型连这个都做不好,说明逻辑能力很差。
MATH 进阶级 (竞赛级) 高难度挑战。包含代数、几何、微积分等。用于测试顶尖模型(如 GPT-4, Qwen-72B)的极限推理能力。
BBH 逻辑类 包含逻辑谜题、常识推理等。考察模型在复杂语境下的逻辑一致性。
数据集-2. 综合知识与学术能力类 (全能学霸)

这类数据集考察模型的"知识储备"和"全科能力",通常包含历史、物理、法律、医学等题目。

数据集 难度/特点 适用场景
MMLU 黄金标准 (全学科) 最权威榜单。涵盖57个学科(从小学到研究生水平)。如果你想知道一个模型"聪不聪明"、"懂不懂行",看 MMLU 分数最准。
CMMLU 中文全能 中文语境必测。类似于 MMLU,但专门针对中国文化和教育体系设计。评测中文大模型(如 Qwen, ChatGLM)时必跑。
ARC 科学问答 包含小学科学题。分为"挑战版"和"简单版",用于测试基础科学常识。
数据集-3. 编程与代码能力类 (程序员助手)
数据集 难度/特点 适用场景
HumanEval 代码入门 行业标准。包含164道编程题。主要看模型能不能根据注释写出正确的 Python 函数。
MBPP 工程实战 题目更贴近实际工程场景,由程序员编写。用于测试模型解决实际代码问题的能力。
数据集-4. 语言理解与指令遵循类 (听话程度)
数据集 难度/特点 适用场景
IFEval 指令遵循 测试"听话"程度。比如"请写一段话,且不能用字母 e"。专门测试模型能不能严格遵守格式限制。
GLUE / SuperGLUE 传统NLP 老牌的阅读理解、情感分析测试。现在主要用于测试基座模型的语言基本功。
self-refine和self-consistence的区别?

最大的区别对象的数量。

self-refine(自我优化)

对当前的结果打分,看是否通过,如果不通过重新生成。这是对一个结果来说的。

self-consistence(自我一致性)

从多个结果中选出最合适的记录。这是对多个结果来说的。

相关推荐
伯恩bourne2 小时前
SpringDoc OpenAPI 3 常用注解详解
java·开发语言
ab1237682 小时前
C++ size() 与 length() 核心笔记
开发语言·c++·笔记
新知图书2 小时前
【图书推荐】《Python大数据分析师的算法手册》
python·数据分析
apcipot_rain2 小时前
Python 脚本生成目录树
开发语言·python
kyriewen113 小时前
本地存储全家桶:从localStorage到IndexedDB,把数据塞进用户浏览器
开发语言·前端·javascript·ecmascript·html5
港股研究社3 小时前
广汽年报里的隐线:组织改革、生态协同与修复起点
python
Sirius.z3 小时前
第T11周:优化器对比实验
python
loriloy3 小时前
Python 环境管理工具 pyenv-win (windows版本)
windows·python·pyenv-win
白藏y3 小时前
【C++】muduo核心类
开发语言·muduo