Project Akari:从零构建数学+CS自适应学习引擎 刷题软件用多了你会发现一个尴尬事实同一道错题今天推、明天推、后天还推结果学生把答案背了下来知识点却还是没学会。传统题库的“根据错题推荐”本质上是拿静态规则代替诊断它根本不理解你。而真正的自适应学习引擎必须回答三个问题你现在真正会什么、你会在什么时候忘掉、你下一题最该做什么。Project Akari 就是我围绕“数学与CS”这两个学科从零开始搭建的现代自适应学习引擎目标是让每一道推荐题目都有据可循让每次学习路径都不再是“题海”的堆叠而是一套可解释、可迭代、可验证的教学决策循环。这篇文章不准备讲空泛的理念只讲我在设计Akari时的核心算法选型、工程架构、内容工程以及在真实试点中踩过的坑适合正在做教育产品、自适应学习系统或者说想改造传统题库算法的同行参考。1. 为什么“数学CS”的自适应学习值得单独做一套引擎1.1 自适应学习引擎解决的是什么问题先把我理解的自适应学习引擎边界说清楚。它不是一个智能组卷工具也不是一个错题本而是一个学习状态的实时估计器加上学习策略的决策器。它会不断根据你的每次点击、每次提交、每次犹豫更新对你知识掌握程度的判断再根据这个判断从题库里选择一条最适合当前学习的路径。传统做法里用户答对一题系统就给这个知识点加上一分答错就扣一分或者干脆把题目里错误的知识点标记成“未掌握”。这样的逻辑在数据量小的时候勉强能用但一旦题目变多、知识点变细它的误差会被迅速放大学生今天状态不好一道题错三次系统就认定这个知识点完蛋了或者学生靠排除法蒙对了一道选择题系统就认为他已经会了。自适应学习引擎的真正价值是把这种“二元判定”升级成“概率估计”。系统不再说“你掌握了”而是说“你大概有73%的可能是掌握了函数单调性”并且随着新数据不断修正这个信念。Project Akari 的核心工作就是把这个概率估计做得足够准再把估计结果翻译成下一道题的选择。这件事听着很学术但落到工程上它决定了整个推荐系统的数据流、特征设计、模型选型和评测方式。我下文要讲的每一层设计都是从“概率估计”这个原点长出来的。1.2 现有方案为何在数学与CS上水土不服市面上已经有不少做自适应学习的商业产品和开源方案但大多是从通识题库出发学科粒度很粗。数学和CS天然适合自适应学习因为两者的知识结构都有非常明显的层次性和前置依赖学不会导数定积分基本无从谈起不理解递归的调用栈就很难真正掌握分治算法。但它们又有截然不同的难点——数学重推理过程和严谨证明学生提交的往往不是简单选项而是一串步骤、一个表达式甚至一张草稿CS重代码实操学生在编辑器里的每个编译错误、每轮测试失败都是比“对错按钮”丰富得多的行为信号。通用引擎在这两个学科上往往会出同一个毛病只看结果不看过程。一道数学证明题学生过程中间走了三次弯路最后写对了答案系统判定满分掌握一道CS编程题学生抄了别人的答案然后运行通过系统也判定掌握。结果学生到综合题面前全线崩溃。这也是我决定做 Akari 的起点必须让引擎理解“数学的推理链路”和“CS的调试过程”而不能只是把选择题换成编程题来打分。1.3 Akari 的定位与目标不是“智能题库”而是“学习状态机”所以 Akari 的定位从来不是“更智能的题库”而是“学习状态机”。它把每个知识点当作一个可能迁移、可能遗忘、也可能被干扰的状态把每一次作答当作一次观测通过观测不断推断真实状态再根据状态计算出学习收益最高的下一步动作。这个定位带来三个直接影响第一推荐不能只看正确率要看知识点的掌握概率变化幅度第二推荐必须考虑时间因素因为记忆会遗忘昨天会不等于今天会第三必须把解题过程结构化让过程性信号进入模型而不只是最终答案。这个定位也决定了 Akari 的整体架构底层是内容工程包含知识点图谱和题目标注中间是推理引擎包含知识追踪和遗忘模型上层是推荐策略包含召回、精排和规则兜底外围是评测模块用来判断每次改动是否真的让学生学会更多。本章先把“为什么做”讲透后面逐一展开每个模块怎么设计、怎么实现。2. Akari 的推理大脑知识追踪、遗忘曲线与推荐策略的设计2.1 知识追踪选型IRT、BKT 与深度模型的取舍知识追踪是整个引擎最核心的一环。我最初面对的是一个三选一的局面项目反应理论IRT、贝叶斯知识追踪BKT、深度知识追踪DKT。三者我都试过收集了各自的表现也逐步摸清了它们的使用边界。算法核心思想数据要求在Akari里的定位IRT用能力值、题目难度、区分度建模答对概率每个题目样本量要足够否则参数漂移严重用于题目校准、能力估计的“基线层”BKT每个知识点视为隐状态掌握/未掌握用观测正确率迭代更新对单知识点内数据要求适中解释性强主干推理模型支持遗忘扩展DKT用循环神经网络从序列中隐式学习知识状态需要大规模作答序列黑盒难解释离线实验备用作为特征补充我最终选择 BKT 作为主干不是因为 DKT 不准而是因为 Akari 的早期场景里解释性比精度更紧急。老师要问“为什么推荐这道题”家长要问“孩子到底哪里不会”学生自己也要知道“系统为什么说我忘了”。BKT 的状态更新逻辑是一步一步可回放的能天然给出这样的解释。DKT 先放在离线通道里做特征比较等数据集足够大了再考虑融合。2.2 BKT 的主干更新从先验概率到后验概率的滚动估计BKT 的核心参数每个知识点一组初始掌握概率这个知识点学生一开始就会的可能性、学习概率做一题后从未掌握转移到掌握的概率、猜测概率未掌握却蒙对的概率、失误概率掌握了却写错的概率。每做一道题系统根据这道题的对错把当前掌握概率更新成后验概率再叠加一个学习概率进入下一回合。我用 Python 写了一个轻量实现核心只有几十行def bkt_update(prior, p_slip, p_guess, p_transit, correct): if correct: # 答对可能来自“已掌握且没失误”也可能来自“未掌握但猜对” posterior prior * (1 - p_slip) posterior / prior * (1 - p_slip) (1 - prior) * p_guess else: # 答错可能来自“已掌握但失误”也可能来自“未掌握且没猜对” posterior prior * p_slip posterior / prior * p_slip (1 - prior) * (1 - p_guess) # 学完这一题状态可能发生迁移 return posterior (1 - posterior) * p_transit真正让这套逻辑“活”起来的是参数估计。你不可能靠拍脑袋定猜测概率。Akari 的离线管线会用历史作答日志用极大似然估计拟合每个知识点的四参数。拟合时不只看最后一步对错还结合学生作答用时作为辅助信号——用时过短的“秒对”要下调观测置信度用时过长的“磨对”也要下调防止把蒙对当成掌握。这个细节是我们实测后加上的不加之前BKT 会把大量“蒙对”判成“掌握”推荐难度一直不敢拉上去学生进步很慢。2.3 遗忘曲线的加入时间间隔不该是推荐的黑盒BKT 原版有个明显短板它假设学生一旦掌握就不会遗忘。但在数学和CS的日常练习里遗忘恰恰是最常见的现象。我给 Akari 加了一层遗忘衰减每次作答间隔超过一定阈值掌握概率会按时间指数衰减衰减参数随知识点类型调整。概念型知识点衰减快操作型知识点衰减慢比如“二次函数求顶点坐标”衰减就比“因式分解”快一些。初始衰减系数来自课程教师的经验标注后续用真实数据回归校正。形式上每次用户重新进入系统时Akari 会根据距离上次作答的小时数重新计算一轮先验概率。这一步让推荐引擎产生了一个关键变化系统开始主动给那些“看似掌握但已经三天没练”的知识点安排复习题而不是永远推新知识点。实测下来学生的周遗忘率明显下降而且学生反馈“感觉系统好像记得我昨天会那个今天忘了一点”——这种真切体验是提升信任感的最大利器。2.4 下一题推荐从“查漏补缺”到“学习收益最大化”掌握了知识状态剩下的问题是给学生推荐哪道题最简单的做法是“推荐最短板”也就是掌握概率最低的知识点。但这样很快就会让学生崩溃因为最短板通常是最难的地方连续受挫会直接导致弃学。Akari 的推荐策略把目标定义为“学习收益最大化”而不是“最短补缺最大”。我给每道题算四个维度掌握增益的期望、知识与当前薄弱点的关联度、难度和当前能力的匹配度、以及题目的新鲜度衰减。精排打分用加权求和权重初值来自回归实验上线后再根据A/B结果调score 0.45 * expected_gain 0.25 * weakness_overlap 0.20 * difficulty_fit - 0.10 * exposure_decayexpected_gain 是用当前知识点掌握概率代入 BKT 更新公式分别计算答对和答错后的后验概率再取期望。difficulty_fit 讲究的是“心流区间”题目正确率在40%到70%之间时学习效率通常最高。低于30%则说明太难学生容易放弃高于80%则说明太简单学习收益有限。这一条规则看起来朴素但实际推荐效果比重排引擎调参显著得多。后面我会在评测章节再展开这套策略如何验证。3. 工程架构与数据闭环延迟、日志与离线训练怎么配合3.1 系统分层与各层的职责划分算法再漂亮落不到低延迟的在线服务上也是白搭。Akari 的系统架构分为内容层、状态层、推荐层和交互层。内容层维护题目库、知识点图谱和标注数据状态层维护每个用户每个知识点的实时掌握概率和遗忘计时器推荐层做召回、精排、规则过滤交互层负责对接前端做题界面并把用户行为写回日志。四层之间通过内部API通信各层职责互不越界。这个分层最大的好处是可以独立迭代。某一天内容层新增了一百道题不需要改推荐层代码推荐算法要改版也不影响交互层。实际开发里早期版本最容易犯的错就是把推荐逻辑写在用户答题的请求处理函数里结果每次改策略都要处理线上请求的兼容性。我后来感受到教育系统后端代码再“小”也建议按模块拆干净因为推荐策略的迭代频率远比你预期的高。3.2 在线推荐的低延迟实现召回、精排与规则兜底在线请求路径分三步。第一步是召回基于当前知识状态从全题库里筛出候选题目集合。召回条件包括知识点范围、难度区间、最近是否做过、上一次推荐时间。Akari 用 Redis 缓存每个学生的近期作答记录和知识点状态避免每次请求都查库。第二步是精排也就是上一章说的打分公式逐题计算后取 Top N。第三步是规则兜底这些规则直接用代码硬写同一道题60天内不重复推荐、连续推荐的知识点不能超过三个、CS 判断题之后尽量接一道代码题等等。延迟方面实测通常在 120ms 到 220ms 之间。这个速度对在线学习场景完全够用。需要注意的是不要把深度模型放在在线请求里跑。Akari 的做法是离线训练完成所有模型更新在线只读取已经算好的知识点参数和用户状态快照。DKT 的特征计算也在离线做在线只查结果。这样既兼顾了模型复杂度又保证了响应速度。3.3 日志事件设计一切训练数据都来自作答过程没有日志自适应学习就是无源之水。Akari 从第一天起就强制统一答题事件的 Schema每个事件包含用户、科目、知识点、题目编号、事件类型、事件结果、时间戳和客户端会话。事件类型不止“提交答案”还包括“查看提示”“多次编辑”“切换题目”“放弃作答”“查看解析”。我把这些统称为过程性行为日志。字段名类型说明user_idstring用户唯一标识item_idstring题目唯一标识knowledge_idstring关联知识点event_typeenum进入/提交/提示/放弃/切题等resultstring正确/部分正确/错误/超时duration_msint作答时长attempt_countint本题尝试次数hint_usedboolean是否使用过提示tsstring事件发生时间这套日志规范看起来繁琐但它是后面所有模型训练的基石。BKT 的参数拟合、DKT 的序列生成、遗忘曲线的回归全都吃这同一套日志。如果你正在从零搭类似系统我建议你在产品上线前就把日志埋好后补的数据往往缺这缺那清洗成本远大于想象中的量。4. 内容工程才是真正的门槛知识点图谱、题目标注与学科信号4.1 知识图谱的粒度太粗推不准太细没数据算法可以复用开源项目内容只能自己砌。Akari 花时间最多的不是模型而是知识点图谱和题目标注。粒度问题尤其折磨人如果把“函数”当一个知识点推荐策略等于没有如果把“二次函数在给定区间内的值域求解”当成叶子知识点数据量又会被稀释得根本拟合不出参数。我们最后的折中方案是三层结构大专题、中知识点、微技能。大专题是章节级别比如“函数与导数”中知识点是可以单独安排教学单元的程度比如“复合函数单调性”微技能则是具体到某类题型的解题动作比如“利用定义法判断单调性”。推荐引擎的操作单位是中知识点但会在微技能上记录掌握状态。这样一来推荐时既有足够的颗粒度做差异判断又不会因为微技能太细导致每个点的训练数据撑不起来。4.2 题目元数据与作答信号数学和CS各自要单独设计一道题进入 Akari 内容库至少带下面这些元数据所属中知识点、关联微技能、认知层级记忆、理解、应用、分析、创造、预期难度、预期用时、是否使用提示词、干扰项对应的典型错误类型。CS 题还要额外标注语言类型、输入输出约束、是否涉及算法复杂度分析、以及评测用例覆盖的知识点。真正让 Akari 区别于通用引擎的是作答信号设计。数学题我们接入了过程逐步提交学生可以先提交第一步化简再提交第二步构造辅助线系统可以定位第一个出错步骤。CS 题我们采集三类信号编译阶段错误未过编译、运行阶段错误超时或崩溃、测试用例级错误哪些用例失败。这些信号的价值在于它们反映了具体的认知障碍模式。CS 里一个学生反复在“边界条件”上丢用例和反复在“算法超时”上丢用例是两个完全不同的问题属性推荐策略也应该不同。4.3 内容审核与迭代标注错误比算法偏差更致命自适应学习引擎有一个信息论层面的对称性模型再好标注错了也是白搭。一道题目被标错知识点模型的所有推断都会被带偏。实测中我们发现最影响在线体验的问题往往是两道题共用了一个文本模板、或者 CS 题被误标为数学题、又或者同一知识点下题目难度排序标反了。因此 Akari 的内容管线里专门有一道“校准回测”每周用真实作答数据对所有题目的标注做一遍一致性检查。看看每道题的实测正确率是否符合标注时的预期难度若某道题标注难度与真实作答难度偏差超过阈值自动进入人工复审队列。这个环节听起来不“高级”但它带来的体验改善经常比算法调整更直接。作为工程师出身的实践者我最大的体会是自适应学习的内容质量是硬门槛含糊不得。5. 冷启动、解释性与实测从“能跑”到“能用”的距离5.1 冷启动的三种情况与对应的降级策略冷启动问题在真实项目里不是一种而是三种新用户、新题、新知识点。新用户没有任何行为日志系统连他当前水平在哪个位置都不知道。Akari 的处理是用一套前置探查题由内容组专门设计覆盖基础中知识点且难度从低到高递增同时结合学生自评和学校年级基准线做先验。新用户进入后先做6到8道探查题系统就有了第一版能力画像。新题没有作答统计不知道真实难度和区分度。我们用三个数值拼出一个先验难度该题的文本复杂度、所属知识点历史平均难度、以及试题编写时的心智标号。上线后随着作答量积累这个先验会被真实统计逐步替换。新知识点则更麻烦——整个知识点都没有历史数据推荐引擎无法判断是否该推荐这个知识点。Akari 的做法是做“相似知识迁移”找到结构上最接近的已有知识点把参数估计结果作为新知识点的初始值上线后可快速校准。5.2 稀疏反馈下的参数估计别想着给每个人单独建模自适应领域一个很容易踩的坑是希望给每个用户单独建立一套模型愈精细愈好。但数学与CS的实际作答数据是非常稀疏的一个用户一个学期也就几百道题单独拟合个人 IRT 参数大概率过拟合。Akari 采用分层贝叶斯的结构路径先学习全局先验分布再让每个用户在后验更新中逐渐拉开差异。实际操作中我们把新用户的前20次作答作为“校准期”这期间系统还是会推荐题目但是推荐的排序关闭个性化只按知识点的基础逻辑来。校准期结束后用户的状态向量才参与个性化精排。这个做法避免了一个让人头疼的现象冷启动阶段因为数据噪声太大推荐结果时好时坏导致学生失去信任。逻辑上更平稳地过渡反而让留存数据更健康。5.3 可解释性学生、老师、家长分别想看什么算法黑盒在新闻资讯场景也许可以容忍但在教育场景里不行。学生希望你告诉他为什么做这道题老师希望你告诉他班级整体弱在哪家长只想知道孩子的知识漏洞长什么样。Akari 给每个推荐生成一条简短的理由卡片内容包含当前知识点掌握度、最近同类题的作答趋势、距离上次练习的间隔、以及这次推荐的学习目标。实践下来最好的理由卡片是一条融合了数据和教学判断的句子比如“你最近三次同类题正确率从40%升到80%但已隔26小时没练推荐一题巩固并加深难度。”这类解释不需要把 BKT 的公式展示出来但要把“概率变化”翻译成人和人之间能理解的因果表达。在线反馈里学生对这样理由的接受度明显高于“系统根据算法向你推荐本题”。记住一点可解释性不是给算法专家看的是给真实使用者看的。6. 评测体系与上线迭代算法改得好不好不能靠感觉6.1 离线评测预测准确率只是最低门槛开发第一个版本时我最关心的指标是预测准确率模型预测答对的概率和真实作答结果是否一致。这个指标当然要有但不能唯它马首是瞻。Akari 在离线评测里同时跟踪三组指标预测类指标AUC、RMSE、LogLoss、教育目标类指标掌握的稳固性、遗忘召回率、后测知识得分相关性、以及推荐体验类指标推荐知识点覆盖度、题目重复率、难度分布、连续挫败率。指标组具体指标作用预测准确度AUC / RMSE / LogLoss判断状态估计是否靠谱教学效果遗忘召回率 / 后测相关判断推荐是否促进长期记忆体验健康重复率 / 挫败率 / 难度分布判断推荐是否导致学生不适这套三维评测的引入让不少自认为有效的改动露出了真实面貌。比如有版本预测准确率上升但续题率下降说明系统虽然更准地在做题判断却变得过于保守所有题目都往学生最熟练的知识点靠——准确率好看了学习收益没了。只看单一指标的话很容易做出“做题很舒服但没什么进步”的伪自适应。6.2 在线上线A/B 实验之外还要盯住健康度指标离线评测通过后Akari 会进入小流量线上实验。实验中我正在意的三件事样本量的最小周期、可能混杂的因素、以及长期效果的迟滞效应。一个推荐改动至少要观察两周均摊到用户和场景里去判断避免被一次考试或一个假期带偏。线上指标除了学习时长和答题量还要细分正确率之外的情绪维度学生平均连续答错两次的次数、放弃率、以及主动查看解析的比例。这几个“健康度指标”往往比总挑战次数更早暴露问题。曾有一次我们调整了遗忘系数导致复习题占比大幅上升虽然正确率看起来稳定但学生主动学习时长下降了不少当时就是靠连续失败率和切题率的变化抓住了这件事。要记住在线实验里你可能永远拿不到统计意义上的完美证据所以总要保留一些“经验判断”的空间但是判断要基于数据不是凭感觉。6.3 一个迭代序列先内容再算法最后做交互项目组总结出了一条稳妥的迭代节奏先修内容再改算法最后动交互。内容层的错误最容易被忽视但也最影响信任算法改动在内容和数据可靠的基础上才有意义的交互层的变动只有在算法效果稳定后才有足够的解释空间去归因。Akari 每次发布都拆成三个独立版本纯内容校准版、纯推荐策略版、纯UI交互版。各方相互不干扰出了问题也能秒级回滚。这套流程看起来很保守但教育产品犯错成本太高。有一次我们几乎同时对难度模型和题目默认顺序做了修改结果推荐合理性数据下降怎么排查都说不清是哪一步造成的最后只能整体回滚。后来我们就立了规矩一次只动一个变量。在自适应学习这类多因子系统里这条规矩省下的时间是惊人的。做 Akari 的时间里我最真切的感受是自适应学习引擎的复杂度不在一道题也不在一个公式而在于如何把数据、算法、内容、反馈在一个真实系统里长期协同跑起来。你可能花了很大力气写的模型最终被一个标注错误打败你也可能因为一个遗忘因子让整个推荐节奏焕然一新。如果你也要做类似系统我的建议只有一条尽早定好日志规范和标注规范然后在一个足够宽的时间窗口里老老实实地看数据、跑实验、解释每一次变化。教育引擎没有灵丹妙药只有持续校准之后的螺旋上升。