我有兴趣为图中未直接连接的所有顶点对计算Jaccard相似性度量。雅卡德度量标准定义为两个顶点的邻居的交点的范数除以相同集合的并集范数。
其中
到目前为止,我已经能够获得所有未直接连接的节点对(仅对这种情况感兴趣以进行链接预测,如果已经存在直接链接,那么我就不需要计算Jaccard度量标准) (x,y)其中x不等于y:
g.V().as('v1').V().where(neq('v1')).as('v2').filter(__.not(inE().where(outV().as('v1'))))
除此之外,我还为标记为v1out和v2out的每个配对成员包括邻居:
g.V().as('v1').out().as('v1out').V().where(neq('v1')).as('v2').filter(__.not(inE().where(outV().as('v1')))).out().as('v2out')
在这里,我将如何执行集合操作以获取两个相邻集合的交集和并集中的元素数?在那之后,我相信我可以追加数学步骤(当前使用TinkerPop 3.4.0)来计算Jaccard相似率,然后是选择步骤以在值大于阈值时添加边缘。如果完全不同的方法比上面的部分解决方案有好处,我很乐意采用它并最终使它起作用。
答案 0 :(得分:3)
让我们逐步进行:
找到顶点对,并收集它们各自的邻居:
g.V().match(
__.as('v1').out().dedup().fold().as('v1n'),
__.as('v1').V().as('v2'),
__.as('v2').out().dedup().fold().as('v2n')).
where('v1', neq('v2'))
确保v1
不是v2
的邻居,反之亦然:
g.V().match(
__.as('v1').out().dedup().fold().as('v1n'),
__.as('v1').V().as('v2'),
__.as('v2').out().dedup().fold().as('v2n')).
where('v1', neq('v2').and(without('v2n'))).
where('v2', without('v1n'))
接下来,计算相交的邻居数和邻居总数:
g.V().match(
__.as('v1').out().dedup().fold().as('v1n'),
__.as('v1').V().as('v2'),
__.as('v2').out().dedup().fold().as('v2n')).
where('v1', neq('v2').and(without('v2n'))).
where('v2', without('v1n')).as('m').
project('v1','v2','i','u').
by(select('v1')).
by(select('v2')).
by(select('v1n').as('n').
select('m').
select('v2n').unfold().
where(within('n')).
count()).
by(union(select('v1n'),
select('v2n')).unfold().
dedup().count())
最后,通过将i
除以u
来计算Jaccard相似度(还要确保滤除没有邻居的顶点以防止被0除):
g.V().match(
__.as('v1').out().dedup().fold().as('v1n'),
__.as('v1').V().as('v2'),
__.as('v2').out().dedup().fold().as('v2n')).
where('v1', neq('v2').and(without('v2n'))).
where('v2', without('v1n')).as('m').
project('v1','v2','i','u').
by(select('v1')).
by(select('v2')).
by(select('v1n').as('n').
select('m').
select('v2n').unfold().
where(within('n')).
count()).
by(union(select('v1n'),
select('v2n')).unfold().
dedup().count()).
filter(select('u').is(gt(0))).
project('v1','v2','j').
by(select('v1')).
by(select('v2')).
by(math('i/u'))
最后一件事:由于比较顶点v1
和v2
与比较v2
和v1
相同,因此查询只需要考虑一种情况。一种方法是确保v1
的ID小于v2
的ID:
g.V().match(
__.as('v1').out().dedup().fold().as('v1n'),
__.as('v1').V().as('v2'),
__.as('v2').out().dedup().fold().as('v2n')).
where('v1', lt('v2')).
by(id).
where('v1', without('v2n')).
where('v2', without('v1n')).as('m').
project('v1','v2','i','u').
by(select('v1')).
by(select('v2')).
by(select('v1n').as('n').
select('m').
select('v2n').unfold().
where(within('n')).
count()).
by(union(select('v1n'),
select('v2n')).unfold().
dedup().count()).
filter(select('u').is(gt(0))).
project('v1','v2','j').
by(select('v1')).
by(select('v2')).
by(math('i/u'))
在the modern toy graph上执行遍历会产生以下结果:
gremlin> g = TinkerFactory.createModern().traversal()
==>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]
gremlin> g.V().match(
......1> __.as('v1').out().dedup().fold().as('v1n'),
......2> __.as('v1').V().as('v2'),
......3> __.as('v2').out().dedup().fold().as('v2n')).
......4> where('v1', lt('v2')).
......5> by(id).
......6> where('v1', without('v2n')).
......7> where('v2', without('v1n')).as('m').
......8> project('v1','v2','i','u').
......9> by(select('v1')).
.....10> by(select('v2')).
.....11> by(select('v1n').as('n').
.....12> select('m').
.....13> select('v2n').unfold().
.....14> where(within('n')).
.....15> count()).
.....16> by(union(select('v1n'),
.....17> select('v2n')).unfold().
.....18> dedup().count()).
.....19> filter(select('u').is(gt(0))).
.....20> project('v1','v2','j').
.....21> by(select('v1')).
.....22> by(select('v2')).
.....23> by(math('i/u'))
==>[v1:v[1],v2:v[5],j:0.0]
==>[v1:v[1],v2:v[6],j:0.3333333333333333]
==>[v1:v[2],v2:v[4],j:0.0]
==>[v1:v[2],v2:v[6],j:0.0]
==>[v1:v[4],v2:v[6],j:0.5]
==>[v1:v[5],v2:v[6],j:0.0]