深度学习实战116-基于Qwen大模型与层次化对齐评分模型(HASM)的中学数学主观题自动批改系统
文章目录
- 摘要
- 1. 引言 (Introduction)
- 1.1 研究背景与动机
- 1.2 研究问题定义
- 1.3 研究目标与贡献
- 1.4 报告结构
- 2. 相关工作综述 (Related Work)
- 2.1 AI在教育评分领域的应用
- 2.2 数学问题求解研究
- 2.3 多模态大语言模型(MLLMs)
- 2.4 手写文本识别(Handwriting Text Recognition, HTR)
- 2.5 研究空白分析
- 3. 研究方法 (Methodology)
- 3.1 总体框架设计 (Overall Pipeline Design)
- 3.2 数据集构建策略 (Dataset Construction Strategy)
- 3.3 核心模型设计与创新点
- 4. 实验设计与评估 (Experimental Design and Evaluation)
- 4.1 数据集详情
- 4.2 评价指标 (Evaluation Metrics)
- 4.3 基线模型与对比实验
- 4.4 预期结果
- 5. 项目规划与时间表 (Project Plan and Timeline)
- 结论
- 附录:参考实现代码(Qwen3 + HASM 原型)
- 环境与依赖
- 目录建议
- 使用说明与扩展建议

摘要
随着人工智能在教育领域的渗透,自动批改技术已在客观题上取得显著成功,但在处理具有主观性、步骤性和逻辑性的数学证明题与应用题时,其准确率和解析深度仍面临巨大挑战。现有方法多依赖于对最终答案的文本匹配,或对整体解题过程的粗粒度评估,难以实现对解题步骤的精确识别、逻辑链的有效验证和公正的步骤分评定。本研究旨在解决这一核心问题,提出一种创新的、面向手写数学主观题的自动批改框架。
本报告提出一个名为“层次化对齐评分模型”(Hierarchical Alignment Scoring Model, HASM)的全新方法论。该模型的核心思想是将复杂的评分任务分解为三个可控的子任务:手写内容识别与结构化、解题步骤层次化对齐、以及基于对齐结果的智能评分。首先,系统接收学生手写的解题过程图像,通过先进的手写文本识别(HTR)技术将其转换为结构化的文本数据(如LaTeX)。其次,利用一个大型多模态模型(MLLM)对结构化文本进行语义解析,构建出能够表达解题逻辑步骤的“学生解答图”。该研究的关键创新在于“层次化对齐”模块,它将“学生解答图”与预设的“标准答案图”进行一种新颖的图匹配对齐,而非简单的文本比对。这种对齐能够精准识别出正确的步骤、错误的推论、以及缺失的关键环节。最后,一个独立的评分模块根据对齐结果,结合预定义的评分细则,生成最终的总分、详细的步骤分以及诊断性的反馈意见。
鉴于目前公开渠道缺乏此类任务所需的手写图像与评分标注数据集的严峻现状 本研究的另一项重要贡献是提出了一套完整的数据集构建方案。该方案将利用现有的高质量文本数学问题库(如 MATH
数据集 中的分步解答,通过程序化的手写合成技术,生成大规模、多样化的手写解答图像,并自动附加精确的步骤与总分标签。本研究的预期成果是一个能够高效、准确、可解释地批改手写数学证明题和应用题的AI系统原型。该系统不仅能提供一个总分,更能深入到解题的每一个逻辑环节,实现真正意义上的“因材施教”式智能辅导。在评估方面,除了传统的评分准确度指标(如MAE),本研究还将引入“步骤匹配准确率”(Step-Matching Accuracy, SMA)和“逻辑一致性得分”(Logical Coherence Score, LCS)等新指标,以更全面地衡量模型的性能。本研究有望填补当前AI教育领域在主观题深度批改方面的技术空白,为实现大规模个性化教育提供关键技术支撑。
1. 引言 (Introduction)
1.1 研究背景与动机
近年来,人工智能(AI)技术,特别是深度学习,已在众多领域引发了革命性的变革。在教育领域,“AI+教育”已成为一个备受关注的热点方向,其应用范围涵盖了个性化学习路径推荐、智能辅导系统、以及自动化作业批改等 。其中,自动作业批改技术能够极大地减轻教师的重复性劳动,使他们能将更多精力投入到更具创造性的教学活动中。
目前,AI在批改有确定答案的客观题(如选择题、填空题)方面已经达到了极高的准确率。然而,对于数学学科中的主观题,如证明题、计算题的详细步骤、以及开放式应用题,AI批改的现状仍不尽如人意。这类题目考察的不仅是最终结果,更重要的是学生的逻辑推理能力、解题策略和对数学概念的理解深度。传统的批改方法往往依赖于关键词匹配或简单的答案比对,无法理解复杂的数学逻辑和多样的解题路径,导致评分准确率低、无法提供有价值的反馈,限制了其在真实教学场景中的应用价值。
与此同时,多模态大语言模型的崛起为解决这一难题带来了新的曙光 。这些模型能够同时处理和理解多种类型的数据,如图像和文本,展现出强大的视觉理解和逻辑推理能力 。例如,Qwen、InternVL等模型已在多种多模态基准测试中表现出色 。这为我们设计一个能够直接从学生手写稿图像中理解解题思路并进行智能评分的系统提供了底层技术基础。然而,如何将这些通用模型的强大能力聚焦于数学主观题评分这一特定且复杂的任务,并保证评分的准确性、公平性和可解释性,是一个亟待解决的科学问题。本研究的动机在于,利用最前沿的多模态AI技术,设计一套能够模拟人类专家教师批改逻辑的自动化系统,以提升数学主观题,特别是手写证明题和应用题的批改质量,使其不仅能“判对错”,更能“知其所以然”,为学生提供精准的、步骤级别的反馈。
1.2 研究问题定义
本研究的核心是解决手写数学主观题的自动评分问题。具体而言,研究问题可以被形式化地定义如下:
给定一个学生的输入,该输入包含两部分:
- 问题描述(Problem Description): 通常为文本或图像形式的数学题目。
- 手写解答(Handwritten Solution): 一张或多张包含学生手写解题过程的图像。
系统的目标是输出一个结构化的评分结果,该结果应包含:
- 总分(Total Score): 对整个解答的最终评分。
- 分步得分(Step-wise Scores): 一个详细列表,标明每个关键解题步骤的得分情况(例如,正确、部分正确、错误)。
- 诊断性反馈(Diagnostic Feedback): 对错误或不规范步骤的解释和修正建议,以文本形式呈现。
这个任务的挑战在于需要模型具备多种能力:高效准确的手写识别、复杂的数学符号与公式理解、对解题步骤的逻辑关联性分析、以及对多样化正确解法和常见错误的知识。
1.3 研究目标与贡献
为解决上述研究问题,本研究设定了以下具体目标:
- 设计并实现一个创新的手写数学主观题自动批改模型(HASM) :该模型应超越传统的端到端方法,通过分层和对齐的策略,提升评分的准确性和可解释性。这需要对现有模型结构和处理流程进行修改和优化 而非纯粹的提示工程。
- 构建一个适用于本任务的大规模、高质量数据集:由于公开可用数据集的缺失是当前研究的主要瓶颈 本研究将提出并实施一套从现有文本数据合成手写图像数据的方案,为本领域的研究提供宝贵资源。
- 定义一套全面的评估体系:除了传统的评分误差指标,本研究将提出并验证新的、更适合主观题评分任务的评价指标,如步骤匹配准确率(SMA),以更细粒度地评估模型性能。
本研究的主要贡献将体现在以下三个方面:
- 方法论创新:提出的HASM框架,特别是其“层次化对齐”机制,为解决AI在主观性、结构化任务上的评分难题提供了一种新的范式。
- 资源贡献:通过合成方式创建的首个大规模手写数学证明与步骤题评分数据集,将极大地推动相关领域的研究进展。
- 实践价值:研究成果可转化为实用的教育工具,辅助教师进行高效批改,并为学生提供即时、个性化的学习反馈,具有广阔的应用前景。
1.4 报告结构
本报告的后续部分将围绕上述目标展开。第二章将回顾相关领域的研究工作,并分析现有技术的局限性。第三章将详细阐述我们提出的HASM模型框架、数据集构建策略以及核心算法设计。第四章将介绍实验设计,包括评价指标、基线模型和预期结果。第五章将给出项目的实施计划和时间安排。最后,第六章对全文进行总结。
2. 相关工作综述 (Related Work)
2.1 AI在教育评分领域的应用
AI辅助评分(Automated Essay Scoring, AES)是教育领域研究较早也较成熟的方向,主要用于评估写作任务。早期的系统如E-rater,依赖于自然语言处理技术提取文本的表层特征(如语法、词汇、篇章结构)进行评分。近年来,基于深度学习的模型,特别是BERT等预训练语言模型,通过捕捉更深层次的语义信息,显著提升了作文评分的性能。然而,这些方法主要处理线性文本,难以直接迁移到具有严密逻辑结构和二维符号表达的数学题批改中。
2.2 数学问题求解研究
AI求解数学问题也是一个活跃的研究领域。早期的研究集中于符号计算系统。近年来,基于大型语言模型(LLMs)的数学求解器取得了显著进展。例如,MATH
数据集 的发布推动了模型在解决竞赛级数学问题上的发展。这些模型通常采用“思维链”(Chain-of-Thought, CoT) 等技术,生成详细的文本解题步骤。一些研究甚至探索了利用大模型进行定理证明 。然而,这些工作的重点在于“生成”正确的解法,而非“评估”一个给定的、可能存在错误的解法。它们可以作为生成标准答案的工具,但直接用于评分则面临挑战,因为它们对错误和非标准解法的理解能力有限。
2.3 多模态大语言模型(MLLMs)
MLLMs的出现是本研究的技术基石。模型如LLaVA、Qwen-VL 、InternVL 等通过将视觉编码器与大型语言模型对齐,实现了对图文混合输入的深度理解。它们在视觉问答(VQA)、图像描述、光学字符识别(OCR)等方面展现了惊人的“零样本”或“少样本”能力。在数学领域,已有基准如 MATP-BENCH
和 MathVista
专门用于评估MLLMs的数学推理能力。研究如“AtomThink” 探索如何提升MLLM在数学习题中的“慢思考”能力。尽管这些模型潜力巨大,但直接将其应用于评分任务存在几个问题:
- 黑盒性:端到端的MLLM模型输出评分结果时,其决策过程不透明,难以提供可信的、分步的批改依据。
- 稳定性:对于相似但略有不同的输入,模型输出的评分可能有较大波动。
- 主观性对齐困难:将模型对解题过程的“理解”映射到具体的分数值,需要精巧的设计和大量的标注数据进行微调,而这正是我们所缺乏的。
2.4 手写文本识别(Handwriting Text Recognition, HTR)
HTR是将手写图像转换为机器可读文本的关键技术。传统HTR依赖于复杂的图像处理和序列建模。现代基于深度学习的HTR系统,特别是基于Transformer架构的模型 ,在识别手写公式和文本方面已达到较高精度。公共数据集如 MNIST
和 HASYv2
分别提供了手写数字和数学符号的样本。然而,识别包含复杂布局、多行公式和符号的完整解题过程,仍然是一个挑战。一个稳健的HTR模块是本研究流水线的关键入口。
2.5 研究空白分析
综合上述分析,当前研究存在以下明显空白:
- 缺乏专用的数据集:这是最核心的制约因素。目前没有任何公开的、大规模的数据集包含手写数学主观题的图像及其对应的、带有步骤分的人工评分标签 。这使得监督学习方法的开发和评估变得异常困难。
- 缺乏专用的模型架构:现有模型要么专注于文本生成(数学求解器),要么是通用的多模态理解模型(MLLMs)。没有专门为数学主观题“评分”这一独特任务设计的、能够兼顾逻辑验证和步骤分析的端到端模型或流程。
- 缺乏专用的评估指标:如何科学、全面地评估一个主观题评分系统的性能,本身就是一个开放性问题。通用的文本生成指标(如BLEU, ROUGE 或分类指标(如准确率 无法完全捕捉评分任务的细微差别,例如对部分正确的认可和对逻辑错误的惩罚。
本研究旨在填补这些空白,通过提出创新的模型架构、数据集构建方法和评估体系,推动该领域的实质性发展。
3. 研究方法 (Methodology)
为了实现对手写数学主观题的精准、可解释评分,我们设计了“层次化对齐评分模型”(Hierarchical Alignment Scoring Model, HASM)。其总体框架、数据集构建策略和核心模型设计如下。
3.1 总体框架设计 (Overall Pipeline Design)
HASM是一个多阶段的流水线式系统,如下图所示。这种设计将复杂的评分任务解耦成几个更易于管理和优化的模块,增强了系统的鲁棒性和可解释性。
(这是一个示意图,实际报告中应绘制)
阶段一:手写识别与结构化 (Handwriting Recognition and Structuring)
- 输入: 学生手写解答的图像 (JPEG/PNG)。
- 处理: 使用一个预训练的、针对数学公式优化的HTR模型,将图像中的手写内容(包括文本、数字、符号和公式)转换为结构化的数字格式。考虑到数学公式的二维特性,输出格式优选为 LaTeX 字符串,因为它能良好地保持公式的结构信息。
- 输出: 包含整个解题过程的LaTeX代码。
阶段二:语义解析与解答图构建 (Semantic Parsing and Solution Graph Construction)
- 输入: HTR模块输出的LaTeX字符串,以及原始的题目描述。
- 处理: 该模块的核心是一个经过微调的多模态大语言模型(MLLM)。它的任务是“理解”学生的解题逻辑。它将线性的LaTeX代码解析成一个有向无环图(DAG),我们称之为“ 学生解答图 (Student Solution Graph) ”。
- 图中的 节点(Node) 代表一个独立的解题步骤,例如一个定义、一个公式、一次代入运算或一个逻辑推论。每个节点包含该步骤的文本内容和类型。
- 图中的 边(Edge) 代表步骤之间的依赖关系或推导关系。例如,从“A=B”和“B=C”两个节点引出的边可以指向“A=C”节点。
- 输出: 一个代表学生解题逻辑的图结构数据(如JSON格式)。
阶段三:层次化对齐 (Hierarchical Alignment)
- 输入: “学生解答图”和预先生成的“ 标准答案图 (Golden Solution Graph) ”。标准答案图可以由专家构建,或由强大的LLM(如GPT-4)根据标准答案生成。
- 处理: 这是HASM的核心创新模块。它负责对比学生解答与标准答案的逻辑结构。我们不采用简单的文本相似度匹配,而是进行图对齐。
- 该模块首先使用一个编码器(如基于Transformer的图编码器)为两个图中的每个节点生成语义向量(embedding)。
- 然后,通过一个对齐算法(例如,基于最优传输或匈牙利算法的变体),在两个图的节点之间寻找最佳匹配。匹配的依据是节点内容的语义相似度以及它们在图结构中的拓扑位置。
- 对齐算法会产生三种结果:匹配的节点对(学生步骤正确)、学生图中有但标准图中无匹配的节点(多余或错误的步骤)、标准图中有但学生图中无匹配的节点(缺失的步骤)。
- 输出: 一个包含对齐信息的“对齐结果报告”。
阶段四:评分与反馈生成 (Scoring and Feedback Generation)
- 输入: “对齐结果报告”。
- 处理: 该模块根据预设的评分规则(Scoring Rubrics)和对齐结果进行打分。
- 一个简单的规则可以是:每个成功匹配的步骤得满分,缺失步骤不得分,错误步骤扣分。
- 更复杂的,可以训练一个小型神经网络(如MLP)或一个轻量级LLM,输入对齐报告,输出最终的总分和步骤分。这种方式可以学习更细致的评分策略,例如对部分正确的步骤给予部分分数。
- 同时,该模块会根据未匹配或错误匹配的节点,生成针对性的诊断反馈。例如,“你在第三步的因式分解中出现了错误”或“你缺少了证明收敛性的关键步骤”。
- 输出: 最终的评分JSON对象,包含总分、步骤分和反馈文本。
3.2 数据集构建策略 (Dataset Construction Strategy)
鉴于公开数据集的严重缺乏 我们必须自行构建数据集。直接雇人手写并标注大量题目成本高昂且周期漫长。因此,我们提出一种高效的合成数据生成方案。
- 数据源 (Data Source):
- 我们将使用高质量的、已有的英文数学问题文本数据集作为基础。首选是
MATH
数据集 。该数据集包含12,500个具有挑战性的竞赛级数学问题,覆盖数论、代数、几何等多个领域,并且最重要的是,它提供了完整的、分步的LaTeX格式解答。
- 数据合成流程:
- 步骤一:标准答案图生成。对于
MATH
数据集中的每个问题及其标准解答,我们使用一个强大的LLM(如GPT-4o)来解析其LaTeX解题步骤,并生成上文定义的“标准答案图”。同时,为每个步骤分配一个分数,并计算总分。 - 步骤二:模拟学生错误。为了让模型学会识别错误,我们需要生成带有错误的解答。我们会设计多种常见的错误模式,并通过程序自动注入到标准解答中,例如:
- 计算错误(如
2+3=6
)。 - 逻辑跳跃(缺少关键步骤)。
- 概念误用(使用错误的公式)。
- 引入无关步骤。
对每个注入错误的解答,同样生成其“学生解答图”并重新计算评分。
- 计算错误(如
- 步骤三:手写图像合成。这是将文本数据转换为多模态数据的关键。我们将使用开源的手写合成模型(如基于GAN或Diffusion的模型),输入LaTeX格式的解答(包括正确和错误的),生成逼真的手写解答图像。为了增加数据多样性,合成时可以模拟不同的书写风格、笔迹粗细、纸张背景和轻微的噪声。
- 步骤四:数据整合。最后,我们将所有数据整合成一个统一的数据集。每条数据记录将包含:
problem_id
: 问题唯一标识。problem_description
: 题目描述(文本)。handwritten_image_path
: 合成的手写解答图像文件路径。solution_latex
: 该图像对应的“真实”LaTeX内容。solution_graph
: 对应的解答图(JSON)。score_total
: 总分。score_steps
: 步骤分详情(JSON)。
-
数据集链接:
由于该数据集是本研究的一部分,目前尚无公开链接。在项目完成后,我们会整理并开源该数据集,暂定名为Handwritten-Math-Grading-Bench (HMG-Bench)
。 -
输入与输出格式定义
- 模型输入 (Input):
image
: 一张手写解答的图像文件。problem_text
: 问题的文本描述。
- 模型输出 (Output):
一个JSON对象,结构如下:{"total_score": 8.5,"max_score": 10.0,"steps": [{"step_id": 1,"description": "Define the variable x as the number of apples.","score": 1.0,"status": "correct","feedback": ""},{"step_id": 2,"description": "Set up the equation 2x + 5 = 15.","score": 1.0,"status": "correct","feedback": ""},{"step_id": 3,"description": "Solve for x: 2x = 10, so x = 4.","score": 0.0,"status": "incorrect","feedback": "Calculation error in this step. 2x = 10 should result in x = 5."}],"overall_feedback": "Your setup of the equation is correct, but there was a calculation error in the final step. Please review your division." }
3.3 核心模型设计与创新点
本研究的核心创新在于提出的 层次化对齐评分模型 (HASM) ,尤其体现在其模型结构和评分策略上。
-
创新点一:从端到端“黑盒”到分层“白盒”的范式转变
传统的端到端MLLM方法 直接将图像映射到分数,过程不可控且难以解释。我们的HASM流水线将任务分解,每个模块的输入输出都是明确定义的(图像 -> LaTeX -> 解答图 -> 对齐报告 -> 分数)。这种设计 带来了几个好处:- 可解释性:当评分出现争议时,我们可以追溯到是HTR识别错误、图构建失败,还是对齐算法有偏差,便于调试和优化。
- 模块化与可扩展性:每个模块都可以独立升级。例如,未来出现更好的HTR模型,可以直接替换,而无需重新训练整个系统。
- 知识注入:评分规则可以由人类专家定义并显式地注入到第四阶段,而不是完全依赖模型隐式学习,保证了评分的公平性和一致性。
-
创新点二:基于图对齐的深度逻辑匹配,而非浅层文本匹配
这是方法论上的关键创新。以往的方法可能使用BLEU或ROUGE等指标 比较学生答案文本和标准答案文本的相似度,但这无法捕捉逻辑等价但表述不同的情况(例如x>0
和0<x
),也无法识别逻辑谬误。我们的方法:- 将解答抽象为逻辑图,关注解题步骤的实体(节点)和关系(边)。这种表示法更接近数学推理的本质。
- 使用图对齐算法,在语义和结构两个层面进行匹配。这意味着模型不仅看一个步骤“写了什么”,还看它“在整个证明中扮演什么角色”。这是一种更深层次的理解,可以有效处理步骤顺序颠倒、使用不同符号、或采用不同但同样有效的证明路径等复杂情况。
- 这个对齐过程可以形式化为一个 对齐损失(Alignment Loss) 的优化问题。虽然搜索结果中没有直接的算法实现 但我们可以借鉴对比学习 和最优传输理论来设计这个损失函数,使其在训练过程中学习如何更好地对齐语义相似且结构位置对应的节点。
-
创新点三:基于知识蒸馏的评分模块
在评分模块,我们可以进一步创新。虽然可以使用固定的规则,但训练一个小型模型能获得更好的泛化能力。这里可以引入教师-学生模型蒸馏 的思想。- 教师模型: 一个非常强大的、但可能推理速度很慢的闭源模型(如GPT-4o API),或者一个由多位人类专家组成的“群体智慧”。我们用它来为一小部分高质量的样本生成非常精细的评分和反馈。
- 学生模型: 我们在HASM第四阶段部署的轻量级评分模型。
- 蒸馏过程: 学生模型的目标不仅是预测分数,还要模仿教师模型输出的“评分理由”或对步骤重要性的“注意力分布”。这通过一个特殊的蒸馏损失函数 来实现,迫使学生模型学习到教师的“评分哲学”,而不仅仅是拟合分数。
4. 实验设计与评估 (Experimental Design and Evaluation)
4.1 数据集详情
我们将基于MATH
数据集 创建 HMG-Bench
。
- 训练集: 包含约15,000个合成手写解答图像。这些样本将覆盖70%的
MATH
问题,并且包含约40%的注入错误样本。 - 验证集: 包含约2,500个合成样本,来自15%的
MATH
问题,用于模型调优和选择。 - 测试集: 包含约2,500个合成样本,来自15%的
MATH
问题,用于最终性能评估。此外,我们会额外雇佣人类标注员,手写并标注一小部分(约200个)全新的题目,作为检验模型在真实世界数据上泛化能力的“黄金测试集”。
4.2 评价指标 (Evaluation Metrics)
由于现有指标的局限性 我们将采用一个多维度的评估体系:
-
评分准确度 (Scoring Accuracy):
- 均方根误差 (RMSE) 和 平均绝对误差 (MAE): 计算模型预测总分与真实总分之间的误差。这是衡量整体评分准确性的核心指标。
- 二次加权Kappa (Quadratic Weighted Kappa, QWK): 常用于衡量评分者之间一致性的指标,适合于有序评分任务。
-
步骤级评估 (Step-level Evaluation):
- 步骤匹配准确率 (Step-Matching Accuracy, SMA): 这是我们提出的新指标。它衡量的是HASM在对齐阶段的性能,计算方法为“学生解答图”中被正确分类(正确、错误、缺失)的节点数量占总节点数的比例的F1分数。高SMA意味着模型能准确理解学生错在哪里。
- 步骤分F1分数 (Step Score F1): 将每个步骤的评分视为一个分类任务(如:满分、部分分、零分),计算其宏平均F1分数。
-
逻辑与反馈评估 (Logic and Feedback Evaluation):
- 逻辑一致性得分 (Logical Coherence Score, LCS): 这是一个半自动评估指标。我们将模型为错误步骤生成的反馈文本,交由另一个强大的LLM(如 Claude 3 Opus)或人类评估员进行打分(1-5分),评估反馈是否准确、清晰、有帮助。
- 文本生成质量: 使用 BLEU 和 ROUGE-L 评估生成的反馈文本与人工撰写的标准反馈之间的相似度。
4.3 基线模型与对比实验
为了验证HASM的有效性,我们将设置以下基线模型进行对比:
- Baseline 1: Random/Average Baseline: 随机猜测分数或总是输出平均分,作为性能下限。
- Baseline 2: HTR + Text Similarity: 一个简单的流水线,使用HTR将图像转为文本,然后计算该文本与标准答案文本的ROUGE-L得分,并将其线性映射到分数。
- Baseline 3: End-to-End MLLM: 微调一个强大的开源MLLM(如 Qwen-VL-Chat 或 LLaVA 。输入图像和问题,直接让它输出一个包含分数的JSON对象。这是我们最主要的对比对象。
消融研究 (Ablation Studies):
我们将对HASM进行消融实验,以验证各模块的贡献:
- HASM w/o Graph: 将阶段三的图对齐替换为简单的句子嵌入向量相似度匹配,以验证图结构的必要性。
- HASM w/o Alignment: 移除对齐模块,直接将学生解答图的整体嵌入向量输入评分模块,以验证显式对齐的优势。
- HASM w/ Rule-based Scoring: 将阶段四的神经网络评分器换成固定的、基于规则的评分逻辑,以验证学习型评分器的优势。
4.4 预期结果
我们预期实验结果将显示:
- HASM在所有评分准确度指标(RMSE, MAE, QWK)上都将显著优于所有基线模型。特别是与端到端MLLM基线相比,我们期望误差能降低20%-30%。
- 在步骤级评估指标(SMA, Step Score F1)上,HASM将表现出压倒性优势,因为这是其设计的核心目标,而基线模型不具备这种细粒度的分析能力。
- 消融研究将证明,图结构和显式对齐模块是HASM性能的关键来源。
- 在“黄金测试集”上,HASM的性能下降幅度将小于端到端MLLM,显示出更好的泛化能力和鲁棒性。
5. 项目规划与时间表 (Project Plan and Timeline)
本研究计划在一个月内完成初稿,具体时间安排如下:
-
第一周: 文献深化与方案定型 (Week 1: Deep Literature Review & Finalizing Methodology)
- 深入调研手写合成技术、图神经网络和对齐算法的最新进展。
- 最终确定HASM各模块的技术选型和实现细节。
- 完成研究报告的引言和相关工作部分。
-
第二周: 数据集构建与预处理 (Week 2: Dataset Construction & Preprocessing)
- 编写脚本,从
MATH
数据集中提取问题和LaTeX解答。 - 实现错误注入逻辑,生成多样化的错误解答。
- 部署手写合成模型,开始生成
HMG-Bench
数据集的图像。
- 编写脚本,从
-
第三周: 模型实现与训练 (Week 3: Model Implementation & Training)
- 实现HASM的四个核心模块。重点是图构建和图对齐算法的PyTorch代码实现 。
- 配置训练环境,在生成的
HMG-Bench
训练集上开始训练基线模型和HASM。
-
第四周: 实验评估与报告撰写 (Week 4: Experiments & Report Writing)
- 在验证集和测试集上运行所有实验,收集并分析结果。
- 完成消融研究,生成对比表格和图表 。
- 撰写报告的方法、实验和结论部分,完成初稿。
结论
本研究报告提出了一套旨在解决当前AI在手写数学主观题自动批改领域核心挑战的综合性解决方案。通过设计创新的 层次化对齐评分模型 (HASM) ,并辅以一套切实可行的合成数据集构建策略和多维度评估体系,我们期望能够开发出一个准确、可解释且实用的智能评分系统。
HASM框架的核心优势在于其分层解耦的结构和基于图对齐的深度逻辑分析能力,这使其能够超越现有方法的局限,实现对学生解题过程的细粒度理解和公正评分。本研究不仅具有重要的学术价值,填补了相关领域的技术空白,也具备巨大的实践潜力,能够赋能下一代智能教育系统,真正实现AI技术对个性化教学的深度支持。未来的工作可以在此基础上进一步探索更复杂的几何证明题(需要更强的视觉推理能力)、生成更具启发性的多步反馈、以及将模型迁移到其他需要结构化推理的学科(如物理、化学)中。
附录:参考实现代码(Qwen3 + HASM 原型)
下面给出一个可运行的原型实现,覆盖本文提出的四个阶段:HTR(图像→LaTeX/文本)、语义解析与“解答图”构建(调用 Qwen3)、层次化对齐(基于嵌入+匈牙利匹配并兼顾结构一致性),以及评分与反馈生成(规则为主,可扩展学习型评分器)。
请注意:此为研究原型,默认采用通用OCR/HTR与文本级解析,公式级LaTeX识别可替换为更强的 pix2tex/LaTeX-OCR 模型;多模态直接解析亦可替换为 Qwen-VL/Qwen3-VL。
环境与依赖
pip install --upgrade openai dashscope sentence-transformers scipy networkx numpy opencv-python pytesseract python-dotenv rapidfuzz# 如果需要更好的公式识别,可选:
# pip install pix2tex
设置大模型相关环境变量(二选一或同时配置,代码会自动探测):
-
使用 OpenAI 协议兼容的 Qwen3 推理服务(如自建、吾志、One-API 等)
QWEN_PROVIDER=openai
OPENAI_API_KEY=...
OPENAI_API_BASE=...
例如http://localhost:3000/v1
QWEN_MODEL=qwen3-7b-instruct
(或你的部署模型名,如qwen2.5-14b-instruct
)
-
使用阿里云 DashScope
QWEN_PROVIDER=dashscope
DASHSCOPE_API_KEY=...
QWEN_MODEL=qwen3-7b-instruct
(或 DashScope 对应可用模型名)
目录建议
本文档内以“拟文件”的形式给出代码。实际工程中建议创建如下结构,并将代码分别保存:
hasm/__init__.pyconfig.pyhtr.pyllm_qwen.pygraph.pyalignment.pyscoring.pypipeline.py
run_demo.py
# hasm/config.py
from dataclasses import dataclass
import os@dataclass
class HASMConfig:# LLM provider: "openai" (OpenAI 兼容协议) 或 "dashscope"qwen_provider: str = os.getenv("QWEN_PROVIDER", "openai").lower()# 模型名称qwen_model_name: str = os.getenv("QWEN_MODEL", "qwen3-7b-instruct")# OpenAI 兼容配置openai_api_key: str = os.getenv("OPENAI_API_KEY", "")openai_api_base: str = os.getenv("OPENAI_API_BASE", "")# DashScope 配置dashscope_api_key: str = os.getenv("DASHSCOPE_API_KEY", "")# 嵌入模型(句向量)embedding_model_name: str = os.getenv("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")# 对齐阈值match_threshold_high: float = 0.78match_threshold_low: float = 0.62# 结构一致性权重(影响匹配得分)structural_consistency_weight: float = 0.15# 评分权重full_score_per_step: float = 1.0partial_score_ratio: float = 0.5
# hasm/htr.py
from typing import Optional
import cv2
import numpy as nptry:import pytesseract
except Exception: # pragma: no coverpytesseract = Noneclass HandwritingRecognizer:"""简化版 HTR:将手写解答图像转为文本/LaTeX。原型默认使用 Tesseract 进行文本识别;如需更强的公式识别,可替换为 pix2tex 等模型。"""def __init__(self, tesseract_cmd: Optional[str] = None) -> None:if tesseract_cmd and pytesseract: # 允许手动指定 tesseract 路径(Windows 常见)pytesseract.pytesseract.tesseract_cmd = tesseract_cmddef _preprocess(self, image: np.ndarray) -> np.ndarray:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)gray = cv2.bilateralFilter(gray, 7, 50, 50)th = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 35, 11)return thdef recognize(self, image_path: str) -> str:image = cv2.imread(image_path)if image is None:raise FileNotFoundError(f"Image not found: {image_path}")proc = self._preprocess(image)if not pytesseract:# 兜底:返回占位文本,保证流程可跑通return "[HTR Placeholder] Please integrate pix2tex for LaTeX formulas."text = pytesseract.image_to_string(proc, lang="eng")return text.strip()
# hasm/llm_qwen.py
from typing import Dict, Any
import json
from .config import HASMConfigclass QwenLLMClient:def __init__(self, cfg: HASMConfig) -> None:self.cfg = cfgprovider = cfg.qwen_providerif provider == "dashscope":from dashscope import Generationself._client = Generationself._mode = "dashscope"else:from openai import OpenAIself._client = OpenAI(base_url=cfg.openai_api_base or None,api_key=cfg.openai_api_key or "sk-")self._mode = "openai"def parse_solution_to_graph(self, problem_text: str, solution_text: str) -> Dict[str, Any]:"""使用 Qwen3 将线性文本/LaTeX 解析为“解答图”JSON。输出 schema:{"nodes": [{"step_id": 1, "description": "...", "type": "definition|equation|inference|..."}],"edges": [{"from": 1, "to": 2, "relation": "implies|uses|..."}]}"""system_prompt = ("You are a precise math solution parser. ""Given a problem and a student's linear solution (text/LaTeX), ""extract atomic steps as nodes and logical dependencies as edges. ""Return ONLY a compact JSON with fields: nodes, edges. Do not include any commentary.")user_prompt = f"""
Problem:
{problem_text}Student Solution (text/LaTeX):
{solution_text}Please output JSON only.
"""if self._mode == "dashscope":# DashScope Generationrsp = self._client.call(model=self.cfg.qwen_model_name,prompt=system_prompt + "\n\n" + user_prompt,# 强制 JSON(如模型支持 JSON schema,可进一步收紧)result_format="json")content = rsp.output_text if hasattr(rsp, "output_text") else str(rsp)else:# OpenAI 兼容接口rsp = self._client.chat.completions.create(model=self.cfg.qwen_model_name,messages=[{"role": "system", "content": system_prompt},{"role": "user", "content": user_prompt},],temperature=0.1,response_format={"type": "json_object"},)content = rsp.choices[0].message.contenttry:data = json.loads(content)except Exception:# 兜底:尝试提取 JSON 子串start = content.find("{")end = content.rfind("}")data = json.loads(content[start : end + 1])return data
# hasm/graph.py
from dataclasses import dataclass
from typing import List, Dict, Any@dataclass
class GraphNode:step_id: intdescription: strnode_type: str@dataclass
class GraphEdge:source: inttarget: intrelation: strclass SolutionGraph:def __init__(self, nodes: List[GraphNode], edges: List[GraphEdge]) -> None:self.nodes = nodesself.edges = edgesself._id_to_index = {n.step_id: idx for idx, n in enumerate(nodes)}@classmethoddef from_json(cls, obj: Dict[str, Any]) -> "SolutionGraph":raw_nodes = obj.get("nodes", [])raw_edges = obj.get("edges", [])nodes = [GraphNode(step_id=int(n.get("step_id")),description=str(n.get("description", "")).strip(),node_type=str(n.get("type", "unknown")),)for n in raw_nodesif "step_id" in n]edges = [GraphEdge(source=int(e.get("from")),target=int(e.get("to")),relation=str(e.get("relation", "related")),)for e in raw_edgesif "from" in e and "to" in e]return cls(nodes, edges)def parents_of(self, step_id: int) -> List[int]:return [e.source for e in self.edges if e.target == step_id]def children_of(self, step_id: int) -> List[int]:return [e.target for e in self.edges if e.source == step_id]
# hasm/alignment.py
from dataclasses import dataclass
from typing import List, Tuple, Dict, Any
import numpy as np
from scipy.optimize import linear_sum_assignment
from sentence_transformers import SentenceTransformer, util as st_util
from .graph import SolutionGraph
from .config import HASMConfig@dataclass
class MatchPair:student_step: intgolden_step: intsimilarity: floatstructural_consistency: float@dataclass
class AlignmentReport:matched: List[MatchPair]student_only: List[int]golden_missing: List[int]raw_similarity_matrix: np.ndarrayclass GraphAligner:def __init__(self, cfg: HASMConfig):self.cfg = cfgself.embed = SentenceTransformer(cfg.embedding_model_name)def _compute_text_sim_matrix(self, student_graph: SolutionGraph, golden_graph: SolutionGraph) -> np.ndarray:s_texts = [n.description for n in student_graph.nodes]g_texts = [n.description for n in golden_graph.nodes]s_emb = self.embed.encode(s_texts, convert_to_tensor=True, normalize_embeddings=True)g_emb = self.embed.encode(g_texts, convert_to_tensor=True, normalize_embeddings=True)sim = st_util.cos_sim(s_emb, g_emb).cpu().numpy() # shape: (S, G)return simdef _structural_consistency(self, student_graph: SolutionGraph, golden_graph: SolutionGraph, s_idx: int, g_idx: int, id_map: Dict[int, int]) -> float:# 基于父节点映射的一致性度量(F1)s_node = student_graph.nodes[s_idx]g_node = golden_graph.nodes[g_idx]s_parents = set(student_graph.parents_of(s_node.step_id))g_parents = set(golden_graph.parents_of(g_node.step_id))# 将学生父节点通过已匹配映射转换到黄金 step_id 空间mapped_s_parents = {golden_graph.nodes[id_map[p]].step_id for p in range(len(student_graph.nodes)) if student_graph.nodes[p].step_id in s_parents and p in id_map}inter = len(mapped_s_parents & g_parents)if inter == 0:return 0.0precision = inter / max(1, len(mapped_s_parents))recall = inter / max(1, len(g_parents))if precision + recall == 0:return 0.0return 2 * precision * recall / (precision + recall)def align(self, student_graph: SolutionGraph, golden_graph: SolutionGraph) -> AlignmentReport:sim = self._compute_text_sim_matrix(student_graph, golden_graph)# 先基于相似度构建代价矩阵cost = 1.0 - simrow_ind, col_ind = linear_sum_assignment(cost)# 计算结构一致性并修正最终匹配分id_map: Dict[int, int] = {} # 学生节点索引 -> 黄金节点索引for r, c in zip(row_ind, col_ind):id_map[r] = cmatched: List[MatchPair] = []for r, c in zip(row_ind, col_ind):s_cons = self._structural_consistency(student_graph, golden_graph, r, c, id_map)matched.append(MatchPair(student_step=student_graph.nodes[r].step_id,golden_step=golden_graph.nodes[c].step_id,similarity=float(sim[r, c]),structural_consistency=float(s_cons),))# 依据阈值判断未匹配(或低匹配)节点used_student = set(row_ind.tolist())used_golden = set(col_ind.tolist())# 低于低阈值的,视为未匹配strong_matched = []for m in matched:final_score = m.similarity + self.cfg.structural_consistency_weight * m.structural_consistencyif final_score < self.cfg.match_threshold_low:used_student.discard(next(i for i, n in enumerate(student_graph.nodes) if n.step_id == m.student_step))used_golden.discard(next(i for i, n in enumerate(golden_graph.nodes) if n.step_id == m.golden_step))else:strong_matched.append(m)student_only = [student_graph.nodes[i].step_id for i in range(len(student_graph.nodes)) if i not in used_student]golden_missing = [golden_graph.nodes[j].step_id for j in range(len(golden_graph.nodes)) if j not in used_golden]return AlignmentReport(matched=strong_matched, student_only=student_only, golden_missing=golden_missing, raw_similarity_matrix=sim)
# hasm/scoring.py
from dataclasses import dataclass
from typing import List, Dict, Any
from .alignment import AlignmentReport, MatchPair
from .config import HASMConfig@dataclass
class StepScore:step_id: intdescription: strscore: floatstatus: str # "correct" | "partial" | "incorrect" | "missing"feedback: strclass RuleBasedScorer:def __init__(self, cfg: HASMConfig):self.cfg = cfgdef score(self, report: AlignmentReport, student_steps: Dict[int, str], golden_steps: Dict[int, str]) -> Dict[str, Any]:steps: List[StepScore] = []max_score = 0.0total_score = 0.0# 先为黄金答案每步计算满分(1分/步)for gid, gdesc in golden_steps.items():max_score += self.cfg.full_score_per_stepmatched_golden = set()matched_student = set()for m in report.matched:matched_golden.add(m.golden_step)matched_student.add(m.student_step)final_score = m.similarity + self.cfg.structural_consistency_weight * m.structural_consistencyif final_score >= self.cfg.match_threshold_high:sc = self.cfg.full_score_per_stepstatus = "correct"feedback = ""elif final_score >= self.cfg.match_threshold_low:sc = self.cfg.full_score_per_step * self.cfg.partial_score_ratiostatus = "partial"feedback = "Step partially matches expected logic; refine reasoning or detail."else:sc = 0.0status = "incorrect"feedback = "This step deviates from the expected logic. Review definitions and transitions."total_score += scsteps.append(StepScore(step_id=m.student_step,description=student_steps.get(m.student_step, ""),score=sc,status=status,feedback=feedback,))# 学生多余/错误步骤for sid in report.student_only:steps.append(StepScore(step_id=sid,description=student_steps.get(sid, ""),score=0.0,status="incorrect",feedback="This step appears unnecessary or incorrect relative to the target solution.",))# 黄金缺失步骤(未出现,给出反馈)for gid in report.golden_missing:steps.append(StepScore(step_id=gid,description=golden_steps.get(gid, ""),score=0.0,status="missing",feedback="A key step is missing. Include this to complete the reasoning chain.",))overall_feedback = ""if report.golden_missing:overall_feedback += "Missing key logical steps; ensure all necessary transitions are justified. "if report.student_only:overall_feedback += "Some steps seem extraneous or incorrect; align steps to the problem's goal. "return {"total_score": round(total_score, 3),"max_score": round(max_score, 3),"steps": [s.__dict__ for s in steps],"overall_feedback": overall_feedback.strip(),}
# hasm/pipeline.py
from typing import Dict, Any
from .config import HASMConfig
from .htr import HandwritingRecognizer
from .llm_qwen import QwenLLMClient
from .graph import SolutionGraph
from .alignment import GraphAligner
from .scoring import RuleBasedScorerclass HASMPipeline:def __init__(self, cfg: HASMConfig) -> None:self.cfg = cfgself.htr = HandwritingRecognizer()self.llm = QwenLLMClient(cfg)self.aligner = GraphAligner(cfg)self.scorer = RuleBasedScorer(cfg)def build_graph_with_llm(self, problem_text: str, solution_text: str) -> SolutionGraph:parsed = self.llm.parse_solution_to_graph(problem_text, solution_text)return SolutionGraph.from_json(parsed)def run(self,image_path: str,problem_text: str,golden_solution_text: str,) -> Dict[str, Any]:# 阶段一:HTRstudent_solution_text = self.htr.recognize(image_path)# 阶段二:解析为图student_graph = self.build_graph_with_llm(problem_text, student_solution_text)golden_graph = self.build_graph_with_llm(problem_text, golden_solution_text)# 阶段三:对齐report = self.aligner.align(student_graph, golden_graph)# 准备步骤字典用于评分详情student_steps = {n.step_id: n.description for n in student_graph.nodes}golden_steps = {n.step_id: n.description for n in golden_graph.nodes}# 阶段四:评分result = self.scorer.score(report, student_steps, golden_steps)return result
# run_demo.py
import json
from hasm.config import HASMConfig
from hasm.pipeline import HASMPipelineif __name__ == "__main__":# 用你自己的样例替换image_path = "./handwritten_sample.jpg" # 学生手写解答图片problem_text = ("Solve for x: 2x + 5 = 15. Provide steps and justification.")golden_solution_text = ("Define variable x. Set up equation 2x + 5 = 15. Subtract 5 from both sides to get 2x = 10. Divide both sides by 2 to get x = 5.")cfg = HASMConfig()pipe = HASMPipeline(cfg)result = pipe.run(image_path=image_path, problem_text=problem_text, golden_solution_text=golden_solution_text)print(json.dumps(result, ensure_ascii=False, indent=2))
使用说明与扩展建议
- 将上述代码保存到建议目录后,运行
python run_demo.py
。首次运行会自动下载句向量模型。 - 若你的 Qwen3 服务需要多模态直接解析,可将
hasm/htr.py
阶段替换为将图像直接传入 Qwen3-VL,并调整parse_solution_to_graph
的输入结构(同时修改 prompt)。 - 若要使用更强的 LaTeX 级 HTR:
- 将
HandwritingRecognizer.recognize
替换为 pix2tex 推理输出 LaTeX,再传入解析阶段。
- 将
- 对齐阶段可进一步引入:
- 基于最优传输的全局匹配(如 POT 库);
- 结构一致性更强的图神经网络编码器(Graph Transformer)以替代句向量。
- 评分阶段可扩展为轻量学习型评分器(MLP/小型 LLM),对
AlignmentReport
的统计特征进行回归,并通过专家样本进行知识蒸馏训练。以上原型与本文方法学一一对应,便于快速复现实验与迭代。
关于项目的细节与项目部署请联系“微学AI”.