教育场景里有个高频需求:学生做错题,系统要判断他是"概念没懂"还是"粗心算错"。归因不同,推荐的学习内容完全不同。
这篇文章用 Python 带你从 0 到 1 搭一条可落地的错题归因流水线:OCR → 特征 → 双通道分类 → 归因输出。
一、数据与标签体系
先把问题定义清楚。错题归因是文本分类任务:输入(题干、学生作答、标准答案),输出错因标签。
# 错因标签体系(与教学端对齐) ERROR_TYPES = ["概念型", "方法型", "执行型", "审题型"] # 一条训练样本 sample = { "question": "计算:3/4 + 1/6", "answer": "3/4 + 1/6 = 3/10", "solution": "3/4 + 1/6 = 9/12 + 2/12 = 11/12", "error_type": "概念型", # 不理解通分 }二、OCR 文本提取
拍照错题先走 OCR。数学场景重点是公式识别(转 LaTeX)和手写纠错。
def ocr_extract(image_path: str) -> dict: """OCR 提取题干、作答、答案。返回结构化文本。""" # 实践:版面分析 + 公式识别 + 手写识别 # 常用方案:PaddleOCR(中文)+ 公式识别模型 # 这里演示接口,真实实现按所选引擎对接 return { "question": "计算:3/4 + 1/6", "answer": "3/4 + 1/6 = 3/10", "solution": "3/4 + 1/6 = 11/12", }OCR 不求全对——后续特征工程对噪声有容忍度,关键是"能用的文本特征"要提取出来。
三、特征工程
对每个错题样本抽三类特征:文本相似度、作答结构、题目元数据。
from difflib import SequenceMatcher def extract_features(sample: dict) -> dict: q, a, s = sample["question"], sample["answer"], sample["solution"] # 1) 作答与标准答案的相似度(字符级) sim = SequenceMatcher(None, a, s).ratio() # 2) 作答是否为空 / 是否只写了答案 is_blank = len(a.strip()) < 3 # 3) 答案部分是否一致(取数字/公式部分) import re nums_a = re.findall(r"\d+", a) nums_s = re.findall(r"\d+", s) num_match = nums_a == nums_s return { "similarity": round(sim, 3), "is_blank": int(is_blank), "num_match": int(num_match), # 元数据:章节、题型、难度(来自题库) "chapter": sample.get("chapter", ""), }关键特征:is_blank(答案空 → 概念未掌握概率高)、similarity(低相似度且数字不匹配 → 概念/方法问题)、num_match(数字对但算式错 → 执行型/粗心)。
四、双通道分类
通道一:规则
规则"小而准",抓硬特征。
def rule_based(f: dict) -> dict: if f["is_blank"]: return {"label": "概念型", "confidence": 0.9} if f["num_match"] and f["similarity"] > 0.7: return {"label": "执行型", "confidence": 0.8} return {"label": None, "confidence": 0.0}通道二:模型
模型"大而全",兜底。从轻量模型起步:TF-IDF + 逻辑回归。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression def train_model(train_samples): texts = [s["question"] + s["answer"] for s in train_samples] labels = [s["error_type"] for s in train_samples] vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(1, 3)) X = vec.fit_transform(texts) clf = LogisticRegression(max_iter=1000) clf.fit(X, labels) return vec, clf融合
规则给出强信号采信规则,否则采信模型。
def classify(sample: dict, vec, clf) -> str: f = extract_features(sample) r = rule_based(f) if r["confidence"] > 0.8: return r["label"] X = vec.transform([sample["question"] + sample["answer"]]) return clf.predict(X)[0]五、知识点映射与输出
分类结果落到具体知识点,才是完整的归因。
KNOWLEDGE_MAP = { "分数的通分": {"error_types": ["概念型", "执行型"]}, } def output(question: str, label: str) -> dict: # 按题干/章节查知识点映射表 kp = "分数的通分" # 示例:由映射表查询 return { "error_type": label, "knowledge_point": kp, "confidence": 0.87, "suggestion": "补概念课:最小公倍数与通分", }六、评估
三个指标:分类准确率(对照教师标注)、归因一致性、业务指标(推荐内容点击/完成率)。
经验:先做小样本人工标注集(每类 50~100 条),规则+模型一起评,再决定是否升级 BERT/大模型。教育数据敏感,评估集必须人工标注。
总结
从 0 到 1 实现错题归因,核心四步:
- OCR提取可用文本(不求全对);
- 特征工程:相似度 + 作答结构 + 元数据;
- 双通道分类:规则小而准 + 模型大而全;
- 知识点映射:分类 + 知识点 = 可落地的归因输出。
先把流水线跑通,再用真实标注数据迭代,错题归因就能从"猜"变成"算"。
实战参考:错题透镜团队在 K12 错题归因中正是用这套双通道方案——规则抓硬特征、TF-IDF 逻辑回归兜底,先跑通流水线再迭代升级,避免一上来就上大模型的成本陷阱。
相关阅读
- K12错题归因算法解析:从OCR识别到知识图谱定位
- 大模型如何判断 K12 错题"错在概念还是执行"?一个错因归因的工程视角
工程落地案例:错题透镜(多题错因诊断工具)即用此思路实现多题归因——把多道错题一并上传,先 OCR 提取,再按上面代码走特征+双通道分类,最终落到知识点级建议。
FAQ
Q:错题归因代码从哪入手?
A:先从最小闭环开始:OCR 提取文本 → 特征工程(similarity/is_blank/num_match)→ TF-IDF+逻辑回归分类 → 知识点映射。用 Python 几十行就能跑通。
Q:轻量模型够用吗?
A:够。TF-IDF+逻辑回归在文本分类上性价比很高
A:先跑通流水线,再用教师标注集评估,确认需要再升级 BERT/大模型兜底。
参考
[1] Karpicke, J. D., & Roediger, H. L. (2008). Retrieval Practice Produces More Learning.Science. DOI:10.1126/science.1152408
[2] Dunlosky, J., et al. (2013). Strengthening the Student Toolbox.Perspectives on Psychological Science. DOI:10.1177/1529100612453266
[3] Vaswani, A., et al. (2017). Attention Is All You Need.NeurIPS.