gAnswer通过自然语言问题转化成查询图,然后再和图数据库中的RDF图做匹配以生成用于查询的SPARQL语句。在将SPARQL语句应用于gStore查询之前还需要进行修复和聚合,以及一些后处理工作,本文聚焦于此。
// step 0: Node (entity & type & literal) Recognition
// step 1: question parsing (dependency tree, sentence type)
// step 2: build query graph (structure construction, relation extraction, top-k join)
// step 3: some fix (such as "one-node" or "ask-one-triple") and aggregation
t = System.currentTimeMillis();
AddtionalFix step3 = new AddtionalFix();
step3.process(qlog);
在前几期关于gAnswer的文章中,我们完成了算法前三步的解析,认识了依存分析,节点提取,关系提取,进一步的查询图生成,子图匹配等模块。上面是第四步修复与聚合的入口函数,注释中,举了两个例子,"one-node"单节点查询和"ask-one-triple",之后都会有具体方法的解析。
public HashMap<String, String> pattern2category = new HashMap<String, String>();
public AddtionalFix()
{
// Some category mappings for DBpedia, try automatic linking methods later. | base form
pattern2category.put("gangster_from_the_prohibition_era", "Prohibition-era_gangsters");
pattern2category.put("seven_wonder_of_the_ancient_world", "Seven_Wonders_of_the_Ancient_World");
pattern2category.put("three_ship_use_by_columbus", "Christopher_Columbus");
pattern2category.put("13_british_colony", "Thirteen_Colonies");
}
-
首先在
AddtionalFix
类内部创建了一个名为pattern2category
的哈希映射,用于将查询模式映射到类别。
public void process(QueryLogger qlog)
{
fixCategory(qlog);
oneTriple(qlog);
oneNode(qlog);
//aggregation
AggregationRecognition ar = new AggregationRecognition();
ar.recognize(qlog);
//query type
decideQueryType(qlog);
}
-
主方法
process
接受了QueryLogger
对象qlog
作为参数。在该方法中,依次调用了以下三个方法:fixCategory
、oneTriple
和oneNode
。这是完成fix的三个方法,然后调用ar.recognize(qlog)
来进行聚合识别。以及调用了decideQueryType(qlog)
来确定查询的类型。
public void fixCategory(QueryLogger qlog)
{
if(qlog == null || qlog.semanticUnitList == null)
return;
String var = null, category = null;
for(SemanticUnit su: qlog.semanticUnitList)
{
if(su.centerWord.mayCategory)
{
var = "?"+su.centerWord.originalForm;
category = su.centerWord.category;
}
}
if(category != null && var != null)
for(Sparql spq: qlog.rankedSparqls)
{
boolean occured = false;
for(Triple tri: spq.tripleList)
{
if(tri.subject.equals(var))
{
occured = true;
break;
}
}
String oName = category;
String pName = "subject";
int pid = Globals.pd.predicate_2_id.get(pName);
Triple triple = new Triple(Triple.VAR_ROLE_ID, var, pid, Triple.CAT_ROLE_ID, oName, null, 100);
spq.addTriple(triple);
}
}
fixCategory
方法用于修复查询中的类别信息。
-
遍历
qlog.semanticUnitList
中的每个语义单元su
,检查其中心词centerWord
是否具有可能的类别信息(mayCategory
标志)。如果有,将中心词的原始形式originalForm
作为变量var
,将类别信息category
赋给category
。 -
如果
category
和var
在上一步的遍历中得到赋值,遍历qlog.rankedSparqls
中的每个Sparql
对象spq
,并检查是否已经存在相同变量的三元组。如果不存在相同变量的三元组,将类别信息添加到查询中作为一个新的三元组。
public void oneNode(QueryLogger qlog)
{
if(qlog == null || qlog.semanticUnitList == null || qlog.semanticUnitList.size()>1)
return;
Word target = qlog.target;
Word[] words = qlog.s.words;
if(qlog.s.sentenceType != SentenceType.GeneralQuestion)
{
//1-1: how many [type] are there | List all [type]
if(target.mayType && target.tmList != null)
{
String subName = "?"+target.originalForm;
String typeName = target.tmList.get(0).typeName;
Triple triple = new Triple(Triple.VAR_ROLE_ID, subName, Globals.pd.typePredicateID, Triple.TYPE_ROLE_ID, typeName, null, 100);
Sparql sparql = new Sparql();
sparql.addTriple(triple);
qlog.rankedSparqls.add(sparql);
}
//1-2: What is [ent]?
else if(target.mayEnt && target.emList != null)
{
if(words.length >= 3 && words[0].baseForm.equals("what") && words[1].baseForm.equals("be"))
{
int eid = target.emList.get(0).entityID;
String subName = target.emList.get(0).entityName;
Triple triple = new Triple(eid, subName, Globals.pd.typePredicateID, Triple.VAR_ROLE_ID, "?"+target.originalForm, null, target.emList.get(0).score);
Sparql sparql = new Sparql();
sparql.addTriple(triple);
qlog.rankedSparqls.add(sparql);
}
}
//1-3: Give me all Seven Wonders of the Ancient World.
else if(target.mayCategory && target.category != null)
{
String oName = target.category;
String pName = "subject";
int pid = Globals.pd.predicate_2_id.get(pName);
Triple triple = new Triple(Triple.VAR_ROLE_ID, "?"+target.originalForm, pid, Triple.CAT_ROLE_ID, oName, null, 100);
Sparql sparql = new Sparql();
sparql.addTriple(triple);
qlog.rankedSparqls.add(sparql);
}
}
else
{
if(target.mayEnt && target.emList != null)
{
//2-2:Was Sigmund Freud married?
String relMention = "";
for(Word word: words)
if(word != target && !word.baseForm.equals(".") && !word.baseForm.equals("?"))
relMention += word.baseForm+" ";
if(relMention.length() > 1)
relMention = relMention.substring(0, relMention.length()-1);
ArrayList<PredicateIDAndSupport> pmList = null;
if(Globals.pd.nlPattern_2_predicateList.containsKey(relMention))
pmList = Globals.pd.nlPattern_2_predicateList.get(relMention);
if(pmList != null && pmList.size() > 0)
{
int pid = pmList.get(0).predicateID;
int eid = target.emList.get(0).entityID;
String subName = target.emList.get(0).entityName;
Triple triple = new Triple(eid, subName, pid, Triple.VAR_ROLE_ID, "?x", null, 100);
Sparql sparql = new Sparql();
sparql.addTriple(triple);
qlog.rankedSparqls.add(sparql);
}
//2-3:Are penguins endangered?
else
{
if(target.position < words.length && pattern2category.containsKey(words[target.position].baseForm))
{
String oName = pattern2category.get(words[target.position].baseForm);
String pName = "subject";
int pid = Globals.pd.predicate_2_id.get(pName);
int eid = target.emList.get(0).entityID;
String subName = target.emList.get(0).entityName;
Triple triple = new Triple(eid, subName, pid, Triple.CAT_ROLE_ID, oName, null, 100);
Sparql sparql = new Sparql();
sparql.addTriple(triple);
qlog.rankedSparqls.add(sparql);
}
}
}
//2-1: Are there any [castles_in_the_United_States](yago:type)
else if(target.mayType && target.tmList != null)
{
String typeName = target.tmList.get(0).typeName;
String subName = "?" + target.originalForm;
//System.out.println("typeName="+typeName+" subName="+subName);
Triple triple = new Triple(Triple.VAR_ROLE_ID, subName, Globals.pd.typePredicateID, Triple.TYPE_ROLE_ID, typeName, null, 100);
Sparql sparql = new Sparql();
sparql.addTriple(triple);
qlog.rankedSparqls.add(sparql);
}
}
}
关于代码中用于识别单节点查询(one-Node query)的逻辑,根据不同情况分成了两大类和六种具体情况:
-
第一大类:特殊问题(Special question)和祈使句(Imperative sentence),它会处理包含一个节点的查询,并根据不同的情况生成相应的查询三元组,并将其添加到 rankedSparqls 列表中。
-
1-1:"how many [type] are there" 和 "list all [type]" 这样的问题,首先检查识别的目标词
target
是否可能是一个类型type
。创建一个三元组,其中实体是变量subName
,谓词是全局定义的表示类型关系的谓词ID(Globals.pd.typePredicateID
),宾语是类型名称。 -
1-2:"What is backgammon?" 和 "What is a bipolar syndrome?" 这样的问题,首先检查识别的目标词
target
是否可能是一个实体entity
。创建一个三元组,其中实体是变量subName
,谓词是全局定义的表示类型关系的谓词ID(Globals.pd.typePredicateID
),宾语是用户查询中的实体描述,以 "?" 加上实体描述("?"+target.originalForm
)。 -
1-3:"Give me all Seven Wonders of the Ancient World." 这样的问题,首先检查识别的目标词
target
是否可能是一个类别category
。创建一个三元组,其中实体是变量"?"+target.originalForm
,谓词是特定分类对应的谓词ID,宾语是用户查询中的特定分类(oName
)。
-
-
第二大类:一般问题(General question),根据目标词
target
是否可能是实体mayEnt
和是否有实体列表emList
,进行不同的处理。-
2-1:"Are there any [castles_in_the_United_States]"这样的问题,首先检查识别的目标词
target
是否可能是一个类型type
,需要检查特定类型的实体是否存在。创建一个三元组,主语是一个变量(由subName
指定),谓词是一个特定的谓词(由Globals.pd.typePredicateID
指定),宾语是一个特定实体的类型(由typeName
指定)。 -
2-2:"Was Sigmund Freud married?" 这样的问题,首先检查识别的目标词
target
是否可能是一个实体entity
,用户查询可能是关于特定实体的事实。创建一个三元组,其中实体是变量?x
,谓词是获取的谓词,宾语是实体名。 -
2-3:"Are penguins endangered?" 这样的问题,首先检查识别的目标词
target
是否可能是一个实体entity
,用户可能在询问特定实体的类别。创建一个三元组,其中实体是实体名,谓词是获取的谓词,宾语是类别名。
-
这些情况将影响代码中单节点查询的处理方式。
public void oneTriple (QueryLogger qlog)
{
if(qlog == null || qlog.semanticUnitList == null)
return;
if(qlog.s.sentenceType == SentenceType.SpecialQuestion)
{
Word[] words = qlog.s.words;
if(qlog.semanticUnitList.size() == 2)
{
Word entWord = null, whWord = null;
for(int i=0;i<qlog.semanticUnitList.size();i++)
{
if(qlog.semanticUnitList.get(i).centerWord.baseForm.startsWith("wh"))
whWord = qlog.semanticUnitList.get(i).centerWord;
if(qlog.semanticUnitList.get(i).centerWord.mayEnt)
entWord = qlog.semanticUnitList.get(i).centerWord;
}
// 1-1: (what) is [ent] | we guess users may want the type of ent.
if(entWord!=null && whWord!= null && words.length >= 3 && words[0].baseForm.equals("what") && words[1].baseForm.equals("be"))
{
int eid = entWord.emList.get(0).entityID;
String subName = entWord.emList.get(0).entityName;
Triple triple = new Triple(eid, subName, Globals.pd.typePredicateID, Triple.VAR_ROLE_ID, "?"+whWord.originalForm, null, entWord.emList.get(0).score);
Sparql sparql = new Sparql();
sparql.addTriple(triple);
qlog.rankedSparqls.add(sparql);
}
}
}
}
}
oneTriple
方法用于处理在句子中能识别三元组但没有合适关系的情况。
-
检查句子类型是否为特殊问题(
SentenceType.SpecialQuestion
)。如果是,继续检查是否识别出了两个语义单元(semanticUnitList.size() == 2
)。 -
如果符合条件,尝试构建一个三元组。这里主要处理了一种情况:
-
遍历语义单元,根据语义单元的属性,识别实体词和疑问词。
-
类似 "What is [ent]?" 这样的问题。根据识别到的实体词(
entWord
)和疑问词(whWord
),构建一个以实体为主语、类型为谓词、疑问词为宾语的三元组,然后将这个三元组添加到 SPARQL 查询列表(qlog.rankedSparqls
)中。
-
// deduplicate in SPARQL
for(Sparql spq: rankedSparqls)
spq.deduplicate();
// Sort (descending order).
Collections.sort(rankedSparqls);
qlog.rankedSparqls = rankedSparqls;
System.out.println("number of rankedSparqls = " + qlog.rankedSparqls.size());
// Detect question focus.
for (int i=0; i<qlog.rankedSparqls.size(); i++)
{
// First detect by SPARQLs.
Sparql spq = qlog.rankedSparqls.get(i);
String questionFocus = QuestionParsing.detectQuestionFocus(spq);
// If failed, use TARGET directly.
if(questionFocus == null)
questionFocus = "?"+qlog.target.originalForm;
spq.questionFocus = questionFocus;
}
return qlog;
}
最后,将得到的SPARQLS查询列表进行去重、排序和问题焦点的检测等后处理。返回包含处理后的信息的 QueryLogger
对象。