十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于知识图谱的智能推荐系统设计与Flask实现全解析

基于知识图谱的智能推荐系统设计与Flask实现全解析 简介推荐系统是解决信息过载的核心技术之一而传统协同过滤算法面临冷启动与可解释性不足的瓶颈。知识图谱作为结构化语义网络通过实体与关系建模为推荐提供深层语义路径显著提升推荐质量与用户信任度。本文从知识图谱的基本概念出发阐述其如何建模物品关联并介绍基于元路径的推荐原理与技术价值适用于电商、影视、资讯等场景的个性化推荐需求。结合工程实践详细讲解选用Flask构建轻量级后端、MySQL存储图谱三元组的实现方案涵盖数据库设计、推荐算法核心代码与前后端对接并给出常见问题排查与答辩避坑经验帮助开发者快速搭建一套完整可运行的智能推荐系统。 毕业设计做推荐系统这个题目选得挺典型的。但很多同学拿到这类题目第一反应是去GitHub上扒源码改个名字交差结果到答辩环节被老师一问就露馅。今天我不讲那些虚的就基于“基于知识图谱的智能推荐系统(Flask)”这个选题把完整的设计思路、核心实现和避坑经验一次性讲透保证你看完能自己动手复现一个能过查重、能过答辩的项目。这个项目本质上解决的是传统推荐系统“冷启动”和“可解释性差”的问题。传统协同过滤只盯着用户行为数据算相似度用户新来没有行为记录就抓瞎。而知识图谱把物品之间的关系、物品属性之间的关系建模成语义网络推荐时可以基于语义路径去解释“为什么推荐这个给你”这也是评委老师比较感兴趣的点。整个项目涉及Python、Flask、MySQL、知识图谱构建、推荐算法、前端展示是一条完整的全栈链路。1. 项目整体设计与技术选型思路1.1 为什么选Flask而不是Django或SpringBoot很多同学纠结后端框架选什么。我直接给结论毕业设计选Flask是最省心的选择。原因有三第一Flask是轻量级框架学起来成本低。整个核心应用可能只需要两三个Python文件就能跑起来对于精力要分散到知识图谱构建和推荐算法上的你来说能省出大量时间。第二Flask和Python生态无缝衔接。知识图谱处理需要用到py2neo、pandas、numpy这些库推荐算法要做余弦相似度计算、矩阵运算这些全是Python的强项。如果用Java的SpringBoot还得在Java和Python之间做服务调用徒增复杂度。第三Flask的灵活性高适合做原型验证。毕业设计的核心是验证思路可行不是做高并发生产系统。Flask的路由定义、请求参数获取、JSON响应返回都非常直接前后端联调效率高。至于为什么不用Django因为Django自带Admin后台、ORM、模板系统等一大堆东西虽然功能全但学习曲线陡而且很多功能在毕业设计里根本用不上属于杀鸡用牛刀。课程设计级别的项目用Flask半周就能把接口全部写完。1.2 知识图谱选型MySQL存储还是Neo4j图数据库这是整个项目里最值得仔细考虑的技术决策。我在网上看到很多博客直接推荐Neo4j但你仔细琢磨一下毕业设计的场景大部分学校的服务器配置一般评委老师更关心你的推荐逻辑而不是图数据库本身的性能。同时用MySQL存三元组实现知识图谱答辩时更有东西可讲。我推荐MySQL的思路是把知识图谱的三元组关系从“图结构”降维成“关系表”。具体来说设计三个核心表实体表、关系表、实体-关系-实体三元组表。实体表存节点信息关系表存边类型三元组表存具体的连接。推荐的时候用SQL做两跳、三跳的关系查询一样能实现知识图谱的语义路径推理。这么设计的好处是显而易见的MySQL的安装和运维成本远低于Neo4j代码调试时数据一目了然直接SELECT * FROM xxx就能查看不用学Cypher查询语言。缺点当然也有多跳查询效率比不上图数据库但毕业设计的数据量撑死几万条记录对MySQL来说完全是小菜一碟。如果你学有余力可以做一个进阶设计三元组存MySQL做备份同时用Neo4j存一份做图查询对比最后在论文里对比两种方案的性能差异。这个差异化设计能让你的论文增色不少答辩时老师会觉得你有独立思考。1.3 推荐算法选型基于路径的语义推荐主流的推荐算法无非三种协同过滤、基于内容、混合推荐。但在这个项目里因为有了知识图谱我们可以用比基于内容更高级的方法——基于元路径的推荐。元路径推荐的核心逻辑是在知识图谱中找到用户偏好实体比如用户喜欢某部电影该电影的类型、导演、主演都是偏好实体和目标物品之间的语义路径通过计算路径的相似度来排序推荐结果。以电影推荐为例子说明用户A喜欢电影《盗梦空间》知识图谱中有“盗梦空间—导演—诺兰”和“星际穿越—导演—诺兰”这两条边那么通过“电影—导演—电影”这条元路径系统就知道用户可能喜欢《星际穿越》。这个逻辑比协同过滤的“买了又买”更聪明因为它背后有语义关系支撑。在具体实现上我用的是基于路径的相似度加权计算找出所有连接用户偏好实体和目标物品的路径路径越短权重越高路径条数越多得分越高。比如“用户喜欢的电影—导演—目标电影”和“用户喜欢的电影—主演—目标电影”两种路径导演路径权重可以设定为0.6主演路径权重设定为0.4最后综合排序。2. 核心前置准备与数据建模2.1 开发环境搭建工欲善其事必先利其器。环境配置这一块我建议你按下面的顺序来能少踩很多坑。Python版本建议用3.8或3.9不要追求最新版本。很多第三方库对Python新版支持的兼容性还没跟上比如一些老版本的py2neo在Python 3.11上会直接报错。我的策略是装一个Anaconda创建独立虚拟环境避免污染全局Python环境。# 创建独立虚拟环境 conda create -n kg_recsys python3.8 conda activate kg_recsysMySQL版本建议用5.7或8.0这两个版本最稳定而且网上教程多遇到问题好搜索。安装时有一个容易忽略的点字符集要选UTF-8否则后面往数据库里写中文数据会乱码。MySQL 8.0默认就是utf8mb4不用担心这个问题。Flask及相关依赖库用pip安装pip install flask flask-cors flask-sqlalchemy pymysql pandas requests注意flask-cors一定要装不然后端接口写好了前端Ajax调用时会遇到跨域报错这属于新手最容易卡住的问题之一。前端方面因为是毕业设计不需要用Vue全家桶直接用HTML CSS 原生JavaScript就能搞定。如果觉得界面太简陋可以引入Bootstrap的CDN样式库十分钟就能做出一个像模像样的管理后台。2.2 数据库表结构设计数据库设计是整个项目的基石这一步做好了后面写代码能顺很多。我设计了三张核心表外加一张用户行为表总共四张表。先看实体表CREATE TABLE entity ( id INT AUTO_INCREMENT PRIMARY KEY, entity_name VARCHAR(255) NOT NULL UNIQUE, entity_type VARCHAR(100) NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_type (entity_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;entity_name是实体的名称比如电影名称或演员名字。UNIQUE约束保证数据不会重复插入。entity_type区分实体类别比如“电影”“导演”“演员”“类型”。“冷启动”场景下新用户的偏好实体一开始为空我们都通过构造虚拟用户补齐。再看关系表CREATE TABLE relation ( id INT AUTO_INCREMENT PRIMARY KEY, relation_name VARCHAR(100) NOT NULL UNIQUE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;relation_name就是关系的类型名比如“导演”“主演”“属于”“出品”。这张表很简单维护好唯一性就行。核心的三元组表CREATE TABLE triple ( id INT AUTO_INCREMENT PRIMARY KEY, head_entity_id INT NOT NULL, relation_id INT NOT NULL, tail_entity_id INT NOT NULL, FOREIGN KEY (head_entity_id) REFERENCES entity(id), FOREIGN KEY (relation_id) REFERENCES relation(id), FOREIGN KEY (tail_entity_id) REFERENCES entity(id), INDEX idx_head (head_entity_id), INDEX idx_tail (tail_entity_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表就是知识图谱在MySQL里的落地形式每一条记录就是知识图谱的一条边头实体、关系、尾实体。比如《盗梦空间》-导演-诺兰在表里就是三个ID的关联记录。这里的关键在于为head_entity_id和tail_entity_id建索引因为推荐算法要做大量的关联查询没有索引表数据多了以后会非常慢。用户行为表CREATE TABLE user_behavior ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(50) NOT NULL, entity_id INT NOT NULL, behavior_type VARCHAR(20) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表记录用户对物品的评分、点攒、收藏等行为behavior_type用字符串区分不同行为类型为后续推荐算法提供输入数据。2.3 知识图谱数据来源与构建流程知识图谱的数据构建是这个项目的重头戏。对于电影领域数据最好从公开的开放图谱源获取或者自己写爬虫从公开信源抓取。考虑到毕业设计的时间限制我不建议自己爬数据容易遇到反爬且浪费时间最稳妥的办法是找现成的数据集。拿到数据后构建流程分三步走第一步数据清洗。把CSV或JSON格式的原始数据读取出来去掉空值、去重、统一格式。比如导演的名字可能有“诺兰”和“克里斯托弗·诺兰”两种写法必须统一成一种。第二步实体入库。把所有不同的实体名称和类型写入entity表这里要用SQL的ON DUPLICATE KEY UPDATE或者先查询后插入避免重复插入导致的主键冲突。第三步三元组构建。根据原始数据的字段关系构造(head_entity, relation, tail_entity)组合。比如电影的导演字段每一个导演就是一条三元组电影的类型字段每一个类型也是一条三元组。这三步看起来简单但实际写代码的时候要特别注意事务处理。我建议用MySQL的事务功能要么全部成功要么全部回滚避免数据写一半出问题导致后续没法排查。我第一版就把这个忽略了数据写了一半报错表里残留了一堆半吊子数据浪费了一下午排查。3. 后端核心实现与推荐逻辑3.1 Flask项目工程结构一个清晰的工程结构能让你写代码的时候神清气爽也方便后期写论文时描述模块划分。我推荐下面的结构kg_recommend/ ├── app.py # Flask应用入口 ├── config.py # 配置文件数据库连接信息 ├── models/ │ ├── __init__.py │ ├── entity.py # 实体模型 │ ├── relation.py # 关系模型 │ └── triple.py # 三元组模型 ├── modules/ │ ├── __init__.py │ ├── user_behavior.py # 用户行为管理 │ ├── knowledge_graph.py # 知识图谱查询模块 │ └── recommender.py # 推荐算法模块 ├── static/ │ ├── css/ │ ├── js/ │ └── images/ ├── templates/ │ ├── index.html │ ├── login.html │ └── detail.html ├── data/ │ ├── raw_data.csv │ └── build_knowledge_graph.py ├── requirements.txt └── README.md这种前后端分离的思考在写代码前就要理清楚。static放静态文件templates放HTML模板models层负责数据库表的ORM映射modules层放具体的业务逻辑。不要把所有代码写在一个文件里答辩时老师一问你代码结构你自己都说不清楚哪些是哪个模块这印象分就没了。3.2 知识图谱查询模块实现知识图谱模块的核心是提供多跳关系查询能力。在MySQL里实现两跳查询其实就是三元组表的自连接。下面我用电影推荐场景做个实际例子这个方法你可以直接套用。假设我们要查询“用户喜欢的电影《盗梦空间》的所有导演执导的其他电影”def get_similar_movies_by_path(entity_name, relation1, relation2, max_results10): sql SELECT t3.tail_entity_id, e.entity_name, e.entity_type FROM triple t1 JOIN triple t2 ON t1.tail_entity_id t2.head_entity_id JOIN entity e ON t2.tail_entity_id e.id WHERE t1.head_entity_id ( SELECT id FROM entity WHERE entity_name %s ) AND t1.relation_id (SELECT id FROM relation WHERE relation_name %s) AND t2.relation_id (SELECT id FROM relation WHERE relation_name %s) AND t2.tail_entity_id ! %s LIMIT %s return query(sql, (entity_name, relation1, relation2, entity_name, max_results))这段SQL看着长但逻辑其实很清晰找出实体实体通过两段关系连接到的其他实体再通过NOT把自身过滤掉。在“电影—导演—电影”这个例子里就能找出同导演的其他电影。同理可以延伸出“电影—类型—电影”、“电影—主演—电影”、“电影—导演—电影—类型—电影”等多跳查询把每条路径的查询结果收集起来就得到了一系列候选推荐项。3.3 推荐算法核心实现到了整个系统最关键的部分推荐算法的实现。我的做法是把各类路径的推荐结果收集后做一个加权融合排序这本质上就是基于元路径的推荐。具体流程如下第一步从用户行为表中找出用户最近一段时间有过正反馈行为的实体列表电影、图书、商品均可。第二步对每一个偏好实体遍历预设的元路径集合查询候选物品。第三步计算每个路径下的候选物品得分。举个例子如果元路径是“电影-导演-电影”那么路径上每经过一个同导演的连接得1分如果同一个物品通过多条路径都被推荐出来分数累加。路径越短的权重越高导演的权重也比主演高这个需要根据数据集来调。第四步汇总得分排序取TopN将结果返回给前端展示。贴一段简化版的伪代码方便你理解def recommend(user_id, top_n20): # 1. 获取用户偏好实体列表 pref_entities get_user_preference(user_id) scores {} # 2. 定义元路径和对应权重 meta_paths [ (导演, 导演, 0.6), (主演, 主演, 0.4), (类型, 类型, 0.37), (导演, 主演, 0.22), (主演, 导演, 0.22), ] # 3. 遍历每个偏好实体通过不同元路径挖掘候选推荐项 for entity in pref_entities: for r1, r2, weight in meta_paths: candidates get_similar_by_path(entity, r1, r2) for cand_id, cand_name in candidates: # 排除用户已看内容 if cand_id in pref_entities: continue scores[cand_id] scores.get(cand_id, 0) weight # 4. 对得分排序 ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return ranked[:top_n]这套算法实现简单但是解释性很强。答辩时如果老师问“你这个推荐和协同过滤有什么区别”你可以直接回答协同过滤基于用户行为相似度而我们的系统是基于知识图谱中的语义路径进行推荐具有更好的可解释性可以明确知道为什么推荐这个物品给用户。3.4 前端展示与接口对接前端我建议采用简洁的卡片式布局左边显示推荐结果右边显示推荐理由这样可以直观地把知识图谱的“可解释性”展示出来。推荐理由可以这样写“你喜欢《盗梦空间》它的导演诺兰也执导了《星际穿越》”这比单纯的“猜你喜欢”高大上很多答辩时老师看了也会眼前一亮。Flask的前端渲染有两种方式一种是用Jinja2模板引擎直接渲染一种是用Flask提供JSON API接口前端通过fetch或Ajax调用然后渲染DOM。我更推荐第二种方式因为逻辑更清晰而且以后扩展小程序端或App端可以直接复用接口。下面是一个简单的API接口定义app.route(/api/recommend, methods[GET]) def api_recommend(): user_id request.args.get(user_id) if not user_id: return jsonify({status: error, message: 缺少user_id参数}) rec_list recommender.recommend(user_id) return jsonify({status: success, data: rec_list})接口的输入输出参数要设计得简单明了方便前端调用和联调。这里的user_id如果是匿名状态就是系统分配的会话ID不要求登录。4. 常见问题排查与答辩避坑实录4.1 环境配置阶段的典型报错我复盘自己做这个项目时踩过的坑整理出来几个高频问题希望你看到后能绕开。编码问题Flask返回JSON包含中文时默认可能乱码。解决办法是在Flask的配置里加上app.config[JSON_AS_ASCII] False不设置这个前端拿到的就是\uXXXX这样的Unicode转义序列显示出来全是乱码别问我怎么知道的。数据库连接问题如果用pip安装的是新版pymysql在连接MySQL 8.0时可能会报认证插件错误。解决办法是在连接URL里加上charset参数SQLALCHEMY_DATABASE_URI mysqlpymysql://root:passwordlocalhost/kg_recommend?charsetutf8mb4跨域问题前端页面直接用file://协议打开或者前端和后端不在同一个端口请求会报CORS错误。解决方法是安装flask-cors并初始化from flask_cors import CORS CORS(app)这是最容易犯的错误之一因为本地调试的时候前端文件和后端服务往往不在一起。4.2 推荐效果不好的排查思路系统跑通了但推荐结果看起来不太对这时候怎么排查我有一套系统性的排查方法分享给你。第一检查数据量。如果三元组表里只有几百条数据推荐效果是肯定不行的。我在做电影场景时至少保证了2000个以上的实体和5000条以上的三元组关系这样才能让推荐结果看起来有模有样。数据量太少时多跳路径覆盖不全候选集自然稀疏。第二检查元路径的权重设置。权重比例会影响最终排序。我调试时会把每个路径的推荐结果都打印出来对比看哪些路径在覆盖的候选物品效果更好。如果某个路径查出来的候选物品和偏好实体完全不沾边大概率是关系定义出了问题比如“导演”关系里存了演员的数据。第三检查是否有冷启动用户测试。如果拿一个没有任何行为记录的新用户去测推荐系统当然推荐不出来。我建议写一个SQL脚本提前给测试用户插入几条合理的偏好行为记录这样才能验证推荐效果。这也是答辩时演示的标准操作。4.3 论文与答辩准备的加分项论文这部分我在最后单独讲一下因为很多同学技术做得好但不会表达答辩被问住了。准备时把这几个问题想清楚基本能稳过。第一个加分项是画清楚系统架构图。从数据采集、知识图谱构建、数据存储、推荐算法到前端展示分层次画出来评委一眼就能看懂你做了什么。画图工具用draw.io或ProcessOn都行不追求花哨追求逻辑清晰。第二个加分项是准备对比实验。哪怕没有真实用户评测你也可以离线模拟构造100个模拟用户用简单协同过滤算法和你的知识图谱推荐算法分别计算推荐列表对比重合度和推荐覆盖率。把实验数据放进论文里说服力非常强。这个对比实验实际实现起来不超过两天对最终答辩的分数影响是很大的。第三个加分项是提前准备“项目局限性与改进方向”这个问题的回答。老师大概率会问“你这个系统的不足是什么”你如果回答“没有不足”反而显得傲慢。合理的方式是承认目前的算法没有做深度特征提取未来可以结合用户画像权重动态调整元路径权重或者引入图神经网络做端到端学习。这个回答既诚实又有高度。4.4 代码与数据安全注意事项这一个小章节也是经验之谈。毕业设计在提交前一定要检查代码中是否包含敏感配置信息。我之前就见过有同学把数据库密码写在代码里直接提交最后代码被传到公有仓库数据库被人恶意删了血泪教训。建议把数据库配置放到config.py文件里然后在.gitignore中排除它。提交作业前再检查一遍是否有API密钥、密码等敏感信息。如果用的是本地数据库答辩演示时不用连接公网完全可以做到信息安全。另一个建议是提前导出数据库备份文件防止答辩现场电脑出现问题。整个项目源码数据库SQL备份说明文档放在一个文件夹里命名规范清晰这既是自己以后的参考也是交给老师的最直接成果。压缩包里的说明文档最好把环境搭建步骤和运行方法写清楚方便老师复现。最后说点实在的项目做到这个程度我觉得已经达到了毕业设计的核心要求有背景调研、有数据建模、有核心算法、有系统实现、有实验分析、有改进方向整个链路是完整的。你实际动手做一遍比看十篇论文都有效。我最后再提醒一句不要把精力全部放在功能堆砌上。答辩时老师最看重的是你把“为什么这么做”想清楚了没有。比如你用了Flask而不用Django原因是什么你选了这个推荐算法而不是其他算法背后的逻辑是什么这些想明白了项目才真正变成你的东西。如果你决定用这个方向做毕业设计从今天开始就按照上面的步骤动手吧遇到问题先自己排查实在卡住了再针对性地搜索解决方案。祝你顺利。本文还有配套的精品资源点击获取
返回列表