相似度演算法在知識圖譜中的實現
隨著知識圖譜的火爆從美國一路燒到了國內,近幾年知識圖譜技術在國內已經得到了飛速的發展,我們對知識圖譜的概念及應用都不再陌生。你可以看到知識圖譜技術的應用出現在越來越多的垂直領域中。從最早大家最為熟悉的在搜尋引擎中的應用,逐漸地擴充到金融領域、醫藥領域等等。今天我們已經在各行各業中,都能夠看到知識圖譜的身影,更多的技術人員也加入了我們知識圖譜工程的大家庭。
那麼今天我們來就知識圖譜的技術問題進行更深層的探討。今天我將和大家分享一個,我在知識圖譜搭建中遇到的棘手問題,相信有不少小夥伴也會遇到這樣的問題。希望今天我分享的解決方案可以給大家一些幫助!
我遇到的問題:
在構建知識圖譜的圖關係時,基礎資料來自很多不同的資料來源。比如金融風控領域中,我們要構建的知識圖譜中,包含地址、公司等出現頻率比較高,並且名稱一模一樣的可能性很低的詞彙。
比如:北京市國貿中心寫字樓和北京朝陽區建外大街1號國貿中心是同一個地址麼?
那在圖關係的構建中,如果把上地址作為兩個地址進行處理的話,那麼就會建立兩個實體,並且這兩個實體之間並沒有什麼關聯關係,這種處理方法,肯定是錯誤的。這個時候,需要進行的工作就是地址消歧,把兩個地址經過處理後,變成同一個地址。
這個時候我們需要做的是進行相似度計算。
我的解決方案:
在Neo4j中的餘弦相似度計算如何滿足這種應用場景。
那麼什麼是餘弦相似度呢?餘弦相似度是n維空間中兩個n維向量之間角度的餘弦。它是兩個向量的點積除以兩個向量的長度(或幅度)的乘積。
餘弦相似度公式:
那麼計算的值介於-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資料庫,輸入 :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]兩組資料的餘弦相似度,那麼返回的結果是:
那麼這個值已經非常的接近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)
neo4j插入結果:
那麼我們用這個資料再進行一次計算
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
相似度計算結果:
以上,我們可以看到Arya和Karin的食物口味最相似,得分為0.889。最高分為1,因此它們非常接近最大相似度
下邊還有很多相似度為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
執行結果:
我們可以看到那些沒有相似性的使用者已被過濾掉了。如果我們正在實現k-Nearest Neighbors型別查詢,我們可能希望k為給定使用者找到最相似的使用者。我們可以通過傳入topK引數來做到這一點。
以下將返回使用者流以及最相似的使用者(即k=1):
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, {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
執行結果:
細心的同學會發現,以上的結果有一點問題,第一行的結果和第二行的結果其實是相同的。
那麼我們現在要做的是為每個使用者找到最相似的使用者,並存儲這些使用者之間的關係:
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
執行結果如下:
然後,我們可以寫一個查詢,以找出與我們相似的其他人可能喜歡的美食型別。
以下將找到與Praveena最相似的使用者
Cypher:
MATCH (p:Person {name: "Praveena"})-[:SIMILAR]->(other),
(other)-[:LIKES]->(cuisine)
WHERE not((p)-[:LIKES]->(cuisine))
RETURN cuisine.name AS cuisine
執行結果:
以上就是整個的計算過程。