互联网的信息越来越多,用户面对大量数据信息的时候,无法获得对自己真正有用的部分,造成信息超载问题。
推荐系统就是解决信息超载的一个办法。
推荐系统依据用户的历史行为、社交关系、兴趣点等信息去判断用户当前需要或感兴趣物品/服务的应用。
推荐是去预测用户对某个他没有使用过的物品/服务的喜欢程度(一般是打分机制),物品/服务可以是电影、书、音乐、新闻等;
推进系统核心任务主要是把用户和信息关联起来,
背景介绍
推荐问题的发展历史
1994年, Minncsota, GroupLcns 研究组论文
目前已经广泛应用到很多商业应用系统
尤其是网络购物平台: 亚马逊,京东,淘宝等
Google新闻: 38%的点击量来自推荐等等。
新闻:今日头条
视频:抖音推荐短视频
User & user Profile(用户画像)
Item(物品) & Item Profile(物品对应特征)
Review (user 对 item 的评价)
推荐列表( TOP-N)
推荐理由
亚马逊个性化推荐
亚马逊相关推荐
电影和视频网站
个性化音乐网络电台
抖音视频的推荐
网易云音乐
其他
用户行为在个性化推荐系统中一般分为两种
协同过滤算法
UserCF推荐步骤
(先找到和他有相同兴趣的其他用户):
比如下面的图: 分为 用户 A、B、C、D。 物品: a、b、c、d
找出对某个物品有共同兴趣的用户,比如对物品a 有兴趣的有用户A和用户B
采用余弦相识度公式:
越接近 1 ,相似度越高 ,A 和 B只有a ,1个,总共 3 × 2 个组合
W u v = ∣ N ( u ) ⋂ N ( v ) ∣ ∣ N ( u ) ∣ ∣ N ( v ) ∣ 则 用 户 A 和 B 相 似 度 : W A B = ∣ { a , b , d } ∣ ⋂ ∣ { a , c } ∣ ∣ { a , b , d } ∣ ∣ { a , c } ∣ = 1 6 W_{uv} = \frac{|N(u)\bigcap N(v)|}{\sqrt{|N(u)|\ \ |N(v)|}} \\ 则用户A 和 B 相似度:\ \ \ \ \ \ W_{AB} = \frac{|\{a,b,d\}|\bigcap|\{a,c\}|} {\sqrt{|\{a,b,d\}|\ \ |\{a,c\}|}} = \frac{1}{\sqrt{6}} Wuv=∣N(u)∣ ∣N(v)∣∣N(u)⋂N(v)∣则用户A和B相似度: WAB=∣{a,b,d}∣ ∣{a,c}∣∣{a,b,d}∣⋂∣{a,c}∣=61
然后 UserCF 算法会给用户推荐和他感兴趣最相近的 K 个用户喜欢的物品
p ( u , i ) = ∑ v ϵ S ( u , K ) ⋂ ( i ) W u v R v i p(u,i) = \sum_{v\epsilon S(u,K)\bigcap(i)}\ W_{uv}R_{vi} p(u,i)=vϵS(u,K)⋂(i)∑ WuvRvi
参数说明:u 用户, i 物品
1. S(u, K): 包含和用户u 兴趣最接近的 K 个用户
2. N(i): 对物品 i 有过行为的用户集合
3. Wuv : 用户u 和 用户 v 的兴趣相似度
4. Rvi: 代表用户v 对物品 i 的兴趣
使用 jupyter notebook分析MovieLens 数据集
导入需要的模块
import numpy as np
import pandas as pd
import matplotlib.pylab as plt
读取对应的数据集显示
header = ['user_id', 'item_id', 'rating', 'timestamp']
src_data = pd.read_csv('ml-100k/u.data', sep='\t', names = header)
src_data.head()
按user_id 排序
user_data = pd.DataFrame(src_data)
user_data.sort_values(by='user_id')
对应的数据集
# 用户信息: 用户ID,年龄,性别,职业,邮政编码
u_cols = ['user_id', 'age', 'sex', 'occupation', 'zip_code']
users = pd.read_csv('ml-100k/u.user', sep='|', names=u_cols, encoding='latin-1')
# 评分信息: 用户ID,电影ID,评分,时间戳
r_cols = ['user_id', 'movie_id', 'rating', 'unix_timestamp']
ratings = pd.read_csv('ml-100k/u.data', sep='\t', names=r_cols, encoding='latin-1')
# 电影信息:电影ID,电影标题,发布时间,上映时间,IMBD地址
m_cols = ['movie_id', 'title', 'release_date', 'video_release_date', 'imbd_url']
movies = pd.read_csv('ml-100k/u.item', sep='|', names=m_cols, usecols=range(5), encoding='latin-1')
把用户,物品,评分这些表结合
# 把这些数据融合
movie_ratings = pd.merge(users, ratings)
merge_data = pd.merge(movie_ratings, movies)
merge_data.head()
找出评论次数最多的电影
安装电影标题分为不同的组(groups), 并且用size()函数得到
每一部电影的个数(即每部电影被评论的次数),按照从大到小排序,取最大的前20部电影
title_size = merge_data.groupby('title').size()
sort_val = title_size.sort_values(ascending=False)[:20]
print(sort_val)
评分最高的十部电影
按照电影名称分组
取出至少被评论过100次的电影按照平均评分从大到小排序,取最大的10部电影
movie_stats = merge_data.groupby('title')
# 使用agg 函数通过一个字典{’rating':[np.size, np.mean]}来按照key即rating
rating_agg = movie_stats.agg({'rating': [np.size, np.mean]})
# 依据布尔(true, false)排序
take_100 = rating_agg['rating']['size'] >= 100
# print(take_100)
sort_raring = rating_agg[take_100].sort_values([('rating', 'mean')], ascending=False)
sort_raring[:10]
直方图查看用户的年龄分布
plt.hist(users.age, bins=30, color='red', alpha=0.75)
plt.xlabel('age')
plt.ylabel('count of users')
plt.show()
用户年龄分组
# 将用户年龄分组
labels = ['10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79']
merge_data['age_group'] = pd.cut(merge_data.age, range(10, 81, 10), right=False, labels=labels)
merge_data[['age', 'age_group']].drop_duplicates()[:10]
# 每个年龄段用户评分偏好
merge_data.groupby('age_group').agg({'rating':[np.size, np.mean]})
Surprise was designed with the following purposes in mind:
大体上是说,推荐系统被设计时,充分考虑了用户控制,减轻数据集的处理,可以使用内置的数据集(Movielens, Jester), 提供了大量的基本算法,能更容易实现新的想法, 提供算法分析、模型评估、比较算法性能的工具。一个简单的Python 推荐系统引擎库
pip install numpy
pip install scikit-surprise
也可以 anaconda 安装:
conda install -c conda-forge scikit-surprise
也可以从git clone github官网安装最新版本
pip install numpy cython
git clone https://github.com/NicolasHug/surprise.git
cd surprise
python setup.py install
算法类名 | 描述 |
---|---|
random_pred.NormalPredictor |
依据训练集的分布特征随机给预测值 |
baseline_only.BaselineOnly |
给定用户和Item, 给出基于baseline的估计值 |
knns.KNNBasic |
最基础的协同过滤 |
knns.KNNWithMeans |
将每个用户评分的均值考虑在内的协同过滤实现 |
knns.KNNWithZScore |
一种基本的协同过滤算法,考虑到每个用户的Z分数标准化。 |
knns.KNNBaseline |
考虑基线评级的协同过滤 |
matrix_factorization.SVD |
SVD(奇异值)实现 |
matrix_factorization.SVDpp |
SVD++ |
matrix_factorization.NMF |
基于矩阵分解的协同过滤 |
slope_one.SlopeOne | 一个简单但精确的协同过滤算法 |
co_clustering.CoClustering | 基于协同聚类的协同过滤算法 |
KFold |
一个基本的交叉验证迭代器。 |
---|---|
RepeatedKFold |
重复KFold 交叉验证。 |
ShuffleSplit |
具有随机顺练集和测试集的基本交叉验证迭代器。 |
LeaveOneOut |
交叉验证迭代器,其中每个用户在测试集合中只有一个评级。 |
PredefinedKFold |
使用load_from_folds 方法加载数据集时的交叉验证迭代器。 |
cosine |
计算所有用户(或物品)对之间的余弦相似度。 |
---|---|
msd |
计算所有用户(或物品)对之间的均方差异相似度 |
pearson |
计算所有用户(或物品)对之间的Pearson相关系数。 |
pearson_baseline |
计算所有用户(或项目)对之间的(缩小的)Pearson相关系数,使用基线进行居中而不是平均值。 |
rmse |
计算RMSE (均方根误差). |
---|---|
mae |
计算MAE (平均绝对误差). |
fcp |
计算 FCP (协调对的分数). |
movielens-100k 数据集:1700部电影的1000名用户获得100,000评级。 发布于4/1998。
movielens-1m 数据集: 4000部电影的6000名用户获得100万评级。 2003年2月发布。
Jester 数据集:在线笑话评分。
上面的都可以在各自的网站下载到,都分有时间段收集的数据集。
Dataset.load_builtin |
加载内置数据集。 |
---|---|
Dataset.load_from_file |
从(自定义)文件加载数据集。 |
Dataset.load_from_folds |
加载数据集,其中折叠(用于交叉验证)由某些文件预定义。 |
Dataset.folds |
生成器函数迭代数据集的折叠。 |
DatasetAutoFolds.split |
将数据集拆分为折叠以供接下来的交叉验证。 |
from surprise import SVD
from surprise import Dataset
from surprise.model_selection import cross_validate
# Load the movielens-100k dataset (download it if needed).
data = Dataset.load_builtin('ml-100k')
# Use the famous SVD algorithm.
algo = SVD()
# Run 5-fold cross-validation and print results.
cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=5, verbose=True)
Output
评估5次分裂的算法SVD的RMSE,MAE。 Fold 1 Fold 2 Fold 3 Fold 4 Fold 5 Mean Std RMSE 0.9311 0.9370 0.9320 0.9317 0.9391 0.9342 0.0032 MAE 0.7350 0.7375 0.7341 0.7342 0.7375 0.7357 0.0015 Fit time 6.53 7.11 7.23 7.15 3.99 6.40 1.23 Test time 0.26 0.26 0.25 0.15 0.13 0.21 0.06
def get_top_n(predictions, n = 10):
top_n = defaultdict(list)
# uid: 用户ID
# iid: 物品ID
# est: 估计得分
for uid, iid, true_r, est, _ in predictions:
top_n[uid].append((iid, est))
# 为每一个用户都寻找 K 个得分最高的item
for uid, user_ratings in top_n.items():
user_ratings.sort(key=lambda x: x[1], reverse=True)
top_n[uid] = user_ratings[:n]
return top_n
# 首先在movielens数据集上训练SVD算法。
data = Dataset.load_builtin('ml-100k')
# 转换,这样才能获取数据集详细信息
trainset = data.build_full_trainset()
algo = SVD()
algo.fit(trainset)
testset = trainset.build_anti_testset()
predictions = algo.test(testset)
top_n = get_top_n(predictions, n = 10)
for uid, user_ratings in top_n.items():
print(uid, [iid for (iid, _) in user_ratings])
from surprise import SVD
from surprise import Dataset, print_perf, Reader
from surprise.model_selection import cross_validate
import os
# 指定文件所在路径,mydata是自己制作的数据集
file_path = os.path.expanduser('mydata.csv')
# sep: 指定数据集以什么方式去读
reader = Reader(line_format='user item rating', sep=',')
# 加载数据
data = Dataset.load_from_file(file_path, reader=reader)
trainset = data.build_full_trainset()
#训练SVD算法
algo = SVD()
algo.fit(trainset)
testset = [('1', '1', 0),
('1', '2', 0),
('1', '3', 0),
]
predictions = algo.test(testset)
from surprise import KNNWithMeans
from surprise import Dataset, print_perf, Reader
from surprise.model_selection import cross_validate
import os
# 指定文件所在路径
file_path = os.path.expanduser('mydata.csv')
reader = Reader(line_format='user item rating timestamp', sep='\t')
# 加载数据
data = Dataset.load_from_file(file_path, reader=reader)
trainset = data.build_full_trainset()
# 读取数据
header = ['user', 'item', 'rating', 'timestamp']
src_data = pd.read_csv('mydata.csv', sep='\t', names = header)
user_data = pd.DataFrame(src_data)
user_data.sort_values(by='user').head()
# user-base CF 得分计算
algo = KNNWithMeans(k=50, sim_options={'user_based': True})
algo.fit(trainset)
uid = str(5)
iid = str(1)
pred = algo.predict(uid, iid)
pred
# item-based CF 得分计算
# 取最相似的用户计算时,只取最相似的k个
algo = KNNWithMeans(k=50, sim_options={'user_based': False})
algo.fit(trainset)
uid = str(1)
iid = str(1)
pred = algo.predict(uid, iid)
pred