• #gStore-weekly | gAnswer源码解析 查询图生成


    简介

    gAnswer通过将自然语言问题转化成查询图,再和图数据库中的RDF图做匹配以生成用于查询的SPARQL语句。今天讲解的就是生成查询图的主要代码。

    1. // step 2: build query graph (structure construction, relation extraction, top-k join) 
    2. = System.currentTimeMillis();
    3. BuildQueryGraph step2 = new BuildQueryGraph();
    4. step2.process(qlog);
    5. qlog.timeTable.put("step2", (int)(System.currentTimeMillis()-t));

    这是生成查询图的函数入口部分,如注释中所示,该部分主要分为三个子模块:构建查询图结构、关系提取和子图匹配(top-k join)。在构建查询图结构之前,已经通过节点提取模块(Node Extraction)确定了图中的所有节点,因此构图的过程其实就是确定每两个点之间是否有连边的过程,通过在依存分析树(Dependency Parsing Tree)上深度优先搜索,可在(为树的大小)时间内完成。接下来,通过一种基于依存分析树和离线构造词典的方法完成关系提取(Relation Extraction)后,gAnswer会采用一种子图匹配的方法将生成的查询图和RDF图匹配,从而生成候选SPARQL.

    因节点提取、关系提取在以往的文章中已经讲过,因此本文聚焦于构建查询图结构和子图匹配的过程。

    构建查询图结构

    在构建查询图之前,需要完成四个准备工作:fix stop nodes, detect modified node, detect target和coreference resolution.

    Fix stop nodes和detect modified node主要是为了提高准确率针对英文语境和DBpedia数据集的一些经验性的修改,比如问句中出现短语“take place”,则实体"place"一定不是图中的节点。而detect modified node是针对一些名词词组(比如“Apollo 11 mission” "De Beers company"),选择其中一个词代表整个词组作为图中的节点。

    在detect modified node中分为两个部分:检测连续的名词词组(getTheModifiedWordBySentence方法)和离散的名词词组(getDiscreteModifiedWordBySentence方法),但这两个部分的思想是一致的,即根据句子结构定义一些经验性的规则。在未来可通过训练一个节点识别模型替换掉当前的启发式规则。

    1. public Word getDiscreteModifiedWordBySentence(Sentence s, Word curWord)
    2.     int curPos = curWord.position - 1;
    3.     
    4.     //[ent1](cur) 's [ent2], ent1->ent2, usually do NOT appear in SPARQL | eg:Show me all books in Asimov 's Foundation_series
    5.     if(curPos+2 < s.words.length && curWord.mayEnt && s.words[curPos+1].baseForm.equals("'s"&& s.words[curPos+2].mayEnt)
    6.         return curWord.modifiedWord = s.words[curPos+2];
    7.     
    8.     //[ent1by [ent2](cur), ent2->ent1, usually do NOT appear in SPARQL | eg: Which museum exhibits The Scream by Munch?
    9.     if(curPos-2 >=0 && (curWord.mayEnt||curWord.mayType) && s.words[curPos-1].baseForm.equals("by"&& (s.words[curPos-2].mayEnt||s.words[curPos-2].mayType))
    10.         return curWord.modifiedWord = s.words[curPos-2];
    11.     
    12.     return curWord.modifiedWord;
    13. }

    getDiscreteModifiedWordBySentence方法

    准备工作的第三步是detect target,即寻找问句的目标变量是什么。gAnswer通过判断Wh-word(即what, where等)来确定问句的类别,并结合句子和依存树的结构来确定目标变量。

    1. if(target.word.baseForm.equals("where"))
    2. {
    3.     int curPos = target.word.position - 1;
    4.     
    5.     //!Where is the residence of
    6.     if(words[curPos+1].baseForm.equals("be"&& words[curPos+2].posTag.equals("DT"))
    7.     {
    8.         for(int i=curPos+4;i<words.length;i++)
    9.             if(words[i-1].posTag.startsWith("N"&& words[i].posTag.equals("IN"))
    10.             {
    11.                 target.word.represent = words[i-1];
    12.                 target = ds.getNodeByIndex(i);
    13.                 break;
    14.             }
    15.         
    16.     }
    17. }

    如上图所示,以"where"为例,除了简单的问句,最常见的问法是"where is the xxx of ...",即where后面跟着be动词、限定词、名词和介词,那么我们会将这个名词作为目标,而不是where本身。

    对于形如"Where is xxx from?"这样的简单问句,gAnswer认为目标就是"where",即查询图中的两个节点是"where"和"xxx".

    最后一步是coreference resolution,即识别并解决指代关系(anaphora resolution)和指代消解(cataphora resolution),gAnswer目前只提供了一个基于规则的简单方法。

    准备工作完成后,在构建时,首先建立一个队列,并将目标节点放进队列中。随后每次取出队首,并用深度优先搜索的方法在依存树上查找周围节点,直到遇到了其他中的节点,则认为这两个节点相连,并将这些节点放进队列。重复以上流程直到队列为空,至此的完整结构就构建出来了。

    1. public void dfs(DependencyTreeNode head, DependencyTreeNode cur, ArrayList<DependencyTreeNode> ret)
    2. {
    3.     if(cur == null)
    4.         return;
    5.     visited.add(cur);
    6.     
    7.     if(isNode(cur) && head!=cur)
    8.     {
    9.         ret.add(cur);
    10.         return;
    11.     }
    12.     
    13.     if(cur.father!=null && !visited.contains(cur.father))
    14.     {
    15.         dfs(head,cur.father,ret);
    16.     }
    17.     for(DependencyTreeNode child: cur.childrenList)
    18.     {
    19.         if(!visited.contains(child))
    20.             dfs(head,child,ret);
    21.     }
    22.     return;
    23. }

    深度优先搜索过程

    需要注意的是,这样建立的图是一个超图,即在和RDF图进行子图匹配时,我们允许有的边失配,这种构图方法也一定程度上解决了语言歧义性的问题。

    之后会通过节点提取方法为每条边寻找对应的谓词,从而生成完整的查询图。

    子图匹配

    子图匹配过程即生成SPARQL过程,调用方法部分如下图所示:

    1. //step3: item mapping & top-k join
    2. = System.currentTimeMillis();
    3. SemanticItemMapping step5 = new SemanticItemMapping();
    4. step5.process(qlog, qlog.semanticRelations); //top-k join (generate SPARQL queries), disambiguation
    5. qlog.timeTable.put("BQG_topkjoin", (int)(System.currentTimeMillis()-t));

    子图匹配的算法的主体是top-k join,即匹配过程中取得分前高的子图作为答案。因子图匹配需要不断和图数据库进行交互,通信成本较高,因此gAnswer采用检查预先离线处理的“fragment”来模拟子图匹配的过程。

    fragment:相当于将RDF图的信息离线存储在本地,每个fragment相当于图中的一个节点,包含该节点信息,以及该节点的出边信息等。

    提取的信息后(点的信息存储在entityPhrasesList中,边的信息存储在predicatePhraseList中),top-k join算法首先枚举图中的所有节点的mention对应的实体,再枚举这些节点之间是否有边,以及边对应的谓词。这些全部枚举出来后,会通过scoringAndRanking方法判断枚举出来的的子图是否是RDF的子图,以及为其打分。

    在scoringAndRanking中,首先会判断子图是否连通(若边的数量大于1,且存在一对节点他们的度数都为1,说明不连通)。若子图连通,会根据fragment判断每条边是否为RDF图上的边。因为RDF图是有向图,因此判断的过程即判断RDF图中是否存在 这样的三元组,若存在,会为这个三元组新建一个Triple类的对象,认为其是一条SPARQL三元组。

    1. if(sr.isArg1Constant && (sr.arg1Word.mayEnt || sr.arg1Word.mayType) ) // Constant 
    2. {
    3.     // For subject, entity has higher priority.
    4.     if(sr.arg1Word.mayEnt)
    5.     {
    6.         EntityMapping em = currentEntityMappings.get(sr.arg1Word.hashCode());
    7.         subjId = em.entityID;
    8.         sub = em.entityName;
    9.         score *= em.score;
    10.     }
    11.     else
    12.     {
    13.         TypeMapping tm = sr.arg1Word.tmList.get(0);
    14.         subjId = Triple.TYPE_ROLE_ID;
    15.         sub = tm.typeName;
    16.         score *= (tm.score*100); // Generalization. type score: [0,1], entity score: [0,100].
    17.     }
    18. }
    19. else // Variable
    20. {
    21.     subjId = Triple.VAR_ROLE_ID;
    22.     sub = "?" + sr.arg1Word.originalForm;
    23. }

    通过fragement进行子图匹配过程(以判断subject节点为例)

    至此,gAnswer根据构建的超图生成了对应的SPARQL,只要在对应的图数据库中执行便能获得问题的答案了。

  • 相关阅读:
    ssh连win10报错:Permission denied (publickey,keyboard-interactive).
    传统过程自动化工厂的智能扩展
    如何用MATLAB对CSI数据进行预处理(卡尔曼滤波篇)
    课程设计 | 教学设备管理系统
    ospf多区域原理和配置
    geoserver2.18系列(6):使用ImageMosaic发布时间序列栅格
    前端面试的话术集锦第 7 篇:高频考点(浏览器渲染原理 & 安全防范)
    【kafka】十五、kafka消费者API
    Doris实战——美联物业数仓
    Spring-@Value用法介绍
  • 原文地址:https://blog.csdn.net/weixin_48167662/article/details/134513281