概览
- 使用场景
- 直观解释
- 计算 & 拓展
- 举个栗子(python)
- 算法复杂度
最后真推荐歌了~
使用场景
想象一个做协同过滤的推荐场景,想象不出来看下图~
先试试传统的基于物品的协同过滤(cf)方法,用户1喜欢摇滚和轻音乐,简单认为摇滚和轻音乐是相似的。同理用户2喜欢轻音乐和民谣,简单认为轻音乐和民谣相似。
最后计算item间相似度的时候,和摇滚相似的是轻音乐,和民谣相似的是轻音乐,传统的基于item的cf是发现不了摇滚和民谣的相似性的。
这里就不考虑基于用户的cf了,确实基于用户的cf是可以发现摇滚和民谣的关系滴~
这里带入SimRank的思想
Two objects are similar if they are related to similar objects.
这里物品的关系由消费物品的用户来表达。
从用户1和用户2的历史记录里面得到的 摇滚 - 轻音乐 - 民谣 的关系,可以得到摇滚和民谣的相似度
说明 SimRank 的思想可以发现 ItemCF 发现不了的 item 相似关系
直观解释
定义与公式
item-item关系图
节点a关联到节点b的关系
节点a的邻居节点集合,即所有指向a的节点集合
节点间相似度s(a,b),C是衰减因子,经验取值为0.8
复现计算过程
6个用户收听5首歌的情景,通过log生成左边的二部图,再通过用户作为联系生成右边的item关系图
按照相似度公式依次计算每个节点,发现I2和I4有共同的入度节点I1,就是从(I1,I1)发现了(I2,I4)
依次进行n轮计算,图中标明了每轮计算新发现的关系,每次新发现的关系又可以反补给以前发现的关系,作为增强
可以发现第一轮发现的关系类似于推荐理由“喜欢x的用户也喜欢y”,这种第一轮计算发现的关系可以看作item之间的一次跳跃,暂时称作一跳关系,第二轮迭代发现的(I3,I5)可以看做是2条关系,第n轮拓展的为n跳关系
基于MR模型的计算方法
map阶段
去寻找每个节点的相邻节点,即寻找I(a)里,I(b)里所有点的相似度,从而计算s(a,b)
reduce阶段
根据计算结果更新全局的相似度
拓展的simrank
打开方法:自行google
- delta-simrank
- simrank++
计算方式的拓展
- 深度优先搜索
- 基于矩阵乘法的计算
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做笛卡尔
后记
- 我是败给复杂度了
- 最开始的例子,我被推荐了一首Fool's Day,循环到手机没电的节奏
http://music.163.com/#/song?id=3951074