把中文试卷里的选择题和判断题原始文本,抽取为干净、规范的 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 字段只保留题干本身):
- 剔除题号:
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 robustPython 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"] # 复制保真校验结果,空列表 = 可信;非空 = 建议人工复核默认采用三层管线,在速度、格式保证和可信度之间取平衡:
- fallback 解码:先自由贪心解码(~1.8s/条);仅当 JSON 解析失败或结构残缺时,用 lm-format-enforcer 按 JSON Schema 约束解码重跑一次,保住 100% 可解析的下限。
- 确定性规范化:正则层面剔除"判断:"前缀、句尾空括号等固定噪声,不依赖模型每次都学到位。
- 复制保真校验:抽取是复制任务,每个字段都应在原文中找得到。校验发现选项值重复、字段不是原文子串、答案不合法时,在
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