【精选优质专栏推荐】


每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

在这里插入图片描述

引言

在众多基于用户偏好和历史行为为其推荐产品、服务或内容的方法中,矩阵分解作为协同过滤的一种高效技术脱颖而出,它能够从大规模用户-物品数据库中捕捉隐藏的交互模式。

本文将逐步展示如何在 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

总结

至此,我们已经构建了第一个基于矩阵分解的电影推荐系统,并实际测试了其效果。进一步探索推荐系统模型的方向可以包括:可视化有趣的数据模式(例如每个用户或电影的评分分布)、基于潜在因子表示寻找相似电影,或者直接可视化潜在因子本身。

更多推荐