做个性化学习路径规划这件事,我一开始就撞上一堵墙:不管你算法模型多先进,没有能刻画“一个人究竟是怎么学习”的数据,推荐逻辑就只能停留在“给用户塞一堆课”的粗放层面。后来我把MOOPer数据集拿来做实验,才真正把个性化学习路径从情境描述变成可计算、可验证、可迭代的工程问题。这篇博文就完整复盘一下,我是怎么用MOOPer数据集一步步把个性化学习路径规划落地的——从认识数据到特征工程,从模型选型到效果评估,最后再聊聊几个只有踩过才知道的坑。
这篇内容适合正在做智能教育产品、自适应学习系统、学习分析与教育数据挖掘的工程师和算法同学,也适合那些被“个性化”三个字折磨过、又不想只做点击率优化的从业者。如果你手里恰好也有MOOC类行为日志数据,那这篇文章的很多思路可以直接搬过去用。
1. 为什么MOOPer数据集适合干这件事:数据形态与问题边界
1.1 MOOPer到底记录了什么
先说结论:MOOPer这类学习行为数据集,和公开的MOOC行为日志在形态上非常接近,只是它在学习过程的完整性上做得更细。我拿到的版本核心是一张又一张按时间戳排列的行为事件表,里面记录的是学习者在课程平台里的所有关键动作——打开哪个视频、从第几秒开始看、有没有拖动进度条、练习提交了几次、每次对错如何、在讨论区发过什么帖子、回帖有没有解决别人的问题。
这类数据的价值,在于它记录的不是一个静止的“成绩快照”,而是一段可以重放的学习过程回放。假设一个学习者期末考了85分,我们只知道结果;但如果他花了三天才学完第7讲、同一道练习反复提交了8次、视频里讲到“极大似然估计为什么要取对数”的位置反复暂停,那我们就能推断这个人卡在了哪里,以及下一阶段最应该补什么。这才是路径规划真正需要的信号。
我习惯把MOOPer里的字段分成几个用途,在后续特征工程和建模时各司其职:
| 字段 | 含义 | 在路径规划里的作用 |
|---|---|---|
| user_id | 学习者唯一标识 | 聚合行为的最小粒度 |
| course_id / module_id | 课程或学习单元标识 | 界定路径的节点 |
| event_type | 行为类型(视频播放、暂停、提交作业、发帖…) | 刻画学习参与方式 |
| object_id | 交互对象(视频、题目、帖子) | 定位卡壳的具体位置 |
| time_stamp | 行为发生时间戳 | 恢复学习时序 |
| score / is_correct | 练习与测验结果 | 衡量知识点掌握度 |
| device / platform | 终端与平台信息 | 做场景化特征时的补充 |
每个版本的数据在字段命名上可能略有区别,但核心不外乎这些。拿到数据后的第一件事,不是跑模型,而是把这些字段的粒度、覆盖面、缺失情况摸清楚。比如有的版本会以“会话”为粒度,有的以“行为”为粒度,这直接决定了你聚合特征时用什么窗口。
1.2 过程数据与结果数据:路径规划的“原材料”差异
这是我特别想强调的一点。做学习路径规划,千万不要只拿课程总成绩或者期末测验分数来建模型,那是典型的“用快照判断一个人的运动轨迹”。成绩单能告诉你他最终站在哪,但完全丢失了他怎么走到这一步的路径信息。
路径规划本质上是一个强依赖时序和依赖关系的任务:A知识点和B知识点到底该谁先学,新知识与已学知识的重叠度怎么算,学习者在什么时候开始出现认知负担过重、需要降阶调整——这些信息在总分里全部是隐性的。MOOPer这类行为日志把隐性过程显性化了,这是它和其他教育数据集比最值钱的地方。
我举个例子。两个学习者在“贝叶斯定理”这一章都拿到80分,但甲是反复看基础讲解、跳过难题直接交卷;乙是直接听拔高内容、在基础概念上反复卡壳。两人的80分背后,下一步最优路径完全相反:甲应该挑战综合应用题,乙必须回头补概率基础。如果只用成绩数据,系统给这两个人推荐的路径会一模一样,个性化就成了笑话。MOOPer这类细粒度日志,才能让模型区分出这种“分数相同、状态不同”的差异。
1.3 动手之前必须先回答的四个问题
我见过不少团队上来就用MOOPer猛跑深度学习模型,结果项目烂尾。问题不在模型,而在没想清楚“路径规划”到底要规划到什么程度。我建议拿到数据后先回答四个问题:
- 路径规划发生在什么粒度?是课程模块粒度,还是知识点粒度,还是练习题目粒度?粒度越细,数据要求越高,特征复杂度也越大。
- 个性化针对谁?是给新生做零基础路径,还是给老学员做补弱路径?两者的特征和奖励函数设计思路完全不同。
- 系统能动态干预吗?产品上能不能只推荐内容、调整难度、改变排序,还是会直接影响课程结构?这个约束决定你用推荐模型还是用强化学习。
- 有没有反馈闭环?推荐路径之后,能不能观测到用户学完某个单元后的练习结果?没有闭环,就无法做后续的模型迭代和效果验证。
这四个问题的答案,基本决定了后面所有技术选型的上限。我自己第一次做的时候跳过这些问题,直接从特征工程开始,结果做了两个星期发现产品上根本没法承接“动态调整难度”这个动作,返工成本很高。
2. 特征工程:把学习日志变成可计算的学习信号
2.1 路径规划到底在优化什么:先定目标再抽特征
在动手抽特征之前,最好先把一句话目标写下来。不要写“提升学习效果”这种无法计算的口号,要写“在单位学习时间内提高掌握度”“降低卡壳率”“提高课程完成率”这种能落到指标上的目标。
我当时的做法是把目标拆成两层。第一层是过程目标:学习内容覆盖率、学习时长合理性、学习节奏平稳度。第二层是结果目标:练习正确率提升、单元测验成绩提升、课程完成率提升。路径规划的输出是“下一步学什么”,但最终被考核的往往不是这一个动作,而是这一串动作之后学习者的状态变化。
这个目标的拆解非常重要,因为它直接决定了特征怎么抽、模型怎么训练、效果怎么评估。如果只把目标定义成“预测用户会学哪个单元”,那你实际上做了一个点击率预测,而不是路径规划。路径规划的评估必须包含“学完之后有没有变好”这个维度。我后面会在评测部分展开讲。
2.2 五组核心特征的抽取方式
基于MOOPer的行为日志,我通常把特征分成五组,每组服务于路径规划中的一个具体判断维度。
第一组是学习投入类特征。它回答的是“这个人到底投入了多少”。常用特征包括:视频观看时长占视频总时长的比例、暂停次数、重复观看次数、单日活跃时长、一周内活跃天数。抽取时要注意,播放时间戳经常有跨天或断线重连的情况,需要先按会话切分再聚合。
第二组是知识掌握类特征。它回答的是“这个人现在会了什么”。常用特征有:练习首次作答正确率、单元测验得分、最近五次练习的滑动窗口正确率、纠错后正确率提升幅度。这里有个小技巧:首次作答正确率比平均正确率更能反映真实掌握度,因为平均正确率会被“反复试错后的正确”污染。
第三组是学习节奏类特征。它回答的是“这个人的学习习惯和稳定性”。常用特征包括:两次行为间隔的标准差、作业提交时间距离截止时间的时长、习惯性学习时段、单次学习会话的平均长度。节奏特征对判断认知负担很有效,比如连续三天学习时长跳水,往往是难度过高的信号。
第四组是交互偏好类特征。它回答的是“这个人偏好用什么方式学习”。常用的有:视频类内容与文本类内容的使用比例、是否快速跳过长的讲解、讨论区只读帖还是经常发帖、是否倾向于先做题后看视频。这组特征在后期做个性化排序时很有用,但不适合单独作为路径跳转的依据。
第五组是卡壳与求助类特征。它回答的是“这个人卡在了哪里,以及他是怎么求助的”。常用特征包括:同一道练习的提交次数、求助后是否解决了问题、回看某个视频片段的次数、在论坛中提问的知识点标签。这组特征直接服务路径规划中“需要补哪个前置知识点”的判断。
举个例子,用MOOPer里的视频事件抽“视频观看完成率”特征,伪代码如下:
import pandas as pd # 原始事件日志,包含 user_id, video_id, event_type, time_stamp logs = pd.read_csv('mooper_events.csv') # 只保留视频播放相关事件 video_events = logs[logs['event_type'].isin([ 'play_video', 'video_start', 'video_stop', 'video_drag' ])] # 计算每个用户、每个视频的有效观看时长 def extract_effective_watch(df): # 这里需要一个会话切分的逻辑,不能用简单的 start-stop 时差 # 具体实现会结合 session_id 和 lag 计算,过滤掉拖动和异常区间 pass video_watch = video_events.groupby(['user_id', 'video_id']).apply(extract_effective_watch) video_watch['completion_rate'] = video_watch['effective_watch_seconds'] / video_watch['video_duration_seconds']从原始日志到最终特征表,中间通常要过清洗、会话切分、窗口聚合三层处理。这个环节不复杂,但最耗时,也最容易被细节坑到。
2.3 时序切分:训练集和验证集必须诚实
个性化路径规划模型的离线评估,最怕的就是随机切分用户。比如把某个用户第3周的行为放进训练集,再用他第4周的行为做验证,表面上看指标很漂亮,实际上已经发生了特征泄漏——同一个用户的学习习惯、内容偏好已经提前被模型看到了。
正确做法是按时间切分。我用的是滚动时间窗口:用第1到第4周的数据训练,预测第5周及以后的行为;再用第1到第8周的数据训练,预测第9周及以后的行为。这样做的好处是模拟了系统上线后的真实场景:你永远只能基于“已经发生的行为”预测“还没发生的下一步”。如果验证集里出现了“未来”的信息,再高的指标都要打问号。
具体操作上,我会在原始行为日志上打一个“截止日”标签:只有发生在预测起始时间点之前的行为才能进特征窗口。还有一个小细节是,特征聚类的窗口结束时间必须严格早于预测目标的时间起始点,不能把同一个小时的未来行为聚合到特征里。
2.4 特征工程里的三个隐蔽坑
第一个坑是学习时长被挂机污染。MOOPer里视频播放事件特别多,但很多人是挂着视频去干别的,播放进度一直在走,人早就离开了。如果直接把视频总时长当观看时长,你会把“挂机学习者”错判成“高投入学习者”。我的处理方式是引入交互事件过滤:只有发生过点击、暂停、拖动、字幕切换等主动交互的片段才算有效观看,再计算有效观看时长占比。
第二个坑是拖动事件干扰观看时长的计算。视频日志里播放和停止事件不总是成对出现的,用户拖一下进度条,前后的start和stop时间就会错乱。我建议计算观看时长时,用“点击播放后没有发生拖动且持续播放超过阈值”的片段,而不是简单用stop时间减start时间,否则特征方差会特别大。
第三个坑是论坛帖子的数量不等于质量。很多MOOPer学习者习惯在讨论区发言,但发帖多可能是求助多,也可能是灌水。直接拿发帖数当特征,会把“积极参与者”和“遇到大量困难的人”混为一谈。更好的做法是给帖子打标签,区分“围绕知识点的提问”“对他人疑问的有效回复”“情感支持类发言”,再分别聚合。
3. 路径规划模型的选型:从规则基线到序列模型
3.1 第一版永远是规则基线
我强烈建议路径规划的第一版不要上深度学习,先做规则系统。原因有三个:规则系统可解释性强,教研团队能直接参与修改路径逻辑;没有训练成本,上线出问题能立刻定位;最重要的是它作为后续模型的对照基线,能告诉你复杂模型到底带来了多少真实增益。
我当时的规则系统是基于“前置知识图谱 + 知识点熟练度排序”的。知识图谱里定义了每个学习单元的前置依赖关系,比如学“矩阵乘法”之前,必须掌握“数乘”和“向量的点乘”。系统的推荐逻辑很直白:
- 如果当前单元的前置知识点熟练度低于0.6,就推荐先补前置知识点对应的学习单元;
- 如果熟练度在0.6到0.8之间,推荐匹配的巩固练习;
- 如果熟练度高于0.8,才推荐进入新的学习单元。
熟练度怎么算?我用的方法是最近N次练习正确率的指数加权平均,时间越近权重越高。这套规则上线之后,虽然看起来“不太智能”,但它给了系统一个清晰的起点,也给教研同事一个可修改的界面。后面模型升级时,规则系统作为基线也能承担A/B测试的对照组角色。
3.2 序列模型:让行为数据自己说话
有了规则基线,我再开始尝试用数据驱动的序列模型。基本思路是把每个学习者在一段时间内的行为整理成按时间排序的事件序列,然后输入序列模型,预测“接下来最应该学习的学习单元”。
我尝试过两种主流方案。第一种是LSTM/GRU,把行为事件映射成“学习单元 + 事件类型”的嵌入向量序列,经过循环网络编码后,输出下一个学习单元的分布。第二种是Transformer,利用它的自注意力机制处理更长的上下文依赖,捕捉“三周前看过的某个概念其实和今天卡住的题目相关”这种远距离关系,但代价是对数据量和训练成本的要求都高很多。
用LSTM建模时,我遇到过几个工程细节问题。首先是序列长度不统一,需要设置截断或padding。其次是负样本怎么采样:如果直接把所有用户没学过的单元都当成负样本,模型会被“太简单/太偏门”的单元带偏。我采用的是“随机采样 + 全局热门但该用户没学过的单元”混合策略,效果比纯随机好不少。
有个经验是:不要把event_type和object_id拼成一个特征,那样会让“用户点击视频A”和“用户提交题目A”变成完全不同的事件,丢失了“它们都发生在A单元”的联系。更好的做法是把object_id映射成学习单元embedding,再把event_type作为另一个特征输入,让模型自己学习事件类型和内容对象之间的关系。
3.3 教育场景的进阶方案:认知诊断 + 强化学习
如果目标不是“预测用户会点击什么”,而是“推荐什么能让用户学得更好”,那Next Item Prediction这类方案就不够了。教育场景里更贴近本质的做法,是把“学习者当前掌握状态”显式建模出来,再去决定下一步动作。
我尝试过的进阶路线是“认知诊断 + 强化学习”。第一步,用认知诊断模型(比如IRF、DINA这类项目反应模型)估计学习者对每个知识点的掌握概率;第二步,把“下一步推荐哪个学习单元”定义为强化学习的动作,把“学习后掌握度提升”作为奖励信号去训练策略网络。
这个方案的难点在奖励设计。我最早直接用“练习正确率”当奖励,结果模型迅速学会了推荐已经掌握的知识点,因为做会做的题正确率当然高,用户一直停留在舒适区,学习效率很低。后来改成“学习收益”奖励:推荐内容与当前薄弱知识点的重合度,再加上巩固后的进步幅度。也就是说,不仅要看用户学完后的正确率,还要看“他有没有从不会到会”。
认知诊断模型输出的掌握概率向量,本身也是极好的特征。它比原始练习正确率稳定得多,因为它会同时考虑题目难度、区分度、猜测概率这些因素,不会因为一道偏难的题就把熟练度判断得特别低。我把这个向量接到规则系统里,用来替代简单的滑动窗口正确率,规则推荐的准确度也有明显提升。
3.4 选型建议:按团队资源和业务阶段来匹配
算法选型没有绝对的答案,我一般按团队情况分三档来建议:
| 团队情况 | 推荐路线 |
|---|---|
| 有教研团队、解释性要求高、数据量有限 | 知识图谱 + 规则系统 |
| 有算法工程师、数据量中等、需要快速迭代 | 序列模型 + 规则约束 |
| 有充足数据和算力、追求长期自适应优化 | 认知诊断 + 强化学习 |
我的实际感受是,多数教育产品项目卡在数据量,而不是算法复杂度。MOOPer虽然能提供细粒度日志,但单个课程的有效样本往往就几千到几万条,Transformer这种大模型很容易过拟合。所以稳妥的路径是:先有规则基线,再上序列模型,同时积累足够的反馈数据,最后再考虑强化学习这种端到端优化方案。
4. 效果验证:怎么证明推荐路径真的有用
4.1 离线指标别只盯着准确率
路径规划模型的离线评估,最容易掉进去的陷阱是只用推荐准确率、Hit@5、AUC这类“预测类指标”。这些指标衡量的是“模型能不能猜到用户会学什么”,而不是“用户学完之后有没有变好”。一个模型可能只是学会了推荐热门的、简单的学习单元,用户点击率看起来很高,但真正的学习效率没有提升。
我建议在离线阶段就补充三组指标。第一组是推荐内容与用户薄弱知识点的匹配度,可以用认知诊断模型输出的薄弱知识点向量做相关性计算;第二组是学习后表现提升,比如推荐后的新单元练习正确率相比用户基线正确率的提升幅度;第三组是留存类指标,比如一周后活跃度、课程完成率是否有正向变化。
这三组指标在离线阶段不一定都能算出来,但至少要明确哪些能在线上观测,哪些只能在实验里拿到。先把评估框架搭好,再调模型,才不会被单一指标的漂亮数字骗过去。
4.2 更贴近学习效果的评价方式
如果数据里有练习记录,我建议用“推荐后的二次学习行为”来替代“点击行为”作为核心评价口径。简单说就是:一个推荐是否有效,要看用户在被推的内容上有没有出现“有效学习动作”,以及这些动作之后他的相关知识点正确率有没有实质提升。
我在MOOPer实验里的具体口径是:模型推荐某个学习单元后,用户是否在该单元的视频上产生了超过阈值的有效观看时长,以及该单元关联练习的正确率是否从0到40%的低水平提升到60%以上。这个口径既避免了“点了但没看”的假点击,也避免了“看了但没学会”的无效果。
引入这个口径之后,很多在点击率指标上表现很好的模型立刻现出原形。这也是教育场景和普通推荐场景最大的区别:推荐系统的KPI可以是点击、成交,但学习路径规划的KPI最终必须落到“学会”上,哪怕这意味着主动把学习者推向更难、更不舒服的内容。
4.3 上线前的小流量实验怎么设计
离线验证做得再好,也必须经过线上小流量实验。我的设计方法是把用户分成三组:规则推荐组、模型推荐组、无推荐对照组。无推荐对照组不是不给推荐,而是给一个固定顺序的“标准学习路径”,这样能单独分离出“个性化推荐”本身带来的增益。
观察指标至少要包含四类:周完成学习单元数、单元测验正确率、退课率、平均学习时长。其中退课率和学习时长要看方向。有时候推荐精准了,用户学得快,学习时长反而会下降,这是好事而不是坏事。所以不能单独看学习时长这一个指标,要结合完成度和正确率一起判断。
实验周期我建议至少1到2周,观察留存曲线是否平稳后再做全量。还有一个容易忽略的细节:实验期间课程内容一定要保持稳定,不能同时上线新课程或调整单元顺序,否则实验变量不干净,最后归因说不清楚。
5. 从MOOPer项目里踩出来的坑
5.1 冷启动:没有历史行为的新用户怎么推路径
MOOPer数据里大量用户只有非常稀疏的行为,尤其是一门课刚开课的前两天,新注册用户几乎是零历史。模型在这些人身上基本瘫痪,因为没有输入特征。我的方案是给新用户先做一次迷你前测:5道覆盖核心前置知识点的题目,用成绩把用户粗略分层,再匹配不同起点的默认路径。
这个方案的另外一个好处是,前测结果可以直接作为认知诊断的初始先验,等用户积累了三到五个有效行为后,再从默认路径切换到个性化推荐模型。我实测下来,这个“前测分层 + 快速切换”的组合,比让新用户直接走大而全的默认路径,在一周留存和首个单元完成率上都有几个百分点的提升。
5.2 特征泄漏:用未来信息预测过去的教训
这是我在第一次离线评估时踩过的大坑。当时我把用户的视频观看时长、练习正确率这些特征按整月聚合,然后用第15天的行为做预测目标,结果验证集AUC高得吓人。一查才发现,特征聚合窗口跨越了预测目标的时间点,相当于用“第10天到第20天的平均表现”预测“第15天的行为”。
这个问题的本质是时间窗口的控制不够严格。修复办法是给所有特征打上“截止时间戳”,保证特征窗口的结束时间严格早于每个样本的预测时间。对于MOOPer这类时序性极强的数据,这一点尤其重要。现在我在所有特征工程代码里都会强制配置一个“prediction_time”参数,所有聚合都基于它做动态窗口切分,从源头杜绝泄漏。
5.3 “看完视频不等于学会”的识别
MOOPer数据里视频事件是最多的,这也是最容易误导模型的地方。很多人用倍速播放、挂着播放、跳段播放把视频“刷”完,但相关练习正确率一点没提升。模型如果只看视频观看行为,就会把这些人当成“高投入学习者”,推荐路径自然不准确。
我的处理方式是引入“有效学习率”概念:有效观看时长占比乘以练习表现加权系数。简单说就是,如果一个人看完视频但练习正确率低于平均线,他的“有效学习率”就要打折。这样模型对“假学习”的容忍度会降低不少。当然,也有人会因为题目太难导致看懂了却做不对,所以这个加权系数我会结合题目难度做调整,不能一刀切。
5.4 课程内容更新带来的特征漂移
MOOC课程不是静态的,几乎每学期都会更新。某个视频被替换、某个练习被删除、某个知识点的讲解顺序发生变化,都会导致旧数据训练出来的模型特征分布偏移。这个问题在MOOPer上尤其明显,因为行为日志天然绑定课程版本。
我的处理方式是给内容加上“版本因子”:同一个知识点在不同课程版本里对应不同的视频或练习,我会用知识点的概念ID把它们归并到同一个聚类里,保证路径规划的基本单位是知识点而不是具体的视频文件。这样即使某个版本的内容被替换,模型依然能识别“学习者卡在概率论基础这个概念上”,只是推荐的资源换成新版本对应的视频而已。
最后说两句
做了这么久的智能教育项目,我最大的体会是:教育数据挖掘和普通推荐系统最大的不同,在于你不能把“用户喜欢什么”当作终极目标。一个学习者喜欢看简单有趣的视频,不代表他一直看简单内容对他最好。真正的个性化学习路径规划,是在尊重学习体验的前提下,把学习者往“舒适区边缘”推——既不太难导致放弃,也不太容易导致停滞。
MOOPer数据集是一个很好的起点,但它不是终局。如果你能把这套“数据理解、特征工程、建模选型、效果评估、踩坑修复”的闭环跑通,迁移到企业培训、K12自适应练习、职业教育等场景只是时间问题。下一步我打算把认知诊断和强化学习的奖励函数再打磨细一点,因为现在最大的瓶颈已经不在模型结构,而在如何更准确地定义“学会了”这件事。