大模型多维度能力评估与幻觉定量评测实战(基于MMLU/BBH评测集)

文档概述

文章核心价值

  1. 系统掌握大模型多维度能力评测体系(MMLU/BBH 核心评测集)

  2. 落地开源模型的自动化评测流程(环境搭建→数据集加载→评测执行→结果分析)

  3. 掌握模型幻觉(Hallucination)的定量评估方法与落地工具

  4. 基于评测结果的模型优化方向与实战技巧

  5. 适配2025年最新开源生态的完整可复用代码体系

学习目标

  1. 理解MMLU/BBH评测集的设计逻辑与适用场景

  2. 掌握lm-evaluation-harness(主流评测框架)的本地部署与定制化使用

  3. 精通大模型幻觉定量评估的核心指标(FactScore、Faithfulness、Hallucination Rate)

  4. 能够独立完成开源大模型(如Qwen2.5、Llama3)的全维度评测

  5. 基于评测结果定位模型短板并提出优化策略

一、大模型能力评估体系概述

1.1 核心评测集定位

大模型的能力评估需覆盖知识广度、推理深度、任务适配性三大维度,MMLU和BBH是目前工业界与学术界最主流的评测基准:

评测集 全称 核心能力维度 数据规模 适用场景
MMLU Massive Multitask Language Understanding 通用知识、学科能力(57个科目) 14k+选择题 基础能力摸底、跨学科知识评估
BBH Big Bench Hard 复杂推理、逻辑思维、任务拆解 200+子任务 高阶推理能力、复杂任务处理评估
TruthfulQA - 事实准确性、反幻觉能力 817个问题 幻觉初步筛查
FactScore - 生成内容事实一致性 自定义文本 幻觉定量评估

1.2 模型幻觉的核心问题

幻觉(Hallucination)是指大模型生成的文本在语法和语义上看似通顺合理,但内容与事实不符、逻辑自相矛盾或与其输入源(Prompt/Context)不一致的现象。在定量评估中,我们不再仅仅将其视为“错误”,而是将其细分为多维度的技术挑战。

1.2.1 幻觉分类学(Taxonomy)树形图

为了精准量化幻觉,必须明确幻觉发生的层级与类型。以下是基于工业界主流标准的幻觉分类体系:

代码段

graph TD
    A[大模型幻觉 Hallucination] --> B(按照与源头的冲突分类)
    A --> C(按照生成机制分类)
    
    B --> B1[忠实度缺失 Unfaithfulness]
    B1 --> B11[输入冲突 Input-Conflicting]
    B11 -->|忽略Prompt约束| B111[指令遵循失败]
    B11 -->|与上下文矛盾| B112[上下文不一致]
    B1 --> B12[逻辑自洽性缺失 Self-Inconsistency]
    B12 -->|前后文矛盾| B121[生成内容内部逻辑冲突]

    B --> B2[事实性错误 Factual Error]
    B2 --> B21[世界知识冲突 World-Knowledge Conflicting]
    B21 -->|张冠李戴| B211[实体关系错误]
    B21 -->|无中生有| B212[完全捏造事实]
    B21 -->|时效性错误| B213[引用过时信息]

    C --> C1[外在幻觉 Extrinsic]
    C1 -->|无法验证| C11[生成内容既无法被证实也无法被证伪]
    C --> C2[内在幻觉 Intrinsic]
    C2 -->|直接冲突| C21[生成内容直接违背现有知识源]
1.2.2 定量评估的三大核心维度

针对上述分类,幻觉的定量评估(如使用FactScore)需重点解决以下三大核心问题:

1. 事实一致性与粒度(Factual Consistency & Granularity)

  • 问题定义:生成内容与客观事实的匹配程度。
  • 评估难点:幻觉往往潜伏在细节中。
    • 实体级(Entity Level):人物、地点、时间错误(例如:将“李白”误认为是“宋朝诗人”)。
    • 关系级(Relation Level):实体间的动作或属性错误(例如:虽然人物和电影都存在,但该人物并未参演该电影)。
  • 量化目标:计算原子级事实(Atomic Facts)的准确率,而非整段文本的相似度。

2. 来源可追溯性与证据归因(Source Traceability & Attribution)

  • 问题定义:模型生成的每一个知识点,是否都能对应到可信的数据源(Reference)。
  • 评估难点:模型可能生成正确的废话,或者引用不存在的文献(引用幻觉)。
  • 量化目标Citation Recall(引用召回率)与Citation Precision(引用精确率)。即模型不仅要回答正确,还必须能正确指出“我之所以这么说,是因为依据了文档X的第Y段”。

3. 忠实度与指令依从(Faithfulness vs. Instruction Following)

  • 问题定义:在RAG(检索增强生成)场景下,模型是否严格基于检索到的上下文回答,而不是通过内部记忆“脑补”。
  • 评估难点:当检索到的上下文是错误的,模型应该“忠实地”回答错误信息,还是利用内部知识修正?(通常在RAG评测中,优先考察对上下文的忠实度)。
  • 量化目标:检测Hallucination Rate(幻觉率),即生成内容中未被上下文支撑的信息比例。
1.2.3 必须关注的“雪球效应”

在长文本生成(Long-context Generation)中,早期的轻微幻觉会导致后续推理基于错误前提进行,从而产生逻辑级联错误(Snowballing Hallucination)。BBH评测集中的推理任务正是为了检测这种在复杂逻辑链条中保持事实一致性的能力。

1.3 评测技术栈选型(2025最新)

组件 选型 版本 核心优势
评测框架 lm-evaluation-harness 0.4.9 支持MMLU/BBH等100+评测集,适配主流开源模型
模型加载 transformers 4.41.2 支持Qwen2.5/Llama3等最新开源模型
加速库 vLLM 0.4.2 大幅提升评测速度(吞吐量提升5-10倍)
幻觉评估工具 factscore 0.4.0 主流的幻觉定量评估工具,支持中文适配
环境管理 Conda 23.10.0 隔离评测环境,避免依赖冲突
计算框架 PyTorch 2.5.1 适配最新GPU架构,支持混合精度推理

二、评测环境搭建(2025最新)

2.1 环境要求

维度 最低配置 推荐配置 说明
操作系统 Ubuntu 20.04+/Windows 11/macOS 14+ Ubuntu 22.04 Linux对CUDA支持最优
CPU 8核 16核+ 数据预处理/评测调度
GPU NVIDIA RTX 3090 (24GB) NVIDIA A100 (40GB) 7B模型评测需24GB+显存,14B需40GB+
CUDA 12.1 12.4 适配PyTorch 2.5+
Python 3.10 3.10 lm-evaluation-harness推荐版本

2.2 环境配置步骤

2.2.1 创建独立Conda环境

# 创建评测专属环境
conda create -n llm-eval python=3.10 -y
# 激活环境
conda activate llm-eval
2.2.2 安装核心依赖

# 基础依赖
pip install numpy==1.26.4 pandas==2.2.2 scipy==1.13.1
pip install tqdm==4.66.4 rich==13.7.1 python-dotenv==1.0.1

# PyTorch(适配CUDA 12.4)
pip install torch==2.5.1 torchvision==0.20.1 torchaudio==2.5.1 --index-url https://download.pytorch.org/whl/cu124

# 模型加载与推理
pip install transformers==4.41.2 accelerate==0.31.0 peft==0.11.1
pip install vllm==0.4.2  # 高性能推理引擎(可选,大幅提升速度)

# 评测框架(安装最新版lm-evaluation-harness)
git clone https://github.com/EleutherAI/lm-evaluation-harness.git
cd lm-evaluation-harness
pip install -e .[all]
cd ..

# 幻觉评估工具
pip install factscore==0.4.0
pip install rouge-score==0.1.2 bert-score==0.3.13  # 辅助评估指标

# 中文适配依赖
pip install jieba==0.42.1 zhconv==1.4.1
pip install modelscope==1.14.0  # 中文数据集下载
2.2.3 环境验证

# 验证环境配置
import torch
import transformers
import lm_eval
import vllm

print(f"PyTorch版本: {torch.__version__}")
print(f"CUDA可用: {torch.cuda.is_available()}")
print(f"Transformers版本: {transformers.__version__}")
print(f"lm-eval版本: {lm_eval.__version__}")
print(f"vLLM版本: {vllm.__version__}")

# 验证GPU配置
if torch.cuda.is_available():
    print(f"GPU名称: {torch.cuda.get_device_name(0)}")
    print(f"GPU显存: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB")
else:
    print("未检测到GPU,建议使用GPU进行评测")

三、MMLU/BBH评测集实战评测

3.1 评测框架核心原理与架构详解

lm-evaluation-harness 之所以成为工业界事实标准,是因为它解决了一个核心难题:如何公平地对比不同架构、不同Tokenizer的模型? 它通过一套高度抽象的流水线,将异构的模型与标准化的任务解耦。

3.1.1 评测流水线架构图

以下是该框架内部的各种组件如何协同工作的拓扑结构:

代码段

graph TD
    User[用户指令 CLI/API] --> Orchestrator[核心调度器 Evaluator]
    
    subgraph Data_Pipeline [数据处理流水线]
        TaskRegistry[任务注册中心] -->|加载YAML配置| TaskDef[MMLU/BBH 任务定义]
        TaskDef -->|构建Few-shot| ContextBuilder[上下文构造器]
        ContextBuilder -->|doc_to_text| Requests[标准化请求对象]
    end
    
    subgraph Model_Bridge [模型适配层]
        Orchestrator -->|分发请求| Model[LM 抽象基类]
        Model -->|HFLM| LocalInfer[本地推理 HFLM/vLLM]
        Model -->|API| RemoteInfer[API调用 OpenAI/Anthropic]
        LocalInfer -->|计算Logits/生成文本| RawOutput[原始输出]
    end
    
    subgraph Scoring_System [评分与后处理]
        RawOutput -->|解析| Filter[过滤器 Regex/Stop-seq]
        Filter -->|计算| Metric[指标计算器]
        Metric -->|acc/acc_norm| FinalScore[最终得分]
    end

    Orchestrator --> Data_Pipeline
    Requests --> Model
    RawOutput --> Scoring_System
3.1.2 核心组件深度解析

框架通过三大核心抽象层实现标准化评测:

1. 任务抽象层 (Task Abstraction)

  • 核心作用:将千奇百怪的数据集(CSV, JSON, HF Datasets)统一为标准格式。
  • 关键机制
    • doc_to_text:将数据行转换为模型输入的 Prompt(例如:“Question: … Answer:”)。
    • doc_to_target:定义标准答案的格式。
    • Few-shot 采样:自动从训练集中抽取 N 个样本作为上下文,确保所有模型看到相同的示例,避免因 Prompt 差异导致的评分偏差。

2. 两种截然不同的推理模式 (The Two Inference Modes) 这是理解 MMLU 和 BBH 评测差异的关键:

  • 模式 A:似然度评分 (Loglikelihood Scoring) —— 适用于 MMLU
    • 原理:模型不需要生成文本。框架计算模型对选项 A、B、C、D 的生成概率(Log Probabilities)。
    • 优势:消除了“模型回答正确但格式不对”的干扰(例如模型回答"选A" vs “答案是A”)。
    • 指标acc_norm(Byte-length normalized accuracy),通过归一化消除 Tokenizer 对长选项的歧视。
  • 模式 B:生成式评测 (Generative Evaluation) —— 适用于 GSM8K/BBH
    • 原理:模型自由生成文本(generate_until),直到遇到停止符。
    • 后处理:使用正则表达式(Regex)提取答案,或通过 exact_match 进行严格比对。
    • 挑战:对模型的指令遵循能力要求更高。

3. 加速与缓存机制 (Acceleration & Caching)

  • 批量推理 (Batching):自动将多个请求打包(Padding),最大化 GPU 利用率。
  • 结果缓存 (Caching):基于 Prompt 的哈希值缓存推理结果。如果评测中断,重启时可直接跳过已跑过的样本,这对于 14k+ 题目的 MMLU 评测至关重要。
3.1.3 为什么需要标准化接口?

在没有框架之前,评测往往存在 “Prompt Hacking” 现象——通过微调 Prompt 措辞让特定模型得分虚高。lm-evaluation-harness 强制使用社区公认的 Prompt Template,确保了:

  1. 可复现性:任何人运行代码,输入给模型的 Prompt 都是完全一致的。
  2. Tokenizer 公平性:通过 acc_norm 指标,防止某些 Tokenizer 因为将单词切分得更碎而导致概率计算吃亏。

3.2 数据集准备

3.2.1 自动下载(推荐)

# 数据集自动下载脚本
import lm_eval.datasets

# 下载MMLU数据集(自动缓存到~/.cache/lm_eval/)
mmlu_tasks = [
    "mmlu_anatomy", "mmlu_astronomy", "mmlu_biology", "mmlu_business_ethics",
    "mmlu_clinical_knowledge", "mmlu_college_biology", "mmlu_college_chemistry",
    "mmlu_college_computer_science", "mmlu_college_mathematics", "mmlu_college_medicine",
    "mmlu_college_physics", "mmlu_computer_security", "mmlu_econometrics", "mmlu_electrical_engineering",
    "mmlu_elementary_mathematics", "mmlu_formal_logic", "mmlu_global_facts", "mmlu_high_school_biology",
    "mmlu_high_school_chemistry", "mmlu_high_school_computer_science", "mmlu_high_school_european_history",
    "mmlu_high_school_geography", "mmlu_high_school_government_and_politics", "mmlu_high_school_macroeconomics",
    "mmlu_high_school_mathematics", "mmlu_high_school_microeconomics", "mmlu_high_school_physics",
    "mmlu_high_school_psychology", "mmlu_high_school_statistics", "mmlu_high_school_us_history",
    "mmlu_high_school_world_history", "mmlu_human_aging", "mmlu_human_sexuality", "mmlu_international_law",
    "mmlu_jurisprudence", "mmlu_logical_fallacies", "mmlu_machine_learning", "mmlu_management",
    "mmlu_marketing", "mmlu_medical_genetics", "mmlu_miscellaneous", "mmlu_moral_disputes",
    "mmlu_moral_scenarios", "mmlu_nutrition", "mmlu_philosophy", "mmlu_prehistory",
    "mmlu_professional_accounting", "mmlu_professional_law", "mmlu_professional_medicine",
    "mmlu_professional_psychology", "mmlu_public_relations", "mmlu_religious_studies",
    "mmlu_security_studies", "mmlu_sociology", "mmlu_us_foreign_policy", "mmlu_virology",
    "mmlu_world_religions"
]

# 下载BBH数据集
bbh_tasks = [
    "bbh_boolean_expressions", "bbh_causal_judgment", "bbh_date_understanding",
    "bbh_disambiguation_qa", "bbh_formal_fallacies", "bbh_gender_pronoun_resolution",
    "bbh_goal_step_writing", "bbh_judgmental_reasoning", "bbh_logical_deduction_five_objects",
    "bbh_logical_deduction_seven_objects", "bbh_logical_deduction_three_objects",
    "bbh_movie_recommendation", "bbh_multiple_choice", "bbh_navigation", "bbh_object_counting",
    "bbh_penguins_in_a_table", "bbh_phrase_bias", "bbh_reasoning_about_colored_objects",
    "bbh_ruin_names", "bbh_salient_translation_error_detection", "bbh_snarks",
    "bbh_sports_understanding", "bbh_temporal_sequences", "bbh_trustworthiness",
    "bbh_web_of_lies", "bbh_written_spanish"
]

# 验证数据集加载
for task in ["mmlu", "bbh"]:
    try:
        lm_eval.tasks.get_task(task)
        print(f"{task} 数据集加载成功")
    except Exception as e:
        print(f"{task} 数据集加载失败: {e}")
3.2.2 手动下载(备用)

若自动下载失败,可从Hugging Face Datasets手动下载:


# 手动下载MMLU数据集
git clone https://huggingface.co/datasets/cais/mmlu
# 手动下载BBH数据集
git clone https://huggingface.co/datasets/suzgunmirac/BigBenchHard

3.3 开源模型评测实战(以Qwen2.5-7B-Instruct为例)

3.3.0 评测流水线架构图

在运行代码之前,理解数据如何在模型(Model)、**评测框架(Harness)结果(Result)**之间流动至关重要。

大模型评测实战流程图 (Evaluation Pipeline)
│
├── 【1. 配置与准备阶段】(Configuration)
│   ├── 用户指令: python eval.py --model_path "Qwen2.5" --tasks "mmlu,bbh"
│   ├── 环境变量: .env (加载 API Key 或 缓存路径)
│   └── 任务注册表: lm_eval.tasks (自动拉取 MMLU 57个学科/BBH 任务定义)
│
▼
[2. 评测引擎初始化] (Engine Initialization) <核心分歧点> ────────┐
│                                                              │
├── 路径 A: 通用兼容模式 (对应 3.3.1 脚本)                       │
│   ├── <核心类>: HFLM (lm-evaluation-harness 内置封装)         │
│   ├── <底层调用>: AutoModelForCausalLM (Transformers)         │
│   ├── <优势>: 兼容性最强,支持 8bit/4bit 量化,适配所有 HF 模型  │
│   └── <适用>: 资源受限环境、新模型架构调试                      │
│                                                              │
├── 路径 B: 高性能加速模式 (对应 3.3.2 脚本)                       │
│   ├── <核心类>: vLLM Engine (PageAttention 技术)              │
│   ├── <底层调用>: CUDA Kernel 优化推理                         │
│   ├── <优势>: 吞吐量提升 5-10 倍,显存利用率极大优化            │
│   └── <适用>: 7B/14B+ 模型全量评测,生产环境批量测试             │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[3. 数据流与推理执行] (Execution Loop) ────────────────────────┐
│                                                              │
├── 步骤 ①: 提示词构建 (Prompt Construction)                    │
│   ├── Few-shot 采样: 从 Dev 集随机抽取 5 个样本 (In-Context)    │
│   └── 格式化: "Question: ... Answer:" (标准化模版,避免偏差)     │
│                                                              │
├── 步骤 ②: 模型推理 (Inference & Scoring)                      │
│   ├── 判别式 (MMLU): 计算选项 A/B/C/D 的 Logits (概率)          │
│   │   > 不生成文本,直接比较 P(A) vs P(B)... 更稳定             │
│   └── 生成式 (BBH): Generate() -> 正则提取 -> Exact Match      │
│       > 让模型生成推理过程 (CoT),测试逻辑思维能力               │
│                                                              │
└── > 输出: 原始结果日志 (eval_results_raw.json)                  │
└──────────────────────────────────────────────────────────────┘
3.3.1 基础评测脚本(CPU/GPU通用)

"""
MMLU/BBH评测核心脚本
支持Qwen2.5/Llama3等开源模型
"""
import os
import json
import argparse
import torch
from dotenv import load_dotenv
import lm_eval
from lm_eval import evaluator, tasks
from lm_eval.models.huggingface import HFLM

# 加载环境变量
load_dotenv()

# 配置参数
parser = argparse.ArgumentParser(description="大模型MMLU/BBH评测脚本")
parser.add_argument("--model_path", type=str, default="/path/to/Qwen2.5-7B-Instruct", help="本地模型路径")
parser.add_argument("--tasks", type=str, default="mmlu,bbh", help="评测任务(mmlu/bbh/all)")
parser.add_argument("--batch_size", type=int, default=8, help="推理批次大小")
parser.add_argument("--output_dir", type=str, default="./eval_results", help="评测结果输出目录")
parser.add_argument("--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu", help="运行设备")
parser.add_argument("--max_samples", type=int, default=None, help="每个任务评测样本数(None表示全部)")

args = parser.parse_args()

# 创建输出目录
os.makedirs(args.output_dir, exist_ok=True)

# 1. 初始化模型
def init_model(model_path, device):
    """初始化HuggingFace模型"""
    model = HFLM(
        pretrained=model_path,
        device=device,
        batch_size=args.batch_size,
        load_in_8bit=False,  # 低显存可开启8bit量化
        load_in_4bit=False,  # 极低显存可开启4bit量化
        trust_remote_code=True,  # 加载自定义模型代码(Qwen2.5需要)
        torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
        max_length=2048,  # 最大序列长度
    )
    return model

# 2. 选择评测任务
def get_selected_tasks(task_str):
    """根据输入选择评测任务"""
    all_tasks = []
    if "mmlu" in task_str.lower():
        # 获取所有MMLU子任务
        mmlu_tasks = [t for t in tasks.ALL_TASKS if t.startswith("mmlu_")]
        all_tasks.extend(mmlu_tasks)
    if "bbh" in task_str.lower():
        # 获取所有BBH子任务
        bbh_tasks = [t for t in tasks.ALL_TASKS if t.startswith("bbh_")]
        all_tasks.extend(bbh_tasks)
    if "all" in task_str.lower():
        all_tasks = tasks.ALL_TASKS
    
    return all_tasks

# 3. 执行评测
def run_evaluation():
    # 初始化模型
    print(f"初始化模型: {args.model_path}")
    model = init_model(args.model_path, args.device)
    
    # 获取评测任务
    selected_tasks = get_selected_tasks(args.tasks)
    print(f"选定评测任务数: {len(selected_tasks)}")
    print(f"任务列表: {selected_tasks[:5]}...")  # 打印前5个任务
    
    # 执行评测
    print("开始评测...")
    results = evaluator.simple_evaluate(
        model=model,
        tasks=selected_tasks,
        num_fewshot=5,  # MMLU/BBH标准评测使用5-shot
        batch_size=args.batch_size,
        max_samples=args.max_samples,
        device=args.device,
        verbose=True,
    )
    
    # 保存原始结果
    results_path = os.path.join(args.output_dir, "eval_results_raw.json")
    with open(results_path, "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=4)
    print(f"原始评测结果已保存至: {results_path}")
    
    # 生成汇总报告
    generate_summary_report(results)
    
    return results

# 4. 生成汇总报告
def generate_summary_report(results):
    """生成易读的评测汇总报告"""
    summary = {
        "model": args.model_path.split("/")[-1],
        "total_tasks": len(results["results"]),
        "mmlu_average": None,
        "bbh_average": None,
        "task_details": {}
    }
    
    # 计算MMLU和BBH平均分
    mmlu_scores = []
    bbh_scores = []
    for task, metrics in results["results"].items():
        # 获取准确率(MMLU/BBH主要指标)
        acc = metrics.get("acc", metrics.get("exact_match", 0))
        summary["task_details"][task] = {
            "accuracy": round(acc * 100, 2),
            "samples": metrics.get("n", 0)
        }
        
        if task.startswith("mmlu_"):
            mmlu_scores.append(acc)
        elif task.startswith("bbh_"):
            bbh_scores.append(acc)
    
    # 计算平均分
    if mmlu_scores:
        summary["mmlu_average"] = round(sum(mmlu_scores) / len(mmlu_scores) * 100, 2)
    if bbh_scores:
        summary["bbh_average"] = round(sum(bbh_scores) / len(bbh_scores) * 100, 2)
    
    # 保存汇总报告
    summary_path = os.path.join(args.output_dir, "eval_summary.json")
    with open(summary_path, "w", encoding="utf-8") as f:
        json.dump(summary, f, ensure_ascii=False, indent=4)
    print(f"评测汇总报告已保存至: {summary_path}")
    
    # 打印汇总结果
    print("\n=== 评测汇总 ===")
    print(f"模型: {summary['model']}")
    print(f"评测任务总数: {summary['total_tasks']}")
    print(f"MMLU平均准确率: {summary['mmlu_average']}%")
    print(f"BBH平均准确率: {summary['bbh_average']}%")
    
    # 打印表现最好/最差的任务
    sorted_tasks = sorted(summary["task_details"].items(), key=lambda x: x[1]["accuracy"], reverse=True)
    print("\n表现最好的5个任务:")
    for task, metrics in sorted_tasks[:5]:
        print(f"  {task}: {metrics['accuracy']}%")
    
    print("\n表现最差的5个任务:")
    for task, metrics in sorted_tasks[-5:]:
        print(f"  {task}: {metrics['accuracy']}%")

# 主执行
if __name__ == "__main__":
    run_evaluation()

在上述代码中,各组件协作逻辑如下:

  1. 任务分发器 (get_selected_tasks)
    • 扮演“图书管理员”角色。它从 lm_eval.tasks 巨大的任务库中,根据用户指令(如 “mmlu”)精准筛选出对应的 57 个子任务 ID。如果不进行筛选,框架默认会加载数千个任务,导致内存溢出。
  2. 模型适配器 (HFLM vs vLLM)
    • HFLM:是 lm-evaluation-harness 提供的标准接口,它把 HuggingFace 模型包装成评测框架能听懂的统一对象。它自动处理了 padding、truncation 和 device placement。
    • vLLM:是“外挂引擎”。在 3.3.2 脚本中,我们跳过了 harness 的标准推理循环,直接用 vLLM 进行极速推理,然后手动计算准确率。这是一种**“用空间换时间”**的策略,通过手动写代码换取 10 倍的速度提升。
  3. Few-shot 构建机制
    • 代码中的 num_fewshot=5 是评测公平性的关键。它强制模型在回答当前问题前,先“看”5个带答案的例子。这模拟了模型在拥有短期记忆(Context)下的表现,能够显著提升 MMLU 分数,是学术界对比模型的标准设定。
3.3.2 高性能评测(vLLM加速)

对于大模型(7B/14B),推荐使用vLLM加速评测(吞吐量提升5-10倍):


"""
基于vLLM的高性能MMLU/BBH评测脚本
"""
import os
import json
import torch
from vllm import LLM, SamplingParams
import lm_eval
from lm_eval import tasks
from tqdm import tqdm

# 配置
MODEL_PATH = "/path/to/Qwen2.5-7B-Instruct"
OUTPUT_DIR = "./eval_results_vllm"
BATCH_SIZE = 32  # vLLM支持更大批次
NUM_FEWSHOT = 5
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"

# 创建输出目录
os.makedirs(OUTPUT_DIR, exist_ok=True)

# 1. 初始化vLLM模型
print(f"加载vLLM模型: {MODEL_PATH}")
llm = LLM(
    model=MODEL_PATH,
    tensor_parallel_size=torch.cuda.device_count(),  # 多卡并行
    gpu_memory_utilization=0.9,  # 显存利用率
    max_num_batched_tokens=8192,
    trust_remote_code=True,
    dtype="bfloat16"
)

# 采样参数(评测用确定性采样)
sampling_params = SamplingParams(
    temperature=0.0,
    max_tokens=10,
    top_p=1.0,
    stop=["\n", "###"]
)

# 2. 加载MMLU任务
task = lm_eval.tasks.get_task("mmlu")
task.build_all()

# 3. 数据预处理
def prepare_fewshot_examples(dataset, num_fewshot=5):
    """准备5-shot示例"""
    fewshot_examples = []
    for i in range(num_fewshot):
        ex = dataset[i]
        fewshot_examples.append(
            f"Question: {ex['question']}\n"
            f"A. {ex['choices'][0]}\n"
            f"B. {ex['choices'][1]}\n"
            f"C. {ex['choices'][2]}\n"
            f"D. {ex['choices'][3]}\n"
            f"Answer: {ex['answer']}\n"
        )
    return "\n".join(fewshot_examples)

# 4. 执行评测
def run_vllm_evaluation():
    # 获取MMLU测试集
    test_set = task.dataset["test"]
    results = {"correct": 0, "total": 0, "task_details": []}
    
    # 准备few-shot示例
    fewshot_text = prepare_fewshot_examples(task.dataset["validation"], NUM_FEWSHOT)
    
    # 批量推理
    batches = [test_set[i:i+BATCH_SIZE] for i in range(0, len(test_set), BATCH_SIZE)]
    
    for batch in tqdm(batches, desc="评测进度"):
        # 构建提示词
        prompts = []
        for ex in batch:
            prompt = (
                f"{fewshot_text}\n"
                f"Question: {ex['question']}\n"
                f"A. {ex['choices'][0]}\n"
                f"B. {ex['choices'][1]}\n"
                f"C. {ex['choices'][2]}\n"
                f"D. {ex['choices'][3]}\n"
                f"Answer:"
            )
            prompts.append(prompt)
        
        # vLLM批量推理
        outputs = llm.generate(prompts, sampling_params)
        
        # 解析结果
        for ex, output in zip(batch, outputs):
            pred = output.outputs[0].text.strip().upper()
            true_answer = ex['answer'].upper()
            is_correct = pred == true_answer
            
            results["total"] += 1
            if is_correct:
                results["correct"] += 1
            
            results["task_details"].append({
                "question": ex["question"],
                "choices": ex["choices"],
                "true_answer": true_answer,
                "pred_answer": pred,
                "is_correct": is_correct
            })
    
    # 计算准确率
    results["accuracy"] = round(results["correct"] / results["total"] * 100, 2)
    
    # 保存结果
    with open(os.path.join(OUTPUT_DIR, "vllm_mmlu_results.json"), "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=4)
    
    # 打印结果
    print(f"\n=== vLLM评测结果 ===")
    print(f"总样本数: {results['total']}")
    print(f"正确数: {results['correct']}")
    print(f"准确率: {results['accuracy']}%")

if __name__ == "__main__":
    run_vllm_evaluation()

3.4 评测结果分析

3.4.0 结果处理流程图

评测不仅是跑分,更是为了发现模型短板。可视化脚本的工作流如下:

结果分析流水线 (Analysis Pipeline)
│
├── [1. 数据加载与清洗] (Data Loading)
│   ├── <输入>: eval_summary.json (3.3 步骤生成)
│   └── <解析>: 提取 metrics["acc"] 或 metrics["exact_match"]
│
▼
[2. 维度重组 (Re-grouping)] ───────────────────────────────────┐
│   (将扁平的任务列表重组为人类可读的知识体系)                     │
│                                                              │
├── 分支 A: MMLU 知识图谱                                       │
│   ├── STEM (理科): Math, Physics, Chemistry...               │
│   ├── Humanities (文科): History, Law, Philosophy...         │
│   └── Social Sciences (社科): Economics, Psychology...       │
│                                                              │
├── 分支 B: BBH 能力图谱                                        │
│   ├── Logical (逻辑): Boolean expressions, Syllogisms...     │
│   ├── Algorithmic (算法): Object counting, Sorting...        │
│   └── Language (语言): Disambiguation, Translation...        │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[3. 决策辅助输出] (Decision Support Output) ───────────────────┐
│                                                              │
├── 可视化 (Visualization):                                     │
│   ├── Matplotlib 绘制水平条形图 (Bar Chart)                    │
│   ├── <色阶映射>: 红色(低分) -> 黄色(及格) -> 绿色(优秀)         │
│   └── > 作用: 一眼识别“偏科”现象                               │
│                                                              │
├── 短板诊断 (Weakness Diagnosis):                              │
│   ├── <算法>: 筛选 Accuracy < (Average - 10%) 的任务           │
│   └── > 作用: 指导后续微调 (SFT) 的数据配比方向                 │
└──────────────────────────────────────────────────────────────┘
3.4.1 结果可视化

"""
评测结果可视化脚本
"""
import json
import matplotlib.pyplot as plt
import numpy as np
from collections import defaultdict

# 设置中文字体
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False

# 加载评测结果
with open("./eval_results/eval_summary.json", "r", encoding="utf-8") as f:
    summary = json.load(f)

# 1. MMLU/BBH分类统计
task_categories = defaultdict(list)
for task, metrics in summary["task_details"].items():
    if task.startswith("mmlu_"):
        # 提取MMLU学科分类
        category = task.replace("mmlu_", "").replace("_", " ").title()
        task_categories["MMLU"].append((category, metrics["accuracy"]))
    elif task.startswith("bbh_"):
        # 提取BBH任务分类
        category = task.replace("bbh_", "").replace("_", " ").title()
        task_categories["BBH"].append((category, metrics["accuracy"]))

# 2. 绘制MMLU学科准确率分布图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))

# MMLU子图
mmlu_data = task_categories["MMLU"]
mmlu_categories = [x[0] for x in mmlu_data]
mmlu_scores = [x[1] for x in mmlu_data]

# 按分数排序
sorted_idx = np.argsort(mmlu_scores)
mmlu_categories = [mmlu_categories[i] for i in sorted_idx]
mmlu_scores = [mmlu_scores[i] for i in sorted_idx]

# 绘制水平条形图
colors = plt.cm.RdYlGn(np.array(mmlu_scores) / 100)
ax1.barh(mmlu_categories, mmlu_scores, color=colors)
ax1.set_xlabel("准确率 (%)")
ax1.set_title(f"MMLU各学科准确率 (平均: {summary['mmlu_average']}%)", fontsize=14)
ax1.axvline(x=summary['mmlu_average'], color='red', linestyle='--', label=f'平均分: {summary["mmlu_average"]}%')
ax1.legend()

# BBH子图
bbh_data = task_categories["BBH"]
bbh_categories = [x[0] for x in bbh_data]
bbh_scores = [x[1] for x in bbh_data]

# 按分数排序
sorted_idx = np.argsort(bbh_scores)
bbh_categories = [bbh_categories[i] for i in sorted_idx]
bbh_scores = [bbh_scores[i] for i in sorted_idx]

# 绘制水平条形图
colors = plt.cm.RdYlGn(np.array(bbh_scores) / 100)
ax2.barh(bbh_categories, bbh_scores, color=colors)
ax2.set_xlabel("准确率 (%)")
ax2.set_title(f"BBH各任务准确率 (平均: {summary['bbh_average']}%)", fontsize=14)
ax2.axvline(x=summary['bbh_average'], color='red', linestyle='--', label=f'平均分: {summary["bbh_average"]}%')
ax2.legend()

# 调整布局
plt.tight_layout()
plt.savefig(os.path.join("./eval_results", "eval_visualization.png"), dpi=300, bbox_inches="tight")
plt.show()

# 3. 生成能力短板分析
print("\n=== 模型能力短板分析 ===")
print(f"MMLU薄弱学科(<{summary['mmlu_average']-10}%):")
for task, metrics in summary["task_details"].items():
    if task.startswith("mmlu_") and metrics["accuracy"] < summary["mmlu_average"] - 10:
        print(f"  {task.replace('mmlu_', '')}: {metrics['accuracy']}%")

print(f"\nBBH薄弱任务(<{summary['bbh_average']-10}%):")
for task, metrics in summary["task_details"].items():
    if task.startswith("bbh_") and metrics["accuracy"] < summary["bbh_average"] - 10:
        print(f"  {task.replace('bbh_', '')}: {metrics['accuracy']}%")

四、模型幻觉(Hallucination)定量评估

4.1 幻觉评估核心指标

指标 计算方式 含义 取值范围 优劣势
FactScore 生成内容中事实正确的比例 事实准确性 0-1 最常用,计算成本中等
Faithfulness 生成内容与参考文档的一致性 忠实度 0-1 需参考文档,精度高
Hallucination Rate 幻觉语句数/总语句数 幻觉发生率 0-1 直观,需人工标注辅助
Rouge-L 生成内容与参考的文本相似度 文本匹配度 0-1 快速,不考虑事实正确性
BERTScore 语义相似度 语义匹配度 0-1 比Rouge更鲁棒

4.2 FactScore幻觉评估实战

4.2.1 环境准备

# 安装FactScore依赖
pip install factscore==0.4.0
pip install wikipedia-api==0.5.8  # 事实验证依赖
pip install nltk==3.8.1
pip install spacy==3.7.4
python -m spacy download en_core_web_sm
4.2.2 核心评测代码
FactScore 幻觉定量评测流程图
│
├── 【1. 评测准备阶段】 (Preparation Phase)
│   ├── <数据集构建>: prepare_eval_data()
│   │   ├── Question: "2024奥运会哪里举办?"
│   │   └── Reference: "法国巴黎..." (金标准事实/Ground Truth)
│   └── <模型加载>: load_model_and_tokenizer()
│       └── Target Model: Qwen2.5-7B (待测对象)
│
▼
[2. 待测模型推理阶段] (Candidate Generation) ──────────────────┐
│                                                              │
├── 输入处理 (Input Processing):                                │
│   ├── <提示词模板>: "请准确回答...不要编造"                    │
│   └── <参数控制>: Temperature=0.1 (抑制随机性,模拟严谨模式)    │
│                                                              │
├── 模型生成 (Inference):                                       │
│   ├── <输入>: Prompt Tokens                                   │
│   └── > 输出: Model Response "2024奥运会在马德里举办" (假设幻觉)│
│                                                              │
└── > 中间产物: responses.json (包含 Q, Reference, Response)    │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[3. FactScore 核心核查阶段] (Verification Phase) <★ 核心逻辑> ─┐
│                                                              │
├── A. 事实原子化 (Atomic Fact Decomposition)                   │
│   ├── <组件>: FactScore.get_score()                           │
│   ├── <动作>: 将长文本拆解为独立的原子声明 (Atomic Facts)       │
│   │   ├── 句子: "2024奥运会在马德里举办"                        │
│   │   └── 拆解: Fact 1: [奥运会-举办地-马德里] (可证伪单元)     │
│   │                                                          │
├── B. 事实验证 (Fact Verification)                             │
│   ├── <知识源>: Reference Text (用户提供的参考答案)             │
│   ├── <裁判>: GPT-4o (通过 API 调用)                          │
│   │   ├── 指令: "根据参考文本,'马德里举办奥运会'是否属实?"     │
│   │   └── 判定: False (检测到幻觉)                            │
│   │                                                          │
└── > 输出结果: FactScore=0.0 (该样本幻觉率 100%)                │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[4. 结果聚合与分析] (Aggregation & Analysis) ──────────────────┐
│                                                              │
├── <统计>: sum(scores) / len(samples)                         │
└── > 最终报告: category_analysis (区分常识/专业/时效性幻觉)      │
└──────────────────────────────────────────────────────────────┘
2. 代码模块协作深度解析

这段代码通过三个核心函数的协作,模拟了人类进行事实查核(Fact Checking)的过程:

1. 考题与考生准备 (prepare_eval_data & generate_responses)
  • 协作逻辑
    • prepare_eval_data 充当出题人,它不仅提供问题,还必须提供“标准答案”(Reference)。没有 Reference,幻觉评测就失去了锚点。
    • generate_responses 充当考生。这里的一个关键细节是 temperature=0.1。在幻觉评测中,我们通常希望评估模型“最确信”的知识,而不是它的创造力,因此需要通过低温采样由“右脑模式”切换到“左脑模式”。
2. 显微镜式评估 (run_factscore_evaluation) 这是整个脚本的灵魂。它解决了“文本相似度(Rouge/BLEU)无法衡量真实性”的问题。
  • 原子化(Decomposition)
    • 传统的评估看重字面重合(Overlap)。但模型如果回答“巴黎没有举办2024奥运会”,字面重合度很高,但事实完全相反。
    • FactScore 会调用裁判模型(如 GPT-4),将复杂的复合句拆解为单条事实。例如:“Python是一门由Guido开发的编译型语言” 会被拆为:
      1. Python由Guido开发 (True)
      2. Python是编译型语言 (False)
    • 最终得分为 0.5,精准捕捉了“半真半假”的幻觉。
3. 裁判降级机制 (manual_hallucination_evaluation)
  • 协作逻辑
    • 由于 FactScore 强依赖 OpenAI API 进行高智商拆解,当网络不可用或 Key 耗尽时,代码会自动降级到 manual 模式。
    • 备用方案使用简单的**关键词重叠率(Jieba Overlap)**作为替代指标。虽然精度不如 GPT-4 裁判,但能保证评测流程不中断(Fail-safe Design)。
3. 为什么需要 Reference(参考事实)?

在代码配置中,fs.get_score(..., refs=refs) 是关键一步。FactScore 支持两种模式:

  1. 基于知识库(Knowledge-based):不提供 ref,让裁判自己去查 Wikipedia(代码中 wikipedia-api 的作用)。
  2. 基于上下文(Context-based):提供 ref,强制裁判只根据这段文字判断。
  • 本脚本采用模式 2:通过 refs=refs 传入,这样可以评估 RAG 系统中的“忠实度”,或者在私有领域(如企业内部文档)评测模型是否产生了领域幻觉。

"""
FactScore幻觉定量评估脚本
支持中文开源模型(Qwen2.5/Llama3-Chinese)
"""
import os
import json
import re
import jieba
from factscore import FactScore
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from tqdm import tqdm

# 配置
MODEL_PATH = "/path/to/Qwen2.5-7B-Instruct"
TOKENIZER_PATH = MODEL_PATH
EVAL_DATA_PATH = "./hallucination_eval_data.json"  # 评测数据集
OUTPUT_DIR = "./hallucination_results"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"

# 创建输出目录
os.makedirs(OUTPUT_DIR, exist_ok=True)

# 1. 准备评测数据集
def prepare_eval_data():
    """
    构建幻觉评测数据集
    格式: [{"question": "问题", "reference": "参考事实", "category": "类别"}]
    """
    eval_data = [
        {
            "question": "2024年奥运会的举办城市是哪里?",
            "reference": "2024年夏季奥林匹克运动会在法国巴黎举办,这是巴黎第三次举办奥运会。",
            "category": "事实性问题"
        },
        {
            "question": "简述量子计算的基本原理",
            "reference": "量子计算利用量子力学的叠加态和纠缠特性进行计算,通过量子比特(Qubit)存储和处理信息,相比经典计算机在特定问题上有指数级加速。",
            "category": "专业知识"
        },
        {
            "question": "中国的首都是哪个城市?有哪些著名的历史建筑?",
            "reference": "中国的首都是北京,著名历史建筑包括故宫、天坛、长城、颐和园等。",
            "category": "常识问题"
        },
        {
            "question": "LangChain框架的主要功能是什么?",
            "reference": "LangChain是用于构建基于大语言模型的应用程序的开源框架,核心功能包括模型集成、工具调用、记忆管理、Agent智能体构建等。",
            "category": "技术问题"
        },
        {
            "question": "2023年诺贝尔物理学奖的获奖者及其贡献是什么?",
            "reference": "2023年诺贝尔物理学奖授予Pierre Agostini、Ferenc Krausz和Anne L'Huillier,以表彰他们在阿秒光脉冲生成方面的实验方法贡献。",
            "category": "时效性事实"
        }
    ]
    
    # 保存数据集
    with open(EVAL_DATA_PATH, "w", encoding="utf-8") as f:
        json.dump(eval_data, f, ensure_ascii=False, indent=4)
    
    return eval_data

# 2. 加载模型和Tokenizer
def load_model_and_tokenizer():
    """加载开源模型和Tokenizer"""
    print(f"加载模型: {MODEL_PATH}")
    
    tokenizer = AutoTokenizer.from_pretrained(
        TOKENIZER_PATH,
        trust_remote_code=True,
        padding_side="right"
    )
    
    model = AutoModelForCausalLM.from_pretrained(
        MODEL_PATH,
        torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
        device_map="auto",
        trust_remote_code=True
    )
    
    # 设置pad token(若不存在)
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token
        model.config.pad_token_id = model.config.eos_token_id
    
    return model, tokenizer

# 3. 生成模型回复
def generate_responses(model, tokenizer, eval_data):
    """生成模型回复"""
    responses = []
    
    for item in tqdm(eval_data, desc="生成回复"):
        # 构建提示词
        prompt = f"""请准确回答以下问题,确保所有信息真实可靠,不要编造事实:
问题:{item['question']}
回答:"""
        
        # 编码
        inputs = tokenizer(
            prompt,
            return_tensors="pt",
            padding=True,
            truncation=True,
            max_length=2048
        ).to(DEVICE)
        
        # 生成回复
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_new_tokens=512,
                temperature=0.1,  # 低温度减少随机性
                top_p=0.95,
                do_sample=True,
                pad_token_id=tokenizer.pad_token_id,
                eos_token_id=tokenizer.eos_token_id
            )
        
        # 解码回复
        response = tokenizer.decode(
            outputs[0][len(inputs.input_ids[0]):],
            skip_special_tokens=True
        ).strip()
        
        responses.append({
            "question": item["question"],
            "reference": item["reference"],
            "category": item["category"],
            "model_response": response
        })
    
    # 保存回复
    responses_path = os.path.join(OUTPUT_DIR, "model_responses.json")
    with open(responses_path, "w", encoding="utf-8") as f:
        json.dump(responses, f, ensure_ascii=False, indent=4)
    
    return responses

# 4. FactScore评估
def run_factscore_evaluation(responses):
    """执行FactScore评估"""
    print("初始化FactScore评估器...")
    
    # 初始化FactScore(支持中文需自定义分词)
    fs = FactScore(
        model_name="gpt-4o",  # 事实验证模型(可选gpt-3.5-turbo)
        api_key=os.getenv("OPENAI_API_KEY"),  # 从环境变量加载API Key
        verbose=True,
        cache_path=os.path.join(OUTPUT_DIR, "factscore_cache")
    )
    
    # 准备评估数据
    texts = [item["model_response"] for item in responses]
    refs = [item["reference"] for item in responses]
    
    # 执行评估
    print("执行FactScore评估...")
    scores = fs.get_score(
        texts=texts,
        refs=refs,
        chunk_size=100,  # 文本分块大小
        granularity="sentence"  # 句子级评估
    )
    
    # 整合结果
    eval_results = []
    for i, item in enumerate(responses):
        eval_results.append({
            "question": item["question"],
            "category": item["category"],
            "reference": item["reference"],
            "model_response": item["model_response"],
            "factscore": scores["scores"][i],
            "factual_sentences": scores["factual_sentences"][i],
            "hallucinated_sentences": scores["hallucinated_sentences"][i]
        })
    
    # 计算平均分
    avg_factscore = sum([r["factscore"] for r in eval_results]) / len(eval_results)
    
    # 保存评估结果
    final_results = {
        "average_factscore": avg_factscore,
        "category_analysis": {},
        "detailed_results": eval_results
    }
    
    # 按类别分析
    category_scores = {}
    for item in eval_results:
        category = item["category"]
        if category not in category_scores:
            category_scores[category] = []
        category_scores[category].append(item["factscore"])
    
    for category, scores in category_scores.items():
        final_results["category_analysis"][category] = {
            "average_score": sum(scores) / len(scores),
            "count": len(scores)
        }
    
    # 保存最终结果
    results_path = os.path.join(OUTPUT_DIR, "hallucination_evaluation.json")
    with open(results_path, "w", encoding="utf-8") as f:
        json.dump(final_results, f, ensure_ascii=False, indent=4)
    
    # 打印结果
    print("\n=== 幻觉评估结果 ===")
    print(f"平均FactScore: {avg_factscore:.4f}")
    print("\n按类别分析:")
    for category, analysis in final_results["category_analysis"].items():
        print(f"  {category}: {analysis['average_score']:.4f} ({analysis['count']}个样本)")
    
    print("\n详细结果:")
    for item in eval_results:
        print(f"\n问题: {item['question']}")
        print(f"FactScore: {item['factscore']:.4f}")
        print(f"模型回复: {item['model_response']}")
        print(f"幻觉语句: {item['hallucinated_sentences']}")
    
    return final_results

# 5. 中文幻觉率手动评估(备用方案)
def manual_hallucination_evaluation(responses):
    """
    中文幻觉率手动评估(无API时使用)
    规则:
    1. 将回复按句子分割
    2. 逐个句子对比参考文档
    3. 计算幻觉语句比例
    """
    def split_chinese_sentences(text):
        """中文句子分割"""
        # 简单的中文分句规则
        sentences = re.split(r'[。!?;]', text)
        sentences = [s.strip() for s in sentences if s.strip()]
        return sentences
    
    eval_results = []
    
    for item in responses:
        # 分句
        response_sentences = split_chinese_sentences(item["model_response"])
        reference_sentences = split_chinese_sentences(item["reference"])
        
        # 事实匹配检查
        hallucinated_count = 0
        factual_sentences = []
        hallucinated_sentences = []
        
        for sent in response_sentences:
            # 简单的事实匹配(基于关键词)
            reference_words = set(jieba.lcut(item["reference"]))
            response_words = set(jieba.lcut(sent))
            overlap = len(reference_words & response_words) / len(response_words) if response_words else 0
            
            if overlap < 0.3 and len(sent) > 5:  # 低重叠且长度足够,判定为幻觉
                hallucinated_count += 1
                hallucinated_sentences.append(sent)
            else:
                factual_sentences.append(sent)
        
        # 计算幻觉率
        hallucination_rate = hallucinated_count / len(response_sentences) if response_sentences else 0
        factscore = 1 - hallucination_rate
        
        eval_results.append({
            "question": item["question"],
            "category": item["category"],
            "factscore": factscore,
            "hallucination_rate": hallucination_rate,
            "total_sentences": len(response_sentences),
            "hallucinated_sentences": hallucinated_sentences,
            "factual_sentences": factual_sentences
        })
    
    # 计算平均分
    avg_factscore = sum([r["factscore"] for r in eval_results]) / len(eval_results)
    avg_hallucination_rate = sum([r["hallucination_rate"] for r in eval_results]) / len(eval_results)
    
    print(f"\n=== 手动幻觉评估结果 ===")
    print(f"平均FactScore: {avg_factscore:.4f}")
    print(f"平均幻觉率: {avg_hallucination_rate:.4f}")
    
    return eval_results

# 主执行函数
def main():
    # 1. 准备评测数据
    eval_data = prepare_eval_data()
    
    # 2. 加载模型
    model, tokenizer = load_model_and_tokenizer()
    
    # 3. 生成回复
    responses = generate_responses(model, tokenizer, eval_data)
    
    # 4. 执行幻觉评估
    try:
        # 优先使用FactScore官方评估
        final_results = run_factscore_evaluation(responses)
    except Exception as e:
        print(f"FactScore官方评估失败: {e}")
        print("使用手动幻觉评估方案")
        final_results = manual_hallucination_evaluation(responses)
    
    return final_results

if __name__ == "__main__":
    main()

4.3 幻觉优化策略

基于评测结果,可通过以下方式降低模型幻觉:

4.3.1 提示词优化

"""
反幻觉提示词模板
"""
ANTI_HALLUCINATION_PROMPT = """你是一个严谨的事实回答助手,必须遵守以下规则:
1. 只回答你确定为真实的信息,不确定的信息必须明确说明
2. 对于时效性问题,说明信息的时间范围
3. 对于专业问题,确保术语准确,不编造概念
4. 如果无法回答,直接说明"无法回答该问题",不要编造答案
5. 回答结构清晰,分点说明,便于验证

问题:{question}
回答:"""

# 使用示例
prompt = ANTI_HALLUCINATION_PROMPT.format(question="2024年奥运会的举办城市是哪里?")
4.3.2 检索增强生成(RAG)

结合LangChain实现检索增强,通过**“上下文压缩”**技术滤除无关噪声,从源头切断幻觉,从可信数据源获取事实:

1. RAG 反幻觉工作流图解
RAG 反幻觉推理流程图
│
├── 【用户输入】
│   └── Query: "LangChain的核心功能是什么?"
│
▼
[1. 语义检索阶段 (Semantic Retrieval)] ────────────────────────┐
│                                                              │
├── A. 问题向量化 (Query Embedding)                            │
│   ├── <调用模型>: BAAI/bge-base-zh-v1.5                      │
│   ├── <动作>: 将文本转化为 768维 向量                         │
│   └── > 输出: Query Vector [0.12, -0.45, 0.88...]            │
│                                                              │
├── B. 向量库匹配 (Vector Search)                              │
│   ├── <读取路径>: ./rag_db (Chroma 持久化目录)                │
│   ├── <计算>: 余弦相似度 (Cosine Similarity) Top-K=3         │
│   └── > 输出: 3个原始文档片段 (Raw Documents)                 │
│       ├── Doc A: "LangChain是开发框架..." (相关)              │
│       ├── Doc B: "LangChain的创始人是..." (部分相关)          │
│       └── Doc C: "Python安装教程..." (噪声)                   │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[2. 上下文压缩阶段 (Context Compression)] <★ 核心去噪步骤> ─────┐
│                                                              │
├── <组件>: ContextualCompressionRetriever                     │
├── <机制>: LLMChainExtractor (基于模型的信息抽取)              │
│                                                              │
├── ↻ 压缩循环 (Extraction Loop)                               │
│   ├── 输入: Query + Raw Doc A/B/C                            │
│   ├── 判别: "这句话能回答用户的问题吗?"                       │
│   └── 动作:                                                  │
│       ├── Doc A -> 保留核心定义                               │
│       ├── Doc B -> 仅保留关键人名                             │
│       └── Doc C -> 丢弃 (DROP)                                │
│                                                              │
└── > 输出: 精炼事实 (Refined Context)"LangChain是开源框架...核心功能包括..."                   │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[3. 受控生成阶段 (Constrained Generation)] ────────────────────┐
│                                                              │
├── A. 提示词构建 (Prompt Engineering)                         │
│   ├── <模板>: "基于以下事实...不要添加其他信息"                 │
│   ├── <注入>: Refined Context (精炼后的事实)                  │
│   └── > 最终 Prompt: [System] + [Facts] + [User Query]       │
│                                                              │
├── B. 模型推理 (Inference)                                    │
│   ├── <模型>: Qwen2.5-7B-Instruct                            │
│   ├── <参数>: Temperature=0.1 (极低随机性)                    │
│   └── > 最终回复: "LangChain的主要功能是..."                  │
└──────────────────────────────────────────────────────────────┘
2. 关键组件协作解析

这段代码不仅仅是简单的查库,它构建了一个**“漏斗式”**的信息处理系统:

1. 向量化引擎 (HuggingFaceEmbeddings)

  • 协作逻辑:这是 RAG 的“翻译官”。它使用 BAAI/bge-base-zh-v1.5(目前中文语义理解最强的开源 Embedding 之一)将用户的问题和知识库内容统一转化为数学向量。
  • 反幻觉作用:只有“翻译”得准,才能找到真正相关的事实,避免因为关键词匹配错误而引入错误的上下文。

2. 上下文压缩器 (ContextualCompressionRetriever)

  • 协作逻辑:这是代码中最亮眼的部分。普通的 RAG 会直接把检索到的 Top-3 文档全部扔给大模型,里面可能包含大量无关废话。
  • 工作原理
    • 它调用 LLMChainExtractor,让大模型充当“荧光笔”。
    • 在生成最终答案之前,它先快速浏览一遍检索到的文档,把跟问题真正相关的句子“高亮”出来,删掉其他部分。
  • 反幻觉作用噪声是幻觉的温床。通过压缩上下文,我们只喂给模型“纯净”的事实,模型“胡说八道”的概率会呈指数级下降。

3. 严格约束生成 (rag_generate 函数)

  • 协作逻辑:将清洗后的 context 填入特定模板。
  • 关键指令prompt 中明确包含了“仅使用提供的事实”和“不要添加其他信息”。配合 temperature=0.1,强迫模型从“创造模式”切换到“阅读理解模式”,最大程度保证回答的忠实度(Faithfulness)。

"""
RAG反幻觉示例
"""
from langchain.vectorstores import Chroma
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

# 初始化Embedding
embedding = HuggingFaceEmbeddings(model_name="BAAI/bge-base-zh-v1.5")

# 加载向量数据库(包含可信事实数据)
vector_db = Chroma(persist_directory="./rag_db", embedding_function=embedding)

# 构建检索器
retriever = vector_db.as_retriever(search_kwargs={"k": 3})

# 上下文压缩(提升检索精度)
compressor = LLMChainExtractor.from_llm(model)
compression_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=retriever)

# 检索+生成
def rag_generate(question):
    # 检索相关事实
    retrieved_docs = compression_retriever.get_relevant_documents(question)
    context = "\n".join([doc.page_content for doc in retrieved_docs])
    
    # 构建RAG提示词
    prompt = f"""基于以下事实回答问题,仅使用提供的事实,不要添加其他信息:
事实:
{context}

问题:{question}
回答:"""
    
    # 生成回复
    inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
    outputs = model.generate(**inputs, max_new_tokens=512, temperature=0.1)
    response = tokenizer.decode(outputs[0], skip_special_tokens=True)
    
    return response, retrieved_docs

五、评测结果落地与模型优化

5.1 评测报告模板

综合评测报告生成流程图 (Evaluation Reporting Pipeline)
│
├── 【多源输入数据】 (Input Data Streams)
│   ├── MMLU 评测结果: [mmlu_results] (57个学科准确率字典)
│   ├── BBH 评测结果: [bbh_results] (23个推理任务得分)
│   └── 幻觉评测结果: [hallucination_results] (FactScore/幻觉率)
│
▼
[1. 智能分析引擎] (Analytical Engine) ──────────────────────────┐
│                                                              │
├── A. 核心指标聚合 (Metric Aggregation)                        │
│   ├── <计算>: 全局平均分 (Global Average)                     │
│   ├── <环境>: 提取 GPU/CUDA 信息 (用于复现环境)               │
│   └── > 产出: 基础体检面板 (Base Dashboard)                   │
│                                                              │
├── B. 能力画像诊断 (Capability Profiling)                      │
│   ├── <强项识别算法>: 单项分 > (平均分 + 5%)                  │
│   ├── <弱项识别算法>: 单项分 < (平均分 - 5%)                  │
│   └── > 产出: 能力偏科表 (Strengths & Weaknesses)             │
│                                                              │
├── C. 策略逻辑推演 (Strategic Reasoning) <★ 核心决策层>        │
│   ├── <规则 1>: FactScore < 0.8  -> 触发 "建议 RAG 集成"      │
│   ├── <规则 2>: 弱项数量 > 5个   -> 触发 "建议专项 SFT"       │
│   ├── <规则 3>: 时效性幻觉高发   -> 触发 "建议搜索增强"       │
│   └── > 产出: 优化建议清单 (Actionable Insights)              │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[2. 双模态报告构建] (Dual-Mode Construction) ───────────────────┐
│                                                              │
├── 路径 A: 机器可读层 (Machine Readable)                       │
│   ├── <格式>: JSON (Nested Dict)                              │
│   ├── <用途>: 存入 MLOps 平台,用于绘制历史趋势图              │
│   └── > 文件: final_evaluation_report.json                    │
│                                                              │
├── 路径 B: 人类可读层 (Human Readable)                         │
│   ├── <格式>: Markdown (Structured Text)                      │
│   ├── <渲染>: 自动生成“核心结论”摘要 (Executive Summary)       │
│   └── > 文件: final_evaluation_report.md                      │
└──────────────────────────────────────────────────────────────┘
2. 代码逻辑深度解析

这段代码实际上实现了一个微型的专家系统(Expert System),各部分协作逻辑如下:

1. 动态阈值门控 (Threshold Gating)

  • 代码逻辑mmlu_threshold = mmlu_results["mmlu_average"] + 5
  • 协作原理:它不使用死板的固定分数(如60分及格),而是根据模型自身的平均表现来动态划定“强项”和“弱项”。
  • 业务价值:这意味着即使是一个 7B 的小模型,也能找出它相对擅长的领域(如“创意写作”),而不是被 70B 模型全方位碾压,有助于发现小模型的垂直应用潜力。

2. 处方生成逻辑 (Prescription Logic)

  • 代码逻辑

    if report["幻觉评估"]["平均FactScore"] < 0.8:
        report["优化建议"].append("优先优化事实准确性...")
    
  • 协作原理:这是代码中最具“智能”的部分。它建立了一套 “症状 -> 疗法” 的映射表:

    • 症状:FactScore 低 →\rightarrow 疗法:外挂 RAG 知识库。
    • 症状:MMLU 弱项太多 →\rightarrow 疗法:进行 SFT 微调。
    • 症状:时效性错误 →\rightarrow 疗法:接入 Search API。

3. 双模态输出 (Dual-Mode Output)

  • JSON:是为了给 CI/CD 流水线用的。你可以写一个脚本,每次模型训练完自动跑评测,如果 JSON 中的 mmlu_average 下降了,就自动终止部署。
  • Markdown:是为了给老板/客户看的。代码中精心设计的 f-string 模板,直接生成格式漂亮的文档,不仅有数据,还有自动生成的“核心结论”(如:“模型综合能力优秀,适合生产环境”),省去了人工写报告的时间。

"""
生成完整评测报告
"""
def generate_final_report(mmlu_results, bbh_results, hallucination_results):
    """生成综合评测报告"""
    report = {
        "评测概要": {
            "模型名称": MODEL_PATH.split("/")[-1],
            "评测时间": str(datetime.datetime.now()),
            "评测环境": f"{DEVICE} ({torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'})",
            "核心结论": ""
        },
        "多维度能力评估": {
            "MMLU平均准确率": mmlu_results["mmlu_average"],
            "BBH平均准确率": bbh_results["bbh_average"],
            "能力强项": [],
            "能力弱项": []
        },
        "幻觉评估": {
            "平均FactScore": hallucination_results["average_factscore"],
            "平均幻觉率": 1 - hallucination_results["average_factscore"],
            "高幻觉类别": [],
            "低幻觉类别": []
        },
        "优化建议": []
    }
    
    # 分析能力强项/弱项
    mmlu_threshold = mmlu_results["mmlu_average"] + 5
    bbh_threshold = bbh_results["bbh_average"] + 5
    
    for task, metrics in mmlu_results["task_details"].items():
        if metrics["accuracy"] > mmlu_threshold:
            report["多维度能力评估"]["能力强项"].append(task.replace("mmlu_", ""))
        elif metrics["accuracy"] < mmlu_results["mmlu_average"] - 5:
            report["多维度能力评估"]["能力弱项"].append(task.replace("mmlu_", ""))
    
    # 分析幻觉类别
    for category, analysis in hallucination_results["category_analysis"].items():
        if analysis["average_score"] < 0.7:
            report["幻觉评估"]["高幻觉类别"].append(category)
        elif analysis["average_score"] > 0.9:
            report["幻觉评估"]["低幻觉类别"].append(category)
    
    # 生成优化建议
    if report["幻觉评估"]["平均FactScore"] < 0.8:
        report["优化建议"].append("优先优化事实准确性,建议集成RAG检索增强")
    if len(report["多维度能力评估"]["能力弱项"]) > 5:
        report["优化建议"].append("针对薄弱学科进行专项微调")
    if "时效性事实" in report["幻觉评估"]["高幻觉类别"]:
        report["优化建议"].append("增加实时数据检索能力,避免编造时效性信息")
    
    # 生成核心结论
    if report["多维度能力评估"]["MMLU平均准确率"] > 80 and report["幻觉评估"]["平均FactScore"] > 0.9:
        report["评测概要"]["核心结论"] = "模型综合能力优秀,事实准确性高,适合生产环境使用"
    elif report["多维度能力评估"]["MMLU平均准确率"] > 70 and report["幻觉评估"]["平均FactScore"] > 0.8:
        report["评测概要"]["核心结论"] = "模型综合能力良好,需针对性优化部分场景的幻觉问题"
    else:
        report["评测概要"]["核心结论"] = "模型综合能力待提升,建议先进行微调优化"
    
    # 保存报告
    with open(os.path.join(OUTPUT_DIR, "final_evaluation_report.json"), "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=4)
    
    # 生成markdown报告
    md_report = f"""# 大模型综合评测报告
## 评测概要
- 模型名称: {report['评测概要']['模型名称']}
- 评测时间: {report['评测概要']['评测时间']}
- 评测环境: {report['评测概要']['评测环境']}
- 核心结论: {report['评测概要']['核心结论']}

## 多维度能力评估
### 整体表现
- MMLU平均准确率: {report['多维度能力评估']['MMLU平均准确率']}%
- BBH平均准确率: {report['多维度能力评估']['BBH平均准确率']}%

### 能力强项
{chr(10).join([f"- {item}" for item in report['多维度能力评估']['能力强项'][:5]])}

### 能力弱项
{chr(10).join([f"- {item}" for item in report['多维度能力评估']['能力弱项'][:5]])}

## 幻觉评估
### 整体表现
- 平均FactScore: {report['幻觉评估']['平均FactScore']:.4f}
- 平均幻觉率: {report['幻觉评估']['平均幻觉率']:.4f}

### 高幻觉类别
{chr(10).join([f"- {item}" for item in report['幻觉评估']['高幻觉类别']])}

### 低幻觉类别
{chr(10).join([f"- {item}" for item in report['幻觉评估']['低幻觉类别']])}

## 优化建议
{chr(10).join([f"- {item}" for item in report['优化建议']])}
"""
    
    with open(os.path.join(OUTPUT_DIR, "final_evaluation_report.md"), "w", encoding="utf-8") as f:
        f.write(md_report)
    
    print("综合评测报告已生成完成!")
    return report

5.2 模型优化实战建议

基于 MMLU(能力)和 FactScore(幻觉)的评测结果,我们不再进行盲目的全量微调,而是采用**“离线修正 + 在线增强”**的双轨制优化策略。

5.2.1 优化系统架构图解
大模型能力迭代与幻觉修正流程图 (Optimization Loop)
│
├── 【输入:评测诊断报告】 (Evaluation Report)
│   ├── 症状 A: MMLU 特定科目低分 (如: "高中数学": 25%)
│   ├── 症状 B: FactScore 低且伴随编造 (Hallucination Rate > 30%)
│   └── 症状 C: 逻辑推理步骤混乱 (BBH CoT 错误)
│
▼
[1. 离线训练修正阶段 (Offline Optimization)] <权重更新层> ───────┐
│                                                              │
├── A. 数据合成工厂 (Data Synthesis)                            │
│   ├── <输入>: 评测中的错题集 (Error Cases)                    │
│   ├── <调用>: GPT-4o / Claude-3.5                             │
│   ├── <动作>: 生成 CoT 解析 + 反事实修正样本 (Counterfactual)   │
│   └── > 产出: preference_dataset.jsonl (DPO 数据集)           │
│                                                              │
├── B. 参数高效微调 (Training / SFT & DPO)                      │
│   ├── <读取配置>: adapter_config.json (LoRA Rank=64, Alpha=16)│
│   ├── <加载权重>: Base Model Weights                          │
│   ├── <执行策略>:                                             │
│   │   ├── 知识注入: 针对 MMLU 弱项进行持续预训练 (CPT)          │
│   │   └── 幻觉抑制: 使用 DPO (Direct Preference Optimization)  │
│   │       (Positive: 事实性回复 | Negative: 幻觉回复)          │
│   └── > 输出: new_adapter.bin (修正后的 LoRA 权重)             │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[2. 在线推理增强阶段 (Online Inference Enhancement)] ──────────┐
│                                                              │
├── A. 动态知识挂载 (RAG & Tool Use)                            │
│   ├── <判断>: 问题包含高时效性/长尾知识?                       │
│   ├── <动作>:                                                │
│   │   ├── 检索: VectorDB (Chroma/Milvus)                     │
│   │   └── 验证: Google Search API (Web Grounding)            │
│   └── > 上下文: [Refined Context] + [User Query]              │
│                                                              │
├── B. 解码策略干预 (Decoding Intervention)                     │
│   ├── <配置文件>: generation_config.json                      │
│   ├── <参数调优>:                                             │
│   │   ├── Temperature: 0.1 (收敛随机性)                       │
│   │   ├── Repetition Penalty: 1.1 (防止死循环)                │
│   │   └── Do_sample: False (贪婪解码,用于严谨任务)            │
│   └── <高级引导>: LogitsProcessor (禁止生成特定敏感词/幻觉词)   │
└──────────────────────────────────────────────────────────────┘
         │
         ▼
[3. 工程级防御护栏 (Engineering Guardrails)] ──────────────────┐
│                                                              │
├── <后处理模块>: Output Validator                              │
├── <动作>:                                                    │
│   ├── 事实一致性检测 (NLI Model): 判断生成内容是否违背上下文    │
│   ├── 拒绝回答机制: 当置信度 (LogProbs) < 阈值时,输出"我不知道" │
│   └── 引用标注: 强制模型在句尾标注 [Source ID]                 │
│                                                              │
└── > 最终交付: 用户端回复 (Safe & Accurate Response)            │
└──────────────────────────────────────────────────────────────┘
5.2.2 优化决策矩阵(Decision Matrix)

评测完成后,请对照下表选择最经济有效的优化方案,避免盲目重训:

评测症状诊断 典型指标特征 推荐优化方案 (按优先级排序) 预计成本
严重知识匮乏 MMLU < 30%, 且对基础概念一无所知 1. RAG 知识库外挂 2. 持续预训练 (CPT) 低 (RAG) 高 (CPT)
严重逻辑幻觉 BBH < 40%, 推理步骤跳跃或自相矛盾 1. CoT (思维链) 提示词优化 2. SFT 微调 (使用高质量推理数据) 极低 中
事实捏造/张冠李戴 FactScore < 0.6, 语言通顺但内容错误 1. RAG + 上下文严格约束 2. DPO 对偏好优化 (抑制幻觉) 中 中
时效性错误 回答过期信息 (如"英国女王还在世") 1. 搜索引擎工具调用 (Tool Use) 2. 知识编辑 (Knowledge Editing, MEMIT) 低 高(技术难度大)
指令遵循失败 无法按要求格式 (JSON/Markdown) 输出 1. Few-shot Prompting (增加示例) 2. 约束解码 (Constrained Decoding) 极低 低
5.2.3 实战操作指南:全栈闭环架构

1. 数据层面:构建“反幻觉”DPO 数据集

  • 方法:不仅收集正确的(Chosen),还要收集模型生成的典型幻觉(Rejected)。
  • 示例
    • Prompt: “介绍量子纠缠。”
    • Chosen: “量子纠缠是量子力学中的一种现象…” (引用维基百科)
    • Rejected: “量子纠缠是由爱因斯坦在1990年发明的…” (包含事实错误)
  • 作用:通过 DPO 训练,让模型学会**“什么是不该说的”**。

2. 训练层面:LoRA + 知识编辑

  • 不要试图通过微调让模型记住所有知识(这是预训练的事)。
  • LoRA 重点:调整模型的**“说话方式”“引用习惯”**。例如,训练模型在不确定时主动说“可以查询一下资料”,而不是强行编造。

3. 推理层面:自洽性投票 (Self-Consistency)

  • 针对 BBH 等推理任务,不要只生成一次。
  • 操作:设置 temperature=0.7 生成 5 条路径,取出现次数最多的答案(Majority Vote)。这能显著提升数理逻辑任务的准确率(通常提升 5-10%)。

4. 工程层面:置信度截断

  • vLLMHuggingFace 生成时,获取每个 Token 的 logprobs
  • 计算整句的平均置信度(Perplexity)。如果困惑度过高,说明模型在“硬编”,此时工程层直接拦截,返回兜底话术。

六、总结与展望

6.1 核心收获

  1. 掌握了大模型多维度能力评测的完整流程(MMLU/BBH)

  2. 落地了模型幻觉的定量评估方法(FactScore)

  3. 能够基于评测结果定位模型短板并制定优化策略

  4. 构建了可复用的评测代码体系(支持2025年最新开源模型)

6.2 未来趋势

  1. 评测集的动态更新:适配实时性、地域性的评测需求

  2. 幻觉评估的自动化:减少人工干预,提升评估效率

  3. 多模态能力评测:覆盖文本、图像、音频的综合评估

  4. 生产环境评测:结合实际业务场景的持续评测体系

6.3 工具生态推荐

工具 用途 优势
lm-evaluation-harness 通用能力评测 覆盖广、标准化、易扩展
FactScore 幻觉定量评估 事实准确性高、支持多语言
LangSmith 评测结果分析 可视化、可追溯、团队协作
vLLM 高性能推理 大幅提升评测效率
HuggingFace Evaluate 自定义评测 灵活、可定制化指标
通过本文的实战指南,开发者可以系统化地评估大模型的综合能力,定量分析幻觉问题,并基于评测结果进行精准优化,为大模型的生产级应用提供可靠的评估依据。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐