最近在整理个人观影记录时,我发现一个有趣的现象:喜欢的电影之间总存在某种隐藏联系。比如钟爱诺兰《盗梦空间》的人,往往也会喜欢他的《星际穿越》;痴迷《指环王》系列的观众,对《霍比特人》三部曲通常也难以抗拒。这种关联性正是知识图谱最擅长的领域——今天我们就用Neo4j图数据库和Python,构建一个能自动发现这些隐藏规律的电影推荐系统。
工欲善其事,必先利其器。我们选择Neo4j作为图数据库核心,不仅因为其直观的图数据可视化能力,更因其专为关系查询优化的Cypher查询语言。搭配Python的py2neo库,能实现高效的数据操作。以下是基础环境配置步骤:
bash复制# 安装Neo4j桌面版(社区版免费)
brew install neo4j # MacOS
choco install neo4j-community # Windows
# Python依赖
pip install py2neo pandas numpy
提示:Neo4j 5.x版本对内存管理做了优化,建议至少分配4GB内存给数据库服务
我们将使用MovieLens 25M数据集,包含:
原始CSV需要预处理为图数据库友好的结构。关键字段映射:
| 原始字段 | 图谱实体类型 | 目标属性 |
|---|---|---|
| movieId | Movie节点 | movieId, title, genres |
| userId | User节点 | userId |
| rating | 关系属性 | score, timestamp |
| tag | 关系属性 | tag, timestamp |
用Pandas进行数据清洗的典型操作:
python复制import pandas as pd
# 读取电影元数据
movies = pd.read_csv('movies.csv')
# 将类型字符串拆分为列表
movies['genres'] = movies['genres'].str.split('|')
# 处理带年份的标题
movies['year'] = movies['title'].str.extract(r'\((\d{4})\)')
优秀的图谱始于合理的本体设计。我们的电影领域核心实体包括:
Movie:核心节点
Person:演艺人员
User:观众
实体关系示意图:
code复制(Movie) <-[:ACTED_IN]- (Person)
(Movie) -[:GENRE]-> (Genre)
(User) -[:RATED {score:5}]-> (Movie)
使用Neo4j的批量导入工具neo4j-admin实现高效初始加载:
python复制from py2neo import Graph, Node, Relationship
graph = Graph("bolt://localhost:7687", auth=("neo4j", "password"))
# 批量创建节点
tx = graph.begin()
for _, row in movies.iterrows():
movie = Node("Movie",
movieId=row['movieId'],
title=row['title'],
year=row['year'])
tx.create(movie)
tx.commit()
对于关系数据,采用参数化查询提升性能:
python复制query = """
UNWIND $batch as item
MATCH (u:User {userId: item.userId})
MATCH (m:Movie {movieId: item.movieId})
MERGE (u)-[r:RATED]->(m)
SET r.score = item.rating,
r.timestamp = item.timestamp
"""
graph.run(query, batch=ratings.to_dict('records'))
图数据库的真正威力体现在复杂关系查询上。例如找出与《盗梦空间》有3度关联的电影:
cypher复制MATCH (m:Movie {title: 'Inception'})<-[:ACTED_IN]-(a1)-[:ACTED_IN]->(m2)
<-[:ACTED_IN]-(a2)-[:ACTED_IN]->(recs)
WHERE m <> recs
RETURN recs.title, count(*) as strength
ORDER BY strength DESC LIMIT 10
查询结果示例:
| 推荐电影 | 关联强度 |
|---|---|
| 星际穿越 | 8 |
| 记忆碎片 | 5 |
| 蝙蝠侠:黑暗骑士 | 4 |
单一推荐方法容易陷入信息茧房。我们组合以下策略:
基于内容的推荐:通过电影类型、导演等属性相似度
python复制# 计算余弦相似度
from sklearn.metrics.pairwise import cosine_similarity
tfidf_matrix = tfidf.fit_transform(movies['genres'].apply(' '.join))
similarities = cosine_similarity(tfidf_matrix)
协同过滤:利用用户评分矩阵
cypher复制MATCH (u1:User {userId: '100'})-[:RATED]->(m:Movie)
MATCH (u2:User)-[:RATED]->(m)
WHERE u1 <> u2
WITH u2, count(*) as common_movies
ORDER BY common_movies DESC LIMIT 5
MATCH (u2)-[:RATED]->(rec:Movie)
WHERE NOT EXISTS((u1)-[:RATED]->(rec))
RETURN rec.title, avg(r.score) as avg_score
图谱特征增强:引入二度关系权重
cypher复制MATCH (u:User {userId: '100'})-[:RATED]->(m:Movie)
MATCH (m)<-[:ACTED_IN]-(a:Actor)-[:ACTED_IN]->(rec:Movie)
WHERE NOT EXISTS((u)-[:RATED]->(rec))
RETURN rec.title, count(a) as actor_overlap
ORDER BY actor_overlap DESC
当数据量超过百万级时,需要特别关注查询效率:
索引优化:为高频查询字段创建索引
cypher复制CREATE INDEX movie_title_index FOR (m:Movie) ON (m.title)
CREATE INDEX person_name_index FOR (p:Person) ON (p.name)
查询模式优化:避免全图扫描
cypher复制// 低效查询
MATCH (m:Movie), (p:Person)
WHERE m.title = 'Inception' AND p.name = 'Leonardo DiCaprio'
// 优化版本
MATCH (m:Movie {title: 'Inception'})
MATCH (p:Person {name: 'Leonardo DiCaprio'})
内存配置:调整Neo4j的堆内存和页面缓存
code复制dbms.memory.heap.initial_size=2G
dbms.memory.heap.max_size=4G
dbms.memory.pagecache.size=2G
生产环境推荐系统通常采用混合架构:
code复制[客户端] -> [API网关] -> [实时推荐服务]
-> [批处理推荐缓存]
-> [图谱特征服务]
Python服务示例代码:
python复制from flask import Flask, jsonify
import neo4j
app = Flask(__name__)
@app.route('/recommend/<user_id>')
def get_recommendations(user_id):
# 实时查询
realtime = graph.run("""
MATCH (u:User {userId: $uid})-[:RATED]->(m:Movie)
MATCH (m)<-[:ACTED_IN]-(a:Actor)-[:ACTED_IN]->(rec:Movie)
WHERE NOT EXISTS((u)-[:RATED]->(rec))
RETURN rec.movieId, rec.title, count(a) as strength
ORDER BY strength DESC LIMIT 10
""", uid=user_id).data()
return jsonify({
'realtime': realtime,
'batch': get_cached_recommendations(user_id)
})
推荐系统的优劣需要量化评估:
| 指标类型 | 具体指标 | 计算方式 |
|---|---|---|
| 预测准确度 | RMSE | $\sqrt{\frac{1}{N}\sum(r-\hat{r})^2}$ |
| 排序质量 | NDCG@10 | 考虑位置权重的折扣累积增益 |
| 多样性 | 推荐覆盖率 | $\frac{ |
| 新颖性 | 平均流行度 | $\frac{1}{ |
通过分流实验验证图谱推荐效果:
python复制# 用户分组逻辑
def assign_group(user_id):
return 'control' if hash(user_id) % 2 == 0 else 'experimental'
# 对照组:传统协同过滤
# 实验组:图谱增强推荐
典型实验结果对比:
| 指标 | 对照组 | 实验组 | 提升 |
|---|---|---|---|
| CTR | 3.2% | 4.7% | +46% |
| 平均观看时长 | 45min | 58min | +29% |
| 多样性得分 | 0.65 | 0.82 | +26% |
在实际项目中,引入知识图谱后最明显的改善是推荐结果的解释性——现在我们可以直观展示推荐路径:"因为您喜欢A电影,而A与B共享三位主演,且导演相同"。这种透明性显著提升了用户对推荐系统的信任度。