ji
这毕业设计我真的会谢,选题的时候看到"基于知识图谱的个性化学习路径推荐系统",觉得挺高大上,结果是给自己挖了一个大坑,前前后后折腾了快两周才跑通,中间差点把电脑砸了。
完整源码链接:https://pan.quark.cn/s/1e54aa2ae950
得到的结果展示
看起来挺唬人的
这个题目到底要干嘛吧,其实就是搞一个知识图谱,把各个知识点之间的前置关系理清楚,然后根据学生已经会了什么、想学什么,推荐出一条最优的学习路径。听起来不就是个带权有向图加个拓扑排序么?我当时也是这么想的,结果写起来各种他*的细节问题。
数据生成这块,我一开始用手写了几十个知识点和自己编的前置依赖关系,想想就觉得累。42个知识点,61条边,手打的时候就在想我是不是脑子有病选了这个题。
KNOWLEDGE_POINTS = [ (1, "Python环境搭建", "基础", 1, "安装Python解释器与IDE开发环境"), (2, "变量与数据类型", "基础", 1, "int、float、str、bool等基本类型"), ... ] PREREQUISITES = [ (1, 2), (1, 12), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), ... ]然后学生画像也得造假的,不然没数据跑。我设计了5个不同水平的学生,张明是学完基础的想搞数据科学,赵雷刚入门啥都不会,王芳是大佬只差几个高阶知识点——这tm不就是我身边同学的真实写照么。
STUDENTS = [ {"id": 1, "name": "张明", "known": [1..12], "targets": [22..27], "prefer_cat": ["数据科学"], "pace": 0.7}, {"id": 4, "name": "赵雷", "known": [1,2,33], "targets": [13..21,36], "prefer_cat": ["进阶","计算机基础"], "pace": 0.5}, ]写到导出csv的时候还遇到了编码问题,中文写到csv里在excel打开全乱码,后来发现要加encoding="utf-8-sig"才对,不过为了代码统一我全都用了utf-8。
接下来是知识图谱的核心部分,用networkx建的图,一开始没注意有向无环图这个约束,随便加了几个环进去,拓扑排序直接炸了——NetworkXUnfeasible报错,我当时就傻了。查了半天才发现是我加依赖关系的时候不小心加了个环形依赖,比如A依赖B、B依赖C、C又依赖A,这谁顶得住啊。
def build_knowledge_graph(): G = nx.DiGraph() for kp in KNOWLEDGE_POINTS: G.add_node(kp[0], name=kp[1], category=kp[2], difficulty=kp[3], description=kp[4]) for from_id, to_id in PREREQUISITES: G.add_edge(from_id, to_id, relation="前置") return G后面长记性了,每次构建完都调用nx.is_directed_acyclic_graph(G)检查一下,有环就回去改数据,改到吐。
推荐算法这块我纠结了好久,一开始想用最短路径,但发现学习路径不是越短越好啊,前置知识没学直接跳到目标知识点这不扯淡么。所以最后还是老老实实拓扑排序+打分排序。
def analyze_knowledge_gaps(self, known_set, target_set): needed = set() for t in target_set: prereqs = get_all_prerequisites(self.G, t) needed |= prereqs needed |= target_set gaps = needed - known_set return gaps打分这部分我综合了几个因素:是不是目标节点(+10分)、是不是偏好的分类(+5分)、难度匹配度(和老知识的平均难度差不超过1就+3分)、还有这个节点影响多少后续知识(0.5分一个)。写完实测效果还不错,最起码看起来像那么回事。
def score_node(self, node, known_set, target_set, preferred_categories): score = 0.0 if node in target_set: score += 10.0 if preferred_categories and node_cat in preferred_categories: score += 5.0 diff_gap = abs(node_diff - avg_known_diff) if diff_gap <= 1: score += 3.0 ...重头戏是可视化,matplotlib这玩意真是又爱又恨。画知识图谱的时候,那些节点挤在一起根本看不清标签,调spring_layout的k参数和iterations调了一万遍。
还有一个坑是SimHei字体,我一开始没设字体,画出来的中文全是方框,查了才知道要加这行:
plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False记得axes.unicode_minus也要设False,不然负号会显示乱码,我也是查了半天才知道的。
画图函数有好几个,最难的是把推荐路径高亮显示在知识图谱上。一开始只高亮了节点,看不出路径方向,后来又加了边的高亮才顺眼。
def draw_knowledge_graph(G, title="知识图谱总览", filename="knowledge_graph.png", highlight_path=None, highlight_nodes=None): ... if highlight_path: edges_in_path = list(zip(highlight_path[:-1], highlight_path[1:])) nx.draw_networkx_edges(G, pos, edgelist=edge_list, edge_color="#FF1744", width=3.5) nx.draw_networkx_nodes(G, pos, nodelist=highlight_path, node_color="#FF1744")看看跑出来的图,全局知识图谱42个节点,分成"基础"“进阶”“数据科学”“Web开发”“工具”"计算机基础"六大类,每种颜色不一样,绿色基础、蓝色进阶、橙色数据科学、紫色Web开发,还挺好看的。难度分布图上能看出来"基础"类难度集中在1-2级,"进阶"和"数据科学"就散在3-5级了,合理。
学生对比图更直观,张明已知12个目标6个缺口7个,赵雷已知3个目标10个缺口20个——这兄弟任务艰巨啊,预估要140天才能学完,节奏系数才0.5。王芳就舒服了,缺口才4个,15天搞定。
最满意的还是每个学生的个性化学习路径图,已掌握知识用绿色圆点亮出来,推荐路径用红色高亮,箭头方向一看就懂。比如给李华推荐的路径是Linux基础→计算机网络基础→Flask基础→RESTful API→SQL数据库→Django基础→Flask数据库→Django认证安全,这个顺序就很合理,先补工具和基础再上Web框架。
其实做完回头看,这个系统核心就三个东西:数据生成、图算法、可视化。networkx的ancestors和topological_sort是主力,matplotlib负责出图,中间那些打分的逻辑完全是自己瞎琢磨的。虽然过程痛苦但结果还行吧,至少图表跑出来了,数据也对得上。
最后跑main.py的时候看到所有图表生成成功,output文件夹里8张png,那一刻还是挺有成就感的,前几天的崩溃都值了。
可以参考一下