从零开始构建推荐系统:Python 中的矩阵分解方法
【精选优质专栏推荐】
- 《AI 技术前沿》 —— 紧跟 AI 最新趋势与应用
- 《网络安全新手快速入门(附漏洞挖掘案例)》 —— 零基础安全入门必看
- 《BurpSuite 入门教程(附实战图文)》 —— 渗透测试必备工具详解
- 《网安渗透工具使用教程(全)》 —— 一站式工具手册
- 《CTF 新手入门实战教程》 —— 从题目讲解到实战技巧
- 《前后端项目开发(新手必知必会)》 —— 实战驱动快速上手
每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

引言
在众多基于用户偏好和历史行为为其推荐产品、服务或内容的方法中,矩阵分解作为协同过滤的一种高效技术脱颖而出,它能够从大规模用户-物品数据库中捕捉隐藏的交互模式。
本文将逐步展示如何在 Python 中构建一个基于矩阵分解的电影推荐系统。
具体来说,本教程将使用 Python 库 surprise,该库提供了便捷的矩阵分解算法实现,适合构建推荐系统。同时,我们将使用 MovieLens 100K 数据集,这是一个流行的电影推荐数据集,非常适合从实践角度熟悉推荐系统的运作。
注意:建议在开始本教程之前,对推荐系统的基本概念和原理有一定了解。
教程正文
ChatGPT 说:
第一步,导入所需的库和包。在此之前,我们需要先手动安装 surprise 库。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from surprise import Dataset, Reader, SVD
from surprise.model_selection import train_test_split, cross_validate
from surprise import accuracy
import requests
import zipfile
import io
import os
我们首先定义一个函数,从官方外部网站加载 MovieLens 100K 数据集,并解压下载的 .zip 文件。
def download_and_extract_movielens():
if not os.path.exists('ml-100k'):
print("Downloading MovieLens 100K dataset...")
url = "https://files.grouplens.org/datasets/movielens/ml-100k.zip"
r = requests.get(url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall()
print("Movielens 100K dataset downloaded and extracted successfully.")
else:
print("The dataset already exists. Download skipped.")
接下来,调用该函数加载数据,将数据存入 Pandas DataFrame,并获取一些基本信息。
download_and_extract_movielens()
ratings_df = pd.read_csv('ml-100k/u.data', sep='\t',
names=['user_id', 'item_id', 'rating', 'timestamp'])
print(f"Dataset shape: {ratings_df.shape}")
print(f"Number of unique users: {ratings_df['user_id'].nunique()}")
print(f"Number of unique movies: {ratings_df['item_id'].nunique()}")
print(f"Range of ratings: {ratings_df['rating'].min()} to {ratings_df['rating'].max()}")
打印输出展示了数据集的重要特性:
- 数据集形状: (100000, 4)
- 唯一用户数: 943
- 唯一电影数: 1682
- 评分范围: 1 到 5
接下来,借助 surprise 库中的 Dataset 和 Reader 两个类,我们将把数据集打包成适合库中矩阵分解实现使用的格式。同时,将数据划分为训练集和测试集,以便进行模型评估。
需要注意的是,在初始化 Reader 对象时,正确指定评分的数值范围非常关键。
reader = Reader(rating_scale=(1, 5))
data = Dataset.load_from_df(ratings_df[['user_id', 'item_id', 'rating']], reader)
trainset, testset = train_test_split(data, test_size=0.2, random_state=42)
接下来,我们进入实际操作,初始化、训练并评估矩阵分解模型。具体而言,我们将使用奇异值分解(SVD),这是一种常用的矩阵分解方法,其实现由 surprise 库的 SVD 类提供。如果你熟悉 scikit-learn 的模型训练流程,会发现操作过程非常类似。
model = SVD(n_factors=20, lr_all=0.01, reg_all=0.01, n_epochs=20, random_state=42)
model.fit(trainset)
predictions = model.test(testset)
rmse = accuracy.rmse(predictions)
mae = accuracy.mae(predictions)
print(f"Test RMSE: {rmse:.4f}")
print(f"Test MAE: {mae:.4f}")
在上述 SVD 模型实例化中,n_factors 是关键超参数,用于定义潜在特征空间的维度(本例中为 20),以便根据原始稀疏的用户-物品评分矩阵构建紧凑的用户和物品向量表示。
其他参数说明如下:
-
lr_all=0.01:学习率
-
reg_all=0.01:正则化参数,用于防止过拟合
-
n_epochs=20:训练轮数
调整这些参数可能会影响模型在测试集上的性能,评价指标包括 RMSE 和 MAE。
在本示例中,得到的结果为:
Test RMSE: 0.9576
Test MAE: 0.7455
为了获得更稳健的评估结果,还可以选择进行交叉验证:
cv_results = cross_validate(model, data, measures=['RMSE', 'MAE'], cv=5, verbose=True)
print(f"Average RMSE: {cv_results['test_rmse'].mean():.4f}")
print(f"Average MAE: {cv_results['test_mae'].mean():.4f}")
效果展示
现在让我们看看推荐系统的实际效果。
首先定义两个自定义函数:一个用于加载电影标题集合,另一个用于根据用户 ID 和所需推荐数量 N,利用训练好的模型生成该用户的 top-N 推荐电影列表,基于其在原始评分数据中反映的偏好。后者函数是整个代码中最具洞察力的部分,因此我们添加了一些内联注释以便更清晰地理解其处理流程。
def get_movie_names():
movies_df = pd.read_csv('ml-100k/u.item', sep='|', encoding='latin-1',
header=None, usecols=[0, 1],
names=['item_id', 'title'])
return movies_df
movies_df = get_movie_names()
def recommend_movies(user_id, n=10):
# 所有电影列表
all_movies = movies_df['item_id'].unique()
# 用户已评分的电影
rated_movies = ratings_df[ratings_df['user_id'] == user_id]['item_id'].values
# 用户尚未评分的电影
unrated_movies = np.setdiff1d(all_movies, rated_movies)
# 使用训练好的 SVD 模型预测未评分电影的评分
predictions = []
for item_id in unrated_movies:
predicted_rating = model.predict(user_id, item_id).est
predictions.append((item_id, predicted_rating))
# 按预测评分排序
predictions.sort(key=lambda x: x[1], reverse=True)
# 获取 top-N 推荐
top_recommendations = predictions[:n]
# 获取 top-N 推荐对应的电影标题
recommendations = pd.DataFrame(top_recommendations, columns=['item_id', 'predicted_rating'])
recommendations = recommendations.merge(movies_df, on='item_id')
return recommendations
使用这些函数生成实际推荐:
user_id = 42
recommendations = recommend_movies(user_id, n=10)
print(f"\nTop 10 recommended movies for user {user_id}:")
print(recommendations[['title', 'predicted_rating']])
示例输出:
Top 10 recommended movies for user 42:
title predicted_rating
0 Braveheart (1995) 4.946602
1 Singin' in the Rain (1952) 4.835148
2 Henry V (1989) 4.811671
3 Great Escape, The (1963) 4.754385
4 Babe (1995) 4.702876
5 Wrong Trousers, The (1993) 4.646727
6 My Fair Lady (1964) 4.631982
7 Air Force One (1997) 4.617786
8 Sabrina (1954) 4.541566
9 Patton (1970) 4.530220
总结
至此,我们已经构建了第一个基于矩阵分解的电影推荐系统,并实际测试了其效果。进一步探索推荐系统模型的方向可以包括:可视化有趣的数据模式(例如每个用户或电影的评分分布)、基于潜在因子表示寻找相似电影,或者直接可视化潜在因子本身。
更多推荐

所有评论(0)