Skip to content

Repository files navigation

exam2json — 试题文本结构化抽取模型(Qwen3-0.6B LoRA 微调)

把中文试卷里的选择题判断题原始文本,抽取为干净、规范的 JSON。 输入是从试卷/题库/文档里粘贴出来的脏文本(带题号、小节标题、难度分值行、答案、解析等噪声),输出是只含题干、选项、答案的结构化数据。

输入(原始试卷文本)                          输出(纯净 JSON)
─────────────────────────                ─────────────────────────
二、判断题                          →     {
难度:易                                    "question": "路由器通常工作在网络层。",
3. 路由器通常工作在网络层。(  )             "answer": "对"
正确答案:对                              }
解析:本题考查基础概念。

模型信息

项目 说明
基座模型 Qwen/Qwen3-0.6B
微调方法 LoRA(PEFT)+ 有监督微调(trl SFTTrainer
可训练参数 约 10M(约占全模型 1.7%),基座权重全程冻结
训练数据 2,108 条中文试题(选择 930 / 判断 1,178),模板合成 + 噪声增强
训练硬件 单卡 RTX 4060 Laptop(8GB 显存),bf16
语言 中文
适配器位置 output_v2/final_model/(含训练时的 system prompt)
模型权重下载 https://huggingface.co/wchyin/qwen3-0.6b-exam2json-lora
许可证 Apache-2.0

任务定义与输出格式

模型自行判断题型,输出两种 schema 之一:

// 选择题
{"question": "题干", "options": {"A": "...", "B": "...", "C": "...", "D": "..."}, "answer": "B"}

// 判断题
{"question": "题干", "answer": ""}   // answer ∈ {"对", "错"}

输出规范(question 字段只保留题干本身):

  • 剔除题号:3.三十九、(5)第5题 等各种写法
  • 剔除题型前缀:判断:判断题:
  • 剔除句尾的空括号答题占位:( )( )
  • 句中有内容的括号(如挖空处)一律保留
  • 剔除小节标题、难度/分值/知识点行、答案行、解析行

评测表现

在 80 条独立测试集(选择/判断各 40,与训练数据同分布的合成数据)上:

指标 free(自由解码) enforced(约束解码) fallback
JSON 可解析 100% 100% 100%
结构正确 100% 100% 100%
question 完全一致 98.8% 98.8% 98.8%
answer 正确 100% 100% 100%
选项完全一致 95.0% 95.0% 95.0%
整条完全一致 96.2% 96.2% 96.2%
平均耗时/条(4060) 1.84s 5.17s 1.87s

分题型整条完全一致:选择题 95.0%,判断题 97.5%。

剩余的 3 条失败均为 0.6B 模型的"抄写走神"(选项值抄重、选项错位、题干复读),全部能被内置的复制保真校验自动检出并标记——生产管线上是"96.2% 全对 + 3.8% 自动标记人工复核 + 0 静默错误"。

训练过程:3 epoch / 357 步,train loss 3.41 → 0.12,eval loss 0.589 → 0.150(单调下降,最优检查点即最终步)。

快速开始

环境:Python 3.10 + torch 2.5.1(cu121),其余依赖见 requirements.txt(版本敏感,建议锁定安装)。模型缓存在本地后全程可离线运行(脚本内置 HF_HUB_OFFLINE=1)。

# 安装依赖(torch 按自己的 CUDA 版本装,见 requirements.txt 注释)
pip install -r requirements.txt
# 内置样例推理
python infer.py

# 抽取自己的题目(--file 按空行分隔多道题)
python infer.py --text "1. 下列哪个是传输层协议?`nA. IP`nB. TCP`nC. ARP`nD. ICMP`n答案:B"
python infer.py --file questions.txt --jsonl-out results.jsonl

# 完整评测(生产管线)
python evaluate_test.py --lora-path output_v2/final_model --mode robust

Python API:

from extract_core import load_model, resolve_system_prompt, JsonEnforcer, extract_question

model, tok = load_model("Qwen/Qwen3-0.6B", "output_v2/final_model")
sp = resolve_system_prompt("output_v2/final_model")
enforcer = JsonEnforcer(tok)          # 一次性构建,之后每条题目复用

r = extract_question(model, tok, raw_text, system_prompt=sp, enforcer=enforcer)
r["pred"]      # 抽取结果 dict
r["issues"]    # 复制保真校验结果,空列表 = 可信;非空 = 建议人工复核

推理策略

默认采用三层管线,在速度、格式保证和可信度之间取平衡:

  1. fallback 解码:先自由贪心解码(~1.8s/条);仅当 JSON 解析失败或结构残缺时,用 lm-format-enforcer 按 JSON Schema 约束解码重跑一次,保住 100% 可解析的下限。
  2. 确定性规范化:正则层面剔除"判断:"前缀、句尾空括号等固定噪声,不依赖模型每次都学到位。
  3. 复制保真校验:抽取是复制任务,每个字段都应在原文中找得到。校验发现选项值重复、字段不是原文子串、答案不合法时,在 issues 字段标记(默认只标记不改写,模型输出保持原样;--repair 可开启从原文的确定性自动修复,开启后测试集 80/80 全对)。

局限性

  • 训练与测试数据均为模板合成的计算机基础类试题;真实扫描件/OCR 文本、其他学科、其他排版风格下的表现未经验证。
  • 选择题固定为 A–D 四个选项(训练数据中仅 3 条 A/B 双选项样本);多选题、非 ABCD 键未支持。
  • 假设输入为单道题目;一次粘贴多道题需要先在上游切分。
  • 0.6B 模型对生僻词存在低概率的抄写失真(见评测表现),依赖校验层兜底标记。
  • answer 抽取的是原文标注的参考答案,模型不做答题,原文答案错它也照抄。

文件结构

finetune.py          训练脚本(LoRA + SFTTrainer,输出到 output_v2/)
build_train_v2.py    训练数据构建:规范化 + 噪声增强 + 新题合成 → train_data_v2.json
extract_core.py      共用推理库:模型加载 / 约束解码 / 规范化 / 校验修复
infer.py             推理 CLI
evaluate_test.py     评测脚本(free / enforced / fallback / robust 四种模式)
train_data_v2.json   训练集(2,108 条)
test_data.json       独立测试集(80 条)
eval_results/        评测输出(预测明细 jsonl + 指标汇总 json)
requirements.txt     锁定版本的依赖清单
LICENSE              Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages