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

简介

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,只要在对应的图数据库中执行便能获得问题的答案了。

相关推荐
疯狂的挖掘机25 分钟前
记一次基于QT的图片操作处理优化思路(包括在图上放大缩小,截图,画线,取值等)
开发语言·数据库·qt
数新网络1 小时前
CyberScheduler —— 打破数据调度边界的核心引擎
人工智能
Codebee1 小时前
Ooder框架8步编码流程实战 - DSM组件UI统计模块深度解析
人工智能
Deepoch1 小时前
智能升级新范式:Deepoc开发板如何重塑康复辅具产业生态
人工智能·具身模型·deepoc·智能轮椅
赋创小助手1 小时前
融合与跃迁:NVIDIA、Groq 与下一代 AI 推理架构的博弈与机遇
服务器·人工智能·深度学习·神经网络·语言模型·自然语言处理·架构
静听松涛1332 小时前
多智能体协作中的通信协议演化
人工智能
奇树谦2 小时前
Qt | 利用map创建多个线程和定时器
网络·数据库·qt
基咯咯2 小时前
Google Health AI发布MedASR:Conformer 医疗语音识别如何服务临床口述与对话转写
人工智能
用户47949283569152 小时前
性能提升 4000%!我是如何解决 运营看板 不能跨库&跨库查询慢这个难题的
数据库·后端·postgresql
白日做梦Q2 小时前
深度学习模型评估指标深度解析:不止于准确率的科研量化方法
人工智能·深度学习