基于知识图谱的电影推荐系统——Neo4j&Python

该文描述了一个电影推荐系统的实现过程,包括从TMDB和NetflixPrize数据集下载数据,使用Python进行数据预处理生成处理后的文件,将处理后的数据导入Neo4j数据库创建知识图谱,以及执行推荐算法查询用户电影推荐。主要涉及数据清洗、图数据库操作和用户相似度计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 数据解下载与配置

选择TMDB电影数据集Netflix Prize 数据集下载。

也可直接从这里下载:链接: https://pan.baidu.com/s/1l6wjwcUzy5G_dIlVDbCkpw 提取码: pkq6 。

在这里插入图片描述

在这里插入图片描述

执行preproc.py文件,进行数据预处理,生成5个处理后的文件:

import pandas as pd
import json
import re

def Netflix(MAX_USER = 1000):
    d_movie = dict()
    s_movie = set()
    
    out_movies = open("../out_movies.csv","w")
    out_movies.write("title\n")

    for line in open("../movie_titles.csv","r",encoding = 'ISO-8859-1'):
        line = line.strip().split(',')
        movie_id = int(line[0])
        title = line[2].replace("\"","")
        title = "\"" + title + "\""
        
        d_movie[movie_id] = title
        
        if title in s_movie:
           continue
        s_movie.add(title)
        
        out_movies.write(f"{title}\n")
        
    out_movies.close()
    
    out_grade = open("../out_grade.csv","w")
    out_grade.write("user_id,title,grade\n")

    files = ["../combined_data_1.txt"]
    for f in files:
        movie_id = -1
        for line in open(f,"r"):
            pos = line.find(":")
            if pos != -1: # is a new user
                movie_id = int(line[:pos])
                continue
            line = line.strip().split(",")
            user_id = int(line[0])
            rating = int(line[1])
            
            if user_id > MAX_USER:
                continue

            out_grade.write(f"{user_id},{d_movie[movie_id]},{rating}\n")
            
    out_grade.close()

def TMDB():
    pattern = re.compile("[A-Za-z0-9]+")
    # 提取电影类型
    out_genre = open("../out_genre.csv","w",encoding='utf-8')
    out_genre.write("title,genre\n")
    # 提取电影关键词
    out_keyword = open("../out_keyword.csv","w",encoding='utf-8')
    out_keyword.write("title,keyword\n")
    # 提取电影制片人
    out_productor = open("../out_productor.csv","w",encoding='utf-8')
    out_productor.write("title,productor\n")
    
    df = pd.read_csv("../tmdb_5000_movies.csv", sep=",")
    json_columns = ['genres', 'keywords', 'production_companies']
    for column in json_columns:
        df[column] = df[column].apply(json.loads)
    df = df[["genres", "keywords", "original_title","production_companies"]]
    for _, row in df.iterrows():
        title = row["original_title"]
        if not pattern.fullmatch(title):
            continue
        title = "\"" + title + "\""
        for g in row["genres"]:
            genre = g["name"]
            genre = "\"" + genre + "\""
            out_genre.write(f"{title},{genre}\n")
        for g in row["keywords"]:
            keyword = g["name"]
            keyword = "\"" + keyword + "\""
            out_keyword.write(f"{title},{keyword}\n")
        for g in row["production_companies"]:
            productor = g["name"]
            productor = "\"" + productor + "\""
            out_productor.write(f"{title},{productor}\n")

    
if __name__ == "__main__":
    Netflix()
    TMDB()

在这里插入图片描述

2. 将处理好的数据导入数据库中

将上面数据预处理生成的5个文件,放入import文件夹中:

在这里插入图片描述

在这里插入图片描述

3. 执行项目

修改main.py中的driver,输入自己数据库的用户名与密码。

执行main.py文件:

from neo4j import GraphDatabase
import pandas as pd

uri = "neo4j://localhost:7687"
driver = GraphDatabase.driver(uri, auth=("neo4j", "Liu881389."))

k = 10 # nearest neighbors (most similar users) to consider
movies_common = 3 # how many movies in common to be consider an user similar
users_common = 2 # minimum number of similar users that have seen the movie to consider it
threshold_sim = 0.9 # threshold to consider users similar

def load_data():
    with driver.session() as session:
        session.run("""MATCH ()-[r]->() DELETE r""")
        session.run("""MATCH (r) DELETE r""")
        
        print("Loading movies...")
        #加载数据,创建Movie标签,title属性的实体
        session.run("""
            LOAD CSV WITH HEADERS FROM "file:///out_movies.csv" AS csv
            CREATE (:Movie {title: csv.title})
            """)
            
        print("Loading gradings...")
        #加载评分数据,MERGE是搜索给定模式,如果存在,则返回结果如果它不存在于图中,则它创建新的节点/关系并返回结果。
        session.run("""
            LOAD CSV WITH HEADERS FROM "file:///out_grade.csv" AS csv
            MERGE (m:Movie {title: csv.title}) 
            MERGE (u:User {id: toInteger(csv.user_id)})
            CREATE (u)-[:RATED {grading : toInteger(csv.grade)}]->(m)
            """)
        #加载影片类型数据    
        print("Loading genres...")
            
        session.run("""
            LOAD CSV WITH HEADERS FROM "file:///out_genre.csv" AS csv
            MERGE (m:Movie {title: csv.title})
            MERGE (g:Genre {genre: csv.genre})
            CREATE (m)-[:HAS_GENRE]->(g)
            """)
            
        print("Loading keywords...")
        #加载关键词数据    
        session.run("""
            LOAD CSV WITH HEADERS FROM "file:///out_keyword.csv" AS csv
            MERGE (m:Movie {title: csv.title})
            MERGE (k:Keyword {keyword: csv.keyword})
            CREATE (m)-[:HAS_KEYWORD]->(k)
            """)
            
        print("Loading productors...")
        #制片人    
        session.run("""
            LOAD CSV WITH HEADERS FROM "file:///out_productor.csv" AS csv
            MERGE (m:Movie {title: csv.title})
            MERGE (p:Productor {name: csv.productor})
            CREATE (m)-[:HAS_PRODUCTOR]->(p)
            """)

def queries():
    while True:
        userid = int(input("请输入要为哪位用户推荐电影,输入其ID即可: "))
        m = int(input("为该用户推荐多少个电影呢? "))
        
        genres = []
        if int(input("是否需要过滤掉不喜欢的类型?(输入0或1)")):#过滤掉不喜欢的类型
            with driver.session() as session:
                try:
                    q = session.run(f"""MATCH (g:Genre) RETURN g.genre AS genre""")
                    result = []
                    for i, r in enumerate(q):
                        result.append(r["genre"])#找到图谱中所有的电影类型
                    df = pd.DataFrame(result, columns=["genre"])
                    print()
                    print(df)
                    inp = input("输入不喜欢的类型索引即可,例如:1 2 3  ")
                    if len(inp) != 0:
                        inp = inp.split(" ")
                        genres = [df["genre"].iloc[int(x)] for x in inp]
                except:
                    print("Error")
                    
        with driver.session() as session:#找到当前ID评分的电影
            q = session.run(f"""
                    MATCH (u1:User {{id : {userid}}})-[r:RATED]-(m:Movie)
                    RETURN m.title AS title, r.grading AS grade
                    ORDER BY grade DESC
                    """)
            
            print()
            print("Your ratings are the following:")
            
            result = []
            for r in q:
                result.append([r["title"], r["grade"]])
                
            if len(result) == 0:
                print("No ratings found")
            else:
                df = pd.DataFrame(result, columns=["title", "grade"])
                print()
                print(df.to_string(index=False))
            print()
            
            session.run(f"""
                MATCH (u1:User)-[s:SIMILARITY]-(u2:User)
                DELETE s
                """)
            #找到当前用户评分的电影以及这些电影被其他用户评分的用户,with是把查询集合当做结果以便后面用where 余弦相似度计算
            session.run(f"""
                MATCH (u1:User {{id : {userid}}})-[r1:RATED]-(m:Movie)-[r2:RATED]-(u2:User)
                WITH
                    u1, u2,
                    COUNT(m) AS movies_common,
                    SUM(r1.grading * r2.grading)/(SQRT(SUM(r1.grading^2)) * SQRT(SUM(r2.grading^2))) AS sim
                WHERE movies_common >= {movies_common} AND sim > {threshold_sim}
                MERGE (u1)-[s:SIMILARITY]-(u2)
                SET s.sim = sim
                """)
                
            Q_GENRE = ""
            if (len(genres) > 0):
                Q_GENRE = "AND ((SIZE(gen) > 0) AND "
                Q_GENRE += "(ANY(x IN " + str(genres) + " WHERE x IN gen))"
                Q_GENRE += ")"
            #找到相似的用户,然后看他们喜欢什么电影 Collect:将所有值收集到一个集合list中
            q = session.run(f"""
                    MATCH (u1:User {{id : {userid}}})-[s:SIMILARITY]-(u2:User)
                    WITH u1, u2, s
                    ORDER BY s.sim DESC LIMIT {k}
                    MATCH (m:Movie)-[r:RATED]-(u2)
                    OPTIONAL MATCH (g:Genre)--(m)
                    WITH u1, u2, s, m, r, COLLECT(DISTINCT g.genre) AS gen
                    WHERE NOT((m)-[:RATED]-(u1)) {Q_GENRE}
                    WITH
                        m.title AS title,
                        SUM(r.grading * s.sim)/SUM(s.sim) AS grade,
                        COUNT(u2) AS num,
                        gen
                    WHERE num >= {users_common}
                    RETURN title, grade, num, gen
                    ORDER BY grade DESC, num DESC
                    LIMIT {m}
                    """)

            print("Recommended movies:")

            result = []
            for r in q:
                result.append([r["title"], r["grade"], r["num"], r["gen"]])
            if len(result) == 0:
                print("No recommendations found")
                print()
                continue
            df = pd.DataFrame(result, columns=["title", "avg grade", "num recommenders", "genres"])
            print()
            print(df.to_string(index=False))
            print()

if __name__ == "__main__":
    if int(input("是否需要重新加载并创建知识图谱?(请选择输入0或1)")):
        load_data()
    queries()

运行main.py文件后:

在这里插入图片描述

系统会询问是否需要重新加载并创建知识图谱,在第一次时输入1。

在这里插入图片描述

加载完成后可以在neo4j中查看:

在这里插入图片描述

接下来输入需要查询的信息:

在这里插入图片描述

输出结果:

在这里插入图片描述

### 如何使用知识图谱构建电影推荐系统 #### 构建方法概述 构建基于知识图谱电影推荐系统可以通过自底向上或自顶向下的方式完成。这两种方法各有优劣,具体选择应依据项目的需求和现有资源[^1]。 #### 知识图谱整体架构 知识图谱的整体架构涉及多个环节,包括但不限于数据收集、预处理、实体识别、关系抽取等。对于电影推荐系统而言,这一流程同样适用。从大量的结构化、非结构化及半结构化数据出发,利用自动化或半自动化的工具和技术从中抽取出有价值的实体及其之间的关联,并最终形成一个能够支持高效查询的知识库体系[^4]。 #### 数据映射与索引构建 为了提高检索效率,在创建好基本框架之后还需要考虑如何有效地存储并访问这些信息。此时就需要引入Elasticsearch这样的搜索引擎来进行索引建立工作。通过对不同类型的数据源实施合理的映射策略,确保每一个条目都能被精准定位到其对应的上下文中去[^3]。 #### 推荐算法集成 考虑到初期可能存在训练样本不足的情况,除了依赖于传统的协同过滤之外还可以尝试融合其他类型的机器学习模型甚至是规则引擎来增强预测准确性。特别是针对那些较为复杂的场景——比如跨领域或多模态的内容理解,则更应该注重灵活性的设计以便随时调整适应变化中的业务逻辑需求[^5]。 ```python # 示例代码片段展示了一个简单的基于Neo4j数据库的操作函数用于获取某部影片的相关评论者列表 def get_movie_reviewers(movie_title, graph_db_session): query = """ MATCH (m:Movie {title:$movieTitle})<-[:RATED]-(u:User) RETURN u.name AS reviewerName """ result = graph_db_session.run(query, movieTitle=movie_title) reviewers = [record["reviewerName"] for record in result] return reviewers ```
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值