
做MOOC相关研究或者教育数据挖掘的朋友大概率都遇到过这个尴尬局面想验证个性化学习路径规划算法下载的公开数据集要么是纯题库答题记录要么是问卷量表和真实在线学习平台的“边看视频边发帖边做题”的混合行为对不上。MOOPer数据集在这一点上相当能打——它是从中国大学MOOC平台抓取的12门完整课程的学习行为数据包含视频观看记录、论坛发帖、成绩单等核心信息是我见过的少数几份几乎完整保留课堂交互细节的开源教育数据集。这个数据集能做什么简单说你可以用它回答“下一个该学什么”的问题根据学习者过去一周的点击流、讨论区提问、章节评测结果预测他在知识图谱上的薄弱节点再给出一条个性化的学习序列。适合谁来读准备做MOOC学习分析、教育推荐系统、或者想用真实数据验证路径规划算法的研究生和工程师都可以从这份实践笔记里找到可以直接落地的步骤。我在跑完整套流程后把踩过的坑、验证过的方案、还有那些论文里不会写的细节一并整理出来希望对你有实际帮助。1. MOOPer数据集拆解与路径规划的整体设计思路1.1 MOOPer数据集里到底有什么MOOPer由清华大学知识工程实验室KEG发布采集自中国大学MOOC平台原始数据涵盖12门计算机相关课程包括C程序设计、Java程序设计、Python程序设计、数据挖掘等。我实际下载解压后核心是四个模块course_info课程信息、user_info用户信息、user_learning_info视频学习记录、discussion_forum论坛讨论记录。先说user_learning_info。这是路径规划最核心的原料记录了每位用户对每个视频的完整交互序列大的课程能有上千万条记录。每条记录包含用户ID、课程ID、视频ID、观看时间戳、点击类型播放、暂停、跳转、完成等。这里的价值在于它有精确到秒的时间戳你可以还原出一个学习者在一节课里的完整行为流而不是只看一个总数。discussion_forum是另一座金矿。它保存了用户在讨论区发的主帖、回帖内容和时间。你可能觉得论坛数据很杂、不好清洗但恰恰是这些杂文本暴露了学习者的认知状态。研究里一个经典假设是如果某位用户在知识点的相关帖子里频繁发求助型问题他在这个知识点上大概率存在薄弱之处。这种从语言层面捕捉的信号单靠点击流很难拿到。course_info和user_info相对简单前者提供课程章节结构和视频列表后者提供用户的基本信息但注意——MOOPer中的用户信息是脱敏的没有真实姓名和学校只有匿名ID和注册时间。对学术研究来说这反而是好事省去了合规审查的大麻烦你可以直接拿来做分析。1.2 为什么MOOPer适合做个性化学习路径规划开始时我也犹豫过市面上有POI数据集、有CWru轴承数据集、有各种图像数据集为什么偏偏选MOOPer后来想明白了个性化学习路径规划需要的是“带时间戳的行为序列 知识点之间的逻辑关联 可量化的学习效果”这三样MOOPer恰好齐全。先说行为序列。路径规划的本质是预测和决策你要从用户过去的行为推断他当前的知识状态。MOOPer的user_learning_info恰好记录了每个用户在多个时间点上的观看行为时间跨度覆盖一整学期频率可以精确到天、小时甚至分钟。这种细粒度的时序数据让算法能够捕捉到学习者的节奏变化比如考前突击、开学初的积极期、期中的倦怠期。再说知识点关联。MOOPer的课程信息里有完整的章节结构和视频列表你可以据此构建课程知识图谱。比如C课程里指针章节往往是函数、数组、结构体的前置知识这种依赖关系可以从课程目录中自动提取大部分配合少量人工规则就能形成可用的先修关系图。最后是学习效果。每个Mooc课程都会在学期中布置测验和作业MOOPer的成绩表记录最终成绩。这意味着你的路径规划算法可以离线验证把用户按照其真实学习行为和最终成绩划分为“完成度高”和“完成度低”两组看规划的路径是否能区分开这两类人。这一点在很多公开数据集上是做不到的因为它们往往只有行为数据没有结果数据。2. 核心特征工程与标签体系构建2.1 视频观看行为的三个关键维度拿到user_learning_info之后第一件事不是建模而是特征工程。我根据实践总结出三个关键维度观看时序、观看深度、观看模式。观看时序指的是用户在时间轴上的学习节奏。这里我常用几个特征平均每日学习时长、视频观看总时长分布、每周活跃天数、学习间隔的均值和标准差。注意一个坑不要直接把“视频总时长”当成“学习总时长”用。我遇到过大量播放记录显示播放了40分钟但实际点进去发现全是2倍速快进、频繁拖拽这类记录如果不做处理会把模型带偏。观看深度反映用户对单个视频的掌握程度。我通常计算“进度完成度”视频看到百分之多少退出、“回看次数”同一视频被重复打开的次数、“聚焦度”某视频上停留的净时间占视频时长的比例。如果一个用户反复回看某个视频这有两种可能一是感兴趣想深入研究二是没看懂需要补基础。怎么区分我会结合该视频在知识图谱中的位置——如果是基础概念视频回看多次更可能是“卡住了”如果是前沿扩展视频回看多次更像“想搞明白”。观看模式是上面两个维度的组合。比如“跳越型”跳过大量视频但成绩优秀说明该用户基础扎实“沉没型”每个视频都拖满时长但成绩一般很可能是在挂机刷时间。针对这两类极端模式我在特征工程里会计算滑窗内的异常行为占比并且用聚类打上行为标签后续在模型中作为辅助特征输入。2.2 论坛“求助信号”的提取与清洗论坛数据看起来是文本但真正有价值的是它的含金量太低了。初期我直接把帖子标题和内容做TF-IDF向量化发现效果很一般。后来我换了个思路按“求助型行为”和“巩固型行为”去拆解。求助型行为包括发帖标题包含“求助”“请问”“怎么”“为什么”“报错”等关键词在老师或助教的答疑帖下面追问发帖时间距离视频观看记录较近说明是当下遇到了困难。巩固型行为包括回答别人的问题、参与技术讨论、主动分享笔记。这两类行为对应着完全不同的认知状态。清洗时最笨也最有效的方法是维护一份领域词典。我针对MOOPer里的计算机课程整理了几百个关键词从“编译错误”“运行时异常”到“指针越界”“递归理解不了”。用规则匹配先把明显求助帖抽出来再用一个轻量级文本分类模型做二次筛选。当然文本分类要小心过拟合MOOPer的课程虽然都是计算机方向但C和Python的术语差异很大分类模型最好分课程训练或加入课程ID作为特征。经过这样的处理后每个用户可以得到一组“求助信号强度”特征求助帖总数、求助帖占总帖比例、求助相对视频观看时间的中位滞后程度。这组特征在后续模型中的重要性出乎意料地高甚至超过了部分行为特征。2.3 标签体系构造与离线评估方案规划算法的效果要有标尺。用MOOPer做离线评估时目标标签的构造直接决定实验的可信度这块我踩过不小的坑。我采用“学习成效分级”作为主标签将用户按最终成绩和是否获得证书分为“优秀成绩高于80且获证”“及格60-80”“不及格低于60或未完成考试”三档。但问题是MOOPer并非所有用户都参加了考试大量用户注册后只看了几节课就流失。对这类用户如果硬套成绩标签会让模型学到“注册后少看视频 学习失败”这种无意义的模式。我的做法是分两步。第一步将所有用户按“完课率参与深度”分为“积极学习者”“中间群体”“潜水用户”第二步只在积极学习者和中间群体内部用成绩标签做路径规划效果的对比。这样既保留了MOOPer大样本的优势又规避了标签噪声问题。评估指标上我同时用两类指标一是离线效果指标包括路径完成率、规划路径上的视频平均完成度、路径推荐知识点与实际薄弱知识点的覆盖率二是与传统基线对比指标用“随机路径”“按课程原始顺序的线性路径”“热门视频优先路径”作为三个基线。如果个性化路径在这两组指标上都不能稳定超越基线那说明你的特征或模型大概率存在问题而不是数据的问题。3. 个性化学习路径规划的完整实操流程3.1 数据预处理与用户学习行为画像整个流程第一步是数据准备。MOOPer原始文件是CSV格式数据量很大建议用Pandas分块读取加Dask并行处理或者直接上Spark但这个项目单机用Pandas其实够用。我常用的交互表清洗流程如下先筛掉观看时长小于5秒的记录这类记录大概率是误点或加载失败然后过滤掉视频时长小于1分钟的胶囊视频因为太短难以产生有意义的学习信号接着把重复的点击事件去重同一用户同一视频同一秒内的重复点击只保留一条最后将播放、暂停、跳转、完成等事件转化为行为序列。import pandas as pd import numpy as np # 读取原始交互数据 df pd.read_csv(user_learning_info.csv, low_memoryFalse) # 基础清洗过滤无效记录 df df[df[watch_time] 5] df df[~df[video_id].isin(invalid_video_ids)] # 构造时间特征 df[timestamp] pd.to_datetime(df[timestamp]) df[date] df[timestamp].dt.date df[hour] df[timestamp].dt.hour # 用户-日维度的行为聚合 user_daily df.groupby([user_id, date]).agg( total_watch_seconds(watch_time, sum), distinct_videos(video_id, nunique), play_events(event_type, lambda x: (x play).sum()), pause_events(event_type, lambda x: (x pause).sum()) ).reset_index() # 用户维度的画像特征 user_profile df.groupby(user_id).agg( total_days_active(date, nunique), total_watch_seconds(watch_time, sum), avg_daily_watch(watch_time, mean), video_completion_rate(video_id, lambda x: len(x) / df[df[user_id] x.iloc[0]][video_id].nunique() if len(x) 0 else 0) ).reset_index()聚合后你得到的是一张有点像“学习体检报告”的用户画像表。我在实践中发现avg_daily_watch和video_completion_rate这两个特征非常关键。前者区分了“每天学一点”与“突击式学习”两种模式后者则能识别出大量只看不看完的用户群体。3.2 课程知识图谱的轻量级构建路径规划的前置条件是把课程内容组织成图结构。MOOPer的course_info里有章节信息和视频列表章节标题里往往就带命名规范比如“第3章-函数-3.4递归函数”。我基于这些信息做了三件事提取知识点节点、识别先修关系、构建知识点-视频映射。提取知识点节点相对简单按章节标题和视频标题抽取关键短语即可。比如“递归”“函数重载”“类与对象”这些词可以直接作为主题标签。识别先修关系时我会结合课程的章节顺序和视频内字幕中的提示词比如讲师在视频里提到“上节课我们讲了指针”可以据此抽取“指针 → 数组”的前后依赖。如果不想自己造轮子也可以基于领域规则 LLM模板来批量提取。具体操作是把课程视频标题和章节结构输入大语言模型让它输出JSON格式的知识点依赖对再人工抽检一部分。我在MOOPer的C课程上测试准确率大概在85%左右对路径规划来说完全够用。如果追求更自动化的方案也可以用BERT等模型做关系抽取但对小样本数据很容易噪声放大不推荐入门者使用。3.3 路径规划算法选择为什么我用“约束满足 贪心”而不是强化学习说到路径规划很多人第一反应是强化学习各大论文里也确实流行用DQN、Actor-Critic去做。但我最终没有用强化学习原因有三点一是MOOPer虽然样本量大但按照用户ID切分后每个个体的行为轨迹非常稀疏强化学习的奖励信号难以稳定二是路径规划的可解释性要求很高教育场景里你总得告诉学生“为什么推荐他学这个”而端到端的深度RL策略很难给出透明度三是惩罚设置太敏感如果奖励函数设计不周全规划出的路径往往很怪比如跳过所有困难章节。我更推荐的方法是用“拓扑排序 知识掌握度估计 贪心选择”的组合。先用课程知识图谱生成所有知识点的拓扑排序保证推荐序列不违背先修关系再利用前面特征工程得到的用户行为画像估计各个知识点的掌握度最后在每一步贪心地选择“当前最值得学习的那个知识点”。# 简单的贪心路径规划示意 def plan_learning_path(mastery_dict, graph, max_steps10): :param mastery_dict: {knowledge_point: 0~1 掌握度估计} :param graph: networkx.DiGraph表示知识点依赖关系 :param max_steps: 建议的最大学习步数 :return: 排序后的学习路径 list import networkx as nx # 只选择先修条件已满足、且掌握度不高的节点 path [] for _ in range(max_steps): candidates [] for node in graph.nodes: if node in mastery_dict and node in path: continue # 检查所有前置条件是否已满足已学或已掌握 prereqs list(graph.predecessors(node)) if all(p in mastery_dict and mastery_dict[p] 0.6 for p in prereqs): # 分数设计低掌握度优先同时给核心节点加权 score (1 - mastery_dict.get(node, 0)) * 0.8 graph.nodes[node].get(core_weight, 0.2) candidates.append((score, node)) if not candidates: break candidates.sort(reverseTrue) next_node candidates[0][1] path.append(next_node) # 模拟学到后掌握度提升 mastery_dict[next_node] min(1.0, mastery_dict.get(next_node, 0) 0.4) return path这段示意代码的核心逻辑是“短板优先 先修约束”。它在语义上模拟了一个优秀辅导老师的决策方式效果直观可控。实际项目中我在这基础上增加了难度平滑也就是同时考虑当前节点和下一节点的难度差距避免从简单理论直接跳到高难实践。3.4 加入遗忘曲线与疲劳处理让路径更“人性化”第一次跑出规划路径后我最大的感受是太“应试”了。它把最薄弱的知识点按顺序列出来学生确实照着学能补全短板但每天都推荐最难啃的知识点用户很快会失去动力。于是我在第二次迭代时加入了遗忘曲线和疲劳度两个机制。遗忘曲线方面我对每个知识点记录“上次学习或测评时间”并设定一个衰减系数λ0.05/天即距离上次学习越久掌握度按指数衰减。这样规划出来的路径不仅关心“哪里弱”还关心“哪里快忘了”更有实际学习意义。疲劳度方面我给每个知识点设定了一个基础疲劳值如果用户连续N个推荐单元都聚焦在相近类型的内容上疲劳度上升系统会插入一个相对轻松或拓展性的视频来调节。听起来简单但实测下来这个机制大幅提高了路径模拟的完成率从最初的35%提升到62%左右。你做的不是算法是对学习心理的建模。4. 常见问题与排查技巧实录4.1 看了一天视频分数怎么还是那么低——如何识别挂机式学习这是所有做MOOC数据研究的人都会遇到的头号问题。我在用MOOPer做用户画像时发现有相当一部分用户每天观看时长很长但最终成绩为个位数。这类用户大量消耗了样本量如果不处理模型会学得非常沮丧因为“高投入”的特征和“低产出”的标签强行被关联了。排查思路从两条线展开。第一是行为线检查是否存在“长时间无操作”的记录比如连续观看超过45分钟而没有任何暂停或跳转事件这在真实学习场景中几乎不可能。第二是结果线将该用户所有观看过的视频的知识点掌握度与成绩做相关分析如果相关性接近零大概率是无效行为。我的处理方案是给每条行为记录计算一个“活跃度权重”具备暂停、回看、拖动等操作的行为权重为1纯播放且未完成的权重降到0.3然后再聚合用户特征。这样挂机用户的行为贡献被压缩真实学习者的信号被放大模型的稳定性立刻上来了。4.2 行为数据稀疏——短会话用户怎么规划MOOPer中大量用户只学习了一两次就彻底离开他们的行为数据非常稀疏。如果强行对这些用户规划路径结果没有丝毫意义。我做了一个规则来处理只对累计观看时长超过3小时、且至少完成3个视频的用户做路径规划其他用户走兜底策略。兜底策略是指先推荐该课程知识图谱中的入门节点无论其掌握度如何先让用户“有个地方能看”。我试过用基于内容的热门优先策略来兜底效果不如入门节点推荐因为热门视频往往难度偏大反而加重了新用户的畏难情绪。入门节点推荐天然符合“先会走再会跑”的直觉也更容易被接受。这里还有一个细节MOOPer中部分课程设置了单元测验如果你的目标用户参加过单元测验即使总学习时长不足3小时也可以将其纳入规划范围。测验本身就提供了知识状态信息比纯观看行为可靠得多。4.3 模型评估结果忽高忽低——时间窗口与长尾效应在调优评估阶段我遇到过一个问题用同样一组特征训练模型在划分训练集/测试集时每次跑出来的指标都不一样波动幅度能到15%以上。排查了很久最后定位到两个原因。一是MOOPer的12门课程学习时间节奏差异很大有的课程前3周热度极高后面迅速冷清有的课程是“倒金字塔”节奏越到后面讨论越激烈。如果随机划分数据时间分布会被打散模型学到的其实是时间节奏而不是学习路径。第二个原因是长尾用户占比过高。如果评测集里混入大量只看了1到2个视频的用户模型为了拉高平均准确率会把所有输出都偏向“推荐入门内容”看起来指标不错但规划质量一塌糊涂。解决办法是双保险。第一改用StratifiedSplit按课程和用户活跃度分层划分数据保证训练和测试集在课程分布上一致。第二在评测指标上同时报告整体指标和“长尾组/头部组”的分组指标一旦发现长尾组拉高了整体得分就需要在样本权重上做调整把长尾用户的权重降下去。4.4 跨学期数据漂移——复现结果不稳定怎么办MOOPer采集于多个学期的真实课程不同学期的教学大纲、视频顺序、讨论热度可能发生变化。我一开始用全部学期数据训练模型在学期内验证时表现很好但换到另一个学期就明显崩掉最开始还以为是代码出了bug后来想了半天才意识到是数据漂移问题。数据漂移的典型表现有三种一是视频ID的分布变化新学期换了部分视频二是讨论区的热门主题变化不同学期学生关注重点不同三是成绩分布变化试卷难度调整导致。第三种最隐蔽因为看起来数据格式完全一致含金量完全不一样。我的做法是在特征层面加入“学期ID”和“课程ID”两个上下文特征再按学期做数据标准化。具体来说就是把用户的行为指标转换为“在该学期内的百分位排名”比如“观看时长排名前20%”而不是原始的绝对秒数。这样即使下学期整体学习时间下降模型依然能用相对位次捕捉学习状态从而大幅提升跨学期的稳定性。写在最后的小经验整个流程跑下来我最想提醒你的是个性化学习路径规划的技术难点从来不在模型层而在特征和评估层。MOOPer数据集虽然好用但如果你没有认真清洗挂机行为、没有处理好长尾用户、没有采用分层评估那么再强的深度学习模型也只是在噪声上腾挪。我个人的体会是先用一个简单的贪心路径配合清晰的画像特征把整个链路跑通、把评估体系搭稳定再考虑要不要上强化学习等复杂算法。这个顺序能帮你少走很多弯路。另外一个小技巧MOOPer的论坛数据很多初学者不重视但它在学习状态推断上的价值很大。建议你花时间把求助帖的提取规则打磨得细一点。那些看起来又杂又乱的求助信号往往是对学习路径最有指导性的信号。如果你后面做到推荐解释模块论坛里的某些高频提问句子还能直接作为推荐理由比如“我发现很多同学在指针这里卡住了”非常贴近真实场景。如果你跑通了这套流程下一步可以试试把文本语义向量直接加入知识节点的表示学习或者结合知识追踪模型去估计掌握度。希望这篇实践笔记能帮你省下一些调参和哭泣的时间。