本博文将介绍PersonalRank算法,以及该算法在推荐系统上的应用。
将用户行为数据用二分图表示,例如用户数据是由一系列的二元组组成,其中每个元组(u,i)表示用户u对物品i产生过行为。
将个性化推荐放在二分图模型中,那么给用户u推荐物品任务可以转化为度量Uv和与Uv 没有边直接相连 的物品节点在图上的相关度,相关度越高的在推荐列表中越靠前。
图中顶点的相关度主要取决与以下因素:
1)两个顶点之间路径数
2)两个顶点之间路径长度
3)两个顶点之间路径经过的顶点
而相关性高的顶点一般有如下特性:
1)两个顶点有很多路径相连
2)连接两个顶点之间的路径长度比较短
3)连接两个顶点之间的路径不会经过出度较大的顶点
下面详细介绍基于随机游走的PersonalRank算法。
假设给用户u进行个性化推荐,从图中用户u对应的节点Vu开始游走,游走到一个节点时,首先按照概率alpha决定是否继续游走,还是停止这次游走并从Vu节点开始重新游走。如果决定继续游走,那么就从当前节点指向的节点中按照均匀分布随机选择一个节点作为下次经过的节点,这样经过很多次的随机游走后,每个物品节点被访问到的概率就会收敛到一个数。最终推荐列表中物品的权重就是物品节点的访问概率。
迭代公式如下:
公式中PR(i)表示物品i的访问概率(也即是物品i的权重),out(i)表示物品节点i的出度。alpha决定继续访问的概率。
以下面的二分图为例,实现PersonalRank算法
#coding:utf-8
import time
def PersonalRank(G,alpha,root,max_depth):
rank=dict()
rank={x:0 for x in G.keys()}
rank[root]=1
#开始迭代
begin=time.time()
for k in range(max_depth):
tmp={x:0 for x in G.keys()}
#取出节点i和他的出边尾节点集合ri
for i,ri in G.items():
#取节点i的出边的尾节点j以及边E(i,j)的权重wij,边的权重都为1,归一化后就是1/len(ri)
for j,wij in ri.items():
tmp[j]+=alpha*rank[i]/(1.0*len(ri))
tmp[root]+=(1-alpha)
rank=tmp
end=time.time()
print 'use_time',end-begin
lst=sorted(rank.items(),key=lambda x:x[1],reverse=True)
for ele in lst:
print "%s:%.3f, \t" %(ele[0],ele[1])
return rank
if __name__=='__main__':
alpha=0.8
G = {'A': {'a': 1, 'c': 1},
'B': {'a': 1, 'b': 1, 'c': 1, 'd': 1},
'C': {'c': 1, 'd': 1},
'a': {'A': 1, 'B': 1},
'b': {'B': 1},
'c': {'A': 1, 'B': 1, 'C': 1},
'd': {'B': 1, 'C': 1}}
PersonalRank(G,alpha,'b',50)
上面算法在时间复杂度上有个明显的缺陷,每次为每个用户推荐时,都需要在整个用户物品二分图上进行迭代,直到整个图上每个节点收敛。这一过程时间复杂度非常高,不仅无法提高实时推荐,甚至离线生产推荐结果也很耗时。
为了解决时间复杂度过高问题,我们可以从矩阵角度出发,personalrank经过多次的迭代游走,使得各节点的重要度趋于稳定,实际上我们根据状态转移矩阵,经过一次矩阵运算就可以直接得到系统的稳态。上面迭代公式的矩阵表示形式为:
r
其中r是个n维向量,每个元素代表一个节点的PR重要度,r0也是个n维向量,第i个位置上是1,其余元素均为0,上面迭代公式左边有个PR(j)和PR(i),其中i是j一条入边。他们迭代最终的概率(权重)都在r中。我们就是要为第i个节点进行推荐。M是n阶转移矩阵:
上式可以变形到:
这就相当于解线性方程组了.