返回列表

[猜你喜欢]冠军 yes,boy! 分享 | 推荐系统也可以很简单

猜你喜欢 | cmptDetail.html?id=163

开始: 2016-05-17 结束: 2027-07-18 商品推荐 数据算法赛
推荐系统竞赛冠军分享 · 猜你喜欢 · yes,boy!
🏆 猜你喜欢 · 推荐系统竞赛 · 冠军 👤 yes,boy! · 东北大学 📅 2016年7月20日 🎯 线上得分 7.89465

[猜你喜欢]冠军 yes,boy! 分享 | 推荐系统也可以很简单

基于聚类 / 协同过滤 / 矩阵分解的三阶段建模 · RMSE优化 · 模型融合

无论是充满“魔性”的“yes,boy!”这个昵称,还是诸神无法超越的7.89645分,都让这次[猜你喜欢]冠军充满了神秘色彩。他是用怎样的方法把竞赛的成绩做到最好,这其中有哪些不为人知的细节?“yes,boy!”亲自操刀,分享他对推荐系统的理解。

关于竞赛

本次比赛的赛题背景是给出了约3400万条数据,包含一个商品网站站内顾客在某一时刻对某一个商品的打分值,分值范围为1至5分。目的是通过对这些数据的学习和训练,准确预测某时刻某个用户对某个未评分商品的评分。

通过背景可知这是一个关于推荐系统的研究问题。而推荐系统在预测准确度上有不同的研究方向,一种是基于TopN的研究,即主要是给用户一个个性化的推荐列表,一般通过准确率度量推荐的优劣;一种是基于评分预测的研究,它的度量方式一般是RMSE或者MAE。在本次比赛就是通过RMSE来评价预测的好坏。那么我们接下来要使用的方法就集中在优化评分预测的RMSE上。

竞赛思路

在具体做的过程中,我觉得有几点需要注意的:

  • 分析数据,了解数据的大致规律
  • 方法先尝试简单方法,再尝试复杂方法
  • 对复杂方法,要一点点的调整

基于此,我由简及繁使用了三类模型:

  1. 基于聚类的推荐
  2. 基于协同过滤的推荐
  3. 基于模型学习的推荐

在这三类中,每一类又包含很多方法,不能绝对的说哪一类模型最好,依照具体的数据形式、数据内容而定。

第一类:基于聚类的推荐

使用的具体方法包括:

  • 全局均值
  • 物品均值
  • 用户均值
  • 用户分类-物品均值
  • 物品分类-用户均值
  • 用户活跃度
  • 物品活跃度
  • 改进的用户活跃度
  • 改进的物品活跃度

这类模型的共同特征是通过设计聚类方法来对用户和物品分类,利用同类用户对同类物品的评分均值来预测用户对物品的评分。另外通过该模型的实现对用户商品的特征有一个基本的了解。

下面是其中一种方法(用户分类-物品均值)的代码示例:

import pandas as pd import numpy as np train = pd.read_csv('data/train.csv') test = pd.read_csv('data/test.csv') rate_rank = train.groupby('uid').mean().loc[:,['score']].iloc[:,-1] rate_rank = pd.DataFrame(np.int32((rate_rank*2).values), index=rate_rank.index, columns=['group']) rate_rank_des = rate_rank.reset_index() train_plus = pd.merge(train, rate_rank_des, how='left', on='uid') test_plus = pd.merge(test, rate_rank_des, how='left', on='uid') res = train_plus.groupby(['iid','group']).mean().reset_index().loc[:,['iid','group','score']] result = pd.merge(test_plus, res, how='left', on=['iid','group']).fillna(3.0)

第二类:基于物品的协同过滤

它的核心思想是当预测用户对一个物品评分时,主要考虑与该物品最相似且用户已打过分的若干物品。所以在这其中相似度的度量方法尤其重要,包括欧氏距离、皮尔逊相似度、余弦相似度、改进的余弦相似度。(之所以不使用基于用户的协同过滤,是由于建立用户-用户的相似度矩阵过于巨大)

实现代码见 similarity.pymodel4.py(附录)。

第三类:基于模型学习的推荐(矩阵分解)

使用的方法有:

  • SVD
  • NMF
  • RSVD
  • SVD++
  • SVDfeature
  • Libmf
  • Libfm

这一类模型的共同特点是矩阵分解。即对用户-物品评分矩阵分解成若干个小矩阵,目的是分解之后的矩阵乘积接近原始矩阵,于是也实现了对原始矩阵为空的值的预测。

在这些方法中,比较重要的几个参数有:隐特征个数,随机梯度下降中的学习率,正则化参数,总迭代次数等。具体在每个方法中这些参数的最优值也不尽相同。

优秀模型详解

具体介绍其中两个在本赛题上表现最好的模型:svdfeaturelibfm

Svdfeature

Svdfeature 是一个 feature-based 协同过滤和排序工具,由陈天启所在的上海交大 Apex 实验室开发,大名鼎鼎的 xgboost 同样来自于他们。里面能够方便实现 svd,svd++ 等方法。

在使用过程中,步骤如下:

  1. 数据预处理:用户和物品的 id 不是连续的,需要进行重新的映射,转换为从1至用户/物品个数这样的连续取值。
  2. 数据格式转换:要转换为模型要求的格式
  3. 为了存储空间和计算速度,最好再转换为二进制形式
  4. 设置各类参数
  5. 预测

在主要参数如下设置的情况下,线上得分能达到7.86

base_score = 3   全局偏置
learning_rate = 0.005   学习率
wd_item, wd_user = 0.004   正则化参数
num_factor = 4000   隐含特征个数

LibFM

LibFM 是专门用于矩阵分解的利器,尤其是其中实现了MCMC(Markov Chain Monte Carlo)优化算法,比常见的SGD优化方法精度要高,但运算速度要慢一些。LibFM中还实现了 SGD、SGDA(Adaptive SGD)、ALS(Alternating Least Squares)等算法。

主要参数设置如下,这个方法的最优线上结果是7.88

-iter 100 –dim 1,1,64 –method MCMC –task –r

除此以外,libmf 模型效果也不错,经过优化之后结果能达到7.85。而基于 scipy 的 svd 和基于 sklearn 的 NMF 在小数据集上效果很好,数据量特别大的情况下效果不理想。

关于融合

一种是采用联级融合,即使一种模型的预测结果作为下一个模型的输入,不过要同时调整下一个模型的目标函数。

另外一种方法是模型加权融合,最简单的是线性融合,通过各个模型在验证集的结果和超参数优化方法 Hyperopt 找到最佳的融合系数,然后在线上使用这些融合系数进行融合。

关于改进

1)时间因素
时间的因素我一直没有使用,后来我读到过有在 svd++ 中加入时间因素的资料,预计加入后能够提升模型效果。

2)预测值取整
现在我的模型对每个用户的预测值绝大部分不是整数,而真实值却是整数,将预测之后的值转换成整型对结果会有提升。不过会存在转换正确和错误的问题,我之前方法比较简单,提升的幅度非常小。

由于比赛期间没有把思路整理成文档,赛后才开始总结自己的思路,有写的不明白的地方大家都可以提出来,然后在讨论中相互启发。

代码附录

model4.py(基于物品协同过滤)

# -*- coding: utf-8 -*- import pandas as pd import numpy as np test = pd.read_csv('data/test.csv') test_arr = test.values.copy() rate_mat = pd.read_csv('data/rate_mat.csv', index_col=0) # 用户-物品评分矩阵 rate_cos = pd.read_csv('data/rate_cos.csv', index_col=0) # 基于余弦相似度的物品评分矩阵 rate_cos_s = pd.read_csv('data/rate_cos_s.csv', index_col=0) # 基于改进余弦相似度的物品评分矩阵 rate_pearson = pd.read_csv('data/rate_pearson.csv', index_col=0) # 基于皮尔逊相关系数的物品评分矩阵 rate_mat = rate_mat.rename(columns=dict(zip(rate_mat.columns, [int(i) for i in rate_mat.columns]))) rate_cos = rate_cos.rename(columns=dict(zip(rate_cos.columns, [int(i) for i in rate_cos.columns]))) rate_cos_s = rate_cos_s.rename(columns=dict(zip(rate_cos_s.columns, [int(i) for i in rate_cos_s.columns]))) rate_pearson = rate_pearson.rename(columns=dict(zip(rate_pearson.columns, [int(i) for i in rate_pearson.columns]))) iid_index = rate_mat.columns def Recommendation_s(uid, iid, k=10, iid_iid_sim=rate_cos_s): score = 0 weight = 0 iid_sim = iid_iid_sim.loc[iid, :].values uid_action = rate_mat.loc[uid, :].values iid_action = rate_mat.loc[:, iid].values sim_indexs = np.argsort(iid_sim)[-(k+1):-1] iid_i_mean = np.sum(iid_action)/iid_action[iid_action!=0].size for j in sim_indexs: if uid_action[j] != 0: iid_j_action = rate_mat.values[:, j] iid_j_mean = np.sum(iid_j_action)/iid_j_action[iid_j_action!=0].size score += iid_sim[j]*(uid_action[j]-iid_j_mean) weight += abs(iid_sim[j]) if weight == 0: return iid_i_mean else: return iid_i_mean + score/float(weight) # 主预测循环(略)

similarity.py(相似度计算)

# -*- coding: utf-8 -*- import pandas as pd import numpy as np rate_mat = pd.read_csv('data/rate_mat.csv', index_col=0).values def cosine_sim(i, j): a = rate_mat[:, i] b = rate_mat[:, j] m = np.dot(a, b) n = np.sqrt(np.dot(a, a)*np.dot(b, b)) return m/float(n) def cosine_sim_s(i, j): a = rate_mat[:, i] b = rate_mat[:, j] intersection = a*b if intersection[intersection!=0].size == 0: return 0.0 c = a[a!=0] d = b[b!=0] p = np.mean(c) q = np.mean(d) m = np.dot(a[intersection!=0]-p, b[intersection!=0]-q) n = np.sqrt(np.dot(c-p, c-p)*np.dot(d-q, d-q)) if n == 0: return 0.0 return m/float(n) def pearson(i, j): a = rate_mat[:, i] b = rate_mat[:, j] intersection = a*b if intersection[intersection!=0].size == 0: return 0.0 c = a[intersection!=0] d = b[intersection!=0] p = np.mean(a[a!=0]) q = np.mean(b[b!=0]) m = np.dot(c-p, d-q) n = np.sqrt(np.dot(c-p, c-p)*np.dot(d-q, d-q)) if n == 0: return 0.0 return m/float(n) # 构建相似度矩阵(略)