| # AQuA Verifier — DeBERTa-v3-large Token Classification |
|
|
| 基于 [DIVERSE](https://github.com/lz1oceani/diverse)(ICLR 2024)的 verifier 实现,在 AQuA 数学推理数据集上训练,用于对解题路径逐条打分。 |
|
|
| ## 模型信息 |
|
|
| | 项目 | 说明 | |
| |------|------| |
| | 基座模型 | microsoft/deberta-v3-large | |
| | 任务类型 | Token 分类(5 类) | |
| | 标签 | SOLUTION-CORRECT(0), SOLUTION-INCORRECT(1), STEP-CORRECT(2), STEP-INCORRECT(3), O(4) | |
| | 训练数据 | AQuA — 250 题 × (20 条候选解/题 + 1 条正确答案/题) = 5250 条 | |
| | 训练轮数 | 1 epoch(建议续训到 3-5 epoch 以获得更好泛化) | |
| | 输入格式 | `[CLS] {步骤用%%分隔} && {题目}`,末尾带 `####{答案字母}` | |
| | 打分方式 | 取 `[CLS]` 位置的 SOLUTION-CORRECT 概率 × 10,范围 0~10 | |
| | 分数含义 | 越高表示模型越认可这条推理路径的正确性 | |
|
|
| ## 文件说明 |
|
|
| ``` |
| aqua-verifier/ |
| ├── run_ner.py # 训练入口 |
| ├── verifier_data_prepare.py # 数据准备(LLM 输出 → CoNLL 格式 train.txt) |
| ├── deberta_model.py # DeBERTa-v2 自定义模型(必须用此类加载,不能用 AutoModel) |
| ├── utils_ner.py # CoNLL 数据加载与特征转换 |
| ├── tasks.py # NER 任务定义 |
| ├── utils.py # 数据结构 / 步骤标注 / 加权投票评估 |
| ├── utils_io.py # 文件读取工具 |
| ├── verifier_metrics.py # 评估指标(weighted voting accuracy) |
| ├── labels.txt # 5 个标签 |
| ├── config.json # 模型配置(含 task_specific_params 权重) |
| ├── model.safetensors # 训练权重 |
| ├── spm.model # SentencePiece tokenizer 模型 |
| └── tokenizer_config.json # Tokenizer 配置 |
| ``` |
|
|
| ## 加载模型并打分 |
|
|
| ```python |
| import torch |
| from transformers import AutoTokenizer |
| from deberta_model import DebertaV2ForTokenClassification |
| |
| # 加载(换成你的 HuggingFace 用户名) |
| tokenizer = AutoTokenizer.from_pretrained("你的用户名/aqua-verifier") |
| model = DebertaV2ForTokenClassification.from_pretrained("你的用户名/aqua-verifier") |
| model.eval() |
| |
| def score(question: str, solution: str) -> float: |
| """ |
| question: "Q: ... Options: A)... B)..." |
| solution: 解题步骤(换行会自动转为 %%),末尾带 Therefore the answer is X. 或 ####X |
| 返回 0~10 的分数 |
| """ |
| sol = solution.strip().replace("\r\n", "\n").replace("\n", "%%") |
| # 如果没有 ####X 但文中有 "answer is X",自动补全 |
| if "####" not in sol: |
| import re |
| m = re.search(r"answer is ([A-E])", sol, re.IGNORECASE) |
| if m: |
| sol += f" ####{m.group(1)}" |
| text = f"[CLS] {sol} && {question}" |
| inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512) |
| with torch.no_grad(): |
| logits = model(**inputs).logits |
| # [CLS] 位置 → SOLUTION-CORRECT 概率 → 映射到 0~10 |
| return torch.softmax(logits[0, 0], dim=-1)[0].item() * 10 |
| |
| # 示例 |
| q = "Q: 2+2=? Options: A)1 B)2 C)3 D)4 E)5" |
| sol = "2+2=4%%Therefore the answer is D. ####D" |
| print(score(q, sol)) # ~9.9 |
| ``` |
|
|
| > **注意**:不能用 `AutoModel.from_pretrained()`,必须从本仓库的 `deberta_model.py` 导入 `DebertaV2ForTokenClassification`。 |
|
|
| ## 完整训练流程 |
|
|
| ### 第一步:准备 LLM 生成结果 |
|
|
| 使用 deepseek-v4-flash 对 AQuA 每道题生成多条候选解(建议每题 10~20 条),按以下 JSONL 格式保存: |
|
|
| ```json |
| { |
| "context": "Q: 示例题1...\\nOptions: ...\\nA: 示例解...\\n...(可放 few-shot prompt)", |
| "samples": ["LLM生成的候选解1", "LLM生成的候选解2", ...], |
| "metadata": { |
| "question": "Q: 目标题目的文本", |
| "ground_truth": "Q: 目标题\\nA: 标准答案推理过程", |
| "correct": "A", |
| "options": ["A)...", "B)...", "C)...", "D)...", "E)..."] |
| } |
| } |
| ``` |
|
|
| ### 第二步:生成 CoNLL 训练数据 |
|
|
| ```bash |
| python verifier_data_prepare.py \ |
| --generator_result_file ./dataset/llm_outputs.jsonl \ |
| --output_dir ./dataset/ \ |
| --dataset_name AQuA \ |
| --split train \ |
| --text_entailment_model_name microsoft/deberta-large-mnli \ |
| --text_entailment_batch_size 512 |
| ``` |
|
|
| > 步骤标注方式:对每条候选解的每个步骤,用 NLI 模型(`deberta-large-mnli`)与正确答案的对应步骤做文本蕴含判断。蕴含则为 STEP-CORRECT,不蕴含则为 STEP-INCORRECT。整体正确性用答案是否匹配判断。 |
|
|
| 输出:`train.txt`(CoNLL 格式,每行 `TOKEN LABEL`,序列之间空行分隔)。 |
|
|
| ### 第三步:训练 Verifier |
|
|
| ```bash |
| python run_ner.py \ |
| --model_name_or_path microsoft/deberta-v3-large \ |
| --dataset_name AQuA \ |
| --train_data ../data/train.txt \ |
| --test_data ../data/test.txt \ |
| --data_labels labels.txt \ |
| --output_dir ../output/ \ |
| --do_train --do_eval \ |
| --per_device_train_batch_size 8 \ |
| --num_train_epochs 5 \ |
| --learning_rate 1e-5 \ |
| --alpha 0.1 \ |
| --overwrite_output_dir \ |
| --save_strategy epoch \ |
| --evaluation_strategy epoch \ |
| --logging_steps 50 \ |
| --save_total_limit 2 |
| ``` |
|
|
| 参数说明: |
|
|
| | 参数 | 说明 | |
| | ---- | ---- | |
| | `--alpha 0.1` | STEP-CORRECT/INCORRECT 的 loss 权重。SOLUTION 类别固定为 1.0 | |
| | `--num_train_epochs 5` | 建议 3-5 epoch。当前权重仅训了 1 epoch | |
| | `--per_device_train_batch_size 8` | V100/A100 可用 8,小显存降到 4 | |
| | `--save_total_limit 2` | 只保留最近 2 个 checkpoint,防磁盘满 | |
| | `--learning_rate 1e-5` | DeBERTa-large 推荐学习率 | |
|
|
| ### 续训 |
|
|
| ```bash |
| python run_ner.py \ |
| ...(同上参数)... \ |
| --resume_from_checkpoint ../output/checkpoint-NNN |
| ``` |
|
|
| ## 环境依赖 |
|
|
| ```bash |
| # 核心依赖 |
| pip install torch transformers datasets numpy scipy tqdm seqeval rouge_score nltk absl-py six multiset conllu |
| |
| # 版本锁定(重要) |
| pip install "transformers==4.38.2" "accelerate==0.27.2" |
| ``` |
|
|
| 如果在华为云 Ascend NPU 上训练,额外安装: |
|
|
| ```bash |
| pip install torch_npu |
| export HF_ENDPOINT=https://hf-mirror.com |
| ``` |
|
|
| ## 评分原理与使用方式 |
|
|
| 训练时对序列中两个位置做标注: |
|
|
| - **`[CLS]` token** → SOLUTION-CORRECT / SOLUTION-INCORRECT(整体解对不对,loss weight = 1.0) |
| - **`%%` 分隔符 token** → STEP-CORRECT / STEP-INCORRECT(NLI 模型逐步骤判断正误,loss weight = 0.1) |
|
|
| 推理打分有两种方式: |
|
|
| 1. **整体打分(推荐)**:取 `[CLS]` 的 SOLUTION-CORRECT 概率,训练最充分(weight 1.0),实测区分度最好。 |
| 2. **逐步骤打分**:取每个 `%%` token 的 STEP-CORRECT 概率,可以看模型认为哪些步骤对。但当前仅训了 1 epoch,单个步骤的打分区分度有限。 |
|
|
| ## 常见问题 |
|
|
| ### 不能用 AutoModel 加载 |
|
|
| 本模型修改了 DeBERTa-v2 的内部代码(兼容新版 PyTorch/numpy),必须用仓库中的 `deberta_model.py`: |
|
|
| ```python |
| from deberta_model import DebertaV2ForTokenClassification |
| model = DebertaV2ForTokenClassification.from_pretrained("用户名/aqua-verifier") |
| ``` |
|
|
| ### 分数偏低/偏高 |
|
|
| 因为预算不够,本模型仅训练了 1 epoch,在训练数据风格的推理文本上区分度良好(CORRECT ~9.9, INCORRECT ~0.01),但在不同 LLM 生成的推理风格上分数整体偏低。建议: |
|
|
| - 同题内做相对比较(选最高分路径),不依赖绝对阈值 |
| - 或续训 3-5 epoch 提升泛化 |
|
|
|
|
| ## 参考 |
|
|
| - [DIVERSE: Towards Diverse and Effective Verifier-guided Reasoning](https://arxiv.org/abs/2410.09328) |
| - [原始代码仓库](https://github.com/lz1oceani/diverse) |
|
|