简介这是一套面向计算机相关专业毕业设计场景的Python电影推荐系统源码采用知识图谱架构融合协同过滤算法可有效缓解传统推荐系统的冷启动问题。项目难度中等适合作为课程作业、学期综合实践或自学训练素材帮助读者理解从数据采集到语义推荐落地的完整链路。压缩包共67个文件约892KB以43个py脚本为核心辅以zbak备份、txt数据、cfg配置、md文档及sql建表文件涵盖爬虫采集、知识图谱构建、用户行为分析与推荐算法等模块代码遵循PEP8规范并配有注释。已有61人学习。读者可获得模块化项目结构、多维度清洗后的电影知识网络、环境配置指南与部署教程以及基于导演、演员、类型等实体关系的语义相似度推荐实现思路便于快速复现与二次开发。1. 知识图谱做电影推荐毕业设计里最容易被低估的加分项很多同学做电影推荐毕业设计第一反应是协同过滤或者矩阵分解跑通 MovieLens 就交差。但答辩老师一问「为什么推荐这部片子」就卡壳了——因为矩阵分解给不出可解释的理由。基于知识图谱的 Python 电影推荐系统核心思路是把电影、导演、演员、类型、评分这些实体和关系建成一张图用图结构去补全用户偏好推荐结果天然带路径解释。这个方向适合计算机专业本科或硕士毕业设计也适合想从「调包侠」进阶到「能讲清推荐逻辑」的开发者。源码层面Python 生态有 Neo4j、py2neo、NetworkX 这些成熟工具不需要从零造轮子。2. 知识图谱电影推荐的技术选型为什么不用纯协同过滤2.1 协同过滤的三个硬伤与知识图谱的补位逻辑协同过滤在电影推荐场景下有三个绕不开的问题。第一是冷启动新用户没有历史行为新电影没有评分记录算法直接失效。第二是稀疏性MovieLens 100K 里用户-电影评分矩阵的稀疏度超过 93%大量用户只看过几十部片子。第三是可解释性差你没法告诉用户「因为和你相似的人喜欢这部」这种解释在答辩时站不住脚。知识图谱的补位逻辑很直接。把电影作为核心实体向外连接导演、演员、类型、年份、地区等属性节点再通过「用户看过某导演的片子」「用户偏好某类型」这类关系把用户节点也挂进图里。推荐时不是算评分矩阵而是在图上做路径搜索或图嵌入。比如用户看过《盗梦空间》图里存在路径「盗梦空间 → 导演 → 克里斯托弗·诺兰 → 导演 → 星际穿越」这条路径本身就是推荐理由。常见做法是用 Neo4j 存图用 Cypher 做多跳查询再用 Node2Vec 或 TransE 做图嵌入把实体和关系映射成向量最后接一个排序模型。这套流程在毕业设计里工作量适中既有工程实现又有算法深度。2.2 环境搭建与 Neo4j 图数据库初始化先装依赖。Python 侧需要 py2neo 做图数据库连接pandas 做数据处理numpy 做向量运算。Neo4j 建议用 Desktop 版社区版免费且够用。pip install py2neo pandas numpy scikit-learnNeo4j Desktop 安装后新建一个本地数据库记下 bolt 端口默认 7687和密码。启动数据库后用 py2neo 测试连接from py2neo import Graph, Node, Relationship # 连接本地 Neo4j替换成你自己的密码 graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) # 测试连接是否正常 try: graph.run(RETURN 1) print(Neo4j 连接成功) except Exception as e: print(f连接失败: {e})这段代码做两件事建立 bolt 协议连接执行一条最简单的 Cypher 验证连通性。参数说明auth里第一个是用户名第二个是密码Neo4j 默认用户是 neo4j首次登录会强制改密码。如果连接失败先检查 Neo4j Desktop 里数据库是否处于 Running 状态再看防火墙有没有拦 7687 端口。2.3 从 MovieLens 到知识图谱实体关系建模与批量导入MovieLens 数据集本身只有 userId、movieId、rating、timestamp 四个字段电影元数据在 movies.csv 里包含 title 和 genres。要建知识图谱得先补全导演和演员信息。常见做法是爬 TMDB 或者用 Kaggle 上现成的 TMDB 5000 数据集里面有 cast、crew、keywords 字段。实体设计如下实体类型属性来源字段UseruserIdratings.csvMoviemovieId, title, yearmovies.csvGenrenamegenres 拆分Personname, rolecast/crewRatingscore, timestampratings.csv关系设计关系起点终点属性RATEDUserMoviescore, timestampBELONGS_TOMovieGenre无DIRECTEDPersonMovie无ACTED_INPersonMoviecharacterHAS_KEYWORDMovieKeyword无批量导入用 py2neo 的Subgraph比逐条create快一个数量级。下面是从 CSV 读数据并建图的骨架代码import pandas as pd from py2neo import Graph, Node, Relationship, Subgraph graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) # 读取电影元数据 movies pd.read_csv(movies.csv) # genres 是竖线分隔的字符串拆成列表 movies[genre_list] movies[genres].str.split(|) # 先建 Genre 节点去重 all_genres set() for glist in movies[genre_list]: all_genres.update(glist) genre_nodes {g: Node(Genre, nameg) for g in all_genres} graph.create(Subgraph(list(genre_nodes.values()))) # 建 Movie 节点并连接 Genre movie_nodes {} rels [] for _, row in movies.iterrows(): m Node(Movie, movieIdint(row[movieId]), titlerow[title]) movie_nodes[row[movieId]] m for g in row[genre_list]: rels.append(Relationship(m, BELONGS_TO, genre_nodes[g])) # 分批提交避免单次事务过大 batch_size 500 for i in range(0, len(rels), batch_size): graph.create(Subgraph(rels[i:ibatch_size])) print(f导入完成{len(movie_nodes)} 部电影{len(all_genres)} 个类型)逻辑说明先建所有 Genre 节点并去重再遍历电影建 Movie 节点同时生成 BELONGS_TO 关系。参数说明batch_size控制单次事务提交量Neo4j 社区版内存有限500 到 1000 比较稳。如果报内存溢出降到 200 再试。movieId强制转 int因为 pandas 读进来可能是 float。用户和评分数据同理User 节点用 userId 做唯一标识RATED 关系带上 score 和 timestamp。全部导入后跑一条 Cypher 验证MATCH (m:Movie)-[:BELONGS_TO]-(g:Genre) RETURN g.name, count(m) AS movie_count ORDER BY movie_count DESC LIMIT 10这条查询统计每个类型下的电影数量能跑出结果说明图结构没问题。3. 推荐算法落地从图查询到图嵌入排序3.1 基于路径的推荐用 Cypher 做多跳查询最直观的推荐方式是在图上做路径搜索。给定一个用户先找到他评分最高的几部电影再沿着导演、演员、类型这些关系往外扩找到候选电影集。// 找到用户 1 评分最高的 3 部电影 MATCH (u:User {userId: 1})-[r:RATED]-(m:Movie) WITH u, m, r.score AS score ORDER BY score DESC LIMIT 3 // 沿导演关系找同导演的其他电影 MATCH (m)-[:DIRECTED]-(p:Person)-[:DIRECTED]-(rec:Movie) WHERE NOT (u)-[:RATED]-(rec) RETURN rec.title, p.name AS director, count(*) AS path_count ORDER BY path_count DESC LIMIT 10这段 Cypher 分两步先锁定用户高评分电影再通过导演关系找到未看过的候选。path_count表示有多少条路径指向同一部候选电影路径越多说明关联越强。参数说明LIMIT 3控制种子电影数量太多会引入噪声LIMIT 10是返回的推荐数量。这种方法的优点是解释性强每条推荐都能回溯到具体路径。缺点是只用了单跳关系覆盖范围有限。实际做毕业设计时可以叠加类型路径和演员路径用加权求和做融合。3.2 Node2Vec 图嵌入把图结构变成向量路径查询适合做可解释推荐但要做排序和评分预测得把图结构转成向量。Node2Vec 是常用方案核心思想是通过随机游走生成节点序列再用 Word2Vec 训练嵌入。import networkx as nx from node2vec import Node2Vec # 从 Neo4j 导出边列表构建 NetworkX 图 query MATCH (a)-[r]-(b) RETURN id(a) AS source, id(b) AS target, type(r) AS rel_type edges graph.run(query).to_data_frame() G nx.DiGraph() for _, row in edges.iterrows(): G.add_edge(row[source], row[target], rel_typerow[rel_type]) # Node2Vec 参数walk_length 游走长度num_walks 每个节点游走次数 node2vec Node2Vec( G, dimensions64, # 嵌入维度 walk_length30, # 每条游走序列长度 num_walks200, # 每个节点生成多少条序列 workers4, p1.0, # 返回参数 q1.0 # 进出参数 ) model node2vec.fit(window10, min_count1, batch_words4) model.wv.save_word2vec_format(graph_embeddings.txt)逻辑说明先从 Neo4j 拉出所有边构建 NetworkX 有向图再跑 Node2Vec 生成嵌入。参数说明dimensions64是嵌入向量维度毕业设计场景 64 到 128 够用walk_length30和num_walks200控制游走规模数值越大越慢但覆盖更全p和q是 Node2Vec 的核心参数p小偏向深度优先q小偏向广度优先默认 1.0 是折中。拿到嵌入后用户向量可以用他评分过的电影向量加权平均得到然后算余弦相似度做召回。3.3 排序层用 LightGBM 做特征融合与评分预测召回阶段拿到候选电影后需要排序。把图嵌入相似度、路径数量、电影平均评分、类型匹配度这些特征拼成一个向量用 LightGBM 做回归预测用户评分。import lightgbm as lgb import numpy as np from sklearn.model_selection import train_test_split # 构造特征矩阵每行是一个用户-电影对 # 特征列embedding_sim, path_count, movie_avg_rating, genre_match, year_diff X np.array(features) # shape: (n_samples, n_features) y np.array(labels) # 真实评分 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) train_data lgb.Dataset(X_train, labely_train) valid_data lgb.Dataset(X_test, labely_test, referencetrain_data) params { objective: regression, metric: rmse, num_leaves: 31, learning_rate: 0.05, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, verbose: -1 } model lgb.train( params, train_data, num_boost_round500, valid_sets[valid_data], callbacks[lgb.early_stopping(50)] ) # 预测 preds model.predict(X_test) rmse np.sqrt(np.mean((preds - y_test) ** 2)) print(fRMSE: {rmse:.4f})逻辑说明把召回阶段的各种信号拼成特征向量用 LightGBM 做回归。参数说明num_leaves31控制树复杂度毕业设计数据量不大31 到 63 比较合适learning_rate0.05配合num_boost_round500和早停防止过拟合feature_fraction和bagging_fraction是行采样和列采样0.8 是常用值。RMSE 能压到 0.85 以下就算不错MovieLens 上纯协同过滤大概在 0.87 到 0.92 之间。4. 避坑与排查知识图谱推荐系统最常见的五个翻车点4.1 坑一Neo4j 导入时内存溢出事务直接崩现象批量导入几万条关系时Neo4j 报OutOfMemoryError或者 py2neo 抛Transaction超时。原因单次Subgraph提交的数据量太大Neo4j 社区版默认堆内存只有 1G 到 2G撑不住大事务。解决把batch_size降到 200 到 500并且在 Neo4j Desktop 的 Settings 里把dbms.memory.heap.max_size调到 2G 以上。另外用graph.create()之前先graph.run(CALL db.awaitIndexes())确保索引建好能显著加快导入。4.2 坑二Node2Vec 跑出来的嵌入向量全是噪声现象用嵌入算相似度推荐结果和随机差不多RMSE 比不用嵌入还高。原因图里节点和边的类型太杂User 节点和 Movie 节点混在一起游走生成的序列没有语义一致性。另外walk_length太短游走覆盖不到多跳关系。解决按节点类型分开做嵌入或者给不同类型的边加权。常见做法是只对 Movie 和 Person、Genre 做嵌入User 节点单独处理。walk_length至少设到 20 以上num_walks不低于 100。如果图规模小可以先用p0.5, q2.0偏向广度游走让同类型节点聚集。4.3 坑三Cypher 查询返回空结果但数据明明导进去了现象跑路径查询时MATCH返回 0 条记录但MATCH (n) RETURN count(n)显示节点数量正常。原因最常见的是属性名大小写不一致比如导入时用了movieId查询时写了movieid。另一个原因是关系方向搞反了DIRECTED关系是 Person 指向 Movie查询时写成了 Movie 指向 Person。解决先用MATCH (n:Movie) RETURN keys(n) LIMIT 1看属性名到底叫什么再用MATCH ()-[r]-() RETURN type(r), count(*)确认关系类型和方向。Cypher 对大小写敏感属性名和关系类型都要严格匹配。4.4 坑四LightGBM 训练集 RMSE 很低但测试集爆炸现象训练集 RMSE 0.3测试集 RMSE 1.2模型完全没法用。原因特征里有泄漏比如把用户对这部电影的真实评分直接或间接喂进了特征。另一个原因是用户-电影对划分时没有按用户分组同一个用户的数据同时出现在训练集和测试集导致模型记住了用户偏好。解决检查特征列确保没有用到预测目标相关的信息。划分数据集时用GroupShuffleSplit按 userId 分组保证同一个用户的数据只出现在训练集或测试集之一。LightGBM 的early_stopping要开num_leaves降到 15 到 31 之间再试。4.5 坑五答辩时被问「图数据库和关系型数据库比优势在哪」现象功能都跑通了但老师一问选型理由就答不上来。原因只关注了实现没想过为什么用图而不是 MySQL 加 JOIN。解决准备一个具体对比。多跳查询在 Neo4j 里是MATCH (a)-[:R*3]-(b)在 MySQL 里要写三层自连接SQL 又长又慢。图数据库的存储结构是邻接表查邻居是 O(1)关系型数据库做 JOIN 是 O(n)。另外图数据库的 schema 灵活加一种新关系不用改表结构。这些点答辩前过一遍能明显提升说服力。5. 让推荐结果可解释路径可视化与答辩演示技巧毕业设计答辩时光有 RMSE 数字不够得让老师看到「为什么推荐这部」。我一般会在系统里加一个路径可视化模块用 py2neo 查出推荐路径再用 NetworkX 加 matplotlib 画出来。import matplotlib.pyplot as plt import networkx as nx from py2neo import Graph graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) # 查询用户到推荐电影的路径 query MATCH path (u:User {userId: 1})-[:RATED]-(m:Movie) -[:DIRECTED]-(p:Person)-[:DIRECTED]-(rec:Movie) WHERE NOT (u)-[:RATED]-(rec) RETURN path LIMIT 5 results graph.run(query).data() # 构建可视化图 G nx.Graph() for record in results: path record[path] nodes path.nodes for i in range(len(nodes) - 1): G.add_edge(nodes[i][title] if title in nodes[i] else nodes[i][name], nodes[i1][title] if title in nodes[i1] else nodes[i1][name]) pos nx.spring_layout(G, k2, seed42) plt.figure(figsize(12, 8)) nx.draw(G, pos, with_labelsTrue, node_colorlightblue, node_size2000, font_size10, edge_colorgray) plt.title(推荐路径可视化) plt.savefig(recommendation_path.png, dpi150, bbox_inchestight) plt.show()逻辑说明先用 Cypher 查出用户到推荐电影的路径再把路径里的节点和边转成 NetworkX 图最后用 spring layout 画出来。参数说明k2控制节点间距数值越大越分散seed42固定随机种子保证每次画出来布局一致dpi150保证截图清晰度够答辩投影用。这个可视化在答辩时很加分因为老师能直观看到「用户看了 AA 的导演也导了 B所以推荐 B」这条逻辑链。比单纯说「模型算出来相似度高」有说服力得多。还有一个技巧准备两到三个典型用户的推荐案例提前跑好结果截图。答辩时直接展示不要现场跑代码避免环境出问题翻车。我见过太多同学现场演示时 Neo4j 连不上或者内存不够场面很尴尬。提前把结果存成图片和 JSON现场只做展示和讲解稳得多。最后说一个血泪经验毕业设计代码一定要写 README把环境依赖、数据下载地址、导入步骤、启动命令写清楚。答辩老师如果让你现场跑你至少能照着 README 一步步来。另外把 Neo4j 的密码、端口这些配置抽到单独的 config 文件里别硬编码在代码里换台机器就崩。这些细节看着小但关键时刻能救命。希望帮到你。本文还有配套的精品资源点击获取