知识图谱中的余弦相似度计算

从谷歌最早提出知识图谱的概念后,知识图谱的火爆从美国一路烧到了国内,近几年知识图谱技术在国内已经得到了飞速的发展,我们对知识图谱的概念及应用都不再陌生。大家可以看到知识图谱技术的应用出现在越来越多的垂直领域中。从最早大家最为熟悉的在搜索引擎中的应用,逐渐地扩充到金融领域、医药领域等等。今天我们已经在各行各业中,都能够看到知识图谱的身影,更多的技术人员也加入了我们知识图谱工程的大家庭。

那么今天我们来就知识图谱的技术问题进行更深层的探讨。今天我将和大家分享一个,我在知识图谱搭建中遇到的棘手问题,相信有不少小伙伴也会遇到这样的问题。希望今天我分享的解决方案可以给大家一些帮助!

知识图谱构建的过程中不可回避的问题: 知识图谱也属于需要建立在海量数据之上的一种应用,在构建知识图谱的图关系时,基础数据来自很多不同的数据源。常见的数据源有抓取的公开信息、业务数据、三方数据、用户授权的数据等。

由于数据来源于不同的渠道,而中文又是一门博大精深的语言,同一种意义却又有很多种表达。那么这个棘手的问题就随之而来:我们收集的数据中,有大量的表达方式所表达的是相同的意义。比如:阿里巴巴网络技术有限公司和阿里巴巴集团是同一家公司么?北京市国贸中心写字楼和北京朝阳区建外大街1号国贸中心是同一个地址么?在金融风控领域中,公司的名称、地址是出现频率非常高的词汇,如果按照传统的字符串比对的方式,就会认为以上信息表示的并不是相同的意义,那这样,就会给知识图谱的构建造成很大的麻烦,因为创建了很多无用的实体,并且很多本应该创建的关系,却没有创建出来。

这个时候,需要进行的工作就是实体消歧,也就是说把地址、公司名称等信息经过处理后,变成同一个地址和公司名称,让他们表示同一种意义。只有这样,才能够将实体和关系成功的创建。

解决思路:

将这类容易造成歧义的字符串进行相似度计算,是解决这类问题的一种高效的手段。
那么在知名的图数据库Neo4j中,如何实现相似度以计算如何满足这种应用场景呢,就是我们接下来讨论的主要话题。

我将会采用余弦相似度计算来解决这个问题。大家都知道余弦相似度是n维空间中两个n维向量之间角度的余弦。它是两个向量的点积除以两个向量的长度(或幅度)的乘积。

具体的计算方式可参见下面的公式:
知识图谱中的余弦相似度计算_第1张图片
以上公式计算的结果介于-1和1之间,其中-1完全不同,1完全相似。

那么在图数据库Neo4j中如何进行余弦相似度计算呢?下面我会简单说明整个计算过程。不得不说我们非常幸运,Neo4j的插件提供了这样的一个功能,让我们能够简单、直接的在其上实现相似度计算。

首先我们来进行环境安装:
环境的安装其实非常的简单,这里你只需要一个jar包,就可以将其搞定。
下载链接:https://github.com/neo4j-contrib/neo4j-graph-algorithms/releases

下载后,具体的操作步骤如下:
将下载的"graph-algorithms-algo-3.5.0.1.jar"包拷贝到"$NEO4J_HOME/plugins"目录中

这里需要注意的是:要修改Neo4j的配置将
dbms.security.procedures.unrestricted=algo.*
添加到neo4j.conf文件当中,一定要做,要不然后边的试验会失败。然后重启Neo4j数据库就可以了。

插件安装成功后,打开你的Neo4j数据库,在Cypher的输入框中输入 :

RETURN algo.similarity.cosine([3,8,7,5,2,9], [10,8,6,6,4,5]) AS similarity

这个语句的意思就是调用Neo4j提供的算法计算库中的函数,并且计算[3,8,7,5,2,9]和 [10,8,6,6,4,5]两组数据的余弦相似度,那么返回的结果是(如下图):
知识图谱中的余弦相似度计算_第2张图片

那么其结果已经非常的接近1了。这就是这两个数字列表的余弦相似度值。这个时候你会发现使用Neo4j库中的插件来实现,非常的简单,远比自己开发一个这样的功能要容易的多。

那么下面,我们可以自己来根据公式进行推理一下,以便于进一步掌握这种算法。首先,我们创建一个图关系,将下面的Cypher在输入框中运行,就会创建好一个图关系:

MERGE (french:Cuisine {name:'French'})
MERGE (italian:Cuisine {name:'Italian'})
MERGE (indian:Cuisine {name:'Indian'})
MERGE (lebanese:Cuisine {name:'Lebanese'})
MERGE (portuguese:Cuisine {name:'Portuguese'})
MERGE (zhen:Person {name: "Zhen"})
MERGE (praveena:Person {name: "Praveena"})
MERGE (michael:Person {name: "Michael"})
MERGE (arya:Person {name: "Arya"})
MERGE (karin:Person {name: "Karin"})
MERGE (praveena)-[:LIKES {score: 9}]->(indian)
MERGE (praveena)-[:LIKES {score: 7}]->(portuguese)
MERGE (zhen)-[:LIKES {score: 10}]->(french)
MERGE (zhen)-[:LIKES {score: 6}]->(indian)
MERGE (michael)-[:LIKES {score: 8}]->(french)
MERGE (michael)-[:LIKES {score: 7}]->(italian)
MERGE (michael)-[:LIKES {score: 9}]->(indian)
MERGE (arya)-[:LIKES {score: 10}]->(lebanese)
MERGE (arya)-[:LIKES {score: 10}]->(italian)
MERGE (arya)-[:LIKES {score: 7}]->(portuguese)
MERGE (karin)-[:LIKES {score: 9}]->(lebanese)
MERGE (karin)-[:LIKES {score: 7}]->(italian)

下面我们再用这个数据进行一次计算,输入以下Cypher:

MATCH (p:Person), (c:Cuisine) OPTIONAL MATCH (p)-[likes:LIKES]->(c) WITH {item:id(p), weights: collect(coalesce(likes.score, 0))} as userData WITH collect(userData) as data CALL algo.similarity.cosine.stream(data) YIELD item1, item2, count1, count2, similarity RETURN algo.getNodeById(item1).name AS from, algo.getNodeById(item2).name AS to, similarity ORDER BY similarity DESC

运行后的结果是:
知识图谱中的余弦相似度计算_第3张图片

我们从结果中可以看到得分为0.889的是最高分,那么我们可以认为Arya和Karin的食物口味最相似。但结果当中还有很多数据是我们不关心的,因为他们的相似度为0,原因是我数据库中原本有一些数据导致,那么现在我们要把这些数据过滤掉,输入以下Cypher:

MATCH (p:Person), (c:Cuisine)
OPTIONAL MATCH (p)-[likes:LIKES]->(c)
WITH {item:id(p), weights: collect(coalesce(likes.score, 0))} as userData
WITH collect(userData) as data
CALL algo.similarity.cosine.stream(data, {similarityCutoff: 0.0})
YIELD item1, item2, count1, count2, similarity
RETURN algo.getNodeById(item1).name AS from, algo.getNodeById(item2).name AS to, similarity
ORDER BY similarity DESC

运行结果如下:
知识图谱中的余弦相似度计算_第4张图片

我们可以看到那些没有相似性的数据已被过滤掉了。如果我们正在实现k-Nearest Neighbors类型查询,我们可能希望k为给定数据找到最相似的数据。我们可以通过传入topK参数来进行条件控制。

使用以下Cypher将返回最相似的数据(即k=1):

MATCH (p:Person), (c:Cuisine)
OPTIONAL MATCH (p)-[likes:LIKES]->(c)
WITH {item:id(p), weights: collect(coalesce(likes.score, 0))} as userData
WITH collect(userData) as data
CALL algo.similarity.cosine.stream(data, {topK:1, similarityCutoff: 0.0})
YIELD item1, item2, count1, count2, similarity
RETURN algo.getNodeById(item1).name AS from, algo.getNodeById(item2).name AS to, similarity
ORDER BY from

执行结果:
知识图谱中的余弦相似度计算_第5张图片

细心的同学会发现,以上的结果有一点问题,第一行的结果和第二行的结果其实是相同的。那么我们现在要做的是为每个用户找到最相似的用户,并存储这些用户之间的关系:

下面在你的Cypher输入框中输入以下Cypher:

MATCH (p:Person), (c:Cuisine)
OPTIONAL MATCH (p)-[likes:LIKES]->(c)
WITH {item:id(p), weights: collect(coalesce(likes.score, 0))} as userData
WITH collect(userData) as data
CALL algo.similarity.cosine(data, {topK: 1, similarityCutoff: 0.1, write:true})
YIELD nodes, similarityPairs, write, writeRelationshipType, writeProperty, min, max, mean, stdDev, p25, p50, p75, p90, p95, p99, p999, p100
RETURN nodes, similarityPairs, write, writeRelationshipType, writeProperty, min, max, mean, p95

执行结果如下:
知识图谱中的余弦相似度计算_第6张图片
然后,我们可以写一个查询,以找出与我们相似的其他人可能喜欢的美食类型。
以下Cypher将找到与Praveena最相似的用户

MATCH (p:Person {name: "Praveena"})-[:SIMILAR]->(other),
(other)-[:LIKES]->(cuisine)
WHERE not((p)-[:LIKES]->(cuisine))
RETURN cuisine.name AS cuisine

很幸运,我们已经找到了,下面就是执行结果:
知识图谱中的余弦相似度计算_第7张图片
以上就是整个的计算过程。解决相似度计算的思路我们已经有了。 在知识图谱的构建中,还有更多的挑战等着我们。


本文转载自"贪心科技AI"公众号
原文链接https://mp.weixin.qq.com/s/wlsVlyRsig3Fisv78pGJug

你可能感兴趣的:(知识图谱)