本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目围绕使用Python3构建基于Movielens数据集的推荐系统展开,重点实现评分预测任务。Movielens数据集包含用户、电影及评分信息,适用于推荐算法研究。项目使用pandas、numpy、scikit-learn等工具完成数据加载、预处理、特征工程、模型训练与评分预测,涵盖协同过滤、矩阵分解等主流方法,并通过RMSE等指标评估模型性能。适合提升推荐系统构建能力与数据分析技能。
基于Movielens的推荐系统—评分预测  (Python3)

1. 推荐系统基础概念

推荐系统(Recommendation System)是信息过滤系统的一种,其核心目标是根据用户的历史行为与偏好,为其推荐可能感兴趣的物品(如商品、电影、新闻等)。它广泛应用于电商、视频平台、社交媒体等领域,已成为提升用户体验和平台转化率的关键技术之一。

从方法论角度,推荐系统主要分为三类: 协同过滤推荐 (Collaborative Filtering)——基于用户或物品之间的相似性进行推荐; 基于内容的推荐 (Content-Based Recommendation)——通过分析物品特征与用户兴趣匹配度进行推荐;以及 混合推荐系统 (Hybrid Recommendation System)——结合多种推荐策略以提升准确性和鲁棒性。

在实际应用中,推荐系统的核心任务通常分为 评分预测 (Rating Prediction)和 Top-N推荐 (Item Ranking)两类。前者旨在预测用户对未评分物品的具体评分值,后者则关注于为用户排序并推荐最可能感兴趣的N个物品。为衡量推荐系统的效果,常用的评价指标包括均方根误差(RMSE)、准确率(Precision)、召回率(Recall)、F1分数以及AUC-ROC曲线等。这些指标帮助开发者在模型迭代中不断优化性能,提升推荐质量。

2. Movielens数据集结构解析

Movielens 是一个广泛应用于推荐系统研究和教学的经典公开数据集。它不仅结构清晰、内容丰富,而且包含用户对电影的评分行为、用户属性和电影元信息等多维度数据。掌握 Movielens 数据集的结构与加载方式,是构建推荐系统的第一步,也为后续的数据分析、特征提取和模型训练打下基础。

本章将围绕 Movielens 数据集的组成结构、下载与加载方式、以及初步统计分析三个方面展开,帮助读者全面了解数据集的内部构成与使用方法,为后续章节中的数据处理与建模提供坚实的数据基础。

2.1 Movielens数据集的组成结构

Movielens 数据集通常包含三个核心数据文件:用户数据(users)、电影数据(movies)和评分数据(ratings)。每个文件都以文本格式(如 CSV 或 TSV)存储,结构清晰,字段定义明确。

2.1.1 用户数据(user数据集字段含义与示例)

用户数据文件(如 users.csv u.user )记录了每个用户的属性信息。典型的字段包括:

字段名 类型 描述
user_id int 用户唯一标识符
gender string 性别(M/F)
age int 年龄
occupation string 职业(编码或文本)
zip_code string 邮编
示例数据(前5行):
1|F|1|student|11111
2|M|53|technician|94043
3|M|31|writer|92515
4|M|42|technician|94041
5|F|17|student|94105
数据加载与字段解析代码:
import pandas as pd

# 加载用户数据,使用 '|' 作为分隔符
user_columns = ['user_id', 'gender', 'age', 'occupation', 'zip_code']
users = pd.read_csv('data/ml-100k/u.user', sep='|', names=user_columns)

# 查看前5行数据
print(users.head())
逻辑分析与参数说明:
  • pd.read_csv() :用于读取文本格式的数据文件。
  • sep='|' :指定字段之间的分隔符为竖线 | ,与数据文件格式一致。
  • names=user_columns :为每一列指定列名,便于后续处理。
  • users.head() :展示数据的前5行,用于快速查看数据结构和内容。

2.1.2 电影数据(movie数据集字段含义与示例)

电影数据文件(如 movies.csv u.item )记录了每部电影的元信息,主要包括电影 ID、标题、上映年份和类型等。

字段名 类型 描述
movie_id int 电影唯一标识符
title string 电影标题(含年份)
genres list 电影类型(多个,用
示例数据(前5行):
1|Toy Story (1995)|0|0|0|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
2|Jumanji (1995)|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0
3|Grumpier Old Men (1995)|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0
4|Waiting to Exhale (1995)|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0
5|Father of the Bride Part II (1995)|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0
加载电影数据代码:
# 定义电影数据列名(仅包含前几列)
movie_columns = ['movie_id', 'title', 'unknown', 'action', 'adventure', 
                 'animation', 'childrens', 'comedy', 'crime', 'documentary', 
                 'drama', 'fantasy', 'film_noir', 'horror', 'musical', 'mystery', 
                 'romance', 'sci-fi', 'thriller', 'war', 'western']

# 加载电影数据
movies = pd.read_csv('data/ml-100k/u.item', sep='|', names=movie_columns, encoding='latin-1')

# 查看前5行数据
print(movies.head())
逻辑分析与参数说明:
  • sep='|' :与用户数据一致,使用 | 作为分隔符。
  • encoding='latin-1' :指定编码格式,避免中文或特殊字符读取错误。
  • movie_columns :电影数据包含多个二进制字段表示电影类型,每个字段代表一个类型是否为1(是)或0(否)。

2.1.3 评分数据(rating数据集字段含义与示例)

评分数据文件(如 ratings.csv u.data )记录了用户对电影的评分行为,是推荐系统中最核心的数据。

字段名 类型 描述
user_id int 用户唯一标识符
movie_id int 电影唯一标识符
rating int 用户评分(1~5)
timestamp int 评分时间戳(Unix时间)
示例数据(前5行):
196 242 3   881250949
186 302 3   891717741
22  377 1   878887116
244 51  4   880606923
166 346 1   886397596
加载评分数据代码:
# 定义评分数据列名
rating_columns = ['user_id', 'movie_id', 'rating', 'timestamp']

# 加载评分数据,使用制表符 '\t' 作为分隔符
ratings = pd.read_csv('data/ml-100k/u.data', sep='\t', names=rating_columns)

# 查看前5行数据
print(ratings.head())
逻辑分析与参数说明:
  • sep='\t' :评分数据文件使用制表符 \t 分隔字段。
  • timestamp :可用于分析用户的评分行为随时间的变化趋势,例如用户活跃度建模。
  • ratings.head() :展示数据,观察评分分布和基本结构。

2.2 数据集的下载与加载方式

Movielens 数据集可通过其官方网站 https://grouplens.org/datasets/movielens/ 下载,提供多个版本,包括 ml-100k ml-1m ml-10m ml-20m 等。

2.2.1 官方网站获取与本地加载

访问 Movielens 官网,选择合适的数据集版本下载。例如:

  • ml-100k :适用于教学和入门实验
  • ml-1m :包含100万条评分数据,适合中等规模模型训练
  • ml-20m :最大规模数据集,适合大型系统测试

下载后解压数据包,通常包含如下文件:

data/
├── ml-100k/
│   ├── u.data       # 评分数据
│   ├── u.user       # 用户数据
│   ├── u.item       # 电影数据
│   └── u.genre      # 电影类型列表
加载方式总结:
  • 使用 Python 的 pandas 读取 CSV 或 TSV 文件。
  • 注意文件路径和编码格式,避免加载错误。
  • 使用 head() shape describe() 等方法初步查看数据分布。

2.2.2 数据格式解析(CSV、TSV等)

Movielens 不同版本的数据可能使用不同的格式:

数据集版本 格式说明
ml-100k 使用 TSV(Tab-separated)
ml-1m 使用 CSV(Comma-separated)
ml-20m 使用 CSV
通用加载代码模板:
import pandas as pd

# 判断文件分隔符(',' 或 '\t')
sep = '\t' if 'u.data' in file_name else ','

# 加载数据
df = pd.read_csv(file_path, sep=sep, header=None, names=columns)

2.3 数据集的统计分析与初步探索

在推荐系统中,数据的统计特性直接影响模型的表现。通过对 Movielens 数据集的初步统计分析,我们可以了解用户行为分布、电影热度分布、评分分布等关键信息。

2.3.1 用户评分行为分析

用户评分数量统计
# 统计每个用户评分的电影数量
user_rating_count = ratings.groupby('user_id')['movie_id'].count()

# 显示评分最多的前10个用户
print(user_rating_count.sort_values(ascending=False).head(10))
逻辑分析与可视化建议:
  • 该统计可识别活跃用户(高评分频率)与冷启动用户(低评分频率)。
  • 可使用 matplotlib seaborn 绘制直方图,观察用户评分行为的分布。
graph TD
    A[用户评分数据] --> B[统计每个用户评分数量]
    B --> C[排序后筛选Top用户]
    C --> D[绘制评分分布直方图]

2.3.2 电影热门程度分布

电影评分次数统计
# 统计每部电影被评分的次数
movie_rating_count = ratings.groupby('movie_id')['user_id'].count()

# 显示评分次数最多的前10部电影
print(movie_rating_count.sort_values(ascending=False).head(10))
逻辑分析与业务意义:
  • 高频评分电影通常具有较高人气,可作为推荐系统的热门推荐候选。
  • 稀疏评分电影可能导致冷启动问题,需引入混合推荐策略。

2.3.3 评分分布可视化与分析

评分值分布统计
import matplotlib.pyplot as plt

# 统计评分值的频率
rating_distribution = ratings['rating'].value_counts().sort_index()

# 绘制柱状图
plt.bar(rating_distribution.index, rating_distribution.values)
plt.xlabel('Rating')
plt.ylabel('Count')
plt.title('Distribution of Movie Ratings')
plt.show()
输出示例图(示意):
Rating: 1   2   3   4   5
Count:  60k 90k 200k 250k 150k
逻辑分析与结论:
  • 多数用户倾向于给出中等偏上评分(如4、5分)。
  • 极端评分(如1分)较少,可能用于识别恶意评分或异常用户。

通过本章的详细解析,我们不仅掌握了 Movielens 数据集的基本组成结构,还学会了如何下载、加载和进行初步统计分析。这些数据处理技能是构建推荐系统的基础,也为后续章节中的模型训练和优化提供了数据支持。在下一章中,我们将进一步探讨如何将这些数据整合并构建用户-物品评分矩阵,为推荐算法提供结构化输入。

3. 用户-电影评分数据加载与处理

推荐系统的核心之一是准确地加载和处理用户-电影的评分数据。本章节将从数据加载开始,逐步深入到数据合并、评分矩阵构建、索引优化以及数据划分策略。整个过程不仅涉及数据结构的转换,还包含性能优化和数据采样策略的应用,是推荐系统构建流程中承上启下的关键环节。

3.1 数据加载与初步处理

在推荐系统中,评分数据是最基础的数据形式之一。Movielens数据集中,评分数据通常存储在 ratings.csv 文件中,包含用户ID、电影ID、评分值以及时间戳等字段。为了高效地处理这些数据,我们通常使用Python中的 pandas 库进行读取和初步清洗。

3.1.1 使用Pandas加载评分数据

Pandas是Python中用于数据处理的强大库,能够轻松地读取CSV、TSV等格式的文件。以下代码展示了如何使用 pandas 加载评分数据:

import pandas as pd

# 加载评分数据
ratings_path = 'ratings.csv'
ratings_df = pd.read_csv(ratings_path)

# 查看前5行数据
ratings_df.head()
userId movieId rating timestamp
1 31 2.5 1260759140
1 1029 3.0 1260759179
1 1065 3.0 1260759182
1 1097 3.5 1260759185
1 1172 3.5 1260759205

代码解释:
- pd.read_csv() :读取CSV格式的评分数据文件。
- ratings_df.head() :查看数据前5行,用于快速了解数据结构。

参数说明:
- userId :用户ID,标识不同用户。
- movieId :电影ID,标识不同电影。
- rating :用户对电影的评分(范围1.0~5.0)。
- timestamp :评分发生的时间戳,可用于时间序列分析。

3.1.2 合并用户与电影信息

评分数据本身只包含用户ID和电影ID,为了后续分析用户的性别、年龄、职业,以及电影的标题、类型等信息,需要将评分数据与用户数据和电影数据进行合并。

# 加载用户数据和电影数据
users_df = pd.read_csv('users.csv')
movies_df = pd.read_csv('movies.csv')

# 合并评分与用户信息
ratings_with_users = pd.merge(ratings_df, users_df, on='userId')

# 合并评分与电影信息
ratings_with_movies = pd.merge(ratings_with_users, movies_df, on='movieId')

# 查看合并后的数据
ratings_with_movies.head()

逻辑分析:
- pd.merge() 函数用于根据指定字段(如 userId movieId )将两个DataFrame合并。
- 合并后的数据包含了用户属性(如性别、年龄)、电影属性(如标题、类型),便于后续的特征提取与分析。

3.1.3 构建用户-物品评分矩阵框架

评分矩阵是推荐系统中最基础的数据结构,其行表示用户,列表示电影,矩阵中的值表示用户对电影的评分。

# 构建用户-电影评分矩阵
rating_matrix = ratings_df.pivot(index='userId', columns='movieId', values='rating')

# 查看评分矩阵的前5行
rating_matrix.head()
movieId 1 2 3 4 5 6
userId
1 NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN
5 NaN NaN NaN NaN NaN NaN

逻辑分析:
- pivot() 函数用于将长格式数据转换为宽格式。
- 评分矩阵中存在大量 NaN 值,表示该用户未对该电影进行评分,后续需要进行填充或稀疏处理。

3.2 数据结构转换与索引优化

在实际应用中,评分矩阵往往是稀疏的,直接使用全矩阵会浪费大量内存资源。因此,需要对评分矩阵进行稀疏化处理,并对用户和电影ID进行索引映射,以提升后续计算效率。

3.2.1 用户ID与电影ID的索引映射

由于原始的用户ID和电影ID可能并不连续,直接使用可能导致索引空间浪费。因此,我们需要将其映射为从0开始的连续整数索引。

from sklearn.preprocessing import LabelEncoder

# 创建用户和电影的标签编码器
user_encoder = LabelEncoder()
movie_encoder = LabelEncoder()

# 对用户ID和电影ID进行编码
ratings_df['user_idx'] = user_encoder.fit_transform(ratings_df['userId'])
ratings_df['movie_idx'] = movie_encoder.fit_transform(ratings_df['movieId'])

# 查看新增索引列
ratings_df.head()
userId movieId rating timestamp user_idx movie_idx
1 31 2.5 1260759140 0 0
1 1029 3.0 1260759179 0 1
1 1065 3.0 1260759182 0 2
1 1097 3.5 1260759185 0 3
1 1172 3.5 1260759205 0 4

逻辑分析:
- LabelEncoder() 将原始ID转换为从0开始的整数索引。
- user_idx movie_idx 可以用于构建稀疏矩阵或模型训练。

3.2.2 稀疏评分矩阵的构建与存储方式

考虑到评分矩阵的稀疏性,我们可以使用 scipy.sparse 库中的 coo_matrix csr_matrix 来构建稀疏矩阵,从而节省内存开销。

from scipy.sparse import csr_matrix

# 构建稀疏评分矩阵
num_users = ratings_df['user_idx'].nunique()
num_movies = ratings_df['movie_idx'].nunique()

sparse_matrix = csr_matrix((ratings_df['rating'], (ratings_df['user_idx'], ratings_df['movie_idx'])),
                           shape=(num_users, num_movies))

# 输出稀疏矩阵的维度
sparse_matrix.shape

逻辑分析:
- csr_matrix 是一种高效的稀疏矩阵存储方式,适合进行矩阵运算。
- 参数 (ratings_df['rating'], (ratings_df['user_idx'], ratings_df['movie_idx'])) 表示评分值与对应的行列索引。

3.2.3 数据结构的内存优化策略

在处理大规模数据时,内存优化至关重要。以下是一些常用的优化策略:

优化策略 说明
使用稀疏矩阵 节省大量内存空间,适合推荐系统中常见的评分矩阵
使用整数索引 比字符串索引更节省内存,且提升访问速度
使用低精度数据类型 例如将 float64 转为 float32 ,减少内存占用
# 将评分数据转为float32类型
ratings_df['rating'] = ratings_df['rating'].astype('float32')

# 查看内存使用情况
ratings_df.memory_usage(deep=True)

3.3 数据采样与划分训练集/测试集

在构建推荐系统模型时,数据划分是评估模型性能的关键步骤。常见的划分方法包括随机采样、时间序列划分和交叉验证。

3.3.1 随机采样方法

随机采样是一种简单而常用的方法,适用于数据分布较为均匀的情况。

from sklearn.model_selection import train_test_split

# 随机划分训练集和测试集(80%训练,20%测试)
train_df, test_df = train_test_split(ratings_df, test_size=0.2, random_state=42)

# 查看训练集和测试集大小
len(train_df), len(test_df)

逻辑分析:
- train_test_split() 函数用于随机划分数据集。
- test_size=0.2 表示测试集占比20%。
- random_state=42 保证结果可复现。

3.3.2 时间序列划分法

对于评分数据具有明显时间特性的场景,时间序列划分能更准确地模拟真实推荐场景。

# 按时间排序
ratings_sorted = ratings_df.sort_values(by='timestamp')

# 按时间划分训练集和测试集(前80%为训练集)
split_idx = int(len(ratings_sorted) * 0.8)
train_df_time = ratings_sorted.iloc[:split_idx]
test_df_time = ratings_sorted.iloc[split_idx:]

逻辑分析:
- sort_values(by='timestamp') 按时间戳排序。
- iloc 用于根据索引划分训练集和测试集。

3.3.3 交叉验证的数据划分策略

交叉验证可以更全面地评估模型性能,尤其适用于小数据集。

from sklearn.model_selection import KFold

# 初始化5折交叉验证
kf = KFold(n_splits=5, shuffle=False)

# 进行交叉验证划分
for train_idx, val_idx in kf.split(ratings_df):
    train_fold = ratings_df.iloc[train_idx]
    val_fold = ratings_df.iloc[val_idx]
    # 在此进行模型训练与验证

mermaid流程图:

graph TD
    A[原始评分数据] --> B{是否按时间划分}
    B -- 是 --> C[按时间排序]
    B -- 否 --> D[随机划分]
    C --> E[划分训练集与测试集]
    D --> E
    E --> F[构建模型并验证]

逻辑分析:
- KFold 将数据划分为多个子集,轮流作为验证集。
- shuffle=False 确保时间顺序不被打乱,适用于时间序列任务。

总结:

本章详细介绍了用户-电影评分数据的加载、合并、评分矩阵构建、索引优化以及数据划分策略。通过Pandas加载数据、合并用户与电影信息,构建评分矩阵,进而进行稀疏化处理和索引映射,最终采用随机、时间序列和交叉验证的方式划分训练集与测试集。这些步骤构成了推荐系统数据预处理的核心流程,为后续的模型训练和评估打下了坚实基础。

4. 数据清洗与缺失值处理

在推荐系统构建过程中,数据清洗和缺失值处理是不可忽视的重要环节。真实世界的数据往往存在缺失、异常和噪声等问题,这些问题如果处理不当,会直接影响模型的训练效果和推荐质量。本章将围绕 Movielens 数据集,系统地讲解如何检测和处理缺失值、识别和清洗异常数据,以及如何进行评分数据的标准化与归一化处理,以提升数据质量并为后续的模型训练打下坚实基础。

4.1 缺失值检测与处理方法

缺失值是数据集中常见的问题之一,尤其在用户行为数据中更为常见。对缺失值的处理直接影响推荐系统的准确性和鲁棒性。

4.1.1 缺失值的统计分析

在处理缺失值之前,首先需要对缺失值的分布和比例进行统计分析。可以使用 Pandas 提供的 isnull() sum() 方法快速统计每个字段的缺失数量。

import pandas as pd

# 假设已加载评分数据
ratings = pd.read_csv('ratings.csv')
missing_values = ratings.isnull().sum()
print(missing_values)

执行结果示例:

userId       0
movieId      0
rating       0
timestamp    0
dtype: int64

逻辑分析与参数说明:

  • isnull() 方法会返回一个布尔类型的 DataFrame,表示每个单元格是否为缺失值。
  • sum() 方法将每列的缺失值数目进行累加。
  • 通过输出结果可以判断是否存在缺失值,并为后续处理提供依据。

如果发现某些字段存在大量缺失值,可以进一步使用 missingno 库进行可视化分析:

import missingno as msno
import matplotlib.pyplot as plt

msno.matrix(ratings)
plt.show()

该图以矩阵形式展示数据缺失情况,黑色线条表示缺失,白色表示完整。

4.1.2 删除缺失样本与插值填充策略

当缺失值比例较小时,可以选择直接删除缺失样本;而当缺失值比例较高时,则可以采用插值填充。

删除缺失样本:

# 删除含有缺失值的行
ratings_cleaned = ratings.dropna()
  • dropna() 方法默认删除任何包含缺失值的行。
  • 适用于缺失值比例较小的情况,避免影响数据完整性。

插值填充:

对于数值型字段(如评分),可以使用线性插值、时间序列插值等方法进行填补。

# 假设 rating 列存在缺失值
ratings['rating'] = ratings['rating'].interpolate(method='linear')
  • interpolate() 方法提供多种插值方法,如线性插值、多项式插值等。
  • 适用于时间序列或连续型数据。

4.1.3 基于用户/物品均值的缺失评分填充

在推荐系统中,评分数据通常以用户-物品评分矩阵形式存在,缺失值可以使用用户均值或物品均值来填充,以保持评分趋势的一致性。

# 计算用户均值
user_mean = ratings.groupby('userId')['rating'].transform('mean')
# 填充缺失值
ratings['rating'] = ratings['rating'].fillna(user_mean)

逻辑分析:

  • groupby('userId') 按用户分组, transform('mean') 返回每个用户的平均评分。
  • fillna() 方法将缺失值用对应的用户平均值进行填充。
  • 该方法保留了用户的评分偏好,适合冷启动用户或评分稀疏的场景。

4.2 异常值与噪音数据识别

异常值和噪音数据同样会影响推荐系统的性能,识别并处理这些数据是数据清洗的重要步骤。

4.2.1 评分范围的合法性校验

Movielens 数据集的评分范围为 0.5 到 5.0,步长为 0.5。因此,可以对评分字段进行合法性校验,过滤掉非法值。

# 过滤合法评分范围
ratings = ratings[(ratings['rating'] >= 0.5) & (ratings['rating'] <= 5.0)]

逻辑分析:

  • 使用布尔索引筛选出评分在 0.5 到 5.0 之间的记录。
  • 避免因异常评分导致模型偏差。

4.2.2 高频用户与冷门电影的异常处理

某些用户可能评分频率极高,或者某些电影评分极少,这些都可能成为异常数据。

高频用户检测:

user_rating_count = ratings.groupby('userId').size()
high_freq_users = user_rating_count[user_rating_count > 1000].index
ratings = ratings[~ratings['userId'].isin(high_freq_users)]
  • groupby('userId').size() 统计每个用户的评分数量。
  • 假设设定评分超过 1000 条的用户为高频用户。
  • 使用 isin() 方法筛选并删除这些用户。

冷门电影检测:

movie_rating_count = ratings.groupby('movieId').size()
cold_movies = movie_rating_count[movie_rating_count < 10].index
ratings = ratings[~ratings['movieId'].isin(cold_movies)]
  • 检测评分不足 10 条的电影,作为冷门电影。
  • 删除这些电影记录以减少噪声。

4.3 数据标准化与归一化处理

为了使评分数据更适用于模型训练,通常需要进行标准化或归一化处理,使得数据在统一尺度上。

4.3.1 评分值的归一化方法

归一化(Normalization)是将评分缩放到 [0,1] 区间,常用于协同过滤等算法中。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
ratings['rating_normalized'] = scaler.fit_transform(ratings[['rating']])

逻辑分析:

  • MinMaxScaler() 会将数据缩放到指定范围(默认 [0,1])。
  • 适用于需要比较评分大小的场景,如 Top-N 推荐。

4.3.2 Z-score标准化在推荐系统中的应用

Z-score 标准化适用于评分分布存在偏态或离群值的情况。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
ratings['rating_standardized'] = scaler.fit_transform(ratings[['rating']])

逻辑分析:

  • StandardScaler() 会将数据转换为均值为 0,标准差为 1 的分布。
  • 适用于基于相似度计算的算法,如基于内容的推荐。

4.3.3 归一化对模型训练的影响

标准化后的评分数据有助于提高模型训练的收敛速度和稳定性。例如在使用协同过滤时,相似度计算会更准确。

graph TD
    A[原始评分数据] --> B{是否需要标准化?}
    B -->|是| C[进行归一化或Z-score标准化]
    B -->|否| D[保留原始评分]
    C --> E[构建相似度矩阵]
    D --> E
    E --> F[训练推荐模型]

流程图说明:

  • 数据是否标准化会影响后续相似度计算和模型训练。
  • 标准化后的数据可以提升模型泛化能力。

4.3.4 实例分析:标准化前后模型对比

我们可以使用简单的用户相似度计算来展示标准化对推荐结果的影响。

from sklearn.metrics.pairwise import cosine_similarity

# 原始评分矩阵
user_item_matrix = ratings.pivot_table(index='userId', columns='movieId', values='rating')

# 标准化后的评分矩阵
user_item_matrix_normalized = ratings.pivot_table(index='userId', columns='movieId', values='rating_normalized')

# 相似度计算
similarity_raw = cosine_similarity(user_item_matrix.fillna(0))
similarity_norm = cosine_similarity(user_item_matrix_normalized.fillna(0))

# 对比相似度矩阵差异
print("原始评分相似度矩阵:")
print(similarity_raw[:5, :5])
print("标准化后相似度矩阵:")
print(similarity_norm[:5, :5])

输出结果示例(仅展示前5x5):

原始评分相似度矩阵:
[[1.   0.78 0.67 0.81 0.6 ]
 [0.78 1.   0.89 0.93 0.72]
 [0.67 0.89 1.   0.85 0.65]
 [0.81 0.93 0.85 1.   0.7 ]
 [0.6  0.72 0.65 0.7  1.  ]]

标准化后相似度矩阵:
[[1.   0.75 0.62 0.79 0.58]
 [0.75 1.   0.88 0.92 0.71]
 [0.62 0.88 1.   0.83 0.63]
 [0.79 0.92 0.83 1.   0.69]
 [0.58 0.71 0.63 0.69 1.  ]]

分析结论:

  • 标准化后的相似度矩阵略低于原始评分矩阵,说明标准化减少了评分极端值对相似度计算的影响。
  • 有助于提升推荐结果的稳定性和一致性。

小结

本章详细介绍了推荐系统构建中数据清洗与缺失值处理的关键步骤,包括缺失值的检测与填充策略、异常值的识别与处理,以及评分数据的标准化与归一化处理。通过代码实例展示了不同处理方法的具体实现及其对推荐模型的影响。数据清洗不仅是模型训练的前提,更是保证推荐质量的基础。下一章将深入探讨推荐系统中的特征工程方法,为构建更强大的推荐模型做准备。

5. 特征工程与数据标准化

在推荐系统中,特征工程是构建高质量模型的关键步骤之一。通过合理的特征提取和处理,可以显著提升模型的表达能力和预测精度。本章将深入探讨用户和电影的特征构建方法,包括用户评分行为的特征提取、电影的类别编码与热度特征构建,以及如何将这些特征融合为统一的向量表示,为后续的推荐模型训练做好准备。

5.1 用户特征提取与建模

用户特征是推荐系统中最重要的输入之一,直接关系到推荐结果的个性化程度。有效的用户特征可以反映用户的兴趣偏好、活跃程度和行为模式。

5.1.1 用户评分行为特征(评分频率、平均评分)

在Movielens等评分数据集中,每个用户对电影的评分记录构成了其行为的基础数据。我们可以从中提取以下特征:

  • 评分频率 :表示用户活跃程度,反映用户参与推荐系统的积极性。
  • 平均评分 :反映用户整体的评分倾向,比如是否偏向于打高分或低分。
  • 评分分布 :如评分的方差、评分的分布区间等,用于刻画用户的评分稳定性。
示例代码:计算用户评分行为特征
import pandas as pd

# 加载评分数据
ratings = pd.read_csv('ratings.csv')

# 用户评分行为特征统计
user_stats = ratings.groupby('userId').agg(
    rating_count=('rating', 'count'),
    avg_rating=('rating', 'mean'),
    rating_std=('rating', 'std'),
    max_rating=('rating', 'max'),
    min_rating=('rating', 'min')
)

print(user_stats.head())
代码解释:
  • ratings.groupby('userId') :按用户ID进行分组;
  • agg :聚合函数,计算每个用户的评分统计特征;
  • rating_count :每个用户的评分数量;
  • avg_rating :平均评分;
  • rating_std :评分的标准差,反映评分的波动情况;
  • max_rating min_rating :评分的极值,用于判断评分范围。
参数说明:
  • userId :用户唯一标识符;
  • rating :用户对电影的评分值(通常为1~5分);
  • count :计数函数;
  • mean :平均值;
  • std :标准差;
  • max / min :最大值和最小值。
特征分析:

通过这些统计特征,我们可以发现活跃用户、偏好特定评分区间的用户等行为模式。例如,评分标准差大的用户可能对电影的评价比较不稳定,模型在处理这类用户时可能需要更复杂的策略。

5.1.2 用户兴趣标签提取

用户兴趣标签是对用户偏好的一种抽象表示,可以通过用户的评分行为提取。例如,如果一个用户频繁给科幻类电影打高分,那么可以认为该用户偏好“科幻”类别。

方法:基于用户评分的电影标签加权平均
# 假设已有movies数据包含genre信息(如:Action|Sci-Fi)
movies = pd.read_csv('movies.csv')

# 将genres字段拆分为多个标签
movies['genres'] = movies['genres'].str.split('|')

# 构建用户-标签评分映射
user_genre_ratings = []

for _, row in ratings.iterrows():
    user_id = row['userId']
    movie_id = row['movieId']
    rating = row['rating']
    genres = movies.loc[movies['movieId'] == movie_id, 'genres'].iloc[0]
    for genre in genres:
        user_genre_ratings.append({
            'userId': user_id,
            'genre': genre,
            'rating': rating
        })

user_genre_df = pd.DataFrame(user_genre_ratings)

# 计算每个用户在每个genre上的平均评分
user_interest = user_genre_df.groupby(['userId', 'genre'])['rating'].mean().unstack(fill_value=0)

print(user_interest.head())
代码解释:
  • 首先将每部电影的 genres 字段拆分为多个标签;
  • 然后对每个用户在每个电影标签上的评分进行汇总;
  • 最后使用 unstack 函数将标签维度展开为列,形成“用户-兴趣标签”矩阵。
参数说明:
  • genres :电影的类型,如“Action|Sci-Fi”;
  • unstack() :将多级索引中的某一层转换为列;
  • fill_value=0 :填充缺失值为0,表示未对该类电影评分。
特征分析:

该方法可以生成用户对各类电影的偏好评分,作为推荐模型的输入特征。例如,若某用户对“Romance”类电影评分较高,推荐系统可优先推荐该类型电影。

5.2 电影特征构建

电影特征是推荐系统中另一类关键输入,用于描述电影本身的属性,如类型、上映年份、热度等。

5.2.1 电影类型One-Hot编码

电影的类型通常是一个多值字段,如“Action|Comedy”,为了便于模型处理,可以将其转换为One-Hot编码。

示例代码:电影类型One-Hot编码
from sklearn.preprocessing import MultiLabelBinarizer

# 拆分genres字段
movies['genres'] = movies['genres'].str.split('|')

# 使用MultiLabelBinarizer进行One-Hot编码
mlb = MultiLabelBinarizer()
genre_binary = pd.DataFrame(mlb.fit_transform(movies['genres']), columns=mlb.classes_, index=movies.index)

# 合并原始电影数据
movies_encoded = pd.concat([movies[['movieId', 'title']], genre_binary], axis=1)

print(movies_encoded.head())
代码解释:
  • str.split('|') :将genres字段按“|”拆分成列表;
  • MultiLabelBinarizer :对多标签进行二值化处理;
  • pd.concat :将编码后的特征与原始电影数据合并。
参数说明:
  • MultiLabelBinarizer() :用于将多个标签转换为二进制向量;
  • fit_transform() :先拟合标签集,再转换为二值化特征;
  • classes_ :返回所有唯一标签列表。
特征分析:

One-Hot编码可以有效表达电影的类型属性,便于模型理解不同类别的电影特征。例如,一部电影属于“Action”和“Sci-Fi”两类,则其对应的编码向量中这两个位置为1。

5.2.2 电影热度特征与时间衰减因子

电影的热度通常与其被评分的次数相关。为了更真实地反映电影的受欢迎程度,可以引入时间衰减因子,对早期评分给予较低权重。

示例代码:计算电影热度与时间衰减评分
import numpy as np

# 假设ratings中包含'timestamp'字段
ratings['timestamp'] = pd.to_datetime(ratings['timestamp'], unit='s')

# 计算当前时间与评分时间的间隔(以天为单位)
ratings['days_ago'] = (pd.Timestamp.now() - ratings['timestamp']).dt.days

# 引入时间衰减因子(如指数衰减)
ratings['decay_factor'] = np.exp(-ratings['days_ago'] / 365)

# 计算带时间衰减的评分
ratings['weighted_rating'] = ratings['rating'] * ratings['decay_factor']

# 汇总每部电影的热度和加权评分
movie_stats = ratings.groupby('movieId').agg(
    num_ratings=('rating', 'count'),
    total_weighted_rating=('weighted_rating', 'sum')
)
movie_stats['popularity'] = movie_stats['total_weighted_rating'] / movie_stats['num_ratings']

print(movie_stats.head())
代码解释:
  • timestamp :评分时间戳;
  • days_ago :评分距离当前时间的天数;
  • decay_factor :时间衰减因子,这里采用指数衰减;
  • popularity :热度值,为加权评分的平均值。
参数说明:
  • unit='s' :将时间戳解析为秒;
  • np.exp() :指数函数;
  • decay_factor 的公式可以根据业务需求调整。
特征分析:

引入时间衰减因子后,可以更好地反映电影的当前热度,避免早期评分对当前推荐产生过大影响。例如,一部电影在五年前评分很高,但近两年无人问津,则其热度应相应降低。

5.3 特征融合与向量化表示

将用户特征与电影特征统一表示为向量,是推荐系统中非常重要的一步。这一步通常涉及特征拼接、嵌入向量(Embedding)引入以及特征缩放。

5.3.1 用户-物品特征拼接

我们可以将用户特征与电影特征拼接成一个统一的特征向量,供模型使用。

示例代码:用户-电影特征拼接
# 假设已有 user_interest(用户兴趣标签) 和 movies_encoded(电影One-Hot编码)
# 创建用户-电影特征对
user_movie_features = []

for _, row in ratings.iterrows():
    user_id = row['userId']
    movie_id = row['movieId']
    user_feat = user_interest.loc[user_id].values
    movie_feat = movies_encoded[movies_encoded['movieId'] == movie_id].iloc[0, 2:].values
    combined_feat = np.concatenate([user_feat, movie_feat])
    user_movie_features.append(combined_feat)

features = np.array(user_movie_features)
代码解释:
  • np.concatenate() :将用户特征与电影特征拼接为一个向量;
  • features :最终的特征矩阵,用于模型输入。
参数说明:
  • user_interest.loc[user_id] :获取该用户的兴趣标签评分;
  • movies_encoded.iloc[0, 2:] :跳过movieId和title,取编码后的特征。
特征分析:

拼接后的特征向量包含了用户偏好和电影属性,模型可基于此进行评分预测或推荐排序。

5.3.2 嵌入向量(Embedding)的初步介绍

除了直接拼接特征,还可以使用嵌入向量(Embedding)将用户和电影映射到一个低维空间中。例如,使用Embedding层将用户ID和电影ID分别映射为向量,再进行融合。

示例代码:使用PyTorch构建简单的Embedding层
import torch
import torch.nn as nn

# 用户和电影的Embedding层
embedding_dim = 32
num_users = ratings['userId'].nunique()
num_movies = ratings['movieId'].nunique()

user_embedding = nn.Embedding(num_users, embedding_dim)
movie_embedding = nn.Embedding(num_movies, embedding_dim)

# 示例:获取用户1和电影100的嵌入向量
user_ids = torch.tensor([1])
movie_ids = torch.tensor([100])

user_vec = user_embedding(user_ids)
movie_vec = movie_embedding(movie_ids)

# 拼接用户和电影的Embedding
combined_vec = torch.cat([user_vec, movie_vec], dim=1)

print("Combined Embedding Vector Shape:", combined_vec.shape)
代码解释:
  • nn.Embedding :用于将离散的ID映射为连续向量;
  • torch.cat :将用户和电影的向量拼接;
  • dim=1 :在特征维度上拼接。
参数说明:
  • num_users num_movies :用户和电影的总数;
  • embedding_dim :嵌入向量的维度;
  • user_embedding movie_embedding :学习得到的用户/电影向量。
特征分析:

Embedding方法可以有效捕捉用户和电影的隐含特征,尤其适用于协同过滤和深度推荐模型。

5.3.3 特征缩放与模型输入格式统一

为了提高模型训练的稳定性,通常需要对特征进行标准化或归一化处理。

示例代码:使用StandardScaler对特征进行标准化
from sklearn.preprocessing import StandardScaler

# 假设features为拼接后的特征矩阵
scaler = StandardScaler()
scaled_features = scaler.fit_transform(features)

print("Scaled Features Shape:", scaled_features.shape)
代码解释:
  • StandardScaler :将特征缩放为均值为0、标准差为1的分布;
  • fit_transform :拟合并转换数据。
参数说明:
  • fit_transform() :先拟合特征分布,再进行标准化;
  • scaled_features :标准化后的特征矩阵。
特征分析:

标准化可以加速模型的收敛过程,尤其对于基于梯度下降的模型(如神经网络)至关重要。

总结

本章从用户特征、电影特征的构建入手,详细介绍了评分行为统计、兴趣标签提取、One-Hot编码、热度计算、时间衰减因子、特征拼接、Embedding嵌入、标准化等关键步骤。这些方法构成了推荐系统中特征工程的核心内容,为后续模型训练和推荐效果优化奠定了坚实基础。

后续章节预告 :第六章将深入讲解协同过滤算法,包括用户-用户和物品-物品推荐的实现细节,以及相似度计算方法。

6. 协同过滤算法原理与实现(用户-用户、物品-物品)

6.1 协同过滤的基本原理

6.1.1 基于用户的行为相似性计算

协同过滤(Collaborative Filtering, CF)是推荐系统中最经典的算法之一。其核心思想是“相似用户喜欢相似内容”。在用户-用户协同过滤中,系统通过计算用户之间的相似度,找出与目标用户兴趣相似的邻居用户,然后根据邻居用户对物品的评分来预测目标用户可能感兴趣的物品。

以用户A和用户B为例,若他们共同评价的电影数量较多且评分接近,则认为这两个用户兴趣相似。计算相似度的方式有多种,最常见的是余弦相似度和皮尔逊相关系数。

例如,余弦相似度计算公式如下:

\text{sim}(u,v) = \frac{\sum_{i \in I_{uv}} r_{ui} \cdot r_{vi}}{\sqrt{\sum_{i \in I_u} r_{ui}^2} \cdot \sqrt{\sum_{i \in I_v} r_{vi}^2}}}

其中,$ r_{ui} $ 表示用户 $ u $ 对物品 $ i $ 的评分,$ I_{uv} $ 表示用户 $ u $ 和用户 $ v $ 共同评分的物品集合。

6.1.2 基于物品的相似性推荐

与用户协同过滤相对应的是物品-物品协同过滤。该方法的核心思想是:如果两个物品经常被同一组用户同时喜欢,则这两个物品是相似的。在实际应用中,物品相似度矩阵的构建通常比用户相似度矩阵更稳定,因为物品数量相对较少且变化较慢。

物品相似度计算方式与用户相似度类似,只不过计算对象是物品之间的评分关系。例如,对于物品 $ i $ 和 $ j $,其相似度可表示为:

\text{sim}(i,j) = \frac{\sum_{u \in U_{ij}} r_{ui} \cdot r_{uj}}{\sqrt{\sum_{u \in U_i} r_{ui}^2} \cdot \sqrt{\sum_{u \in U_j} r_{uj}^2}}}

其中,$ U_{ij} $ 是同时对物品 $ i $ 和 $ j $ 评分的用户集合。

6.1.3 相似度计算方法(余弦、皮尔逊、Jaccard等)

不同的相似度计算方法适用于不同的数据场景,以下是三种常用的相似度计算方式:

相似度方法 公式 特点
余弦相似度 $ \text{sim}(u,v) = \frac{u \cdot v}{|u| |v|} $ 适用于评分向量方向相近的情况
皮尔逊相关系数 $ \rho(u,v) = \frac{\sum (r_{ui} - \bar{r} u)(r {vi} - \bar{r} v)}{\sqrt{\sum (r {ui} - \bar{r} u)^2} \cdot \sqrt{\sum (r {vi} - \bar{r}_v)^2}}} $ 考虑了用户评分的偏差,适合评分分布不一致的数据
Jaccard相似度 $ \frac{ I_u \cap I_v

以下是一个使用Python计算用户相似度的代码示例:

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# 用户-物品评分矩阵,行表示用户,列表示电影
ratings = np.array([
    [5, 3, 0, 1],
    [4, 0, 0, 1],
    [1, 1, 0, 5],
    [1, 0, 0, 4],
    [0, 1, 5, 4],
])

# 计算用户相似度矩阵
user_similarities = cosine_similarity(ratings)
print("用户相似度矩阵:\n", user_similarities)
代码逐行解释:
  1. ratings :构建一个用户-物品评分矩阵,其中0表示用户未对该物品评分。
  2. cosine_similarity :使用余弦相似度计算用户之间的相似度,输出一个 $ n \times n $ 的矩阵(n为用户数量)。
  3. print(...) :输出用户之间的相似度矩阵,可用于后续推荐逻辑。

6.2 用户-用户协同过滤实现

6.2.1 用户相似度矩阵构建

在用户-用户协同过滤中,首先需要构建用户相似度矩阵。该矩阵的每个元素 $ S_{uv} $ 表示用户 $ u $ 和用户 $ v $ 的相似程度。该矩阵是后续推荐计算的基础。

我们可以使用之前构建的 user_similarities 矩阵进行后续计算。

6.2.2 Top-K邻居用户选取

为了提高推荐效率,通常不会使用所有用户进行推荐,而是选择相似度最高的K个邻居用户(Top-K Nearest Neighbors)来进行预测。

例如,假设我们选择K=2,即为每个用户找出与其最相似的两个用户:

def get_top_k_users(similarities, k=2):
    top_k_indices = np.argsort(similarities)[-k-1:-1][::-1]
    return top_k_indices

# 为用户0找出最相似的2个用户
top_users = get_top_k_users(user_similarities[0], k=2)
print("用户0最相似的Top-2用户索引:", top_users)
代码逐行解释:
  1. np.argsort(...) :对相似度数组排序,返回从小到大排序的索引。
  2. [-k-1:-1] :取最后k个最大值的索引(去掉自己)。
  3. [::-1] :反转数组,使得索引按相似度从高到低排列。

6.2.3 评分预测公式与实现代码

评分预测是用户协同过滤的核心步骤。评分预测公式如下:

\hat{r} {ui} = \bar{r}_u + \frac{\sum {v \in N(u)} \text{sim}(u,v) \cdot (r_{vi} - \bar{r} v)}{\sum {v \in N(u)} |\text{sim}(u,v)|}

其中:

  • $ \hat{r}_{ui} $:用户 $ u $ 对物品 $ i $ 的预测评分
  • $ \bar{r}_u $:用户 $ u $ 的平均评分
  • $ N(u) $:用户 $ u $ 的邻居用户集合
  • $ r_{vi} $:用户 $ v $ 对物品 $ i $ 的评分

以下是一个实现评分预测的Python函数:

def predict_user_rating(user_idx, item_idx, ratings, similarities, k=2):
    user_ratings = ratings[user_idx]
    if user_ratings[item_idx] != 0:
        return user_ratings[item_idx]  # 已评分,无需预测

    # 找出邻居用户
    top_users = get_top_k_users(similarities[user_idx], k=k)
    # 用户平均评分
    user_mean = np.mean(ratings[user_idx][ratings[user_idx] > 0])
    numerator = 0
    denominator = 0
    for v in top_users:
        if ratings[v, item_idx] == 0:
            continue  # 邻居用户未评分,跳过
        v_mean = np.mean(ratings[v][ratings[v] > 0])
        sim = similarities[user_idx, v]
        numerator += sim * (ratings[v, item_idx] - v_mean)
        denominator += abs(sim)
    if denominator == 0:
        return user_mean  # 无法计算时返回用户平均评分
    predicted = user_mean + numerator / denominator
    return predicted

# 示例:预测用户0对电影2的评分
predicted_rating = predict_user_rating(0, 2, ratings, user_similarities)
print("用户0对电影2的预测评分:", predicted_rating)
代码逐行解释:
  1. predict_user_rating :定义评分预测函数,参数包括用户索引、物品索引、评分矩阵和相似度矩阵。
  2. if user_ratings[item_idx] != 0: :如果用户已经评分,直接返回评分值。
  3. get_top_k_users(...) :获取Top-K邻居用户。
  4. user_mean :计算当前用户的平均评分。
  5. for v in top_users: :遍历每个邻居用户,计算加权评分差值。
  6. predicted :根据公式计算预测评分。

6.3 物品-物品协同过滤实现

6.3.1 物品相似度矩阵构建

物品-物品协同过滤的核心同样是构建相似度矩阵,但这次是基于物品之间的评分行为进行计算。

以下是一个物品相似度矩阵的构建示例:

# 转置评分矩阵得到物品-用户矩阵
item_ratings = ratings.T

# 计算物品相似度矩阵
item_similarities = cosine_similarity(item_ratings)
print("物品相似度矩阵:\n", item_similarities)
代码逐行解释:
  1. item_ratings = ratings.T :将评分矩阵转置,使得每行代表一个物品,每列表示一个用户。
  2. cosine_similarity(...) :使用余弦相似度计算物品之间的相似度。
  3. print(...) :输出物品相似度矩阵。

6.3.2 推荐列表生成逻辑

物品协同过滤的推荐逻辑是:根据用户历史评分的物品,找出相似度最高的物品进行推荐。例如,用户A喜欢物品X,物品X与物品Y相似度高,则推荐物品Y。

以下是一个物品推荐函数的实现:

def recommend_items_for_user(user_idx, ratings, item_similarities, k=2):
    user_ratings = ratings[user_idx]
    watched_items = np.where(user_ratings > 0)[0]
    scores = np.zeros(ratings.shape[1])
    for item in range(ratings.shape[1]):
        if user_ratings[item] > 0:
            continue  # 已评分的物品不推荐
        numerator = 0
        denominator = 0
        for w_item in watched_items:
            sim = item_similarities[item, w_item]
            rating = user_ratings[w_item]
            numerator += sim * rating
            denominator += abs(sim)
        if denominator > 0:
            scores[item] = numerator / denominator
    # 返回推荐分数最高的Top-K物品
    return np.argsort(scores)[::-1][:k]

# 示例:为用户0推荐Top-2电影
recommended_items = recommend_items_for_user(0, ratings, item_similarities)
print("用户0的推荐电影索引:", recommended_items)
代码逐行解释:
  1. watched_items = np.where(user_ratings > 0)[0] :获取用户已评分的物品索引。
  2. scores :初始化一个用于存储推荐得分的数组。
  3. for item in range(...) :遍历所有未评分的物品。
  4. for w_item in watched_items: :对每个已评分的物品,查找与其相似的未评分物品。
  5. scores[item] = numerator / denominator :计算推荐得分。
  6. np.argsort(...) :对推荐得分排序并返回Top-K物品。

6.3.3 优化策略与稀疏矩阵处理

由于用户-物品评分矩阵通常是高度稀疏的(大部分用户只对少量物品评分),直接计算相似度可能导致效率低下和结果偏差。以下是几种优化策略:

  • 使用稀疏矩阵存储 :如 scipy.sparse.csr_matrix 来节省内存。
  • 基于邻居采样 :在计算Top-K邻居时使用近似方法(如LSH)。
  • 加入正则化项 :避免相似度计算中出现除零错误。
  • 时间衰减因子 :考虑用户行为的时间衰减,给予近期评分更高的权重。
from scipy.sparse import csr_matrix

# 使用稀疏矩阵存储评分数据
sparse_ratings = csr_matrix(ratings)
print("稀疏矩阵存储大小:", sparse_ratings.data.nbytes)
代码解释:
  1. csr_matrix :将评分矩阵转换为稀疏矩阵格式,减少内存占用。
  2. data.nbytes :查看非零元素占用的内存大小。

小结

在本章中,我们详细介绍了协同过滤的基本原理与实现方式,包括用户-用户协同过滤和物品-物品协同过滤的算法流程、评分预测公式以及Python代码实现。同时,我们探讨了如何优化推荐系统,特别是在面对评分矩阵稀疏问题时,如何通过稀疏矩阵存储、Top-K邻居选取等方式提升推荐效果与计算效率。

下一章我们将进入推荐系统中的经典矩阵分解技术——SVD,进一步提升评分预测的精度。

7. 矩阵分解技术(SVD)评分预测实现

7.1 矩阵分解在推荐系统中的应用

7.1.1 SVD分解的基本数学原理

SVD(Singular Value Decomposition,奇异值分解)是一种经典的矩阵分解方法,广泛应用于推荐系统中用于填补用户-物品评分矩阵中的缺失值。其核心思想是将原始的评分矩阵 $ R \in \mathbb{R}^{m \times n} $(其中 m 为用户数量,n 为物品数量)近似分解为三个较小的矩阵:

R \approx U \cdot \Sigma \cdot V^T

其中:

  • $ U \in \mathbb{R}^{m \times k} $:用户隐向量矩阵;
  • $ \Sigma \in \mathbb{R}^{k \times k} $:奇异值对角矩阵;
  • $ V \in \mathbb{R}^{n \times k} $:物品隐向量矩阵;

其中 $ k $ 是隐向量的维度,通常远小于 m 和 n。

在推荐系统中,我们通常会将 $ \Sigma $ 合并进 $ U $ 和 $ V $ 中,从而将模型简化为:

\hat{r}_{ui} = q_i^T p_u

其中 $ p_u $ 是用户 u 的隐向量,$ q_i $ 是物品 i 的隐向量,$\hat{r}_{ui}$ 是用户 u 对物品 i 的预测评分。

7.1.2 低秩矩阵近似与评分预测

SVD 通过将高维稀疏评分矩阵分解为低秩矩阵,从而学习用户与物品的潜在特征表示。低秩假设认为,用户和物品的偏好和属性可以被少量的隐因子(latent factors)所刻画。

在推荐过程中,我们通过用户和物品的隐向量点积来预测缺失评分,公式如下:

\hat{r}_{ui} = \mu + b_u + b_i + q_i^T p_u

其中:

  • $ \mu $:全局平均评分;
  • $ b_u $:用户 u 的偏差;
  • $ b_i $:物品 i 的偏差;
  • $ q_i $:物品 i 的隐向量;
  • $ p_u $:用户 u 的隐向量;

这种形式考虑了用户和物品本身的评分偏好,提升了预测的准确性。

7.2 SVD模型的构建与训练

7.2.1 损失函数定义与优化目标

为了训练 SVD 模型,我们需要定义一个损失函数,并通过优化算法来最小化该损失。典型的损失函数包括均方误差(MSE)加上正则化项:

\min_{p_u, q_i, b_u, b_i} \sum_{(u,i) \in \mathcal{R}} (r_{ui} - \hat{r}_{ui})^2 + \lambda (||p_u||^2 + ||q_i||^2 + b_u^2 + b_i^2)

其中:

  • $ r_{ui} $:用户 u 对物品 i 的真实评分;
  • $ \hat{r}_{ui} $:预测评分;
  • $ \lambda $:正则化系数,防止过拟合;

7.2.2 随机梯度下降(SGD)优化方法

SGD 是训练 SVD 模型常用的方法。其基本步骤如下:

  1. 初始化所有参数 $ p_u, q_i, b_u, b_i $ 为小的随机数;
  2. 对每一条训练样本 $ (u, i, r_{ui}) $,计算预测评分 $ \hat{r}_{ui} $;
  3. 计算误差 $ e_{ui} = r_{ui} - \hat{r}_{ui} $;
  4. 更新参数:

\begin{align }
p_u &:= p_u + \gamma (e_{ui} \cdot q_i - \lambda \cdot p_u) \
q_i &:= q_i + \gamma (e_{ui} \cdot p_u - \lambda \cdot q_i) \
b_u &:= b_u + \gamma (e_{ui} - \lambda \cdot b_u) \
b_i &:= b_i + \gamma (e_{ui} - \lambda \cdot b_i)
\end{align
}

其中 $ \gamma $ 是学习率。

7.2.3 实现SVD模型的Python代码框架

以下是一个使用 Python 和 NumPy 实现的简化版 SVD 模型示例:

import numpy as np

class SVDModel:
    def __init__(self, num_users, num_items, k=10, lr=0.01, reg=0.02, epochs=20):
        self.num_users = num_users
        self.num_items = num_items
        self.k = k
        self.lr = lr
        self.reg = reg
        self.epochs = epochs

        # 初始化隐向量和偏置
        self.p = np.random.normal(scale=1./k, size=(num_users, k))
        self.q = np.random.normal(scale=1./k, size=(num_items, k))
        self.bu = np.zeros(num_users)
        self.bi = np.zeros(num_items)
        self.mu = 0.0

    def fit(self, train_data):
        for epoch in range(self.epochs):
            np.random.shuffle(train_data)
            total_error = 0.0
            count = 0
            for u, i, r in train_data:
                # 预测评分
                pred = self.mu + self.bu[u] + self.bi[i] + np.dot(self.q[i], self.p[u])
                e = r - pred
                total_error += e ** 2
                count += 1

                # 更新参数
                self.p[u] += self.lr * (e * self.q[i] - self.reg * self.p[u])
                self.q[i] += self.lr * (e * self.p[u] - self.reg * self.q[i])
                self.bu[u] += self.lr * (e - self.reg * self.bu[u])
                self.bi[i] += self.lr * (e - self.reg * self.bi[i])

            # 计算RMSE
            rmse = np.sqrt(total_error / count)
            print(f"Epoch {epoch+1}, RMSE: {rmse:.4f}")

    def predict(self, u, i):
        return self.mu + self.bu[u] + self.bi[i] + np.dot(self.q[i], self.p[u])

参数说明:

  • num_users :用户总数;
  • num_items :物品总数;
  • k :隐向量维度;
  • lr :学习率;
  • reg :正则化系数;
  • epochs :训练轮数;
  • train_data :训练数据格式为 (user_id, item_id, rating)

7.3 SVD模型的评估与调优

7.3.1 超参数调优(学习率、正则化系数)

SVD 模型的性能对超参数非常敏感,常见的调优策略包括:

  • 学习率(lr) :通常设置在 0.001~0.1 之间,学习率过高会导致模型不稳定,过低则训练缓慢;
  • 正则化系数(reg) :控制模型复杂度,防止过拟合,一般在 0.01~0.1 之间;
  • 隐向量维度(k) :维度越大模型表达能力越强,但也更容易过拟合,通常设置为 10~100;
  • 训练轮数(epochs) :训练过少可能导致欠拟合,过多可能过拟合;

可以使用网格搜索或贝叶斯优化等方法进行自动调参。

7.3.2 隐向量维度对模型性能的影响

隐向量维度 $ k $ 决定了模型的表达能力。通常我们通过实验观察不同 $ k $ 下的 RMSE 表现:

隐向量维度(k) RMSE
10 0.92
20 0.88
50 0.85
100 0.86

可以看到,随着 $ k $ 增加,模型表现先提升后趋于稳定或略有下降,说明过大的维度可能引发过拟合。

7.3.3 模型过拟合与欠拟合的识别与解决

  • 过拟合表现 :训练误差低,验证误差高;
  • 解决方法:增加正则化系数、减少隐向量维度、早停法;
  • 欠拟合表现 :训练误差和验证误差都高;
  • 解决方法:增加隐向量维度、调整学习率、增加训练轮数;

可以通过绘制训练误差和验证误差随训练轮数变化的趋势图来辅助判断。

graph TD
    A[开始训练] --> B[计算预测评分]
    B --> C[计算误差]
    C --> D[更新参数]
    D --> E[记录RMSE]
    E --> F{是否达到最大训练轮数?}
    F -- 否 --> A
    F -- 是 --> G[训练结束]

通过上述流程图,可以清晰地看到 SVD 模型的训练过程是一个迭代优化的过程。下一章节将深入探讨深度学习在推荐系统中的应用,进一步提升推荐效果。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目围绕使用Python3构建基于Movielens数据集的推荐系统展开,重点实现评分预测任务。Movielens数据集包含用户、电影及评分信息,适用于推荐算法研究。项目使用pandas、numpy、scikit-learn等工具完成数据加载、预处理、特征工程、模型训练与评分预测,涵盖协同过滤、矩阵分解等主流方法,并通过RMSE等指标评估模型性能。适合提升推荐系统构建能力与数据分析技能。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐