gAnswer通过将自然语言问题转化成查询图,再和图数据库中的RDF图做匹配以生成用于查询的SPARQL语句。今天讲解的就是生成查询图的主要代码。
- // step 2: build query graph (structure construction, relation extraction, top-k join)
- t = System.currentTimeMillis();
- BuildQueryGraph step2 = new BuildQueryGraph();
- step2.process(qlog);
- 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方法),但这两个部分的思想是一致的,即根据句子结构定义一些经验性的规则。在未来可通过训练一个节点识别模型替换掉当前的启发式规则。
- public Word getDiscreteModifiedWordBySentence(Sentence s, Word curWord)
- {
- int curPos = curWord.position - 1;
-
- //[ent1](cur) 's [ent2], ent1->ent2, usually do NOT appear in SPARQL | eg:Show me all books in Asimov 's Foundation_series
- if(curPos+2 < s.words.length && curWord.mayEnt && s.words[curPos+1].baseForm.equals("'s") && s.words[curPos+2].mayEnt)
- return curWord.modifiedWord = s.words[curPos+2];
-
- //[ent1] by [ent2](cur), ent2->ent1, usually do NOT appear in SPARQL | eg: Which museum exhibits The Scream by Munch?
- 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))
- return curWord.modifiedWord = s.words[curPos-2];
-
- return curWord.modifiedWord;
- }
getDiscreteModifiedWordBySentence方法
准备工作的第三步是detect target,即寻找问句的目标变量是什么。gAnswer通过判断Wh-word(即what, where等)来确定问句的类别,并结合句子和依存树的结构来确定目标变量。
- if(target.word.baseForm.equals("where"))
- {
- int curPos = target.word.position - 1;
-
- //!Where is the residence of
- if(words[curPos+1].baseForm.equals("be") && words[curPos+2].posTag.equals("DT"))
- {
- for(int i=curPos+4;i<words.length;i++)
- if(words[i-1].posTag.startsWith("N") && words[i].posTag.equals("IN"))
- {
- target.word.represent = words[i-1];
- target = ds.getNodeByIndex(i);
- break;
- }
-
- }
- }
如上图所示,以"where"为例,除了简单的问句,最常见的问法是"where is the xxx of ...",即where后面跟着be动词、限定词、名词和介词,那么我们会将这个名词作为目标,而不是where本身。
对于形如"Where is xxx from?"这样的简单问句,gAnswer认为目标就是"where",即查询图中的两个节点是"where"和"xxx".
最后一步是coreference resolution,即识别并解决指代关系(anaphora resolution)和指代消解(cataphora resolution),gAnswer目前只提供了一个基于规则的简单方法。
准备工作完成后,在构建时,首先建立一个队列,并将目标节点放进队列中。随后每次取出队首,并用深度优先搜索的方法在依存树上查找周围节点,直到遇到了其他中的节点,则认为这两个节点相连,并将这些节点放进队列。重复以上流程直到队列为空,至此的完整结构就构建出来了。
- public void dfs(DependencyTreeNode head, DependencyTreeNode cur, ArrayList<DependencyTreeNode> ret)
- {
- if(cur == null)
- return;
- visited.add(cur);
-
- if(isNode(cur) && head!=cur)
- {
- ret.add(cur);
- return;
- }
-
- if(cur.father!=null && !visited.contains(cur.father))
- {
- dfs(head,cur.father,ret);
- }
- for(DependencyTreeNode child: cur.childrenList)
- {
- if(!visited.contains(child))
- dfs(head,child,ret);
- }
- return;
- }
深度优先搜索过程
需要注意的是,这样建立的图是一个超图,即在和RDF图进行子图匹配时,我们允许有的边失配,这种构图方法也一定程度上解决了语言歧义性的问题。
之后会通过节点提取方法为每条边寻找对应的谓词,从而生成完整的查询图。
子图匹配过程即生成SPARQL过程,调用方法部分如下图所示:
- //step3: item mapping & top-k join
- t = System.currentTimeMillis();
- SemanticItemMapping step5 = new SemanticItemMapping();
- step5.process(qlog, qlog.semanticRelations); //top-k join (generate SPARQL queries), disambiguation
- 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三元组。
- if(sr.isArg1Constant && (sr.arg1Word.mayEnt || sr.arg1Word.mayType) ) // Constant
- {
- // For subject, entity has higher priority.
- if(sr.arg1Word.mayEnt)
- {
- EntityMapping em = currentEntityMappings.get(sr.arg1Word.hashCode());
- subjId = em.entityID;
- sub = em.entityName;
- score *= em.score;
- }
- else
- {
- TypeMapping tm = sr.arg1Word.tmList.get(0);
- subjId = Triple.TYPE_ROLE_ID;
- sub = tm.typeName;
- score *= (tm.score*100); // Generalization. type score: [0,1], entity score: [0,100].
- }
- }
- else // Variable
- {
- subjId = Triple.VAR_ROLE_ID;
- sub = "?" + sr.arg1Word.originalForm;
- }
通过fragement进行子图匹配过程(以判断subject节点为例)
至此,gAnswer根据构建的超图生成了对应的SPARQL,只要在对应的图数据库中执行便能获得问题的答案了。