知识图谱就是图数据库吗?

刚开始学习知识图谱的小小白有疑问? 知识图谱和图数据库的区别是什么?知识图谱就是图数据库吗?或者说图数据库就是知识图谱的应用,知识图谱就是图数据库的理…
关注者
63
被浏览
72,448

12 个回答

导读

几个月前,数学家 Andrew Beveridge 和Jie Shan在数学杂志上发表《权力的网络》( maa.org/sites/default/f ),主要分析畅销小说《冰与火之歌》第三部《冰雨的风暴》中人物关系,其已经拍成电视剧《权力的游戏》系列。他们在论文中介绍了如何通过文本分析和实体提取构建人物关系的网络。紧接着,使用社交网络分析算法对人物关系网络分析找出最重要的角色;应用社区发现算法来找到人物聚类。

其中的 分析和可视化是用Gephi做的 Gephi是非常流行的图分析工具 。但作者觉得使用Neo4j来实现更有趣。

导入原始数据到Neo4j

  • 数据下载: macalester.edu/~abeveri
  • 上面是人物关系的之邻接表以及关系权重。作者使用简单的数据模型:

    (:Character {name})-[:INTERACTS]->(:Character {name})
    
    • 带有标签Character的节点代表小说中的角色
    • 单向关系类型INTERACTS代表小说中的角色有过接触。
    • 节点属性会存储角色的名字name,
    • 两角色间接触的次数作为关系的属性:权重(weight)。
    • 首先创建节点c,并做唯一限制性约束c.name唯一,保证schema的完整性

      CREATE CONSTRAINT ON (c:Character) ASSERT c.name IS UNIQUE;
      

      一旦约束创建即相应的创建索引,这将有助于通过角色的名字查询的性能。作者使用Neo4j的Cypher(Cypher是一种声明式图查询语言,能表达高效查询和更新图数据库)LOAD CSV语句导入数据:

      # 使用网络直接加载数据(自己尝试没有成功)
      LOAD CSV WITH HEADERS FROM "https://www.macalester.edu/~abeverid/data/stormofswords.csv" AS row
      MERGE (src:Character {name: row.Source})
      MERGE (tgt:Character {name: row.Target})
      MERGE (src)-[r:INTERACTS]->(tgt)
      SET r.weight = toInt(row.Weight)
       ##########################################
      # 下载到本地,加载到nei4j中
      LOAD CSV WITH HEADERS FROM "file:/stormofswords.csv" AS row
      MERGE (src:Character {name: row.Source})
      MERGE (tgt:Character {name: row.Target})
      MERGE (src)-[r:INTERACTS]->(tgt)
      SET r.weight = toInt(row.Weight)
      

      我们能可视化整个图形,但是这并不能给我们很多信息,比如哪些是最重要的人物,以及他们相互接触的信息:

      MATCH p=(:Character)-[:INTERACTS]-(:Character)
      RETURN p
      

      人物网络分析

      作者使用Neo4j的图查询语言Cypher来做《权力的游戏》图分析,应用到了网络分析的一些工具,具体见《网络,人群和市场:关于高度连接的世界》

      万事以简单开始。先看看上图上由有多少人物

      MATCH (c:Character) 
      RETURN count(c)
      

      统计每个角色接触的其它角色的数目:

      MATCH (c:Character)-[:INTERACTS]->()
      WITH c, count(*) AS num
      RETURN min(num) AS min, max(num) AS max, avg(num) AS avg_characters, stdev(num) AS stdev
      

      图(网络)的直径

      网络的直径或者测底线或者最长最短路径:

      // Find maximum diameter of network
      // maximum shortest path between two nodes
      MATCH (a:Character), (b:Character) WHERE id(a) > id(b)
      MATCH p=shortestPath((a)-[:INTERACTS*]-(b))
      RETURN length(p) AS len, extract(x IN nodes(p) | x.name) AS path
      ORDER BY len DESC LIMIT 4
      我们能看到网络中有许多长度为6的路径。

      作者使用Cypher 的shortestPath函数找到图中任意两个角色之间的最短路径。让我们找出凯特琳·史塔克(Catelyn Stark )和卓戈·卡奥(Kahl Drogo)之间的最短路径:

      // Shortest path from Catelyn Stark to Khal Drogo
      MATCH (catelyn:Character {name: "Catelyn"}), (drogo:Character {name: "Drogo"})
      MATCH p=shortestPath((catelyn)-[INTERACTS*]-(drogo))
      RETURN p
      

      所有最短路径

      联结凯特琳·史塔克(Catelyn Stark )和卓戈·卡奥(Kahl Drogo)之间的最短路径可能还有其它路径,我们可以使用Cypher的allShortestPaths函数来查找:

      // All shortest paths from Catelyn Stark to Khal Drogo
      MATCH (catelyn:Character {name: "Catelyn"}), (drogo:Character {name: "Drogo"})
      MATCH p=allShortestPaths((catelyn)-[INTERACTS*]-(drogo))
      RETURN p
      

      在网络中,如果一个节点位于其它两个节点所有的最短路径上,即称为关键节点。下面我们找出网络中所有的关键节点:

      // Find all pivotal nodes in network
      MATCH (a:Character), (b:Character)
      MATCH p=allShortestPaths((a)-[:INTERACTS*]-(b)) WITH collect(p) AS paths, a, b
      MATCH (c:Character) WHERE all(x IN paths WHERE c IN nodes(x)) AND NOT c IN [a,b]
      RETURN a.name, b.name, c.name AS PivotalNode SKIP 490 LIMIT 10
      上面Cypher执行错误,错误原因是a,b可能相同,无法求最短路径
      修改代码如下,增加where使得a,b不同;

      // Find all pivotal nodes in network
      MATCH (a:Character), (b:Character)  WHERE id(a) > id(b)
      MATCH p=allShortestPaths((a)-[:INTERACTS*]-(b)) WITH collect(p) AS paths, a, b
      MATCH (c:Character) WHERE all(x IN paths WHERE c IN nodes(x)) AND NOT c IN [a,b]
      RETURN a.name, b.name, c.name AS PivotalNode SKIP 490 LIMIT 10
      

      从结果表格中我们可以看出有趣的结果:(上图第一行)罗柏·史塔克(Robb)是卓戈·卡奥(Drogo)和拉姆塞·波顿(Ramsay)的关键节点。这意味着,所有联结卓戈·卡奥(Drogo)和拉姆塞·波顿(Ramsay)的最短路径都要经过罗柏·史塔克(Robb)。我们可以通过可视化卓戈·卡奥(Drogo)和拉姆塞·波顿(Ramsay)之间的所有最短路径来验证:

      MATCH (a:Character {name: "Drogo"}), (b:Character {name: "Ramsay"})
      MATCH p=allShortestPaths((a)-[:INTERACTS*]-(b))
      RETURN p
      

      节点中心度(Centrality measures)

      节点中心度给出网络中节点的重要性的相对度量。有许多不同的方式来度量中心度,每种方式都代表不同类型的“重要性”。

      • 度中心性(Degree Centrality)
      • 加权度中心性(Weighted Degree Centrality)
      • 介数中心性(Betweenness Centrality)
      • 紧度中心性(Closeness centrality)
      • 度中心性(Degree Centrality)

        度中心性是最简单度量,即为某个节点在网络中的联结数。在《权力的游戏》的图中,某个角色的度中心性是指该角色接触的其他角色数。作者使用Cypher计算度中心性:

        MATCH (c:Character)-[:INTERACTS]-()
        RETURN c.name AS character, count(*) AS degree 
        ORDER BY degree DESC
        

        从上面可以发现,在《权力的游戏》网络中提利昂·兰尼斯特(Tyrion)和最多的角色有接触。鉴于他的心计,我们觉得这是有道理的。

        加权度中心性(Weighted Degree Centrality)

        作者存储一对角色接触的次数作为INTERACTS关系的weight属性。对该角色的INTERACTS关系的所有weight相加得到加权度中心性。作者使用Cypher计算所有角色的这个度量:

        MATCH (c:Character)-[r:INTERACTS]-()
        RETURN c.name AS character, sum(r.weight) AS weightedDegree ORDER BY weightedDegree DESC
        

        介数中心性(Betweenness Centrality)

        介数中心性:在网络中,一个节点的介数中心性是指其它两个节点的所有最短路径都经过这个节点,则这些所有最短路径数即为此节点的介数中心性。 介数中心性是一种重要的度量,因为它可以鉴别出网络中的“信息中间人”或者网络聚类后的联结点。

        为了计算介数中心性,作者使用Neo4j 3.x或者apoc库。安装apoc后能用Cypher调用其170+的程序:
        https://neo4j.com/docs/labs/apoc/current/

        MATCH (c:Character)
        WITH collect(c) AS characters
        CALL apoc.algo.betweenness(['INTERACTS'], characters, 'BOTH') YIELD node, score
        SET node.betweenness = score
        RETURN node.name AS name, score ORDER BY score DESC
        

        紧度中心性(Closeness centrality)

        紧度中心性是指到网络中所有其他角色的平均距离的倒数。在图中,具有高紧度中心性的节点在聚类社区之间被高度联结,但在社区之外不一定是高度联结的。

        MATCH (c:Character)
        WITH collect(c) AS characters
        CALL apoc.algo.closeness(['INTERACTS'], characters, 'BOTH') YIELD node, score
        RETURN node.name AS name, score ORDER BY score DESC
        

        使用python-igraph

        Neo4j与其它工具(比如,R和Python数据科学工具)完美结合。我们继续使用apoc运行 PageRank和社区发现(community detection)算法。这里接着使用python-igraph计算分析。Python-igraph移植自R的igraph图形分析库。 使用pip install python-igraph安装它。

        从Neo4j构建一个igraph实例

        为了在《权力的游戏》的数据的图分析中使用igraph,首先需要从Neo4j拉取数据,用Python建立igraph实例。作者使用 Neo4j 的Python驱动库py2neo。我们能直接传入Py2neo查询结果对象到igraph的TupleList构造器,创建igraph实例:

        from py2neo import Graph
        from igraph import Graph as IGraph
        graph = Graph()
        query = '''
        MATCH (c1:Character)-[r:INTERACTS]->(c2:Character)
        RETURN c1.name, c2.name, r.weight AS weight
        ig = IGraph.TupleList(graph.run(query), weights=True)
        

        现在有了igraph对象,可以运行igraph实现的各种图算法来

        PageRank

        作者使用igraph运行的第一个算法是PageRank。PageRank算法源自Google的网页排名。它是一种特征向量中心性(eigenvector centrality)算法。

        PageRank: The size of each node is proportional to the number and size of the other nodes pointing to it in the network. Image credit wikipedia CC-BY

        在igraph实例中运行PageRank算法,然后把结果写回Neo4j,在角色节点创建一个pagerank属性存储igraph计算的值:

        pg = ig.pagerank()
        pgvs = []
        for p in zip(ig.vs, pg):
            print(p)
            pgvs.append({"name": p[0]["name"], "pg": p[1]})
        write_clusters_query = '''
        UNWIND {nodes} AS n
        MATCH (c:Character) WHERE c.name = n.name
        SET c.pagerank = n.pg
        graph.run(write_clusters_query, nodes=pgvs)
        

        现在可以在Neo4j的图中查询最高PageRank值的节点:

        MATCH (n:Character)
        RETURN n.name AS name, n.pagerank AS pagerank ORDER BY pagerank DESC LIMIT 10
        

        社区发现算法用来找出图中的社区聚类。作者使用igraph实现的随机游走算法( walktrap)来找到在社区中频繁有接触的角色社区,在社区之外角色不怎么接触。

        在igraph中运行随机游走的社区发现算法,然后把社区发现的结果导入Neo4j,其中每个角色所属的社区用一个整数来表示:

        clusters = IGraph.community_walktrap(ig, weights="weight").as_clustering()
        nodes = [{"name": node["name"]} for node in ig.vs]
        for node in nodes:
            idx = ig.vs.find(name=node["name"]).index
            node["community"] = clusters.membership[idx]
        write_clusters_query = '''
        UNWIND {nodes} AS n
        MATCH (c:Character) WHERE c.name = n.name
        SET c.community = toInt(n.community)
        graph.run(write_clusters_query, nodes=nodes)
        

        我们能在Neo4j中查询有多少个社区以及每个社区的成员数:

        MATCH (c:Character)
        WITH c.community AS cluster, collect(c.name) AS  members
        RETURN cluster, members ORDER BY cluster ASC
        

        角色“大合影”

        《权力的游戏》的权力图。节点的大小正比于介数中心性,颜色表示社区(由随机游走算法获得),边的厚度正比于两节点接触的次数。 现在已经计算好这些图的分析数据,让我们对其进行可视化,让数据看起来更有意义。

        Neo4j自带浏览器可以对Cypher查询的结果进行很好的可视化,但如果我们想把可视化好的图嵌入到其它应用中,可以使用Javascript可视化库Vis.js。从Neo4j拉取数据,用Vis.js的neovis.js构建可视化图Neovis.js提供简单的API配置( github.com/neo4j-contri),例如:

        var config = {
          container_id: "viz",
          server_url: "localhost",
          labels: {    "Character": "name"
          label_size: {    "Character": "betweenness"
          relationships: {    "INTERACTS": null
          relationship_thickness: {    "INTERACTS": "weight"
          cluster_labels: {    "Character": "community"
        var viz = new NeoVis(config);
        viz.render();