SimRank for 协同过滤

概览

  • 使用场景
  • 直观解释
  • 计算 & 拓展
  • 举个栗子(python)
  • 算法复杂度

最后真推荐歌了~

使用场景

想象一个做协同过滤的推荐场景,想象不出来看下图~

user-item graph

先试试传统的基于物品的协同过滤(cf)方法,用户1喜欢摇滚和轻音乐,简单认为摇滚和轻音乐是相似的。同理用户2喜欢轻音乐和民谣,简单认为轻音乐和民谣相似。

最后计算item间相似度的时候,和摇滚相似的是轻音乐,和民谣相似的是轻音乐,传统的基于item的cf是发现不了摇滚和民谣的相似性的。

这里就不考虑基于用户的cf了,确实基于用户的cf是可以发现摇滚和民谣的关系滴~

这里带入SimRank的思想

Two objects are similar if they are related to similar objects.

这里物品的关系由消费物品的用户来表达。

image.png

从用户1和用户2的历史记录里面得到的 摇滚 - 轻音乐 - 民谣 的关系,可以得到摇滚和民谣的相似度

说明 SimRank 的思想可以发现 ItemCF 发现不了的 item 相似关系

直观解释

定义与公式

item-item关系图

item-item关系图

节点a关联到节点b的关系

节点a关联到节点b的关系

节点a的邻居节点集合,即所有指向a的节点集合

节点a的邻居节点集合

节点间相似度s(a,b),C是衰减因子,经验取值为0.8

节点间相似度s(a,b)

复现计算过程

6个用户收听5首歌的情景,通过log生成左边的二部图,再通过用户作为联系生成右边的item关系图

image.png

按照相似度公式依次计算每个节点,发现I2和I4有共同的入度节点I1,就是从(I1,I1)发现了(I2,I4)

image.png

依次进行n轮计算,图中标明了每轮计算新发现的关系,每次新发现的关系又可以反补给以前发现的关系,作为增强

image.png

可以发现第一轮发现的关系类似于推荐理由“喜欢x的用户也喜欢y”,这种第一轮计算发现的关系可以看作item之间的一次跳跃,暂时称作一跳关系,第二轮迭代发现的(I3,I5)可以看做是2条关系,第n轮拓展的为n跳关系

基于MR模型的计算方法

map阶段

去寻找每个节点的相邻节点,即寻找I(a)里,I(b)里所有点的相似度,从而计算s(a,b)

reduce阶段

根据计算结果更新全局的相似度

image.png

拓展的simrank

打开方法:自行google

  1. delta-simrank
  2. simrank++

计算方式的拓展

  1. 深度优先搜索
  1. 基于矩阵乘法的计算

python的local Example


G = nx.read_graphml("/Users/Matter/paper_code/simrank/widom.graphml")
r=0.8
max_iter=10
eps=1e-4
nodes = G.nodes()   #['1', '0', '3', '2', '4']

pred_func = G.predecessors if isinstance(G, nx.DiGraph) else G.neighbors
#  nodes_i      {'0': 1, '1': 0, '2': 3, '3': 2, '4': 4}
nodes_i = {nodes[i]: i for i in range(0, len(nodes))}

sim_prev = numpy.zeros(len(nodes))  # array([ 0.,  0.,  0.,  0.,  0.])
sim = numpy.identity(len(nodes))
# ###  单位矩阵
# array([[ 1.,  0.,  0.,  0.,  0.],
#        [ 0.,  1.,  0.,  0.,  0.],
#        [ 0.,  0.,  1.,  0.,  0.],
#        [ 0.,  0.,  0.,  1.,  0.],
#        [ 0.,  0.,  0.,  0.,  1.]])
# ###

# round 1
sim_prev = numpy.copy(sim)

for u, v in itertools.product(nodes, nodes):
    if u==v:continue
    u_ps, v_ps = pred_func(u), pred_func(v)
    s_uv = 0
    for u_n, v_n in itertools.product(u_ps, v_ps):
        s_uv += sim_prev[nodes_i[u_n]][nodes_i[v_n]]
    sim[nodes_i[u]][nodes_i[v]] = (r * s_uv) / (len(u_ps) * len(v_ps) + DIV_EPS)

对应图的结构

<?xml version="1.0" encoding="utf-8"?><graphml xmlns="[http://graphml.graphdrawing.org/xmlns](http://graphml.graphdrawing.org/xmlns)" xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance](http://www.w3.org/2001/XMLSchema-instance)" xsi:schemaLocation="[http://graphml.graphdrawing.org/xmlns](http://graphml.graphdrawing.org/xmlns) [http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd](http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd)">
  <graph edgedefault="directed">
    <node id="0" />
    <node id="1" />
    <node id="2" />
    <node id="3" />
    <node id="4" />
    <edge source="0" target="1" />
    <edge source="2" target="0" />
    <edge source="1" target="2" />
    <edge source="0" target="3" />
    <edge source="3" target="4" />
    <edge source="4" target="3" />
  </graph>
</graphml>

算法复杂度

n_i:item的数量
n:item对的数量,大概是n_i * n_i
neighbor:每个item的平均邻居数量
d:每一对vid对里的结点邻居平均数量乘积,大概 neighbor * neighbor
一轮迭代的空间复杂度:O(n*n)
一轮迭代的空间复杂度:O(n*n*d)

对应到item数量就是O(n_i * n_i * n_i * n_i)的复杂度
简而言之就是item做笛卡尔得到item pair,再对item pair做笛卡尔

后记

推荐阅读更多精彩内容