首页 / 资讯中心 / 文章详情

知识图谱与图神经网络如何提升电影推荐系统效果

知识图谱与图神经网络如何提升电影推荐系统效果 ★ FEATURED ARTICLE
简介基于Python的知识图谱与图神经网络电影推荐系统是一份面向高校毕设、课程设计及推荐系统入门者的完整工程包。项目以MovieLens 1Mml-1m为数据集在Python 3.8与TensorFlow 2.3.8环境下运行通过main.py训练生成KCGN模型再执行test.py即可完成结果预测复现流程清晰。压缩包共32个文件涵盖21个Python脚本负责模型构建、数据处理、训练与测试、5个dat数据文件、3个txt说明、2个readme及1个md文档整体大小约12.69MB目录结构按知识图谱构建、KGCN模型、工具模块、Web展示等划分。目前已有490人学习下载。这份工程不仅提供从数据预处理到模型评估的整套源码还包含知识图谱构建、数据加载、GPU配置等工具脚本便于读者直接运行学习或在此基础上扩展改进适合具备一定Python基础并希望深入理解知识图谱与图神经网络结合推荐系统的开发者。1. 电影推荐为什么卡在“猜你喜欢”知识图谱和图神经网络在解决什么“猜你喜欢”大概是推荐系统里最让人又爱又恨的一句话。我见过太多电影推荐项目数据集用的是 MovieLens算法从协同过滤换成矩阵分解最后指标漂亮了可拿给同学一测结论永远是“推来推去都是那几部高分片”。问题出在一个地方用户和电影的交互矩阵太稀疏模型看不到两部电影为什么像。把用户、电影、演员、导演、类型全部放进一张知识图谱再让图神经网络沿着关系路径做特征传播电影之间的语义关联才能真正参与推荐决策。这也是近几年“知识图谱 图神经网络 电影推荐”成为高分毕设热门方向的原因——它既有建模深度又有可视化空间还能把推荐结果讲出道理来。这篇文章就从数据准备讲到模型训练再讲到答辩时怎么证明你的系统确实有效。2. 先把数据变成图MovieLens到Neo4j知识图谱的建模与导入2.1 为什么用知识图谱而不是用户-电影评分矩阵传统的推荐系统里用户和电影分别被映射成一个 ID 向量模型的任务就是拟合“用户向量”和“电影向量”之间的距离。这个思路在评分数据稠密时问题不大可一旦用户只看过三五部电影协同过滤基本等于盲猜。知识图谱的价值在于给电影增加了上下文一个电影节点连接着导演、演员、类型用户看过《盗梦空间》和《星际穿越》哪怕两部片子的评分矩阵重叠度很低图谱里依然有“同一个导演”这条路径把它们关联起来。知识图谱不替代评分矩阵它是在评分矩阵外面加一层语义网络。用户和电影之间的交互边保留了评分行为电影和电影之间通过演员、导演、类型形成多跳路径GNN 里的消息传递可以让用户节点沿着这些路径聚合到更丰富的偏好信号。换句话说矩阵分解只能告诉你“你可能会喜欢这一类电影”知识图谱能告诉你“你喜欢的导演新拍了一部片”后者在推荐理由上明显更有说服力。数据集方面我强烈建议用公开的 MovieLens而不是自己去爬。爬虫听起来很酷但电影数据的清洗成本超出想象同一个演员在不同平台叫不同名字类型标签一会儿是“喜剧”一会儿是“搞笑”还有大量海报链接失效。MovieLens 的 ratings.csv、movies.csv、links.csv 字段干净且有配套的 train/test 划分惯例用来做毕设能把时间花在模型而不是清洗上。每个人的评分标准不一样但我们从项目落地角度公开数据集加规范化处理是最稳的起点。2.2 本体设计六类实体与五类关系怎么定知识图谱建模的第一步不是写代码而是画图。所谓本体建模就是先定义清楚“图里有哪些节点、哪些边、边上有哪些属性”。我一般把电影推荐系统的本体现在做五类实体和四类关系下面这个表就是最常用的结构实体类型主要属性说明UseruserId用户节点后续还可以加 age、occupationMoviemovieId, title电影节点title 用来展示推荐结果Genrename电影类型如 Comedy、ActionActorname演员一个电影对应多位演员Directorname导演一个电影通常一位或多位关系类型连接实体属性作用RATEDUser → Movierating, timestamp用户对电影的评分行为HAS_GENREMovie → Genre无电影属于某类型ACTED_BYMovie → Actor无电影由某演员出演DIRECTED_BYMovie → Director无电影由某导演执导第一次做毕设的人容易犯一个错误把关系设计得特别多比如给同一类型的两部电影加“相似”边给同一导演的不同电影也加“相似”边。图看着挺丰富模型一跑就发现问题——整张图的密度变大每个节点都能通过两跳连到几乎所有其他节点GNN 的消息聚合逐渐失去区分度这个现象业内叫 over-smoothing。我的建议是本体先做减法四类基础关系足够支撑一个高分项目后面想提升再考虑加“用户看过同一导演的其他电影”这类高阶路径。本体的属性也要克制。MovieLens 的 rating 字段在构建知识图谱时通常转成二值标签评分大于等于 4 视为正反馈小于 4 视为负反馈。timestamp 在毕设里一般用不上保留在边上只是为了体现你做了建模但别指望它能给模型带来多少提升。2.3 用 py2neo 批量写入从 CSV 到知识图谱的落地代码确定好本体下一步就是把 CSV 文件写进 Neo4j。常见的做法是用 py2neo它是 Python 操作 Neo4j 最成熟的库之一。先读电影数据创建 Movie 节点再处理类型关系。# kg_build/neo4j_import.py import pandas as pd from py2neo import Graph, Node, Relationship graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) movies pd.read_csv(data/movies.csv, encodingutf-8) ratings pd.read_csv(data/ratings.csv, encodingutf-8) # 给 movieId 建索引后续关系建立时按索引匹配速度能差出几十倍 graph.run(CREATE INDEX movie_id_index IF NOT EXISTS FOR (m:Movie) ON (m.movieId)) # 批量创建电影节点MERGE 防止重复 with graph.begin() as tx: for _, row in movies.iterrows(): movie_node Node(Movie, movieIdint(row[movieId]), titlerow[title]) tx.merge(movie_node, Movie, movieId)这段代码里有两个细节很关键。graph.begin()开启事务事务结束统一提交比一行一行单独写入快得多tx.merge()带三个参数第一个是要创建的节点第二个是标签第三个是唯一键属性意思是“如果这个 movieId 已经存在就跳过”。如果不加 merge脚本跑第二次时会出现大量重复节点。处理完电影节点继续建立评分关系。这里我习惯用 Cypher 的UNWIND批量提交而不是在 Python 里逐条建 Relationship因为 rating 数据量大逐条创建的耗时是批量的十几倍。def import_rating_relations(ratings_df, batch_size5000): records [ {userId: int(row.userId), movieId: int(row.movieId), rating: float(row.rating)} for _, row in ratings_df.iterrows() ] for i in range(0, len(records), batch_size): batch records[i:i batch_size] cypher UNWIND $batch AS item MERGE (u:User {userId: item.userId}) WITH u, item MATCH (m:Movie {movieId: item.movieId}) CREATE (u)-[r:RATED {rating: item.rating}]-(m) graph.run(cypher, batchbatch) import_rating_relations(ratings)这段的写法解决了两个痛点。第一MERGE (u:User)自动创建之前不存在的用户节点省掉了单独遍历用户表的代码。第二MATCH (m:Movie)命中建好的索引不需要在 Python 里手动查一次节点再传回事务。batch_size 取 5000 是经验值太大会导致 Neo4j 单个事务内存压力过大太小又体现不出批量优势。类型、演员、导演的关系建立方式完全一致区别只在节点类型不同。如果 MovieLens 原始数据里没有演员和导演字段可以用 links.csv 里边的 imdbId 去 IMDB 官网查也可以直接用 Kaggle 上公开的 tmdb_5000_movies.csv 做关联。工作量大一点但换来的是知识图谱丰富的语义结构这两张属性表正是 R-GCN 模型区分普通协同过滤的核心。数据层面搞定后可以在 Neo4j Browser 里执行一句MATCH (n:Movie)-[r:HAS_GENRE]-(g:Genre) RETURN n, r, g LIMIT 25先肉眼确认图的拓扑结构没问题再进模型。3. 用 DGL 搭一个关系图卷积网络从图谱构图到模型训练3.1 为什么选 R-GCN 而不是普通 GCN异构图里的关系感知图神经网络里最常见的 GCN把所有邻居一视同仁地聚合过来。放到电影知识图谱里就有问题User 与 Movie 之间是评分边Movie 与 Actor 之间是出演边边类型不同含义完全不同。如果把演员节点传来的消息和用户节点传来的消息当成同一类信息模型学出来的 embedding 会丢掉关系语义。R-GCNRelational Graph Convolutional Network正是为解决这个场景提出的它给每种关系类型分配一组独立的可学习权重。消息计算时节点拿到的不只是邻居的特征还有“这条边是什么关系”。比如电影节点收到来自用户的消息会走“被谁看过”的权重矩阵收到来自演员的消息会走“谁出演了它”的权重矩阵。这样学出来的电影向量既能体现大众口味又能保留内容属性。至于 GAT图注意力网络它虽然能给不同邻居分配注意力权重但注意力机制处理的是邻居的重要性而不是关系的类别差异。在异构知识图谱上R-GCN 的结构匹配度明显更高。这也是知识图谱方向毕设里 R-GCN 成为默认选择的原因。实践中有同学用 GCN 也能跑通但消融实验一对比R-GCN 在 Recall10 上的优势通常非常直观。3.2 构图这一步索引映射、边类型和反向边用 DGL 搭图之前要先想清楚输入格式。DGL 不直接吃字符串节点名所有实体都要映射成从 0 开始的整数索引。这一步做不对后面全乱。# model/dataset.py import torch import dgl all_entities set() # 从三元组中收集所有实体名称实体ID统一转字符串 for h, r, t in triples: all_entities.add(h) all_entities.add(t) entity2id {name: i for i, name in enumerate(sorted(all_entities))} num_nodes len(entity2id) # 边类型编号正向边和反向边都要占一个编号 rel2id { user_rate_movie: 0, movie_rate_user: 1, # 反向边 movie_has_genre: 2, genre_has_movie: 3, movie_acted_by: 4, actor_acted_movie: 5, movie_directed_by: 6, director_directed_movie: 7, } num_rels len(rel2id) src_list, dst_list, rel_list [], [], [] # 评分边只保留 rating 4 的正反馈并拆成训练/测试两组 for uid, mid, rating in rat_data: if rating 4: continue u_idx, m_idx entity2id[str(uid)], entity2id[m_ str(mid)] src_list.append(u_idx); dst_list.append(m_idx); rel_list.append(0) src_list.append(m_idx); dst_list.append(u_idx); rel_list.append(1)这里最容易忽略的是反向边。R-GCN 的消息传递是有方向的如果只在图里保留 User 指向 Movie 的边消息只能从用户流到电影电影节点永远聚合不到用户的特征。补上 Movie 指向 User 的反向边消息才能在两类节点之间回流。另一个细节评分边在拆分训练测试时不能只拆正向边而忘了同步处理对应的反向边否则图中会出现“测试边参与消息传递”的数据泄漏。构图时把三元组放进 DGL 的同构图即可。注意这里不建异构图所有实体拉平到一套节点空间边类型用rel_list表示。g dgl.graph((src_list, dst_list), num_nodesnum_nodes) g.edata[rel_type] torch.tensor(rel_list, dtypetorch.long) g.ndata[node_type] torch.tensor(node_type_list, dtypetorch.long)rel_type是模型判断边语义的唯一依据而node_type是为了后续做节点级别的消融实验比如只排除 Actor 节点看推荐效果下降多少。每张图还有对应的训练边掩码、验证边掩码和测试边掩码把这些都放进g.edata后训练循环会非常干净。3.3 R-GCN 模型定义与训练循环完整可跑代码DGL 官方库已经封装好了RelGraphConv不需要自己手写关系权重矩阵。模型部分我的写法如下# model/rgcn.py import torch import torch.nn as nn import torch.nn.functional as F from dgl.nn.pytorch import RelGraphConv class RGCNRecommender(nn.Module): def __init__(self, num_nodes, hidden_dim, out_dim, num_rels, num_bases16, dropout0.2): super().__init__() # 所有实体的共享Embedding层 self.node_embedding nn.Embedding(num_nodes, hidden_dim) self.conv1 RelGraphConv( hidden_dim, hidden_dim, num_rels, regularizerbasis, num_basesnum_bases ) self.conv2 RelGraphConv( hidden_dim, out_dim, num_rels, regularizerbasis, num_basesnum_bases ) self.dropout nn.Dropout(dropout) def forward(self, g, node_ids, edge_types): h self.node_embedding(node_ids) h self.conv1(g, h, edge_types) h F.relu(h) h self.dropout(h) h self.conv2(g, h, edge_types) return hRelGraphConv的regularizerbasis参数值得单独说明一下。知识图谱边类型多如果每种关系都配一个独立的in_dim * out_dim权重矩阵参数总量会非常大小数据集上很容易过拟合。basis 正则化把关系矩阵分解成若干基矩阵的线性组合通过num_bases16控制基矩阵数量相当于让不同关系共享底层变换模型参数少了一个数量级还照样能学。经验上num_bases取 8 到 32 之间比较合理毕设场景 16 是一个稳妥的起点。训练循环我用 BCE 损失函数配合负采样。正样本是评分大于等于 4 的用户电影对负样本从“用户没看过”的电影里随机抽数量与正样本按 1:1 配对。这样设计的好处是损失函数计算简单AUC 指标也能直接对齐。# model/train.py optimizer torch.optim.Adam(model.parameters(), lr0.001, weight_decay1e-5) loss_fn nn.BCEWithLogitsLoss() for epoch in range(200): model.train() # 正向边与负样本边打成标签 scores model(g, all_node_ids, g.edata[rel_type]) user_vec scores[user_node_idx] movie_vec scores[movie_node_idx] logits (user_vec * movie_vec).sum(dim1) loss loss_fn(logits, labels) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 10 0: auc compute_auc(model, g, val_edges) print(fepoch {epoch} loss{loss.item():.4f} auc{auc:.4f})计算用户对电影的偏好分时我没有用额外的全连接层而是直接把用户向量和电影向量做逐元素乘积再求和。这个方法在文献里叫内积交互参数少、表现稳定特别适合 embedding 维度不高的情况。如果训练时发现 loss 降得很慢先检查是不是 learning rate 太大导致震荡再检查负采样是不是抽到了用户已经看过的电影——后者会造成标签矛盾模型会被玩坏。4. 调参才能出高分损失函数、负采样与三个决定指标的关键参数4.1 损失函数选型BPR Loss 和交叉熵各自适合什么场景推荐系统的训练目标通常有两种选择一种是像分类任务一样把“用户喜欢某电影”当成二分类用交叉熵另一种是把问题当成排序任务用 Bayesian Personalized Ranking 损失。两条路在毕设数据集上都能收敛但语义差别很大。BCE 损失要求模型给每对用户电影输出一个 0 到 1 之间的概率正样本得分尽量高负样本得分尽量低。它的优点是稳定AUC 曲线可以直接作为评估指标我的训练循环里就是这么写的。缺点在于它把每个样本独立看待没有显式区分“用户可能在两个正样本之间更喜欢哪一个”。BPR Loss 的思路是对每个用户从正样本和负样本各取一个要求模型把正样本的得分压过负样本的得分。排序的意图更强公式大致是-log(sigmoid(pos_score - neg_score))。如果毕设题目强调“推荐列表排序质量”BPR 更合适如果强调评分预测准确率BCE 更直接。绝大多数电影推荐方向的毕设用 BCE 已经足够因为最终评估仍落在 AUC 和 Recall10 上BPR 带来的排序提升在中小数据集上通常只有零点几个百分点。# 用 logits 的方式实现 BPR Loss def bpr_loss(pos_logits, neg_logits): diff pos_logits - neg_logits return -torch.log(torch.sigmoid(diff) 1e-8).mean()注意BPR 的输入不是单个 label而是正负样本的 logits 差值。如果你沿用之前的 BCE 数据加载器每批里同时返回正样本和负样本的索引才能算 bpr_loss。还要在损失里加一个小的 epsilon 防止log(0)导致梯度爆炸我常用1e-8。4.2 负采样策略随机负样本为什么会让模型变笨刚跑通模型的时候很多人会直接从所有电影里均匀随机抽取没看过的作为负样本。这个做法看起来合理实则有毒。MovieLens 数据集的评分分布极其不均衡少数头部电影占据了大量评分绝大多数长尾电影几乎没有交互记录。随机抽到的负样本大多是用户根本没听说过、也不会主动去看的电影模型很容易学会“只要用户没看过就是不喜欢”的粗暴逻辑。更稳妥的做法是流行度负采样负样本的抽取概率与该电影在训练集中的被评分次数成正比。这样模型更常拿“热门但该用户未看”的电影来挑战负面影响更强推荐结果不会一味滑向头部电影。工程上可以先统计每部电影的评分次数然后生成一个权重列表用torch.multinomial采样。popularity torch.tensor(movie_rating_count, dtypetorch.float) popularity popularity ** 0.75 # 平滑分布避免头部电影被抽爆 neg_index torch.multinomial(popularity, num_negs_per_user, replacementFalse)**0.75是 Word2Vec 负采样里常用的平滑指数目的是压低头部电影的采样概率让长尾电影也有机会被选中。如果你发现模型在训练集上 AUC 很高、验证集上掉得厉害先检查负采样是不是只有随机模式再考虑换成流行度采样。负样本与正样本的比例建议从 1:1 开始调增加到 2:1 时 AUC 通常会小幅上升继续加压会适得其反。4.3 学习率、正则化系数和 embedding 维度三个关键参数的调节经验这三个参数是模型训练里最常见的翻车点。学习率我一般从 0.001 起步。开 0.01 会在前十个 epoch 里见到 loss 像过山车一样乱跳退到 0.0005 又慢得让人怀疑模型没在学。配合 Adam 优化器0.001 是绝大多数知识图谱推荐模型的甜点区。如果图特别大可以加一个线性 warmup 前 1000 步从 0.0001 升到 0.001。L2 正则化系数 weight_decay 很容易被忽略。图神经网络本身就具备一定的抗过拟合能力但 MovieLens 这种中等规模数据如果不加正则200 轮训练后验证集指标会稳步下降。经验值在1e-5到1e-4之间。我习惯固定为1e-5只通过 embedding 维度来控制模型容量。你也可以在验证集上扫一遍1e-6, 1e-5, 1e-4三个档位基本能看出差别。embedding 维度直接决定模型容量。MovieLens 100K 这种规模的数据集64 维足够128 维收益很小但训练时间明显增加。只有当你把数据量换到 MovieLens 1M才值得试 128 维。判断维度是否过大有一个很简单的信号训练 loss 不断下降但 AUC 停滞说明模型在背训练集先把维度砍半试试。最后强调一个容易被忽视的配置随机种子。图神经网络的训练涉及随机初始化、负采样、dropout同一份代码跑两遍结果都可能不一样。在 train.py 顶部固定torch.manual_seed(42)、dgl.seed(42)、numpy.random.seed(42)不然你在答辩前夜调出来一个好结果第二天重新跑又没了那种感觉真的会让人崩溃。5. 避坑专题从 Neo4j 导入到模型推理的高频踩坑现场5.1 Neo4j 导入写到一半失败重启后数据消失现象使用 py2neo 往 Neo4j 里导 rated 关系脚本执行到一半抛出ConnectionResetError重启 Neo4j 后部分数据丢失另外一个事务里已经创建的用户节点却还在。原因单个事务包含的数据量太大比如一次性graph.begin()把 10 万条评分关系全部塞进一个事务Neo4j 端事务日志把内存撑爆连接被强制断开事务未提交的部分全部回滚。用户节点之所以还在是因为它们在另一个事务里已经提交成功。解决拆成小事务批量提交我用的是UNWIND批处理每批 5000 条记录。另外导入前先跑graph.run(CALL db.constraints())确认唯一约束建好MERGE本身需要唯一键来判重。如果数据量超过百万条就不要再用 py2neo 了直接用 Neo4j 官方的neo4j-admin import工具做离线导入速度能到每分钟几十万条。5.2 GPU 显存够用却报 OOM问题出在构图方式现象显卡是 24G 显存的高端卡图节点数也只有一万多训练一开始就报CUDA out of memory。原因你没有做邻居采样每次 forward 都是整张图全量消息传递。R-GCN 的反向边会把边数放大两倍MovieLens 1M 的评分边加上属性边后轻松超过两百万条边整图嵌入和中间激活全部留在显存里24G 照样不够。解决训练时不要全图前向换成 mini-batch 训练用 DGL 的dgl.dataloading.NeighborSampler采样邻居子图。每批只计算当前用户和采样到的邻居子图显存占用直接降一个数量级。除此之外num_bases16能大幅压缩关系权重矩阵的显存占用这个参数比调小 hidden 维度的收益更明显。5.3 验证集 AUC 不升反降训练集指标却一路高涨现象训练到第 80 个 epoch 时训练集 AUC 到了 0.95验证集 AUC 从 0.78 掉到 0.74之后再也拉不回来。原因两个叠加因素。第一图里同时包含了训练边和测试边测试边参与消息传递后模型相当于提前看到了测试答案训练 loss 虚低。第二R-GCN 消息聚合的层数过多两层以上在中小知识图谱上很容易过平滑节点向量趋于一致。解决构图时严格把评分边拆成 train 和 test 两份训练时在去掉测试边的图上做消息传递评估时再把测试边加回来。嵌入传播层数控制在两层以内毕设项目用一层 hidden 加一层输出就够了。记住一个原则测试边可以参与评估时的消息传递但绝对不能参与训练时的消息聚合否则你的所有指标都不可信。5.4 新用户没有任何历史行为预测分数全部一样现象把模型部署成演示接口后输入一个系统里不存在的新用户 ID推荐结果五花八门可仔细一看分数全部相同只是随机排序。原因新用户在知识图谱里没有节点embedding 查询直接落到了一个随机初始化的向量上而且这个向量不参与任何消息聚合自然对任何电影都给出相同的内积分数。这就是典型的冷启动问题知识图谱能缓解但不会自动解决。解决建模阶段就把用户的属性边放进图里比如 age、occupation、gender 关联到 User 节点。新用户注册时只要填了属性就能通过属性边参与到消息传递中embedding 不再是白板。另一个补救方案是在演示脚本里做基于内容的推荐兜底新用户选择一个喜欢的类型从知识图谱里检索出该类型下最热门的电影等用户产生几条评分后再切换成 R-GCN 预测结果。这个混合策略在答辩时是加分项因为它体现了你对推荐系统实际落地问题的理解。5.5 同一份代码跑两遍结果不一致现象白天训练出来的验证集 AUC 是 0.812晚上重新运行训练脚本变成了 0.803模型训练过程没改过任何代码。原因缺少随机种子固定。PyTorch 的 embedding 初始化、DGL 的邻居采样、负采样的torch.multinomial都依赖随机状态不固定种子结果自然不可复现。解决在入口脚本第一行固定所有随机源。PyTorch 固定torch.manual_seed(42)DGL 固定dgl.seed(42)NumPy 固定np.random.seed(42)如果开了 CUDA 还要加torch.cuda.manual_seed_all(42)。另外把每次实验的关键配置learning rate、weight_decay、负采样比例用argparse记录到日志文件里这是答辩时被问到“这个参数怎么定的”唯一能拿出证据的路径。6. 从能跑到能答辩用消融实验和数据可视化给项目加分模型跑通只是高分毕设的开始。答辩评委最常问的问题是“你怎么证明知识图谱和图神经网络比普通推荐算法更好”这个问题的答案不是口头辩解而是拿数据说话。我建议准备一个消融实验表横向上对比矩阵分解、普通 GCN 和 R-GCN纵向上对比去掉某些关系类型后的效果。实验设置数据规模AUCRecall10矩阵分解 MF100K 评分0.7210.184普通 GCN 知识图谱100K 评分 属性边0.7440.207R-GCN 完整知识图谱100K 评分 全部属性边0.7860.231R-GCN 去掉导演关系100K 评分 部分属性边0.7650.218R-GCN 去掉演员关系100K 评分 部分属性边0.7710.221上面的数值是我在自己的实验集上得到的一个参考区间不同的随机种子和数据集划分会有一两个点的波动。设计这个表格时要注意逻辑矩阵分解是基线GCN 证明图结构本身有效R-GCN 证明关系类型建模带来增量去掉导演或演员关系则证明每种关系都对推荐有贡献。如果去掉某个关系后指标没有下降不要强行解释直接删掉这个关系还能让模型更简洁。可视化部分是另一个容易打动评委的环节。Neo4j Browser 里能展示局部的知识图谱子图t-SNE 可以把电影 embedding 降维到二维平面画出 Drama、Comedy、Action 的类型聚类。推荐结果演示时不要只列电影名要把“为什么推荐”的图谱路径打出来用户看过《盗梦空间》《盗梦空间》被导演诺兰执导《星际穿越》同样由诺兰执导所以推荐《星际穿越》。这一条路径就是知识图谱相比矩阵分解最直观的优势。最后是工程习惯的问题。训练脚本要能一键复现中间结果要落盘模型 checkpoint 按 epoch 命名保存。答辩现场如果演示环境出了意外你要能在三十秒内切换到预计算好的推荐结果而不是当场调模型。这不是投机取巧是工程系统该有的冗余设计。我自己做项目时吃过现场演示翻车的亏后来所有演示都准备两套方案一套在线推理一套离线结果兜底希望这个习惯能帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站