资讯动态

知识图谱+图神经网络:构建可解释电影推荐系统实战

发布时间:2026/10/25 9:43:21 来源:尧图企业网站定制
简介一套基于Python的知识图谱与图神经网络电影推荐系统毕业设计源码主要面向计算机、软件工程等专业的学生适用于毕业设计、期末大作业或课程设计场景。项目以知识图谱为背景借助图神经网络建模用户与电影之间的高阶关系完整覆盖数据预处理、知识图谱构建、模型训练与评估、Web交互展示等环节。资源包为zip格式共31个文件以21个Python脚本为主辅以电影评分等dat数据文件、txt与readme说明、md文档整体大小约14.84MB目录按功能拆分为数据处理、图谱构建、模型层、应用层等模块结构清晰便于按需阅读和替换。目前已有172人浏览学习。代码注释较详细新手也能较快上手内置KGCN等网络模型实现系统功能完整、界面简洁且经过运行调试可直接演示或二次开发同时能帮助读者梳理推荐系统从原始数据清洗到图谱构建再到模型推理的完整实现思路。1. 知识图谱图神经网络电影推荐系统不是炫技是毕业设计最稳的“可解释”路线打开各种招聘网站算法岗 JD 里出现频率最高的三个词就是知识图谱、图神经网络和推荐系统。把它们拼成一套电影推荐系统正好卡住多个热门技术点这大概是为什么这个题目能成为高分毕设方向。这个项目的本质不是做一个能看懂的 Demo而是把用户、电影、导演、演员、类型这些分散的数据建成一张图让图神经网络在图上做预测——同一部电影为什么推荐给 A 不推荐给 B答案由关系和路径决定天然可解释。这套系统适合三类人想用 Python 体系串起数据处理、模型训练和 Web 展示的毕业生想在简历里放一个完整推荐项目、但不想只写“调了 sklearn”的初学者以及想把知识图谱构建和图神经网络同时落地的同学。这个课题的难度不在模型而在把知识图谱构建、图神经网络训练和 Django 工程拼起来缝好。下面就是我从选型、建模到代码实现的完整链路。2. 推荐系统选型判断为什么这个组合能拿高分而不是堆砌技术2.1 三种推荐路线对比协同过滤、深度兴趣网络与 KG-GNN 的边界最常见的推荐系统毕业设计是协同过滤比如基于用户的 UserCF、基于物品的 ItemCF或者矩阵分解 SVD。早期用 MovieLens 数据集跑一个 SVD能出 ROC 曲线、能算召回率一个不错的毕设就有了。但它的硬伤也很明显推荐结果没有理由用户看不到“为什么推荐这部电影”。面试官问一句“你的系统怎么解释推荐结果”就只能答“因为相似用户看过”。深度兴趣网络 DIN 这类点击率模型做了特征交叉效果不错但特征工程太重观众年龄、职业、地域都得清洗、拼接。对于电影域来说最有用的一类特征是内容之间的语义关系——比如诺兰导演、克里斯托弗·诺兰、星际穿越和盗梦空间——这类关系用稠密特征表达很差但用图结构表达却非常自然。知识图谱和图神经网络的组合解决了这两个问题知识图谱构建提供关系语义图神经网络做关系推理。同一个知识图谱里“诺兰导演了星际穿越”“马修·麦康纳演过星际穿越”这两条路径就能解释为什么系统给喜欢“星际穿越”的用户推荐“盗梦空间”。这是协同过滤给不了的东西。2.2 知识图谱 图神经网络一个“图特征 用户行为”的双塔架构常见做法是把推荐系统拆成“用户侧”和“物品侧”两个塔。用户侧只处理用户的历史行为序列看过什么、评分多少、点没点收藏物品侧则把所有电影放在知识图谱里用图神经网络把每部电影编码成一个向量。两个向量做内积就是用户对这部电影的偏好得分。知识图谱在里面的角色是物品特征生成的来源图神经网络的角色是从图中邻居节点聚合信息。用户的物品是电影但电影的特征不只是类型和年份还包括演员、导演、编剧、获奖信息、系列归属等。这些结构信息用 TF-IDF 没法表达用 embedding 又过于隐晦只有图结构能同时保留实体和关系。这里要提醒一句不是非得上大模型才有用。对毕业设计而言一个只有 34 层的图卷积网络就能跑出明显优于矩阵分解的效果。真正的难点在召回和排序的数据准备以及把模型嵌入到一个能在浏览器里演示的系统。2.3 为什么这个项目“对新手不友好但值得做”关键技能清单坦率说这不是一个几天能写完的小项目它同时涉及 Python 数据处理、图构建、PyTorch 的 GNN 模块和 Django 工程。但它的价值也正在于此。一个系统里有四种技能栈面试或答辩时能讲的东西足够撑满 20 分钟。技能清单大致是爬虫与数据清洗拿电影元数据、关系三元组建模、图数据库或图存储设计、PyTorch Geometric 的 GNN 训练、Django REST Framework 接口开发和简单的 Vue 或 Bootstrap 前端。不会图神经网络可以只做知识图谱构建和协同过滤的对比实验不会 Django可以用 Flask ECharts 做一个交互式可视化。核心是知识图谱和周 neural 网络能同时在系统里跑起来。3. 第一步建模把残缺的原始数据做成可以训练的知识图谱3.1 数据源选型和字段设计不要一上来抓“全量数据”电影推荐领域最常见的公开数据集是 MovieLens 1M——100 万条评分6040 个用户、3900 部电影。这个集子适合做模型训练但不适合做知识图谱展示原因是它只有电影的 ID、标题和类型没有导演、演员、剧情这样的实体关系。所以必须做实体对齐把 MovieLens 的 movieId 和 IMDb/TMDb 的 ID 对齐再去补导演、演员、编剧、类型、语言、发行年份等属性。对齐的方法很直接用电影的标题、年份和类型做一个相似度匹配。常见做法是人工抽 100 部电影建立映射表再用模糊匹配补全。不要试图把 3900 部全部对齐能对齐到 1500–2000 部就足以构建一个看起来丰满了。对齐后过滤掉没有导演或演员信息的条目再把 Q 条评分过滤成至少看过两部有完整信息的电影的用户的记录这样 GNN 训练时不会出现大量“只有一个交互”的冷启动节点。数据类型分两类实体表和关系表。实体表包括 user、movie、person导演/演员、genre、keyword关系表则包括 rate、directed_by、acted_by、has_genre、belongs_to_series 等。一张电影表存静态属性年份、时长、语言另外的人、类型、关键词单独建表。3.2 构建三元组与邻居采样从关系表到图结构知识图谱在代码里的最小单元是三元组 (h, r, t)即头实体、关系、尾实体。例如“盗梦空间 - 导演 - 克里斯托弗·诺兰”就是一个三元组。这个三元组再换成内部的数字 ID 存储就变成一个边表head_id, relation_id, tail_id。所谓“构建知识图谱”本质上是把多张 MySQL/Pandas 表压成三列 edge list再加上每个节点的类型标注。这里不建议新手直接上 Neo4j因为大部分实验室机器上装 Neo4j 需要 JVM 环境学生机容易卡而且训练 GNN 时最终要的仍然是内存里的 PyTorch 张量。Neo4j 适合做可视化和交互查询不适合直接训练。常见做法是用 pandas 清洗三元组并用 PyTorch Geometric 构建图数据另用 Neo4j 做前后端联调时的知识图谱可视化查询服务。两条线可以并行但核心训练逻辑不依赖图数据库。import pandas as pd import torch from torch_geometric.data import Data # 加载三元组triplet.csv 的三列分别是 head_id, relation_id, tail_id triplet_df pd.read_csv(data/triplet.csv) head torch.tensor(triplet_df[head_id].values, dtypetorch.long) tail torch.tensor(triplet_df[tail_id].values, dtypetorch.long) edge_type torch.tensor(triplet_df[relation_id].values, dtypetorch.long) # 自环处理确保知识图谱中每个孤立节点能聚合到自身 self_loop_head torch.arange(triplet_df[[head_id, tail_id]].max().max() 1) self_loop_tail self_loop_head.clone() self_loop_type torch.zeros_like(self_loop_head) # 合并成最终的边表 edge_index torch.stack([torch.cat([head, self_loop_head]), torch.cat([tail, self_loop_tail])], dim0) edge_type torch.cat([edge_type, self_loop_type]) data Data(edge_indexedge_index, edge_typeedge_type)这段代码的核心是构建 PyTorch Geometric 所需的边表结构。edge_index是 2×E 的张量第一行表示边的起点第二行表示边的终点。edge_type表示每条边的类型也就是知识图谱里的关系类型。最后加了自环让每个节点都能通过 relation_type0 的自环边聚合到自己的邻居否则孤立节点在 GNN 的邻居传递中会丢失自身信息。训练前要重点检查三个参数max()1这里必须是整个数据集中实体的总数而不是三元组表的行数如果实体 ID 没有从 0 开始连续编号要先做 LabelEncoder 转换edge_type的类型得是 long 型因为 R-GCN 卷积层按关系类型建立权重矩阵时要用它做索引。3.3 邻居特征与初始向量为什么不能把 ID 直接当成特征图神经网络做的是“邻居聚合”每个节点在每层都会把邻居的特征加权求和然后更新自己的表示。如果在构建知识图谱时不给节点任何特征模型只能学一个“ID 的嵌入”冷启动用户和电影基本没有意义。所以构建节点初始特征这一步不能省。常见做法是给每部电影拼接一个文本向量比如用 TF-IDF 把“类型剧情”压成 128 维的稀疏向量用户节点则用其历史评分过的电影 embedding 均值初始化。这里最省力又有效的办法是给电影做一个 300 维的预训练词向量平均沿着“title genres overview”生成的文本做 embedding pooling。用 gensim 的 KeyedVectors 加载中文或英文向量都行但注意版本要匹配。import gensim.downloader as api import numpy as np from sklearn.preprocessing import normalize # 加载预训练英文词向量66MB 左右 wv api.load(glove-wiki-gigaword-100) dim 100 def movie_embedding(row, wv, dim): tokens str(row[title] row[genres] row.get(overview, )).lower().split() vecs [wv[t] for t in tokens if t in wv] if len(vecs) 0: return np.zeros(dim) return np.mean(vecs, axis0) vecs [] for _, row in movies_df.iterrows(): vecs.append(movie_embedding(row, wv, dim)) feat_matrix normalize(np.vstack(vecs), norml2)这段代码把文本特征转成节点特征矩阵。加载的glove-wiki-gigaword-100是 100 维向量训练快、内存占用小值得作为首选。movie_embedding先做小写和分词再对所有 token 的词向量做均值池化。如果一部电影的标题类型里一个词都不在词表里就用零向量兜底但这会带来一个所有冷启动电影都相同的坑。这个环节最讽刺的问题是知识图谱构建完成后模型性能往往不看图谱有多“满”而看有没有特征对齐。实体、关系的数量做到 300 万条反而会拖慢 GNN 训练速度而 10 万条以内的三元组、1 万以内的节点数已经足够支撑一个能毕业演示的图了。知识图谱构建的目标是“刚好够用”不是“完整复刻 IMDb”。4. GNN 模型选型与实现把知识图谱喂给 R-GCN 并做推荐4.1 为什么用 R-GCN 而不是 GCN关系类型决定聚合权重知识图谱里的关系不是无差别的“连了一条边”导演关系和演员关系在语义上完全不同。普通 GCN 把所有边当成同一种关系做消息传递会把“导演了”和“演过”混在一起显然不科学。R-GCNRelational Graph Convolutional Network为每一种关系学习一个独立的权重矩阵聚合时按关系类型区分处理是最贴合知识图谱的标准选择。R-GCN 的更新公式是一次 sum 聚合加上一个自连接。具体到实现PyTorch Geometric 的RGCNConv层会自动完成按关系类型分开的nn.Linear计算和加和。需要手动指定的是关系类型数num_relations也就是三元组里 relation_id 的取值个数。如果清洗后只有导演、演员、类型、编剧本、系列五类关系这个参数就是 5 加上自环的 1。这个参数直接影响性能表现。关系类别太少模型退化成 GCN不同边的语义会被平均掉关系类别太多又容易过拟合因为每一种关系相当于一个子图。经验值是控制在 510 个之间能把评分预测 AUC 做到 0.85 以上。4.2 编写 R-GCN 模型和一版训练代码这里给出一个可以直接运行的 R-GCN 模型主体。用户节点和电影节点位于同一个图里通过“user-rate-movie”这条边连接。输入特征是上一节构建的feat_matrix输出维度设为 64。import torch import torch.nn.functional as F from torch_geometric.nn import RGCNConv class RGCNRec(torch.nn.Module): def __init__(self, in_dim, hidden_dim, out_dim, num_relations, num_basesNone): super().__init__() # 第一层 R-GCN将输入特征映射到隐层 self.conv1 RGCNConv(in_dim, hidden_dim, num_relations, num_basesnum_bases) # 第二层 R-GCN把隐层压缩到输出向量 self.conv2 RGCNConv(hidden_dim, out_dim, num_relations, num_basesnum_bases) def forward(self, x, edge_index, edge_type): x self.conv1(x, edge_index, edge_type) x F.relu(x) x F.dropout(x, trainingself.training) x self.conv2(x, edge_index, edge_type) return x训练时把评分大于等于 4 的边当作正样本评分为 0 或小于 3 的当作负样本。为了加快迭代训练逼近的目标是“用户对电影的偏好得分”而偏好得分是用户节点的嵌入与电影节点的嵌入的内积。这样模型输出经过sigmoid之后可以直接当点击率使用。from sklearn.metrics import roc_auc_score model RGCNRec(in_dim100, hidden_dim64, out_dim64, num_relations6) optimizer torch.optim.Adam(model.parameters(), lr0.005) def train(model, data, epochs50): model.train() for epoch in range(epochs): optimizer.zero_grad() out model(data.x, data.edge_index, data.edge_type) # 正样本用户节点 n_id 到电影节点 m_id 的偏好分 pos_score (out[user_ids] * out[movie_ids]).sum(dim1) # 负样本随机采样同样数量的不存在的观看关系 neg_score (out[neg_user_ids] * out[neg_movie_ids]).sum(dim1) loss F.binary_cross_entropy_with_logits( torch.cat([pos_score, neg_score]), torch.cat([torch.ones_like(pos_score), torch.zeros_like(neg_score)]) ) loss.backward() optimizer.step() if epoch % 10 0: print(fEpoch {epoch}, Loss {loss.item():.4f})训练周期一般 3050 轮就够因为知识图谱节点数量不到 1 万。lr0.005是初始值如果 loss 出现震荡降回调到 0.001。num_bases参数这里有意没有设置它会用完整的按关系独立矩阵如果关系数量太多可以通过num_bases4让不同关系共享一组基矩阵能显著减少参数。这里是不是一定要用内积做最后评分可以选择 MLP。但在小数据集上内积泛化更稳定因为知识图谱嵌入本身已经在一个高维空间里用户和电影的内积等价于计算余弦相似度不需要额外的非线性层。往后做要做冷启动测试直接把新电影的文本特征经过 GNN 得到一个向量和用户向量算内积就能推荐。4.3 评价与消融用 AUC、RecallK 验证不是“黑匣子”模型必须跑出对比实验否则答辩环节会被追问到怀疑人生。建议三组对比UserCF 协同过滤作为基线、GCN 加上关系类型混淆作为次基线、R-GCN 作为最终方案。数据集切分按时间戳80% 历史行为训练20% 最近行为测试。评估方式采用 leave-one-out 协议每个测试用户抽出最后一次评分作为正确项再从全部电影中随机采样 99 部错误项模型对这 100 部排序算 Recall10 和 AUC。from sklearn.metrics import ndcg_score, roc_auc_score # pred_scores: (n_test_user, 100) 每行是用户对100部电影的排序得分 # true_flag: (n_test_user, 100) 每行第0列是1后面99列为0 auc roc_auc_score(true_flag.flatten(), pred_scores.flatten()) recall_at_10 (true_flag[:, :10].sum(axis1) 0).mean() ndcg ndcg_score(true_flag, pred_scores) print(fAUC{auc:.4f}, Recall10{recall_at_10:.4f}, NDCG{ndcg:.4f})这套评估代码在复现时要注意true_flag中正样本必须放在第 0 列排序时按分数从高到低排。如果正样本混在 100 部随机电影里每次运行重复抽样会让指标有波动建议设置固定随机种子。AUC 能上 0.85 以上、Recall10 在 0.3 左右已经可以放进论文了。5. 整个系统怎么串起来从训练好的模型到能在浏览器里玩的推荐应用5.1 Django REST Framework 搭建后端推理接口模型训练只是第一步能演示的系统才是真正的毕业设计。常见方案是 Django Django REST Framework 做后端接口前端用 Vue 或 Bootstrap 展示。模型训练在离线阶段完成将用户嵌入和电影嵌入保存为 numpy 数组或 torch tensor在线推理阶段只查库不再跑模型。在线推理只需要两个操作根据当前用户 ID 取出用户嵌入向量与所有电影嵌入向量做内积排序取 top-N。这个计算量在几千部电影的规模上毫秒级完成远不需要 GPU。如果用户是全新的没有历史行为就从图神经网络里找到与它最像的 K 个用户再把这 K 个用户看过的电影融合排序这是最朴素的冷启动策略。# inference/views.py import numpy as np from django.http import JsonResponse user_emb np.load(model/embedding/user_emb.npy) movie_emb np.load(model/embedding/movie_emb.npy) movie_list np.load(model/embedding/movie_id.npy, allow_pickleTrue) def recommend(request, uid): u_vec user_emb[uid].reshape(1, -1) scores (movie_emb u_vec.T).flatten() top_idx scores.argsort()[-10:][::-1] movies movie_list[top_idx] return JsonResponse({uid: uid, movies: movies.tolist(), scores: scores[top_idx].tolist()})这里的符号就是矩阵乘法等价于批量计算用户向量和所有电影向量的点积。Django 框架里每次请求都加载三个 numpy 文件是可以接受的但如果在高并发下就先把movie_emb放进内存缓存。调用接口后前端拿到movies数组展示卡片推荐理由则从知识图谱里查询第一条邻接路径。5.2 知识图谱可视化让答辩评委一眼看懂项目在做什么毕设最终需要“能看见的东西”。推荐结果可以只展示一张电影海报列表但那样体现不了知识图谱。最省事的做法是用 Neo4j 的 Browser 或 D3.js 画一个子图然后通过电影 ID 从图里取出“用户兴趣-导演-电影”的路径。具体路径查询用 CypherMATCH (u:User {uid: 42})-[r:RATED]-(m1:Movie)-[d:DIRECTED_BY]-(p:Person)-[d2:DIRECTED_BY]-(rec:Movie) WHERE rec.movie_id m1.movie_id RETURN u, m1, p, rec LIMIT 5这条展示的核心是让评委看到“你为什么推荐这部电影”。路径意思是用户 42 给了电影 A 高分电影 A 的导演 P 同时导演了电影 B因此系统把 B 推荐给了用户。这比任何指标都直观。Django 后端把查询结果拼成 JSON前端 D3 渲染成节点关系图即可。5.3 离线和在线架构的边界模型可以通过 ONNX 导出或固化向量这里讲一个常见误用把 PyTorch 模型直接嵌进 Django 里做推理。Django 进程每次重启都要重新加载模型权重不仅慢而且容易引入大量依赖冲突。常见做法是离线训练完把每层权重转成 numpy 并使用矩阵乘法推理或者用torch.jit.script导出为一个静态模型文件。我在这个项目里用前者因为嵌入表的大小不会超过几 MBnumpy 足以支撑负载。同时要区分“离线任务”和“在线请求”。每天凌晨跑一次全量重算用户新行为、新入库的电影、新知识图谱实体都转入离线模块重新生成嵌入表。在线请求只做读取和排序。这样既不会让推荐结果过时也不会因为高频批训练拖垮演示服务器。6. 避坑指南知识图谱 GNN 电影推荐常见的 5 个翻车点6.1 知识图谱构建完毕后边表里有重复边模型不收敛现象是训练 loss 一直在高位摆动验证集 AUC 停滞在 0.5 左右。原因是 userId 和 movieId 在原始评分表里非常离散许多用户对同一部电影的历史评分重复出现。在构建三元组和边表前没有做drop_duplicates()去重导致同一条 user-rate-movie 三元组出现多次RGCNConv 的消息传递权重被重复累加。解决方法是三元组构建函数里加一行triplet_df triplet_df.drop_duplicates(subset[head_id, relation_id, tail_id])。这一步虽然简单但直接影响边表统计。做完后看边表的规模MovieLens 1M 的数据集三元组应该在 120 万条以内超过这个数就要怀疑是重复边问题了。6.2 节点 ID 没有做连续化使用 embedding 时报 IndexError现象是训练时embedding层报“index out of range”。原因是电影表里的 movieId 是字符串或者从 1 开始的稀疏 ID但 R-GCN 要求节点索引是从 0 开始的连续整数。比如只有 3900 部电影但 movieId 可能达到 2 万多。把原始 ID 直接当索引放进张量当然会越界。解决方法是先做 LabelEncoder把用户和电影统一映射到 0~N-1。要特别注意的是三元组中的 head/tail 同时包含用户和电影实体空间要跨两个表一起编码。做le.fit_transform(pd.concat([head_id, tail_id]))而不是分别对用户表、电影表编码。6.3 MovieLens 里的用户和 IMDb 里的实体对不齐推荐结果串戏现象是知识图谱里 “诺兰导演的电影” 包含了 5 部 MovieLens 里根本不在用户评分范围内的作品导致推荐结果里总混入“查无此片”的条目。原因是两种数据源 ID 体系不同没有经过统一的实体对齐。解决方法是构建一个 title 层面的映射表先用年份和标准化后的 title 做交集匹配匹配不上的用模糊匹配最后人工抽检 100 条记录保证准确率在 90% 以上。只有对齐好的实体才能进入三元组构建环节这个约束必须写进数据处理流程不能事后补救。6.4 空特征节点太多graph 里出现大量零向量现象是冷启动的电影没有任何评分也没有任何文本特征它的 embedding 从一开始就是零向量。经过两层 GNN 聚合后它会把自己的零向量传给邻居导致同时推荐出大量垃圾电影。原因可追溯到电影文本特征拼接时glove词典里没有对应的词返回了零向量。解决方法是把movie_embedding的兜底策略从零向量改为随机小向量或类中心向量。具体做法先用所有有特征的电影向量计算一个全局均值向量遇到没有词命中的电影就把这个均值向量加上微小高斯噪声赋给它。这样既不引入语义偏置也避免零向量污染消息传递。6.5 推荐结果看起来有道理但演示时用户切到新账号会产生空白页现象是系统上线演示时新注册用户没有历史行为进入首页推荐列表报错。原因是代码只考虑了有嵌入的用户没处理user_emb[uid]的越界问题。解决方法是建一个“游客用户”的兜底逻辑把未登录或新注册用户映射到一个随机用户 ID或者直接用所有用户向量的均值作为其表示。方法是在推荐接口开头判断uid是否超出user_emb.shape[0]如果超出或该用户没有正向行为记录就取全局用户向量的均值。如果想让效果更好让前端传几个感兴趣的标签后端从知识图谱里按标签召回电影这也是一种很精致的冷启动演示方案。7. 最后一步让系统在答辩中显得“很值钱”的三个加分细节7.1 推荐理由可解释性把路径变成一句话GNN 给了一个分数但用户和评委不会只看分数他们想看到的是“为什么”。实现方式是在推荐接口返回的 JSON 里加一个字段reason_path比如 “你给《星际穿越》打了高分该片导演诺兰也执导了《盗梦空间》”。这个理由不是模型生成的是从知识图谱路径里查出来的。具体做法是从推荐列表里挑出 top-1 的一部电影在知识图谱中执行一跳查询从用户已高分的电影出发找同导演或同演员的路径。Cypher 不通时可以直接用 pandas 扫一遍三元组def explain(user_id, rec_movie_id): user_watched triple_df[(triple_df[head_id] user_id) (triple_df[relation_id] RATE)] for movie, title in user_watched: path triple_df[(triple_df[head_id] movie) (triple_df[tail_id] rec_movie_id)] if not path.empty: return f因为你看过《{movie_name[movie]}》 return 因为这部电影与你的兴趣图谱距离较近这个解释虽然朴素但效果立竿见影——评委看到的不只是一个 JSON 打分而是一条有逻辑链的推荐依据。答辩时演示一遍路径可视化项目的高度立刻就不一样了。7.2 消融实验证明“知识图谱和 GNN 都比没有更强”如果有时间再做一组消融实验把 R-GCN 的关系分支减掉变成 GCN把知识图谱边表随机打乱 20% 再加进模型观察指标下降。比如 GCN 的 AUC 掉到 0.81打乱边的 R-GCN 掉到 0.78完整版 R-GCN 是 0.86这个数据放进论文就是很有说服力的一张表。7.3 可视化把图嵌入和知识图谱展示成“可讲故事”的页面最后在效果页面上用 D3.js 画一个力导向图展示用户节点和电影节点把 GNN 的注意力权重或一跳邻居共现画成边的粗细。我习惯把查询出的推荐理由路径存成 JSON 片段用 ECharts 的关系图类型直接渲染不需要为由前端框架做复杂定制。这个可视化的核心目的是让非技术背景的评委也能 20 秒看懂项目在做什么。这套系统能做到的边界在哪里老实说它不是业界领先的推荐引擎离 YouTube 的排序模型差着十万八千里。但对一个毕业设计而言它同时覆盖了知识图谱、图神经网络、工程化部署和可视化而且每一步都有东西能讲、能演示、能应对追问。我做这类项目时最大的教训就是别贪多把 10 万级三元组和 1 万级节点做好、把可解释路径做完整已经足够拿高分了。希望这些细节和踩坑记录能帮你的毕业设计少走弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑