在之前我也看了很多人写的推荐系统的博客,理论的、算法的都有,多是个人的理解和感悟,虽然很深刻,但是对于自己而言还是不成系统,于是我参考大牛项亮编著的《推荐系统实践》将该领域知识系统整理一遍,与大家一起学习。
本系列对应的代码请查看https://github.com/wangyuyunmu/Recommended-system-practice
前面总结推荐系统常用的数据、算法、架构,但是都是在讨论TopN推荐,本文总结评分预测问题。
目录
TopN即给定一个用户,如何给他生成一个长度为N的推荐列表,使该推荐列表能够尽量满足用户的兴趣和需求。实际系统绝大多数情况下就是给用户提供一个包括N个物品的个性化推荐列表。
但是推荐系统研究最早是评分预测问题开始的。评分预测问题最基本的数据集就是用户评分数据集。该数据集由用户评分记录组成,每一条评分记录是一个三元组 ( u , i , r ) (u,i, r) (u,i,r),表示用户 u u u 给物品 i i i 赋予了评分 r r r,本章用 r u i r_{ui} rui表示用户u对物品i的评分。
因为用户不可能对所有物品都评分,因此评分预测问题就是如何通过已知的用户历史评分记录预测未知的用户评分记录。 比如下图所示:
我们希望能够给出一个分数表明我们认为用户是否会喜欢这部电影,同时这个分数也可以帮助用户绝是否要看这部电影。因此,如何提高这个分数的预测精度就是评分预测要解决的问题。
1,离线实验方法
一般评分预测问题都是离线实验,在给定用户评分数据集后,研究人员会将数据集按照一定的方式分成训练集和测试集,然后根据测试集建立用户兴趣模型来预测测试集中的用户评分。一般用均方根误差RMSE度量预测的精度。
R
M
S
E
=
∑
(
u
,
i
)
∈
T
(
r
u
i
−
r
^
u
i
)
∣
T
e
s
t
∣
RMSE = \frac{\sum_{(u,i)\in T}(r_{ui}-\hat{r}_{ui})}{|Test|}
RMSE=∣Test∣∑(u,i)∈T(rui−r^ui)
划分训练集与测试集:
1)如果与时间无关的任务,可以以均匀分布随机划分数据集。
2)如果时域时间相关的任务,需要将用户的旧行为作为训练集,新行为作为测试集。
2,评分预测算法
2.1 平均值
最简单的,利用平均值预测用户对物品的评分。
2.1.1 全局平均
它的定义为训练集中所有评分记录的评分平均值。
r
^
u
i
=
∑
(
u
,
i
)
∈
t
r
a
i
n
r
u
i
∑
(
u
,
i
)
∈
t
r
a
i
n
1
\hat{r}_{ui}=\frac{\sum_{(u,i)\in{train}}r_{ui}}{\sum_{(u,i)\in{train}}1}
r^ui=∑(u,i)∈train1∑(u,i)∈trainrui
2.1.2 用户评分平均值
定义为用户u在训练集中所有评分的平均值。
r
^
u
=
∑
i
∈
N
(
u
)
r
u
i
∑
i
∈
N
(
u
)
1
\hat{r}_u=\frac{\sum_{i\in N(u)}r_{ui}}{\sum_{i\in N(u)}1}
r^u=∑i∈N(u)1∑i∈N(u)rui
2.1.3 用户评分平均值
定义为物品i在训练集中接受的所有评分的平均值
r
^
i
=
∑
u
∈
N
(
i
)
r
u
i
∑
u
∈
N
(
i
)
1
\hat{r}_i = \frac{\sum_{u\in{N(i)}}r_{ui}}{\sum_{u\in{N(i)}}1}
r^i=∑u∈N(i)1∑u∈N(i)rui
以上三种平均值预测,分别是从0维度、用户维度、物品维度进行计算。
2.1.4 用户分类对物品分类的平均值
我们可以利用训练集中同类用户对同类物品评分的平均值预测用户对物品的评分。
r
^
u
i
=
∑
(
v
,
j
)
∈
T
r
a
i
n
,
ϕ
(
u
)
=
ϕ
(
v
)
,
ϕ
(
i
)
=
ϕ
(
j
)
r
v
j
∑
(
v
,
j
)
∈
T
r
a
i
n
,
ϕ
(
u
)
=
ϕ
(
v
)
,
ϕ
(
i
)
=
ϕ
(
j
)
1
\hat{r}_{ui}=\frac{\sum_{(v,j)\in{Train},\phi(u)=\phi(v),\phi(i)=\phi(j)}r_{vj}}{\sum_{(v,j)\in{Train},\phi(u)=\phi(v),\phi(i)=\phi(j)}1}
r^ui=∑(v,j)∈Train,ϕ(u)=ϕ(v),ϕ(i)=ϕ(j)1∑(v,j)∈Train,ϕ(u)=ϕ(v),ϕ(i)=ϕ(j)rvj
上式进一步对用户维度和物品维度进行粒度划分,相似用户、相似物品。这是一种普遍意义的表达方式,比如用户类别为u,不区分物品类别,就成了用户评分平均值。
用户类别、物品类别如何划分?可以从如下角度区分。
1)用户和物品的平均分
计算用户或者物品的评分平均分,排序,分成N份。
#对于每个用户,用户对物品评价的平均分进行排名。这里区别于用户活跃度。
def __init__(self, records):
Cluster.__init__(self, records)
vote, cnt = {}, {}
for r in records:
if r.test: continue
if r.user not in vote:
vote[r.user] = 0
cnt[r.user] = 0
vote[r.user] += r.rate
cnt[r.user] += 1
# 按照物品平均评分进行分组
for user, v in vote.items():
c = v / (cnt[user] * 1.0)
self.group[user] = int(c * 2)
2)用户活跃度和物品流行度
用户行为的物品数量定义活跃度,给物品评分的用户数目定义流行度。
# 按照用户活跃度进行分组,用户id对应不同的group。
def __init__(self, records):
Cluster.__init__(self, records)
activity = {}
for r in records:
if r.test: continue
if r.user not in activity:
activity[r.user] = 0
activity[r.user] += 1
# 按照用户活跃度进行分组
k = 0
for user, n in sorted(activity.items(), key=lambda x: x[-1], reverse=False):
c = int((k * 5) / len(activity))
self.group[user] = c
k += 1
2.2 基于邻域的方法
基于用户的邻域算法和基于物品的邻域算法都可以应用到评分预测中。
基于用户的邻域算法认为,预测一个用户对一个物品的评分,需要考虑和这个用户相似用户对该物品的评分。
r
^
u
i
=
r
ˉ
u
+
∑
v
∈
S
(
u
,
K
)
∩
N
(
i
)
w
u
v
(
r
v
i
−
r
ˉ
v
)
∑
v
∈
S
(
u
,
K
)
∩
N
(
i
)
∣
w
u
v
∣
\hat{r}_{ui} = \bar{r}_{u}+\frac{\sum_{v\in{S(u,K)\cap N(i)}}w_{uv}(r_{vi}-\bar{r}_v)}{\sum_{v\in{S(u,K)\cap N(i)}}|w_{uv}|}
r^ui=rˉu+∑v∈S(u,K)∩N(i)∣wuv∣∑v∈S(u,K)∩N(i)wuv(rvi−rˉv)
公式的解释:用户u对物品i的预测值,包含两部分:1)用户u本身的评分习惯
r
ˉ
u
\bar{r}_u
rˉu;2)与用户u相关的用户v的评分(去除v的评分习惯)的期望值。
这里用户的相关性不再是通过用户行为极计算,用皮尔逊相关系数计算。
w
u
v
=
∑
i
∈
I
(
r
u
i
−
r
ˉ
u
)
(
r
v
i
−
r
ˉ
v
)
∑
i
∈
I
(
r
u
i
−
r
ˉ
u
)
2
(
r
v
i
−
r
ˉ
v
)
2
w_{uv}=\frac{\sum_{i\in I}(r_{ui}-\bar{r}_u)(r_{vi}-\bar{r}_v)}{\sqrt{\sum_{i\in I}(r_{ui}-\bar{r}_u)^2(r_{vi}-\bar{r}_v)^2}}
wuv=∑i∈I(rui−rˉu)2(rvi−rˉv)2∑i∈I(rui−rˉu)(rvi−rˉv)
同样的基于物品的邻域算法,预测用户u对物品i的评分时,会参考用户u对物品i相似的物品j的评分:
r
^
u
i
=
r
ˉ
i
+
∑
j
∈
S
(
i
,
K
)
∩
N
(
u
)
w
i
j
(
r
u
j
−
r
ˉ
j
)
∑
j
∈
S
(
i
,
K
)
∩
N
(
u
)
∣
w
i
j
∣
\hat{r}_{ui} = \bar{r}_{i}+\frac{\sum_{j\in{S(i,K)\cap N(u)}}w_{ij}(r_{uj}-\bar{r}_j)}{\sum_{j\in{S(i,K)\cap N(u)}}|w_{ij}|}
r^ui=rˉi+∑j∈S(i,K)∩N(u)∣wij∣∑j∈S(i,K)∩N(u)wij(ruj−rˉj)
对于物品相似度w的计算一般有三种方法:
1)余弦相似度
w
i
j
=
∑
u
∈
U
r
u
i
r
u
j
∑
u
∈
U
r
u
i
2
∑
u
∈
U
r
u
j
2
w_{ij}=\frac{\sum_{u\in U}r_{ui}r_{uj}}{\sqrt{\sum_{u\in U}r_{ui}^2}\sqrt{\sum_{u\in U}r_{uj}^2}}
wij=∑u∈Urui2∑u∈Uruj2∑u∈Uruiruj
2)皮尔逊系数
w
i
j
=
∑
u
∈
U
(
r
u
i
−
r
ˉ
i
)
(
r
u
j
−
r
ˉ
j
)
∑
u
∈
U
(
r
u
i
−
r
ˉ
i
)
2
∑
u
∈
U
(
r
u
j
−
r
ˉ
j
)
2
w_{ij}=\frac{\sum_{u\in U}(r_{ui}-\bar{r}_i)(r_{uj}-\bar{r}_j)}{\sqrt{\sum_{u\in U}(r_{ui}-\bar{r}_i)^2\sqrt{\sum_{u\in U}(r_{uj}-\bar{r}_j)^2}}}
wij=∑u∈U(rui−rˉi)2∑u∈U(ruj−rˉj)2∑u∈U(rui−rˉi)(ruj−rˉj)
3)改进的余弦相似度
w
i
j
=
∑
u
∈
U
(
r
u
i
−
r
ˉ
u
)
(
r
u
j
−
r
ˉ
u
)
∑
u
∈
U
(
r
u
i
−
r
ˉ
u
)
2
∑
u
∈
U
(
r
u
j
−
r
ˉ
u
)
2
w_{ij}=\frac{\sum_{u\in U}(r_{ui}-\bar{r}_u)(r_{uj}-\bar{r}_u)}{\sqrt{\sum_{u\in U}(r_{ui}-\bar{r}_u)^2\sqrt{\sum_{u\in U}(r_{uj}-\bar{r}_u)^2}}}
wij=∑u∈U(rui−rˉu)2∑u∈U(ruj−rˉu)2∑u∈U(rui−rˉu)(ruj−rˉu)
三种方法不分优劣,根据不同的情况效果也不一样。
2.3 隐语义模型与矩阵分解
隐含类别模型(Latent Class Model)、隐语义模型(Latent Factor Model)、pLSA、LDA、Topic Model、Matrix Factorization、Factorized Model。
以上这些算法本质上是同一种思想体系。推荐领域提的最多的是隐语义模型与矩阵分解模型,他们解决的问题都是通过降维的方法将评分矩阵补全。
用户的评分行为可以表示成一个评分矩阵R,其中R[u][i]就是用户u对物品i的评分。但是,用户不会对所有的物品评分,所以这个矩阵里有很多元素都是空的,这些空的元素称为缺失值(missing value)。因此,评分预测从某种意义上说就是填空。
2.3.1 传统的SVD分解
补全一个矩阵要对矩阵造成比较小的扰动,怎样算扰动小呢?一般意义来说,补全后矩阵的特征值变化不大,则扰动较小。所以最早的矩阵分解模型都是从SVD分解开始的。
首先将矩阵R中的缺失值简单的填充,比如平均值,得到R’,然后对矩阵进行SVD分解:
R
′
=
U
T
S
V
R'=U^TSV
R′=UTSV选取最大的f个奇异值组成对角矩阵S’
R
f
′
=
U
f
T
S
f
V
f
R'_f=U_f^TS_fV_f
Rf′=UfTSfVf即用户u对物品i的评分预测矩阵。
该方法很难再实际系统应用,缺点:
1)该方法首先需要用一个简单的方法补全稀疏评分矩阵。其实,一般的评分矩阵95%都是缺失的,如果补全,就成了稠密矩阵,存储空间很大。
2)SVD分解计算复杂,速度慢。
2.3.2 Simon Funk的SVD分解(又名LFM)
为了解决SVD的两个缺点,提出了LFM,从矩阵分解的角度说,如果我们将评分矩阵R分解为两个低维矩阵相乘:
R
^
=
P
T
Q
\hat{R}=P^TQ
R^=PTQ那么,对于用户u和物品i的评分的预测值可以通过以下公式计算:
r
^
u
i
=
∑
f
P
u
f
Q
i
f
\hat{r}_{ui}=\sum_fP_{uf}Q_{if}
r^ui=f∑PufQif那么,Simon Funk的思想很简单:可以直接通过训练集中的观察值利用最小化RMSE学习P、Q矩阵。
C
(
p
,
q
)
=
∑
(
u
,
i
)
∈
T
r
a
i
n
(
r
u
i
−
∑
f
=
1
F
p
u
f
q
i
f
)
2
+
λ
(
∣
∣
p
u
∣
∣
2
+
∣
∣
q
i
∣
∣
2
)
C(p,q)=\sum_{(u,i)\in Train}(r_{ui}-\sum_{f=1}^Fp_{uf}q_{if})^2+\lambda(||p_u||^2+||q_i||^2)
C(p,q)=(u,i)∈Train∑(rui−f=1∑Fpufqif)2+λ(∣∣pu∣∣2+∣∣qi∣∣2)其中的梯度优化方法参考推荐系统3——隐语义分析(LFM)
LFM提出以后获得了巨大成功,后来的很多模型都是对LFM的修修补补。
2.3.3 加入偏置后的LFM
r
^
u
i
=
u
+
b
u
+
b
i
+
p
u
T
q
i
\hat{r}_{ui}=u+b_u+b_i+p_u^Tq_i
r^ui=u+bu+bi+puTqi
这个预测公式加入了三项:
u
u
u: 训练集中所有记录的评分的全局平均分。从整体数据集分布的角度给出平均分,作为一个基准,有些数据集评分比较高,有的普遍低
b
u
b_u
bu,用户偏置,每个用户的评分习惯
i
u
i_u
iu,物品偏置,每个物品自身的品质因素。
2.3.4 SVD++(考虑邻域影响的LFM)
前面的LFM模型中并没有显式地考虑用户的历史行为对用户评分预测的影响,SVD++将用户的历史评分加入到LFM模型中。
在介绍SVD++之前,我们首先讨论一下如何将基于邻域的方法也像LFM那样设计成一个可以学习的模型:
1)itemCF
r
^
u
i
=
1
N
(
u
)
∑
j
∈
N
(
u
)
w
i
j
r
u
j
\hat{r}_{ui}=\frac{1}{\sqrt{N(u)}}\sum_{j\in N(u)}w_{ij}r_{uj}
r^ui=N(u)1j∈N(u)∑wijrujw的优化方法如下:
C
(
w
)
=
∑
(
u
,
i
)
∈
T
r
a
i
n
(
r
u
i
−
∑
j
∈
N
(
u
)
w
i
j
r
u
j
)
2
+
λ
w
i
j
2
C(w)=\sum_{(u,i)\in Train}(r_{ui}-\sum_{j\in N(u)}w_{ij}r_{uj})^2+\lambda w_{ij}^2
C(w)=(u,i)∈Train∑(rui−j∈N(u)∑wijruj)2+λwij2个人觉得这里的
1
N
(
u
)
\frac{1}{\sqrt{N(u)}}
N(u)1权值没必要再这里体现。这里的权值,先不用管,首先从loss函数上看,并没有这个系数,说明优化的时候没有考虑,实际预测的时候加的,为什么会加,往后看。
对比公式:
LFM计算u对i预测值,需要同时优化用户u与隐语义f、隐语义与物品item之间的关系。
ItemCF计算u与i的相关性,需要考虑用户u与item j、item j与item i之间的相关性(cos)。
ItemCF计算u对i的预测值,计算所有与用户物品j相关的i的系数、用到u对i的评分。
改进1)
这里w是一个比较稠密的矩阵,如果物品有n个,其参数的个数将会是nn个,参数多,容易过拟合。这里对矩阵w进行分解,参数降低到2n*F个。
r
^
u
i
=
1
N
(
u
)
∑
j
∈
N
(
u
)
x
i
T
y
j
=
1
N
(
u
)
x
i
T
∑
j
∈
N
(
u
)
y
j
\hat{r}_{ui}=\frac{1}{\sqrt{N(u)}}\sum_{j\in N(u)}x_{i}^Ty_{j}=\frac{1}{\sqrt{N(u)}}x_{i}^T\sum_{j\in N(u)}y_{j}
r^ui=N(u)1j∈N(u)∑xiTyj=N(u)1xiTj∈N(u)∑yj
改进2):
将本模型与LFM相加,得到如下模型:
r
^
u
i
=
u
+
b
u
+
b
i
+
p
u
T
q
i
+
1
N
(
u
)
x
i
T
∑
j
∈
N
(
u
)
y
j
\hat{r}_{ui}=u+b_u+b_i+p_u^Tq_i + \frac{1}{\sqrt{N(u)}}x_{i}^T\sum_{j\in N(u)}y_{j}
r^ui=u+bu+bi+puTqi+N(u)1xiTj∈N(u)∑yj为了不增加太多参数,令x=q得到最终的SVD++模型(这里pq是一个标量):
r
^
u
i
=
u
+
b
u
+
b
i
+
q
i
T
(
p
u
+
1
N
(
u
)
∑
j
∈
N
(
u
)
y
j
)
\hat{r}_{ui}=u+b_u+b_i+q_i^T(p_u + \frac{1}{\sqrt{N(u)}}\sum_{j\in N(u)}y_{j})
r^ui=u+bu+bi+qiT(pu+N(u)1j∈N(u)∑yj)现在来看SVD++模型,公式在隐语义项中添加
y
j
y_j
yj,
y
j
y_j
yj为隐藏的“评价了电影 j”反映出的个人喜好偏置,其中设置了收缩因子,收缩因子取集合大小的根号是一个经验公式,并没有理论依据。
2.4 加入时间信息
利用时间信息的方法也主要分成两种,一种是将时间信息应用到基于邻域的模型中,另一种是将时间信息应用到矩阵分解模型中。下面将分别介绍这两种算法。
2.4.1 基于邻域的模型融合时间信息
将时间信息融入到基于邻域的模型itemCF中。
r
^
u
i
t
=
∑
j
∈
N
(
u
)
f
(
w
i
j
,
Δ
t
)
r
u
j
∑
j
∈
N
(
u
)
f
(
w
i
j
,
Δ
t
)
\hat{r}_{uit}=\frac{\sum_{j\in{N(u})}f(w_{ij},\Delta t)r_{uj}}{\sum_{j\in{N(u})}f(w_{ij},\Delta t)}
r^uit=∑j∈N(u)f(wij,Δt)∑j∈N(u)f(wij,Δt)ruj这里
Δ
t
\Delta t
Δt是用户对物品i和物品j评论的时间差,w是ij的相似度:
f ( w i j , Δ t ) = σ ( δ w i j e x p ( − ∣ Δ t ∣ β ) + γ ) f(w_{ij},\Delta t)=\sigma(\delta w_{ij}exp(\frac{-|\Delta t|}{\beta})+\gamma) f(wij,Δt)=σ(δwijexp(β−∣Δt∣)+γ) σ ( x ) = 1 1 + e x p ( − x ) \sigma(x)=\frac{1}{1+exp(-x)} σ(x)=1+exp(−x)1
2.4.2 基于矩阵分解的模型融合时间信息
引入时间信息后,矩阵不再是二维的,变成了一个三维的矩阵。
没有时间信息的时候,BiasSVD的公式如下。
r
^
u
i
=
u
+
b
u
+
b
i
+
p
u
T
q
i
\hat{r}_{ui}=u+b_u+b_i+p_u^Tq_i
r^ui=u+bu+bi+puTqi分解来看,其中包含了二维矩阵的0矩阵分解
u
u
u、一维矩阵分解
b
u
b_u
bu,
b
i
b_i
bi,二维矩阵分解
p
q
pq
pq加入时间信息后我们可以对用户—物品—时间三维矩阵做如下分解:
r
^
u
i
t
=
u
+
b
u
+
b
i
+
b
t
+
p
u
T
q
i
+
x
u
T
y
t
+
s
i
T
z
t
+
∑
f
g
u
f
h
i
f
l
t
f
\hat{r}_{uit}=u+b_u+b_i+b_t+p_u^Tq_i + x_u^Ty_t + s_i^Tz_t + \sum_fg_{uf}h_{if}l_{tf}
r^uit=u+bu+bi+bt+puTqi+xuTyt+siTzt+f∑gufhifltf
SVD++模型的基础上也可以引入时间效应
这里,
u
t
u t
ut 是用户所有评分的平均时间。period(t)考虑了季节效应,可以定义为时刻t所在的月份。该模型同样可以通过随机梯度下降法进行优化。
2.5 模型融合
Netflix Prize的最终获胜队伍通过融合上百个模型的结果才取得了最终的成功。由此可见模型融合对提高评分预测的精度至关重要。本节讨论模型融合的两种不同技术。
2.5.1 模型级联融合
假设已经有一个预测器rˆ(k ) ,对于每个用户—物品对(u, i)都给出预测值,那么可以在这个预测器的基础上设计下一个预测器rˆ(k +1)来最小化损失函数:
C
=
∑
(
u
,
i
)
∈
T
r
a
i
n
(
r
u
i
−
r
^
u
i
(
k
)
−
r
^
u
i
(
k
+
1
)
)
C=\sum_{(u,i)\in Train}(r_{ui}-\hat{r}_{ui}^{(k)}-\hat{r}_{ui}^{(k+1)})
C=(u,i)∈Train∑(rui−r^ui(k)−r^ui(k+1))由上面的描述可以发现,级联融合很像Adaboost算法。和Adaboost算法类似,该方法每次产生一个新模型,按照一定的参数加到旧模型上去,从而使训练集误差最小化。不同的是,这里每次生成新模型时并不对样本集采样,针对那些预测错的样本,而是每次都还是利用全样本集进行预测,但每次使用的模型都有区别。
2.5.2 模型加权融合
假设我们有K个不同的预测器,如何将他们的结果组合在一起?
最简单的方法就是线性融合:
r
^
=
∑
k
=
1
K
α
k
r
^
(
k
)
\hat{r}=\sum_{k=1}^K\alpha_{k}\hat{r}^{(k)}
r^=k=1∑Kαkr^(k)由此看来,模型的训练过程分为两步。第一步训练每个模型,第二步训练模型的权值。所以一般会将训练集分成两部分,一部分做模型训练,一部分做权值训练。
除了线性融合,还有很多复杂的融合方法,比如利用人工神经网络的融合算法。其实,模型融合问题就是一个典型的回归问题,因此所有的回归算法都可以用于模型融合。