上一部分介绍了《推荐系统实践》中关于推荐系统常用的一些评测指标,那么从这一部分开始,将真正进入到推荐算法部分。
这个系列分为如下几个部分:
《推荐系统实践》算法纯享(附代码链接)(一)—— 评价指标篇
《推荐系统实践》算法纯享(附代码链接)(二)—— 协同过滤篇
《推荐系统实践》算法纯享(附代码链接)(三)—— 冷启动篇
《推荐系统实践》算法纯享(附代码链接)(四)—— UGC推荐篇
《推荐系统实践》算法纯享(附代码链接)(五)—— 借助上下文信息推荐篇
《推荐系统实践》算法纯享(附代码链接)(六)—— 借助社交网络推荐篇
《推荐系统实践》算法纯享(附代码链接)(七)—— 评分预测篇
完整代码链接:https://github.com/Magic-Bubble/RecommendSystemPractice
下面将开始本系列的第二部分——协同过滤篇。
文章目录
2 协同过滤
这里要先声明一点,仅仅基于用户行为数据设计的推荐算法一般称为协同过滤算法。
2.1 基于用户的协同过滤算法
代码链接:https://github.com/Magic-Bubble/RecommendSystemPractice/blob/master/Chapter2/基于用户的协同过滤算法.ipynb
主要包含两个步骤:
a. 计算用户之间的相似度:找到和目标用户兴趣相似的用户集合。
b. 根据用户相似度及用户对物品的评价为物品打分:找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。
Step1: 计算用户相似度
根据协同过滤算法的定义,这里主要是利用用户行为的相似度来计算兴趣的相似度。给定用户u和用户v,令 N ( u ) N(u) N(u)和 N ( v ) N(v) N(v)分别表示用户u和用户v曾经有过正反馈的物品集合,则有如下三种方式计算相似度:
- Jaccard公式
w u v = N ( u ) ⋂ N ( v ) N ( u ) ⋃ N ( v ) w_{uv}=\frac{N(u) \bigcap N(v)}{N(u) \bigcup N(v)} wuv=N(u)⋃N(v)N(u)⋂N(v)
- 余弦相似度(UserCF算法)
w u v = N ( u ) ⋂ N ( v ) ∣ N ( u ) ∣ ∣ N ( v ) ∣ w_{uv}=\frac{N(u) \bigcap N(v)}{\sqrt{|N(u)||N(v)|}} wuv=∣N(u)∣∣N(v)∣N(u)⋂N(v)
- 改进的余弦相似度(UserIIF算法)
w u v = ∑ i ∈ N ( u ) ⋂ N ( v ) 1 l o g 1 + ∣ N ( i ) ∣ ∣ N ( u ) ∣ ∣ N ( v ) ∣ w_{uv}=\frac{\sum_{i\in{N(u) \bigcap N(v)}} \frac{1}{log1+|N(i)|}}{\sqrt{|N(u)||N(v)|}} wuv=∣N(u)∣∣N(v)∣∑i∈N(u)⋂N(v)log1+∣N(i)∣1
在UserIIF算法中, N ( i ) N(i) N(i)是物品i的热度,可见其对热门物品进行了惩罚,因为两个用户对冷门物品采取过同样的行为更能说明他们兴趣的相似度。
这里要强调一个工程实现上的Trick。在计算用户行为之间的相似度时,如果按照定义实现的话,需要对两两用户的行为集合进行统计,这样的时间复杂度为 O ( ∣ U ∣ ∗ ∣ U ∣ ) O(|U|*|U|) O(∣U∣∗∣U∣),但用户行为往往是十分稀疏的,很多用户之间的行为并没有交集,导致时间浪费在这些不必要的计算上。这时就可以建立Item-User的倒排表,这样在同一个Item下面的User两两之间一定是在这个Item上有交集的,所以只需要遍历所有的Item,对其下所有的User两两进行统计即可,这样可以极大降低时间复杂度。
Step2: 为物品打分
在统计完用户之间的相似度之后,就可以利用这种用户相似度以及用户对物品的评价为物品打分。其公式如下:
p ( u , i ) = ∑ v ∈ S ( u , K ) ⋂ N ( i ) w u v r v i p(u, i) = \sum_{v \in S(u, K) \bigcap N(i)}w_{uv}r_{vi} p(u,i)=v∈S(u,K)⋂N(i)∑wuvrvi
w u i w_{ui} wui表示用户u对物品i的感兴趣程度, S ( u , K ) S(u, K) S(u,K)表示和用户u兴趣最接近的K个用户, N ( i ) N(i) N(i)是对物品i有过行为的用户集合, w u v w_{uv} wuv是用户u和用户v的兴趣相似度, r v i r_{vi} rvi是用户v对物品i的兴趣。
在实现的时候,往往会取一个较小的K值,先召回一批物品,然后为这些物品中没出现在目标用户中的物品利用上述公式进行打分。
2.2 基于物品的协同过滤算法
代码链接:https://github.com/Magic-Bubble/RecommendSystemPractice/blob/master/Chapter2/基于物品的协同过滤算法.ipynb
与基于用户的协同过滤算法一样,基于物品的协同过滤算法也是基于邻域的一种做法。它也可以分为两步:
a. 计算物品之间的相似度。
b. 根据物品的相似度和用户的历史行为为用户生成推荐列表。
Step1: 计算物品相似度
计算物品相似度主要还是利用用户的行为数据,即比较对两个物品有过正反馈的用户集合的相似性。令 N ( i ) N(i) N(i)为喜欢物品i的用户集合,则有如下几种相似度计算方法:
- 购买了该商品的用户也经常购买的其他商品
w i j = ∣ N ( i ) ⋂ N ( j ) ∣ ∣ N ( i ) ∣ w_{ij}=\frac{|N(i) \bigcap N(j)|}{|N(i)|} wij=∣N(i)∣∣N(i)⋂N(j)∣
- 余弦相似度(ItemCF算法)
上面的公式在计算的时候会导致物品与热门物品的相似度都很高,因此可以加上物品j的热度惩罚项,变成了如下的余弦相似度的形式:
w i j = N ( i ) ⋂ N ( j ) ∣ N ( i ) ∣ ∣ N ( j ) ∣ w_{ij}=\frac{N(i) \bigcap N(j)}{\sqrt{|N(i)||N(j)|}} wij=∣N(i)∣∣N(j)∣N(i)⋂N(j)
- 改进的余弦相似度(ItemIUF算法)
w i j = ∑ u ∈ N ( i ) ⋂ N ( j ) 1 l o g 1 + ∣ N ( u ) ∣ ∣ N ( i ) ∣ ∣ N ( j ) ∣ w_{ij}=\frac{\sum_{u\in{N(i) \bigcap N(j)}} \frac{1}{log1+|N(u)|}}{\sqrt{|N(i)||N(j)|}} wij=∣N(i)∣∣N(j)∣∑u∈N(i)⋂N(j)log1+∣N(u)∣1
与UserIIF算法类似,这里也对热门用户进行了惩罚,即活跃用户对物品相似度的贡献应该小于不活跃的用户。
Step2: 为物品打分
在统计完物品之间的相似度之后,就可以利用这种物品相似度以及用户对历史物品的评价为物品打分。其公式如下:
p ( u , j ) = ∑ i ∈ N ( u ) ⋂ S ( j , K ) w j i r u i p(u, j) = \sum_{i \in N(u) \bigcap S(j, K)}w_{ji}r_{ui} p(u,j)=i∈N(u)⋂S(j,K)∑wjirui
这里 N ( u ) N(u) N(u)是用户喜欢的物品的集合, S ( j , K ) S(j,K) S(j,K)是和物品j最相似的K个物品的集合, w j i w_{ji} wji是物品j和i的相似度, r u i r_{ui} rui是用户u对物品i的兴趣。
这里还有一个Trick,可以对 w i j w_{ij} wij进行归一化,使得物品之间计算相似度的时候保持同样的量级,如下所示:
w i j ′ = w i j m a x j w i j w_{ij}^{'}=\frac{w_{ij}}{max_{j}w_{ij}} wij′=maxjwijwij
2.3 隐语义模型
代码链接:https://github.com/Magic-Bubble/RecommendSystemPractice/blob/master/Chapter2/隐语义模型.ipynb
隐语义模型LFM其实就是机器学习的模型了,它其实是一种对矩阵分解的模拟,用两个低阶向量相乘来模拟实际的User-Item矩阵,如用如下公式计算用户u对物品i的兴趣:
P r e f e r e n c e ( u , i ) = r u i = p u T q i = ∑ k = 1 K p u , k q i , k Preference(u, i)=r_{ui}=p_{u}^{T}q_{i}=\sum_{k=1}^{K}p_{u,k}q_{i,k} Preference(u,i)=rui=puTqi=k=1∑Kpu,kqi,k
这里的 p u , k p_{u,k} pu,k和 q i , k q_{i,k} qi,k是模型的参数,其中 p u , k p_{u,k} pu,k度量了用户u的兴趣和第k个隐类的关系,而 q i , k q_{i,k} qi,k度量了第k个隐类和物品i之间的关系。
那么,label要怎么标注?一般数据集里面的都是只有正向反馈,即只有标签1,这时就需要进行负采样,即采出标签0来。采样是针对每个用户来进行的,对于每个用户,负采样的Item要遵循如下原则:
- 对每个用户,要保证正负样本的平衡(数目相似)。
- 对每个用户采样负样本时,要选取那些很热门,而用户却没有行为的物品。
在定义完打分公式和label标注之后,就可以利用机器学习中常用的梯度下降法进行模型的学习了。最后的模型还要加上一些正则化项,其loss为:
C = ∑ ( u , i ) ∈ K ( r u i − r ^ u i ) 2 = ∑ ( u , i ) ∈ K ( r u i − ∑ k = 1 K p u , k q i , k ) 2 + λ ∣ ∣ p u ∣ ∣ 2 + λ ∣ ∣ q i ∣ ∣ 2 C=\sum_{(u,i)\in K}(r_{ui}-\hat{r}_{ui})^2 = \sum_{(u,i)\in K}(r_{ui}-\sum_{k=1}^{K}p_{u,k}q_{i,k})^2 + \lambda||p_u||^2 + \lambda||q_i||^2 C=(u,i)∈K∑(rui−r^ui)2=(u,i)∈K∑(rui−k=1∑Kpu,kqi,k)2+λ∣∣pu∣∣2+λ∣∣qi∣∣2
主要是求 p u , k p_{u,k} pu,k和 q i , k q_{i,k} qi,k两组参数,其梯度为:
∂
C
∂
p
u
k
=
−
2
q
i
k
+
2
λ
p
u
k
\frac{\partial C}{\partial p_{uk}}=-2q_{ik}+2\lambda p_{uk}
∂puk∂C=−2qik+2λpuk
∂
C
∂
q
i
k
=
−
2
p
u
k
+
2
λ
p
i
k
\frac{\partial C}{\partial q_{ik}}=-2p_{uk}+2\lambda p_{ik}
∂qik∂C=−2puk+2λpik
根据随机梯度下降法,每次的更新为( α \alpha α为学习率):
p
u
k
=
p
u
k
+
α
(
q
i
k
−
λ
p
u
k
)
p_{uk} = p_{uk} + \alpha (q_{ik} - \lambda p{uk})
puk=puk+α(qik−λpuk)
q
i
k
=
q
i
k
+
α
(
p
u
k
−
λ
p
i
k
)
q_{ik} = q_{ik} + \alpha (p_{uk} - \lambda p{ik})
qik=qik+α(puk−λpik)
2.4 基于图的模型
代码链接:https://github.com/Magic-Bubble/RecommendSystemPractice/blob/master/Chapter2/基于图的模型.ipynb
用户的行为(User, Item)是可以表示为二分图的,比如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/0dc3d00c38c20c37da8b3d2c8875d418.png)
那么任务就变成了在二分图上为用户进行个性化推荐,即给用户u推荐物品的任务可以转化为度量用户顶点 v u v_u vu和与 v u v_u vu没有边直接相连的物品节点在图上的相关性,相关性越高的物品在推荐列表中的权重就越高。
正常来说,相关性高的一对节点之间一般具有如下特征:
- 两个顶点之间有很多路径相连;
- 连接两个顶点之间的路径长度都比较短;
- 连接两个顶点之间的路径不会经过出度比较大的顶点。
基于上面这三个主要因素,可以用PersonalRank算法进行图上的随机游走推荐,其原理为:要给用户u进行个性化推荐,可以从用户u对应的节点 v u v_u vu开始在用户物品二分图上进行随机游走。游走到任何一个节点时,首先按照概率 α \alpha α决定是继续游走,还是停止这次游走并从 v u v_u vu节点开始重新游走。如果决定继续游走,那么就从当前节点指向的节点中按照均匀分布随机选择一个节点作为游走下次经过的节点。这样,经过很多次随机游走后,每个物品节点被访问到的概率会收敛到一个数。最终的推荐列表中物品的权重就是物品节点的访问概率。
表述为递推公式如下:
P R ( v ) = { α ∑ v ′ ∈ i n ( v ) P R ( v ′ ) ∣ o u t ( v ′ ) ∣ v ≠ v u ( 1 − α ) + α ∑ v ′ ∈ i n ( v ) P R ( v ′ ) ∣ o u t ( v ′ ) ∣ v = v u PR(v)=\left\{ \begin{array}{rcl} \alpha \sum_{v' \in in(v)} \frac{PR(v^{'} )}{|out(v')|} & & {v \neq v_u}\\ (1-\alpha)+\alpha \sum_{v' \in in(v)} \frac{PR(v' )}{|out(v')|} & & {v = v_u}\\ \end{array} \right. PR(v)=⎩⎨⎧α∑v′∈in(v)∣out(v′)∣PR(v′)(1−α)+α∑v′∈in(v)∣out(v′)∣PR(v′)v̸=vuv=vu
在工程实现上,可以真的用循环来模拟游走,但还是推荐用矩阵的形式,令 M ( v , v ′ ) = 1 ∣ o u t ( v ) ∣ M(v, v') = \frac{1}{|out(v)|} M(v,v′)=∣out(v)∣1为用户物品二分图的转移概率矩阵,则迭代公式转化为:
r = ( 1 − α ) r 0 + α M T r r = (1 - \alpha) r_0 + \alpha M^Tr r=(1−α)r0+αMTr
这个公式是有解析解的,比模拟游走要方便很多。
r = ( 1 − α ) ( 1 − α M T ) − 1 r 0 r = (1 - \alpha) (1 - \alpha M^T)^{-1}r_0 r=(1−α)(1−αMT)−1r0
2.5 各算法比较
2.5.1 UserCF和ItemCF的综合比较
- UserCF比较古老,而ItemCF则相对较新。
- UserCF给用户推荐那些和他有共同兴趣爱好的用户喜欢的物品,而ItemCF给用户推荐那些和他之前喜欢的物品类似的物品。从这个算法的原理可以看到,UserCF的推荐结果着重于反映和用户兴趣相似的小群体的热点,而ItemCF的推荐结果着重于维系用户的历史兴趣。换句话说,UserCF的推荐更社会化,反映了用户所在的小型兴趣群体中物品的热门程度,而ItemCF的推荐更加个性化,反映了用户自己的兴趣传承。
- UserCF比较适合用于新闻推荐等热门程度和实时性较强的场景。比如一个人看新闻不可能每天只看和之前看的相关的那些,更多的是看今天有哪些新的热门的。从原理上讲,UserCF可以给用户推荐和他有相似爱好的一群其他用户今天都在看的新闻,这样在抓住热点和时效性的同时,保证了一定程度的个性化。从技术角度讲,UserCF主要是维护用户相似度表,在这种场景下,物品的更新速度远超用户,所以维护物品相似度表难度和成本都较高。
- ItemCF则适用于图书、电商、电影等场景。首先这些场景中用户的兴趣是比较固定和长久的,而且一般物品更新的速度不会特别快。
总结如下:
![](https://i-blog.csdnimg.cn/blog_migrate/bcc2a61622daece302296e022b05ef43.png)
2.5.2 LFM与基于邻域的方法的比较
- 理论基础:LFM是一种机器学习模型,有最优化等理论支撑。而基于邻域的方法更多的是基于统计,没有学习。
- 离线计算的空间复杂度:基于邻域的方法需要维护一张离线的相关表。假设有 M M M个用户和 N N N个物品,UserCF需要 O ( M ∗ M ) O(M*M) O(M∗M)的空间,ItemCF需要 O ( N ∗ N ) O(N*N) O(N∗N)的空间,而对于LFM,有 F F F个隐类的话,需要 O ( F ∗ ( M + N ) ) O(F*(M+N)) O(F∗(M+N))的空间。
- 离线计算的时间复杂度:假设有 M M M个用户、 N N N个物品、 K K K条用户对物品的行为记录。那么,UserCF计算用户相关表的时间复杂度是 O ( N ∗ ( K / N ) 2 ) O(N * (K/N)^2) O(N∗(K/N)2),而ItemCF计算物品相关表的时间复杂度是 O ( M ∗ ( K / M ) 2 ) O(M*(K/M)^2) O(M∗(K/M)2)。而对于LFM,如果用 F F F个隐类,迭代 S S S次,那么它的计算复杂度是 O ( K ∗ F ∗ S ) O(K * F * S) O(K∗F∗S)。那么,如果 K / N > F ∗ S K/N > F*S K/N>F∗S,则代表UserCF的时间复杂度低于LFM,如果 K / M > F ∗ S K/M>F*S K/M>F∗S,则说明ItemCF的时间复杂度低于LFM。在一般情况下,LFM的时间复杂度要稍微高于UserCF和ItemCF,这主要是因为该算法需要多次迭代。但总体上,这两种算法在时间复杂度上没有质的差别。
- 在线实时推荐:UserCF和ItemCF在线服务算法需要将相关表缓存在内存中,然后可以在线进行实时的预测。而从LFM的预测公式可以看到,LFM在给用户生成推荐列表时,需要计算用户对所有物品的兴趣权重,然后排名,返回权重最大的N个物品。那么,在物品数很多时,这一过程的时间复杂度非常高,可达 O ( M ∗ N ∗ F ) O(M*N*F) O(M∗N∗F)。因此,LFM不太适合用于物品数非常庞大的系统,如果要用,我们也需要一个比较快的算法给用户先计算一个比较小的候选列表,然后再用LFM重新排名。另一方面,LFM在生成一个用户推荐列表时速度太慢,因此不能在线实时计算,而需要离线将所有用户的推荐结果事先计算好存储在数据库中。因此,LFM不能进行在线实时推荐,也就是说,当用户有了新的行为后,他的推荐列表不会发生变化。
- 推荐解释:ItemCF算法支持很好的推荐解释,它可以利用用户的历史行为解释推荐结果。但LFM无法提供这样的解释,它计算出的隐类虽然在语义上确实代表了一类兴趣和物品,却很难用自然语言描述并生成解释展现给用户。
协同过滤篇到这里就分享完了,后面将继续分享第三部分——冷启动篇。