基于Neo4j的个性化Pagerank算法文章推荐系统实践

时间:2021-08-18 16:04:07

新版的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本身很强大,在特定的领域内使用相应的插件时,他会变的更强大。