电影推荐系统
一、项目概述
本项目旨在基于 MovieLens ml-latest-small 数据集,分别实现以下三种推荐方法:
- 基于内容的推荐(Content-Based Filtering)
- 协同过滤推荐(Collaborative Filtering)
- 混合模型推荐(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()
六、项目总结
- 基于内容的推荐:利用电影的内容信息,能够为用户推荐相似的电影,但无法捕捉用户的个性化偏好。
- 协同过滤推荐:基于用户行为数据,能够提供个性化的推荐,但对新用户和新物品存在冷启动问题。
- 混合模型推荐:结合了基于内容和协同过滤的优势,提高了推荐的准确性和覆盖率。
七、后续改进
- 引入深度学习模型:如神经协同过滤、自动编码器等,提升推荐效果。
- 考虑时间因素:模型中加入时间衰减函数,反映用户兴趣的时效性。
- 上下文感知推荐:结合用户的地理位置、设备等上下文信息,提供更精准的推荐。