电影推荐系统

电影推荐系统

一、项目概述

本项目旨在基于 MovieLens ml-latest-small 数据集,分别实现以下三种推荐方法:

  1. 基于内容的推荐(Content-Based Filtering)
  2. 协同过滤推荐(Collaborative Filtering)
  3. 混合模型推荐(Hybrid Model)

通过对比这三种方法的效果,深入理解电影推荐系统的构建流程和关键技术。


二、数据集介绍

2.1 数据集概述

  • ratings.csv:用户对电影的评分数据,共包含 100,000 条评分记录。
  • movies.csv:电影的元数据信息,包括标题和类型,共有 9,000 部电影。
  • tags.csv:用户为电影打的标签,共有 3,600 条标签记录。
  • links.csv:电影在不同网站的链接信息。

2.2 数据字段说明

  • ratings.csv

    userId,movieId,rating,timestamp
    
  • movies.csv

    movieId,title,genres
    
  • tags.csv

    userId,movieId,tag,timestamp
    
  • links.csv

    movieId,imdbId,tmdbId
    

三、项目流程

3.1 数据加载与预处理

3.1.1 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 矢量化和相似度计算
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel, cosine_similarity

# 矩阵分解
from sklearn.decomposition import TruncatedSVD

# 协同过滤
from surprise import SVD, Dataset, Reader, accuracy
from surprise.model_selection import cross_validate, train_test_split

# 忽略警告信息
import warnings
warnings.filterwarnings('ignore')
3.1.2 加载数据
# 加载数据集
ratings = pd.read_csv('ratings.csv')
movies = pd.read_csv('movies.csv')
tags = pd.read_csv('tags.csv')
links = pd.read_csv('links.csv')
3.1.3 数据预览
print('Ratings 数据集预览:')
print(ratings.head())

print('\nMovies 数据集预览:')
print(movies.head())

print('\nTags 数据集预览:')
print(tags.head())

print('\nLinks 数据集预览:')
print(links.head())
3.1.4 数据清洗
  • 处理缺失值
# 检查缺失值
print('Ratings 缺失值:\n', ratings.isnull().sum())
print('Movies 缺失值:\n', movies.isnull().sum())
print('Tags 缺失值:\n', tags.isnull().sum())
print('Links 缺失值:\n', links.isnull().sum())
  • 去除重复值
# 去除重复的评分记录
ratings.drop_duplicates(subset=['userId', 'movieId'], keep='last', inplace=True)
  • 数据格式转换
# 将 timestamp 转换为日期时间格式
ratings['timestamp'] = pd.to_datetime(ratings['timestamp'], unit='s')
tags['timestamp'] = pd.to_datetime(tags['timestamp'], unit='s')
3.1.5 文本数据预处理
  • 处理电影类型
# 将 genres 中的类型拆分并去除空格
movies['genres'] = movies['genres'].str.replace('|', ' ')
  • 处理标签数据
# 合并同一电影的所有标签
all_tags = tags.groupby('movieId')['tag'].apply(lambda x: ' '.join(x)).reset_index()
# 将标签数据与电影数据合并
movies = pd.merge(movies, all_tags, on='movieId', how='left')
# 对缺失的标签填充空字符串
movies['tag'] = movies['tag'].fillna('')
3.1.6 查看预处理后的数据
print('预处理后的 Movies 数据集预览:')
print(movies.head())

3.2 基于内容的推荐

基于内容的推荐主要利用电影的内容信息(如类型、标签、简介等)来计算电影之间的相似度,从而为用户推荐与其喜爱的电影相似的影片。

3.2.1 特征提取
  • 合并文本特征
# 创建一个新的列,包含所有文本信息
movies['content'] = movies['tag'] + ' ' + movies['genres'] + ' ' + movies['title']
  • TF-IDF 向量化
# 初始化 TfidfVectorizer
tfidf = TfidfVectorizer(stop_words='english')

# 对 content 列进行 TF-IDF 处理
tfidf_matrix = tfidf.fit_transform(movies['content'])

print('TF-IDF 矩阵的形状:', tfidf_matrix.shape)
3.2.2 计算相似度矩阵
# 计算余弦相似度
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)

print('相似度矩阵的形状:', cosine_sim.shape)
3.2.3 建立电影索引
# 建立电影标题与索引的映射
indices = pd.Series(movies.index, index=movies['title']).drop_duplicates()
3.2.4 定义推荐函数
def content_recommendations(title, cosine_sim=cosine_sim, num_recommendations=10):
    """
    根据电影标题推荐相似的电影

    参数:
    title: 电影标题
    cosine_sim: 预计算的相似度矩阵
    num_recommendations: 推荐的电影数量

    返回:
    推荐的电影列表
    """
    # 获取输入电影的索引
    idx = indices[title]

    # 获取该电影与所有电影的相似度分数
    sim_scores = list(enumerate(cosine_sim[idx]))

    # 按相似度分数排序
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)

    # 获取最相似的电影索引
    sim_scores = sim_scores[1:num_recommendations+1]
    movie_indices = [i[0] for i in sim_scores]

    # 返回推荐的电影标题
    return movies['title'].iloc[movie_indices]
3.2.5 测试推荐函数
# 测试推荐函数
print('与 "Toy Story (1995)" 相似的电影:')
print(content_recommendations('Toy Story (1995)'))

3.3 协同过滤推荐

协同过滤通过分析用户的行为数据(如评分、浏览历史等),根据相似用户或物品的偏好进行推荐。

3.3.1 构建用户-电影评分矩阵
# 创建用户-电影评分矩阵
user_movie_matrix = ratings.pivot_table(index='userId', columns='movieId', values='rating')
3.3.2 基于内存的协同过滤
  • 计算物品相似度
# 计算物品之间的相似度矩阵(使用皮尔逊相关系数)
item_similarity = user_movie_matrix.corr(method='pearson', min_periods=50)
  • 定义推荐函数
def item_based_recommendations(user_id, num_recommendations=5):
    """
    根据用户的历史评分,使用物品-物品协同过滤推荐电影

    参数:
    user_id: 用户 ID
    num_recommendations: 推荐的电影数量

    返回:
    推荐的电影列表
    """
    # 获取用户已评分的电影及评分
    user_ratings = user_movie_matrix.loc[user_id].dropna()
    similar_items = pd.Series()

    # 遍历用户已评分的电影
    for movie_id, rating in user_ratings.items():
        # 获取与该电影相似的电影及相似度
        sims = item_similarity[movie_id].dropna()
        # 根据用户的评分调整相似度
        sims = sims.map(lambda x: x * rating)
        # 累加相似度分数
        similar_items = pd.concat([similar_items, sims])

    # 汇总分数并排序
    similar_items = similar_items.groupby(similar_items.index).sum()
    similar_items = similar_items.sort_values(ascending=False)

    # 去除用户已评分的电影
    similar_items = similar_items.drop(user_ratings.index, errors='ignore')

    # 返回推荐的电影列表
    return movies.set_index('movieId').loc[similar_items.index[:num_recommendations]]['title']
3.3.3 测试推荐函数
# 测试推荐函数
print('为用户 1 推荐的电影:')
print(item_based_recommendations(1))
3.3.4 基于模型的协同过滤
  • 加载数据到 Surprise 库
from surprise import Dataset, Reader

# 定义 Reader
reader = Reader(rating_scale=(0.5, 5.0))

# 加载数据
data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)
  • 训练 SVD 模型
# 划分训练集和测试集
trainset, testset = train_test_split(data, test_size=0.25, random_state=42)

# 定义 SVD 模型
svd = SVD()

# 训练模型
svd.fit(trainset)
  • 模型评估
# 预测
predictions = svd.test(testset)

# 计算 RMSE
rmse = accuracy.rmse(predictions)
  • 定义推荐函数
def svd_recommendations(user_id, num_recommendations=5):
    """
    使用 SVD 模型为指定用户推荐电影

    参数:
    user_id: 用户 ID
    num_recommendations: 推荐的电影数量

    返回:
    推荐的电影列表
    """
    # 获取用户已评分的电影
    user_rated_movies = ratings[ratings['userId'] == user_id]['movieId'].tolist()

    # 获取用户未评分的电影
    all_movies = movies['movieId'].tolist()
    unrated_movies = [movie for movie in all_movies if movie not in user_rated_movies]

    # 预测用户对未评分电影的评分
    predictions = [svd.predict(user_id, movie_id) for movie_id in unrated_movies]

    # 按预测评分排序
    predictions.sort(key=lambda x: x.est, reverse=True)

    # 获取推荐的电影 ID
    recommended_movie_ids = [int(pred.iid) for pred in predictions[:num_recommendations]]

    # 返回推荐的电影标题
    return movies.set_index('movieId').loc[recommended_movie_ids]['title']
3.3.5 测试推荐函数
# 测试推荐函数
print('使用 SVD 模型为用户 1 推荐的电影:')
print(svd_recommendations(1))

3.4 混合模型推荐

混合模型结合了基于内容的推荐和协同过滤的优势,提供更准确的推荐结果。

3.4.1 定义混合推荐函数
def hybrid_recommendations(user_id, title, num_recommendations=10):
    """
    结合基于内容和协同过滤的推荐结果

    参数:
    user_id: 用户 ID
    title: 用户最近观看并喜爱的电影标题
    num_recommendations: 推荐的电影数量

    返回:
    推荐的电影列表
    """
    # 基于内容的推荐
    content_recs = content_recommendations(title, num_recommendations=50)
    content_recs = movies[movies['title'].isin(content_recs)]
    
    # 协同过滤的推荐
    svd_recs = svd_recommendations(user_id, num_recommendations=50)
    svd_recs = movies[movies['title'].isin(svd_recs)]
    
    # 合并推荐结果
    hybrid_recs = pd.merge(content_recs, svd_recs, on='title')
    
    # 去除用户已评分的电影
    user_rated_movies = ratings[ratings['userId'] == user_id]['movieId'].tolist()
    hybrid_recs = hybrid_recs[~hybrid_recs['movieId_x'].isin(user_rated_movies)]
    
    # 返回推荐的电影标题
    return hybrid_recs['title'].head(num_recommendations)
3.4.2 测试混合推荐函数
# 测试混合推荐函数
print('为用户 1 推荐的混合推荐电影:')
print(hybrid_recommendations(1, 'Toy Story (1995)'))

四、模型评估

4.1 离线评估

  • 评价指标

    • RMSE(均方根误差):评估预测评分的准确性。
# 已在协同过滤部分计算 RMSE
  • Precision@K 和 Recall@K
from surprise import KNNBasic
from collections import defaultdict

def precision_recall_at_k(predictions, k=10, threshold=3.5):
    """
    计算 Precision@K 和 Recall@K

    参数:
    predictions: 预测结果列表
    k: 推荐的物品数量
    threshold: 好评的阈值

    返回:
    精确率和召回率
    """
    # 将预测结果按用户分组
    user_est_true = defaultdict(list)
    for uid, iid, true_r, est, _ in predictions:
        user_est_true[uid].append((est, true_r))
    
    precisions = dict()
    recalls = dict()
    for uid, user_ratings in user_est_true.items():
        # 排序预测评分
        user_ratings.sort(key=lambda x: x[0], reverse=True)
        
        # 取前 K 个物品
        n_rel = sum((true_r >= threshold) for (_, true_r) in user_ratings)
        n_rec_k = sum((est >= threshold) for (est, _) in user_ratings[:k])
        n_rel_and_rec_k = sum(((true_r >= threshold) and (est >= threshold))
                              for (est, true_r) in user_ratings[:k])
        
        # 计算精确率和召回率
        precisions[uid] = n_rel_and_rec_k / n_rec_k if n_rec_k != 0 else 0
        recalls[uid] = n_rel_and_rec_k / n_rel if n_rel != 0 else 0
    
    # 返回平均精确率和召回率
    precision = sum(precisions.values()) / len(precisions)
    recall = sum(recalls.values()) / len(recalls)
    return precision, recall

# 计算 Precision@K 和 Recall@K
precision, recall = precision_recall_at_k(predictions, k=10, threshold=4.0)
print(f'Precision@10: {precision:.4f}')
print(f'Recall@10: {recall:.4f}')

五、结果展示

5.1 推荐列表示例

# 为用户 1 推荐电影
user_id = 1
title = 'Toy Story (1995)'

print(f'为用户 {user_id} 推荐的电影:')
print(hybrid_recommendations(user_id, title))

5.2 模型性能指标

  • RMSE:已在前面计算。
  • Precision@K 和 Recall@K:已在前面计算。

5.3 可视化

# 绘制评分分布图
plt.figure(figsize=(8,6))
sns.histplot(ratings['rating'], bins=10, kde=True)
plt.title('Rating Distribution')
plt.xlabel('Rating')
plt.ylabel('Count')
plt.show()

在这里插入图片描述


六、项目总结

  • 基于内容的推荐:利用电影的内容信息,能够为用户推荐相似的电影,但无法捕捉用户的个性化偏好。
  • 协同过滤推荐:基于用户行为数据,能够提供个性化的推荐,但对新用户和新物品存在冷启动问题。
  • 混合模型推荐:结合了基于内容和协同过滤的优势,提高了推荐的准确性和覆盖率。

七、后续改进

  • 引入深度学习模型:如神经协同过滤、自动编码器等,提升推荐效果。
  • 考虑时间因素:模型中加入时间衰减函数,反映用户兴趣的时效性。
  • 上下文感知推荐:结合用户的地理位置、设备等上下文信息,提供更精准的推荐。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

机智的小神仙儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值