做知识追踪(KT)的同学应该都有同感:一个学生连续做错两道同类题,模型给出一个结论——“他对这个知识点的掌握度是0.73”。但0.73和0.71之间的差别到底意味着什么?这个判断本身有多可信?大多数时候没人知道。AAAI 2025 上这篇 UKT(Uncertainty-Aware Knowledge Tracing,不确定性感知知识追踪),讨论的就是这件事。它把学习状态建模从单点估计,推向了一种带不确定性度量的分布估计,相当于把“我觉得你会”升级成了“我觉得你会,但可能有三成概率是我看走眼”。
这个改动听起来不大,实际上把训练方式、推理逻辑、教学应用全部带上了新轨道。过去我们反复优化的目标是让掌握度数字更准,而 UKT 这一类思路是在回答一个更本质的问题:什么时候模型对自己的判断不该那么自信。这篇文章我把自己的理解、对方法细节的拆解、以及在类似项目里踩过的坑一起放出来,适合做自适应学习、智能辅导系统、教育数据挖掘的朋友读,也适合正在做序列数据建模、想了解不确定性建模如何落地的人参考。
我先把话说在前头:知识追踪不是“预测下一题对错”这么简单,它本质上是在用学生稀疏的答题记录,重构一个看不见、摸不着的认知状态。单点估计只能告诉你“可能的状态值”,却丢掉了大量关于“这个状态值有多可靠”的信息。UKT 正是从建模范式层面补上了这块拼图,这也是我觉得它值得单独写一篇的原因。
1. 知识追踪到底难在哪:单点估计的先天缺陷
1.1 从一道错题说起:学习状态是怎么被推断的
先给不熟悉这个领域的读者补个背景。知识追踪(Knowledge Tracing,KT)的核心任务,是根据学生过往的答题序列,推断他对每个知识点的掌握程度,并预测他在下一道相关题目上的表现。注意,这里“掌握程度”不是直接问出来的,而是要从行为反推。
早期方法像贝叶斯知识追踪(BKT),假设每个知识点有“已掌握”和“未掌握”两个隐状态,用观测到的对错来不断更新状态概率。后来深度学习把这件事做的更“端到端”:DKT 用 RNN 类结构读入答题序列,在每一步输出一个向量,向量中每个维度对应一个知识点的掌握概率;DKVMN 则引入了记忆矩阵,把“动态掌握状态”和“静态知识状态”分开维护。这类模型在公开数据集上的 AUC 一路涨,看上去效果不错,但问题也随之而来。
一个典型的场景:学生甲连续答对三道“一元一次方程”,模型给的掌握度是 0.78;学生乙在同一知识点上答对两道、做错一道,模型给的掌握度可能也是 0.78。两个数字一样,背后的信心完全不一样。甲的情况很稳定,只能说明基础扎实;乙呢?他可能整体会,但某个特定题型没接触过,也可能是当天状态不好手滑了一道。传统模型只输出一个均值,所有这些信息全部被压扁成了一个数字。
这就是我常说的“点估计的尴尬”。模型不仅要知道状态是什么,还应该知道自己对状态估计的把握有多大。这个把握程度,和状态值本身一样重要。
1.2 为什么传统模型的“上限”被单点输出卡住了
单点输出不只是信息展示不完整,它在训练和推理层面都埋了隐患。
从训练目标看,主流深度知识追踪模型基本都是拿“下一题答对概率”当监督信号,用交叉熵或 MSE 这类损失函数。这意味着模型只需要把“答对概率”拟合好就够了,没有任何机制要求它区分“我确定你会”和“我猜你会”。一个见过大量相似样本的模型,和一个只在边缘数据上勉强拟合的模型,只要输出的概率相同,两个模型在损失函数眼里就是等价的。
可到了推理阶段,这种差异会直接影响应用决策。在做个性化出题时,系统通常会对某个知识点设定阈值:掌握度高于 0.8 就不出题,低于 0.5 就补基础题。阈值判断是一种极端敏感的操作,一个 0.81 和一个 0.79 的系统行为可能完全相反。如果模型根本没有置信度信息,那这两个数字到底谁更可信?不知道。
再往深一层说,单点输出也限制了模型对噪声的建模能力。学生答题本身是有随机性的:一道题干有歧义、选项设置存在误导、学生疲劳或者考试焦虑,都会造成“会但答错”或“蒙对”的情况。传统模型把这些噪声全部丢进隐变量里,却无法在输出层显式表达“这条记录的噪声有多大”。结果就是模型对每一条历史记录的处理逻辑始终是全局统一的,没法根据实际情况动态调整。
从信息论角度理解,单点输出相当于把预测分布强制压成了一个均值。均值是分布的一阶矩,信息量非常有限。真实的学习状态应该是某种分布,只是我们没直接观测到它。UKT 的思路就是把这个分布建模出来,然后让模型在输出时同时给出均值和不确定性,而不是只给一个孤零零的数字。
下面这张表可以比较直观地说明传统深度知识追踪模型和 UKT 的差异:
| 对比维度 | 传统深度 KT 模型(DKT/DKVMN 等) | 不确定性感知知识追踪(UKT) |
|---|---|---|
| 输出形式 | 知识点掌握度的单点数值 | 掌握度的分布(均值 + 不确定性) |
| 对答题噪声的处理 | 隐式压缩进隐变量 | 显式建模,训练时动态区分高/低噪声样本 |
| 训练目标 | 预测下一题正确概率为主 | 概率拟合 + 不确定性估计联合优化 |
| 推理时的附加信息 | 无 | 每个预测附带置信区间,能判断“不知道”与“不确定” |
| 教学干预支持 | 需要额外规则做二次判断 | 可以直接把不确定性作为干预信号 |
| 对新知识的冷启动表现 | 统一输出,容易过度自信 | 样本少的知识点倾向于输出高不确定性 |
2. UKT 的核心创新:不确定性从“附属品”到“一等公民”
2.1 两类不确定性:偶然不确定性与认知不确定性
聊 UKT 之前,我觉得有必要把“不确定性”这个概念拆开。机器学习里的不确定性一般分两类:偶然不确定性(Aleatoric Uncertainty)和认知不确定性(Epistemic Uncertainty)。这个区分在知识追踪场景里尤其关键。
偶然不确定性来自数据本身的随机性。打个比方:判断一个学生“会不会做因式分解”,模型不可能 100% 确定,因为学生可能在考场上粗心、可能被选项误导、甚至可能看错数字。这类噪声即便数据再多也无法完全消除,因为它就是人类行为本身的一部分。这不是坏事,认清这种不可约减的噪声,能避免模型对单个样本过度拟合。
认知不确定性来自模型自身知识的不足。如果你只见过三个学生的做题记录,你对“这届学生整体水平如何”的判断肯定充满不确定性;当你见过三千个学生,同样的判断就会稳得多。这种不确定性是可以通过增加数据、改进特征来降低的。
有趣的是,知识追踪同时包含这两类不确定性,而且二者会纠缠。一道新知识点题目,既有学生随机犯错带来的偶然不确定性,又有模型从未见过足够样本、知识掌握不准带来的认知不确定性。传统模型把这两坨东西搅在一起,通过增大训练集来硬压。UKT 想做的,是让模型把这两种不确定性都显式地学出来——至少在输出层让模型学会表达“我因为见得太少所以不确定”,而不是把所有不确定性一股脑归结为数据噪声。
2.2 UKT 如何重构学习状态建模范式
UKT 这个名字里的“不确定性感知”,意味着建模过程中不确定性不是一个附加输出,而是融进了整个学习状态的表示里。我理解的核心转变有三层。
第一层是表示层面:学习状态从“一个向量”变成“一个分布的参数”。换句话说,模型内部不再只维护每个知识点的掌握度均值,还维护对应的方差(或置信度)。有了这个方差,知识状态就不再是一个点,而是一团概率云。对于学习比较稳定的学生,这团云很窄;对于表现飘忽、记录稀疏的学生,这团云自然就很宽。
第二层是训练层面:损失函数不能只惩罚“预测错了”,还得惩罚“预测错了却还很自信”。这是 UKT 和传统模型在训练目标上最本质的差别。拟合一个分布而不是拟合一个均值,意味着模型必须学会在证据不足时给出较大的不确定性,否则损失函数会通过“错误但过度自信”的样本反向施压。这种训练方式对模型的整体校准性有明显帮助,模型不再为了压低错误率而盲目输出激进概率。
第三层是推理层面:模型做出的每个预测都伴随一个可信区间。下游系统拿到的不再是孤立数字,而是“这个学生在乘法分配律上的掌握度为 0.72,不确定性 0.25,可信区间大概在 0.47 到 0.92 之间”。这个区间信息能够直接驱动更精细的教学策略,比如决定是否要让系统多测几道题来降低不确定性,再决定是否推进到下一个知识点。
从范式的角度看,UKT 做的事情不是给某个模型打个补丁,而是把“不确定性”这个概念从一个可有可无的附属品,提到了建模目标的第一位。这也是标题里说“重构学习状态建模”的原因。
3. 方法细节与复现视角:UKT 关键环节初步拆解
3.1 整体架构设计:在经典序列模型上加一个“不确定性头”
虽然我没有直接拿到 UKT 论文的全部源码,但按这类模型的一般设计思路推断,UKT 的主体架构应该是延续了深度学习知识追踪的序列建模框架,然后在输出层上做了关键扩展。底层的编码器可以是 RNN 类结构,也可以是 Transformer 类结构,负责把答题序列编码成隐向量;真正有差异的是模型头部的设计。
传统模型的输出是一个 sigmoid(或 softmax)映射,直接给出掌握度概率。UKT 的做法应当是输出一组分布参数,通常是一对值:均值 μ 和方差 σ²。均值对应掌握度的点估计,方差对应不确定性的度量。由于方差存在非负约束,输出层通常会经过 softplus 或者 exp 变换,而不直接输出原始 logits。
在具体实现上,有一种常见路径是使用深度集成的近似思路:训练多个子模型,用子模型预测的均值来估计掌握度,用子模型之间的离散程度来估计不确定性。另一种路径是在网络中加入变分推断层,显式地把模型参数或隐变量建模成分布。两种路线各有优缺点,深度集成实现简单、估计稳健,但训练成本成倍增加;变分方法参数更紧凑,但训练不稳定、调参难度高。UKT 到底选哪条路线,取决于它对效率和精度的权衡。以 AAAI 这类会议的口味,我猜测作者会采用一个不太花哨但理论上更清晰的设计,大概率是让模型直接预测分布参数,并在损失函数里做不确定性加权。
如果按工程视角来理解,整个模型可以用下面这几块来大致描述:
- 输入嵌入层:把知识点编号、答题结果、答题时间等特征映射为稠密向量;
- 序列编码器:利用 RNN/GRU/Transformer 结构捕捉答题序列中的动态变化;
- 状态分布头:输出当前学习状态的均值向量 μ_t 和方差向量 σ_t²;
- 预测解码器:根据 μ_t 和知识点嵌入,计算下一题答对的概率,并将 σ_t² 传递给下游用于决策。
这里的核心就是状态分布头。它让模型内部不再是“死板的单维状态”,而是带噪声感知的状态表达。之前我在一个练手项目里试着实现过类似结构,最大的感受是输出层设计不难,难的是训练时如何让方差这个输出真正有意义,而不是退化成摆设。后面我会专门讲这个坑。
3.2 训练策略、损失函数与关键参数
训练不确定性感知模型,核心差异在损失函数。如果只看最终预测的对错,模型仍然可以走“只拟合均值”的老路,方差变成无意义的常数。要让方差活起来,就得用负对数似然一类的目标,让模型学会为置信度“买单”。
假设模型对第 i 个样本给出预测均值 μ_i 和预测方差 σ_i²,真实标签是 y_i(比如答对为 1,答错为 0),在回归形式下,高斯负对数似然损失长这样:
# 不确定性感知知识追踪训练循环(伪代码示意) # 这里的输出是每个知识点的掌握度分布参数 model = UKTEncoder(hidden_dim=256, output_dim=num_skills) optimizer = Adam(model.parameters(), lr=1e-3) for batch in train_loader: seq, mask, label = batch # 前向:模型返回掌握度均值与不确定性 mean, log_var = model(seq) # 方差必须为正,这里用 softplus 保证数值稳定 sigma = softplus(log_var) + 1e-6 # 高斯负对数似然:既惩罚均值偏差,也惩罚“错得还自信” loss = 0.5 * ((label - mean) ** 2 / sigma + torch.log(sigma)) loss = (loss * mask).sum() / mask.sum() optimizer.zero_grad() loss.backward() optimizer.step()上面这段伪代码里,最关键的一行是loss = 0.5 * ((label - mean) ** 2 / sigma + torch.log(sigma))。仔细拆一下:当模型预测错误且方差很小,也就是“错得还很自信”时,第一项的惩罚会非常大;反过来,如果模型预测错误,但同行输出一个较大的方差,惩罚就会被自动稀释。这就给了模型一个明确信号:错误可以犯,但你必须知道自己什么时候不该自信。
分类任务里也可以做类似的处理,把 sigmoid 输出改造为带置信度的分布输出,比如用贝塔分布或直接用 Gaussian CDF 拟合。实践中我更推荐先做回归版本,把“答对概率”当成连续变量拟合,这样负对数似然的形式更直观,调参也好定位问题。
再补几个我实测下来比较重要的训练细节:
- 方差的输出初始化不要太极端。建议把 log_var 的偏置初始化成接近 0 的小值,让模型一开始保持一个比较“谦虚”的状态,再在训练中自行调整;
- 数据集样本量少时,可以给不确定性部分加一点正则。完全不约束方差的话,模型有概率学到“全部输出大方差”的捷径,直接把损失压得很低但什么都没学到;
- 学习率建议设置在 1e-3 到 1e-4 区间,太大的学习率容易让方差层震荡,出现 NaN 是一回事,更麻烦的是方差和均值两只头之间出现跷跷板式的训练不稳定。
4. 应用场景与影响范围:从论文走向系统
4.1 自适应练习与试题推荐:不确定性怎么用得漂亮
我见过不少自适应学习系统,出题策略基本都是“哪里掌握度低就出哪里”。这个逻辑听上去没毛病,但实际效果很差,因为掌握度低有两种情况:一种是学生确实没学会(均值低、不确定性也低),另一种是系统还没收集够证据(均值可能低、但不确定性很高)。这两种情况需要的教学动作完全不同。
对前者,应该推送基础讲解和简单题目;对后者,更好的策略是先“测”而不是先“教”——继续出几道同知识点题目,快速把不确定性降下来,再判断要不要干预。UKT 这类模型正好提供了这种决策依据:系统可以设置一个不确定性阈值,当某个知识点的不确定性过高时,进入“探索模式”,优先出诊断性题目;当不确定性收敛且均值较高时,再进入“过关模式”进入下一主题。
在更宏观的个性化学习路径规划里,不确定性还能参与全局平衡。比如有 A、B 两个知识点,A 的均值是 0.6、不确定性是 0.4,B 的均值是 0.65、不确定性是 0.15。单看均值,似乎应该优先学 B;但考虑到不确定性,A 其实更“不确定”,可能恰恰说明系统对 A 的判断还太草率,需要先补数据。这种决策逻辑只有不确定性感知模型才支持。
4.2 可解释性与教学干预:把“不确定”转化为教师信号
一线老师是很反感“黑箱结论”的。你告诉老师“这个学生三角函数的掌握度是 0.72”,老师会问:他是不稳定、是没学过、还是粗心?传统模型答不出来。UKT 的价值在于它能提供一个多一维的解释通道。
实际操作中,我见过一种很直观的可视化方式:把每个知识点画成一个小方块,颜色表示掌握度均值,透明度或边框粗细表示不确定性。老师一眼就能看出“红色且发虚”的知识点是最需要关注的——因为学生不仅弱,而且状态不稳定;相比之下“红色但很实”的知识点,可能是确定性的薄弱项,适合直接安排补强练习。
教学干预也因此更精准。我给团队的建议是:不确定性本身就可以作为一个预警信号。当某个学生在某个知识点上连续多次预测不确定度过高,系统就自动生成一条提醒,让老师单独找学生聊一下,看看是题目读不懂、概念混淆,还是另有原因。这种“模型提示、人来确认”的模式,我觉得是教育 AI 落地最稳妥的路径。
4.3 评测方式与数据集的注意事项
很多团队拿到一个新的知识追踪模型,第一反应是刷 AUC。这没错,但用在 UKT 这类模型上远远不够,因为它的卖点不是预测概率更准,而是预测带置信度。因此评测至少要分成两条线:预测精度和不确定性质量。
预测精度沿用常规指标:AUC、ACC、RMSE,重点看模型在测试集上的整体表现是否还和传统模型持平或更好。不确定性质量的指标相对新一些:一个是负对数似然(NLL),直接衡量分布预测的好坏;另一个是期望校准误差(ECE),把模型预测的置信区间和真实频率做对比,看 95% 置信区间的实际覆盖率是不是真的在 95% 附近。如果模型声称不确定性很高,但实际错误率并不高,说明不确定性模块过度保守,同样要扣分。
数据集方面,公开可用的教育数据集像 ASSISTments 和 EdNet 都是常见的基准,但用的时候有几个坑。首先是数据集里的日志噪声,学生有时刷题会中途退出、重做、看答案,这些交互会对知识追踪模型产生污染,做实验前要按交互完整性清洗。其次是训练集和测试集的划分方式:随机按交互划分会让模型“看到”某个学生的部分历史,再预测他的未来,这会高估模型在冷启动场景下的表现;更严格的做法是随机按学生划分,确保测试集中的学生完全没出现在训练集里。这两种划分方式下,UKT 的不确定性表现差异会非常大,论文里一定要交代清楚。
5. 踩坑笔记与实操建议
5.1 训练时最容易被忽略的 3 个细节
第一,方差输出层的数值稳定性。我之前自己尝试实现类似结构时,第一次跑训练直接 NaN。排查半天,问题出在方差层用了纯线性输出,网络在一个 batch 里输出了负方差,损失函数里的对数项直接炸掉。后来改成softplus + 1e-6才稳定下来。这个坑几乎每个做不确定性建模的人都会踩一遍,所以代码里一定要在方差输出后面加一个小的 epsilon,防止除零和对数崩溃。
第二,不确定性模块退化成“花瓶”。有几次训练完,我发现不确定性头输出的数值几乎不随输入变化——模型学了半天,方差变成了一个常数。原因是模型找到了一个很舒服的平衡点:把方差设成合理范围的固定值,然后靠调整均值来拟合训练数据。虽然 NLL 损失没有报错,但不确定性并没有起到建模噪声的作用。我的解决办法是增加不确定性模块的网络容量,同时在训练初期给方差输出加一点噪声扰动,避免它一开始就滑入退化解。
第三,冷启动阶段的不确定性设置。新学生、新知识点在训练数据里出现次数极少,模型在数据缺失时很容易神经质地打出高方差。但我见过相反的问题:模型因为之前见过足够多同类学生,即使某个具体学生的记录很少,它也会习惯性地输出较低的不确定性,造成“伪自信”。这个问题比较隐蔽,需要用按学生划分的验证集专门测冷启动样本的不确定性表现,如果平均方差显著低于期望,就要考虑给输入序列增加一个“样本量”特征,或者对不确定性输出做额外的样本量加权。
提示:不确定性感知模型的调试,不能只看 loss 曲线,还要定期抽查实际输出。把一批验证样本的“均值-方差”拿出来看,如果出现大量“高置信但错误”的样本,说明模型校准出了问题,优先检查方差输出层的激活函数和初始化方式。
5.2 模型输出“不确定”时,怎么解读才不误导教学
不确定性高这个信号,离“学生不会”还有很长一段距离。最典型的例子是题目质量差导致的全局不确定性上升:一道表述含糊、两个选项模棱两可的题目,所有学生做的时候正确率都不稳定,模型在这道题上的不确定性自然高。这种不确定性是问题本身的属性,不是学生的问题。所以我在工程落地时习惯把两个不确定性的来源分开统计:学生侧的不确定性(同一学生在不同知识点上的平均不确定性)和知识点测题侧的不确定性(同一知识点下不同题目引发的模型不确定性)。前者用于个性化干预,后者用于题库质检,两者千万别混着用。
另一个容易踩的解读误区是把“不确定性低”等同于“掌握度一定对”。一个知识点只有两三条历史记录,模型给出的不确定性未必高——如果这两三条记录全是做对的,某些模型会因为证据一致而给出低不确定性。但三条记录在统计上根本不够,这只是“小样本下的虚假稳定”。更稳妥的做法是同时设置一个“最小证据量门槛”,样本量低于门槛时,无论模型输出什么,都先按高不确定性处理。
在实际系统里,我建议把不确定性阈值设计成可调参数,而不是写死。低年级学生的做题噪声比高年级大,数学主观题比选择题的噪声大,同一个不确定性阈值不可能在全场景通用。上线后可以先用小流量跑一到两周,收集教师标注与学生反馈,用“高不确定性样本与教师认为需要干预样本的重合率”来反向校准阈值,这种做法比任何离线指标都更接地气。
我在实际项目里最深的体会是:UKT 这类工作真正的价值,不是把 AUC 刷高几个点,而是把一个被行业长期忽视的信息维度重新摆上了桌面。过去我们拼命追求“预测得更准”,却很少问“这个预测凭什么让人相信”。不确定性感知知识追踪最有启发的部分,是让模型在关键节点学会说“我不知道”——这种能力在教育场景里,恰恰比假装全知更值得信任。
最后分享一个落地经验:如果团队资源有限,不一定非要完整复现 UKT 的全部设计。可以先在一个已有的知识追踪模型上做一个轻量改造,在输出层加一个方差头,用负对数似然损失替换原来的 MSE,拿一个小规模数据集跑几轮,观察不确定性输出是否合理。这一步投入不大,但对团队理解“学习状态建模”能带来的体验升级,帮助非常直接。等验证有收益了,再考虑投入资源做完整的分布建模版本。