基於Neo4j的個性化Pagerank算法文章推薦系統實踐


新版的Neo4j圖形算法庫(algo)中增加了個性化Pagerank的支持,我一直想找個有意思的應用來驗證一下此算法效果。最近我看Peter Lofgren的一篇論文《高效個性化Pagerank算法》(Efficient Algorithms for Personalized PageRank)(https://arxiv.org/pdf/1512.04633.pdf),在論文中,有一個比較有趣的示例:

我們想在論文引用網絡中進行個性化搜索的嘗試,但是要怎樣設置個性化PageRank的參數,才能得到不同的排序結果?論文引用數據采用Citeseer檢索中開放的。我們計划創建一個論文查詢應用,用戶輸入一個關鍵詞和一個作者名稱,得到所有包含此關鍵詞的論文,排序是從輸入作者的角度去考慮。對於每一位作者,其所有論文都給以相同的權重,然后再使用個性化PageRank對關鍵詞搜索出來的論文進行排序。例如,關鍵詞“entropy”對於不同的作者有不同的含義,這樣,我們就可以從不同的角度去比較關鍵詞“entropy”搜索出來的結果。

接下來,我們就使用Neo4j來重建這個場景

前提

  • Neo4j
  • Neo4j圖像庫(algo)
  • Neo4jAPOC庫
  • Graphaware的NLP插件

我們需要下載所有插件並做如下配置:

dbms.unmanaged_extension_classes=com.graphaware.server=/graphaware
com.graphaware.runtime.enabled=true
com.graphaware.module.NLP.1=com.graphaware.nlp.module.NLPBootstrapper
dbms.security.procedures.whitelist=ga.nlp.*,algo.*,apoc.*
dbms.security.procedures.unrestricted=apoc.*,algo.*
apoc.import.file.enabled=true

圖模型

基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

從上圖可見,我們的模型很簡單,模型里的結點分為兩類,標簽分別為Author和Article,每個Author節點有一個或多個到Article節點的AUTHOR關系,同時,Article節點與其他Article節點還有REFERENCE關系。

為了優化請求,此圖模型還需要定義一些索引。分別在Article節點的index屬性和Author節點的name屬性上建立唯一約束。

CALL apoc.schema.assert(
{},
{Article:['index'],Author:['name']})

數據導入

我們使用aminer.org網站上提供的論文引用數據(https://static.aminer.cn/lab-datasets/citation/dblp.v10.zip),這是此數據的最新版本,最重要的是他以json方式存儲的。

關於此數據庫的更多信息可以看這篇論文《ArnetMiner:學術社交網絡的提取與挖掘》(http://keg.cs.tsinghua.edu.cn/jietang/publications/KDD08-Tang-et-al-ArnetMiner.pdf)

譯者言:《ArnetMiner:學術社交網絡的提取與挖掘》一文的第一作者是清華大學唐傑教授

將數據導入到Neo4j中分為兩步,第一步導入所有論文及他們的作者,第二步建立這些論文的引用關系。

導入數據我們使用 apoc.periodic.iterate 進行批量導入。

導入論文及作者

CALL apoc.periodic.iterate(
'UNWIND ["dblp-ref-0.json","dblp-ref-1.json","dblp-ref-2.json","dblp-ref-3.json"] as file
CALL apoc.load.json("file:///neo4j/import/" + file)
yield value return value',
'MERGE (a:Article{index:value.id})
ON CREATE SET a += apoc.map.clean(value,["id","authors","references"],[0])
WITH a,value.authors as authors
UNWIND authors as author
MERGE (b:Author{name:author})
MERGE (b)-[:AUTHOR]->(a)'
,{batchSize: 10000, iterateList: true})

建立引用關系

CALL apoc.periodic.iterate(
'UNWIND ["dblp-ref-0.json","dblp-ref-1.json","dblp-ref-2.json","dblp-ref-3.json"] as file
CALL apoc.load.json("file:///neo4j/import/" + file)
yield value return value',
'MERGE (a:Article{index:value.id})
WITH a,value.references as references
UNWIND references as reference
MERGE (b:Article{index:reference})
MERGE (a)-[:REFERENCES]->(b)'
,{batchSize: 10000, iterateList: true})

PageRank算法

PageRank設計之初是用來分析網頁重要性的。它主要考慮的是網站擁有的連接個數和質量,如一網站,從reddit首頁有一個鏈接到它,和從我的blog有一個鏈接到它,那么這兩個鏈接的結果就完全不同了。

而這樣一過程很容易應用到論文的引用網絡上,論文的引用可以視為一篇文章對另一篇文章投了一個“贊成”票,而哪篇文章的“贊成”票最多?這正是PageRank最擅長解決的問題。

使用PageRank算法在全球論文引用網絡上可以找到在圖中最重要的文章和最有影響力的文章。

運行PageRank並把結果存儲到結點的屬性中

CALL algo.pageRank('Article', 'REFERENCES')

通過pagerank得到最重要的文章

MATCH (a:Article)
RETURN a.title as article,
a.pagerank as score
ORDER BY score DESC
LIMIT 10

運行結果如下:

基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

自然語言處理(NLP)

如果我們要通過關鍵詞來推薦文件,那么就需要從圖中提取關鍵詞。這里要感謝Graphaware的NLP插件,讓這一過程非常簡單,即使你完全不了解NLP算法也可以做NLP相關工作。

NLP過程將會在我們的圖模型上增加一些節點和關系,具體如下圖所示:

基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

定義NLP模型

為了優化NLP處理,這里需要定義一些特殊的約束和索引。

CALL ga.nlp.createSchema()

增加處理管道

定義一些處理管道的配置,關於處理管道的更多信息見這里(https://github.com/graphaware/neo4j-nlp#pipelines-and-components)

CALL ga.nlp.processor.addPipeline({
textProcessor: 'com.graphaware.nlp.processor.stanford.StanfordTextProcessor',
name: 'defaultPipeline',
threadNumber: 4
processingSteps: {tokenize: true,
ner: true,
dependency: false}})

設置默認管道

CALL ga.nlp.processor.pipeline.default('defaultPipeline')

文本標注

原始的文本被拆成了單詞、段落和函數。這里對文本的分析還僅僅只是一個開始。

如果想了解更多關於文本標注,推薦你看Christophe Willemsen寫的這篇文章《用Neo4j和NLP插件逆向工程書籍存儲》(https://graphaware.com/neo4j/2017/07/24/reverse-engineering-book-stories-nlp.html)

CALL apoc.periodic.iterate(
"MATCH (n:Article) WHERE exists (n.title) RETURN n",
"CALL ga.nlp.annotate({text: n.title, id: id(n)})
YIELD result MERGE (n)-[:HAS_ANNOTATED_TEXT]->(result)",
{batchSize:1, iterateList:true})

關鍵詞提取

TextRank算法是一種相對簡單、無監督的文本摘要方法,其可以直接進行主題提取。它的目標就是運用檢索關鍵詞及構建詞共現關系圖,得到對文檔具有描述性的關鍵短語,而PageRank算法則對詞的重要性進行排序。

---取之《使用圖進行高效無監督關鍵詞提取》(https://graphaware.com/neo4j/2017/10/03/efficient-unsupervised-topic-extraction-nlp-neo4j.html)

CALL apoc.periodic.iterate(
"MATCH (a:AnnotatedText) RETURN a",
"CALL ga.nlp.ml.textRank({annotatedText: a}) YIELD result
RETURN distinct 'done' ",
{batchSize:1,iterateList:true}

獲取文章標題中出現次數最多的10個關鍵詞

MATCH (k:Keyword)-[:DESCRIBES]->()
WHERE k.numTerms > 1
RETURN k.value as Keyphrase,
count(*) AS n_articles
ORDER BY n_articles DESC
LIMIT 10

結果如下:

基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

最基本的文章推薦

如果你跟着本文一步一步執行下來,那么你現在已經有了一個最基本的基於PageRank分數和NLP關鍵詞提取的文章推薦系統。

關鍵詞“social networks”的前十推薦文章

MATCH (k:Keyword)-[:DESCRIBES]->()<-[:HAS_ANNOTATED_TEXT]-(a:Article)
WHERE k.value = "social networks"
RETURN a.title as title, a.pagerank as p
ORDER BY p DESC
LIMIT 10

結果如下:

基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

個性化PageRank算法

個性化PageRank是從一個或多個源節點的視角給出其他節點的pagerank分。

我們再計算一次pagerank分數,但這次我們把描述中帶有關鍵詞“social networks”的文章作為源節點。

MATCH (k:Keyword)-[:DESCRIBES]->()<-[:HAS_ANNOTATED_TEXT]-(a:Article)
WHERE k.value = "social networks"
WITH collect(a) as articles
CALL algo.pageRank.stream('Article', 'REFERENCES', {sourceNodes: articles})
YIELD nodeId, score
WITH nodeId,score order by score desc limit 10
MATCH (n) where id(n) = nodeId
RETURN n.title as article, score
基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

可以看到Sergey Brin和Larry Page所著的《大型超文本搜索引擎解析》(http://infolab.stanford.edu/pub/papers/google.pdf) 排在第一位。因此,可以看出,谷歌早期在圖和PageRank方面的研究對社交網絡方面有着巨大的影響。

個性化的推薦系統

需要再次重申,本文的目標是要重現這個場景

關鍵詞“entropy”對於不同的人意味着不同的東西,我們希望從不同的角度還比較關鍵詞“entropy"的結果。

首先我們找到某一作者的所有文章,這些文章將會作為個性化Pagerank的源節點。接着,我們運行pagerank算法並投影關鍵詞”entropy“描述的文章節點,同時也投影這些文章節點之間的REFERENCES關系。

我們可以通過cypher投影語句過濾掉不需要的關系

只有在源節點和目標節點都被節點查詢語句中所描述時,其在關系查詢語句的關系才會被投影。源節點和目標節點任一個不在節點查詢語句中描述時,則此關系會被忽略。

推薦示例

下面給出的是Jose C. Principe視角下搜索關鍵詞“entropy”所得到的推薦文章。

MATCH (a:Article)<-[:AUTHOR]-(author:Author)
WHERE author.name="Jose C. Principe"
WITH collect(a) as articles
CALL algo.pageRank.stream(
'MATCH (a:Article)-[:HAS_ANNOTATED_TEXT]->()<-[:DESCRIBES]-(k:Keyword)
WHERE k.value contains "entropy" RETURN distinct id(a) as id',
'MATCH (a1:Article)-[:REFERENCES]->(a2:Article)
RETURN id(a1) as source,id(a2) as target',
{sourceNodes: articles,graph:'cypher'})
YIELD nodeId, score
WITH nodeId,score order by score desc limit 10
MATCH (n) where id(n) = nodeId
RETURN n.title as article, score
基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

HongWang視角下搜索關鍵詞“entropy”所得到的推薦文章

MATCH (a:Article)<-[:AUTHOR]-(author:Author)
WHERE author.name="Hong Wang"
WITH collect(a) as articles
CALL algo.pageRank.stream(
'MATCH (a:Article)-[:HAS_ANNOTATED_TEXT]->()<-[:DESCRIBES]-(k:Keyword)
WHERE k.value contains "entropy" RETURN distinct id(a) as id',
'MATCH (a1:Article)-[:REFERENCES]->(a2:Article)
RETURN id(a1) as source,id(a2) as target',
{sourceNodes: articles,graph:'cypher'})
YIELD nodeId, score
WITH nodeId,score order by score desc limit 10
MATCH (n) where id(n) = nodeId
RETURN n.title as article, score
基於Neo4j的個性化Pagerank算法文章推薦系統實踐

 

結論

正如我們所料,從兩位作者的不同視角進行搜索,得到的推薦結果也是不一樣的。

Neo4j本身很強大,在特定的領域內使用相應的插件時,他會變的更強大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM