📑 查看全课大纲(第 24 / 26 节)

知识问答

约 129 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

知识问答:从下一代搜索引擎到 KBQA 的三类方法

小象实战讲义 · 知识图谱

前面几章解决了知识图谱的建模、抽取、存储、融合与推理,本章回答最后一公里:用户只会说一句自然语言(如「姚明的老婆的国籍是哪儿」),并不会写 SPARQL,系统怎样把这句话落到图谱上、再把答案还给他?本节是第 9 章「知识问答」的总览与方法主干,讲三部分:问答系统的演进与评测数据集、KBQA(Knowledge-Based Question Answering,基于知识库的问答)的基本概念与挑战、三类主流方法(模板、语义解析、深度学习)。学完你应能说清自然语言问题如何被翻译成结构化查询,模板槽位如何填充排序、资源映射与 Bridging 补边在做什么、端到端嵌入为何当时只能处理简单问题,并能用 Python(rdflib + 标准库)把 TBSL 模板问答、远程监督词典、候选逻辑表达式打分、子图嵌入匹配四个机制各跑一遍最小版本。

💡 核心导读

  • 问答系统被视为下一代搜索引擎基本形态,沿「模板/专家系统 → 信息检索 → 社区问答 → 知识库问答」四阶段演进;KBQA 关键是把自然语言问题语义解析后映射到知识库。
  • 用七个概念(问句短语、问题/答案类型、问题主题、来源/领域类型、答案格式)刻画问答,沿「预处理 → 问题分析 → 数据匹配 → 查询构造 → 排序 → 答案抽取」六组件流水线落地。
  • 根本困难是自然语言与结构化数据之间的鸿沟:词汇缺口、结构/粒度缺口(一词对应多跳路径或专门属性)、隐含关系、多语言、数据异构不完整、分布式互联与实时性能。
  • 三类方法各有取舍:模板法快而准、可答复合问题但模板维护昂贵;语义解析法可答复杂/时序问题但规则工程量大;深度学习法全自动但当时主要解决单关系简单问题、缺显式聚合算子。
  • 语义解析两个关键动作是 Alignment(短语到 KB 谓词的词典对齐)Bridging(用上下文与类型约束补出未明说的谓词);端到端模型把问题与候选答案子图嵌入成向量、用点积排序。

1. 问答系统的形态、历史与评测数据集

1.1 交互方式转变与问答系统定位

信息服务形态随终端变化:PC 时代屏幕大、键盘输入为主,返回链接列表尚可接受;移动互联网与可穿戴设备时代屏幕变小、输入变慢,用户不愿再从链接堆里翻答案,倒逼信息服务从「信息检索(给文档自己读)」转向「问答(直接给答案)」。问答系统因此被看作下一代搜索引擎的基本形态。标志事件是 2011 年 IBM 沃森(Watson)在《危险边缘》(Jeopardy!)中战胜人类冠军 Ken Jennings 与 Brad Rutter(前者曾连续 74 场获胜),融合了信息检索、知识图谱、证据打分与推理。

1.2 问答系统的四个历史阶段

1960 ── 基于模板的问答 / 专家系统:人工规则与模板匹配,领域窄、可解释但不可扩展
1990 ── 基于信息检索(IR)的问答:关键词匹配 + 信息抽取,基于浅层语义分析
2000 ── 基于社区的问答(CQA):依赖网民贡献问答对,检索仍靠关键词
2010 ── 基于知识库的问答(KBQA):以知识库为底座做语义解析,直接返回结构化答案

IR 问答本质是「关键词匹配 + 信息抽取」、只做浅层语义分析;社区问答答案来自网民贡献、质量取决于社区沉淀;知识库问答把问题映射到知识库(Knowledge Base,KB),经语义解析翻译成结构化查询(RDF 图谱上即 SPARQL)再查询、推理得答案——知识库 + 语义解析是它区别于前三代的根本特征。

1.3 问答形式分类、典型应用与答题机器人

按交互形式分三类:一问一答(单轮,本节方法主要围绕它);交互式问答(多轮,需结合上下文,典型是共指 coreference——后句用 it、「她」指代上句实体,要维护对话状态);阅读理解(给文章再回答,常为完形填空或选择题,偏文本侧、与 KBQA 互补)。

KBQA 应用已很普遍:手机个人助理(如 Siri)以知识问答为支撑;对话产品(如微软小冰)主打情感陪伴、强调「情商」,话题常识同样依赖知识库;电商、铁路订票、运营商等智能客服;Watson 把问答能力输出到医疗等行业做辅助决策。答题机器人(高考机器人) 是另一标志:日本 2011 年起由富士通与国立信息学研究所联合研制(東ロボ/Todai,即“东大机器人”,课件转写为“多达一”,目标通过大学入学考试);Paul Allen 资助华盛顿大学研制能通过高中生物考试的系统;中国 863 计划部署「基于大数据的类人智能关键技术与系统」研制高考答题机器人。

1.4 评测数据集:QALD、WebQuestions 与 Free917

(1)QALD(Question Answering over Linked Data) 是面向链接数据(Linked Data)的多语言问答评测,在 ESWC workshop 开展,数据来源包括 DBpedia、YAGO、MusicBrainz;自 2011 年起每年约 100 个问题,课件记录时共办 7 届。三类任务:

  • 任务 1 多语种问答(基于 DBpedia):同一问题用七种语言(英、西、德、意、法、荷、罗马尼亚)提问,取回答案或给出对应 SPARQL;数据为 DBpedia 3.9(带多语言标签),200 训练 + 50 测试。标准答案形如:
PREFIX res: <http://dbpedia.org/resource/>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT DISTINCT ?uri WHERE {
  res:Yenisei_River dbo:country ?uri .
}
  • 任务 2 基于互联链接数据(interlinked data):数据来自 SIDER、Diseasome、Drugbank,25 训练 + 25 测试,要求跨数据集融合信息。典型问题「治疗肺结核的药物有什么副作用」要经 Diseasome 找药、owl:sameAs 对齐 Drugbank、再到 SIDER 取副作用:
PREFIX disease: <http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseases/>
PREFIX diseasome: <http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseasome/>
PREFIX drugbank: <http://www4.wiwiss.fu-berlin.de/drugbank/resource/drugbank/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX sider: <http://www4.wiwiss.fu-berlin.de/sider/resource/sider/>
SELECT DISTINCT ?x WHERE {
  disease:1154 diseasome:possibleDrug ?v2 .
  ?v2 a drugbank:drugs .
  ?v3 owl:sameAs ?v2 .
  ?v3 sider:sideEffect ?x .
}

注:drugbank:sider: 指向的是词汇(类/属性)命名空间,而非实例命名空间。若误写成实例命名空间(如 …/drugbank/resource/drugs/),则 drugbank:drugs 会被拼成 …/drugbank/resource/drugs/drugs 这类不存在的 URI,查询返回空。

  • 任务 3 混合问答(Hybrid QA):基于 DBpedia 3.9(带英文摘要),25 训练 + 10 测试,需同时用结构化数据与摘要自由文本,如「给我所有 G8 国家的货币」中「G8 成员」要靠文本匹配:
SELECT DISTINCT ?uri WHERE {
  ?x text:"member of" text:"G8" .
  ?x dbo:currency ?uri .
}

注意:text:"member of" text:"G8" 是 QALD Hybrid 任务自定义的文本匹配伪谓词(表示在英文摘要里做自由文本命中),并非标准 SPARQL 1.1 语法;标准 SPARQL 1.1 的谓词位置可以是 IRI、变量,也可以是属性路径(property path)——用 /(路径串联)、^(逆属性)、|(分支并)、*/+/?(传递闭包等迭代)把多个属性组合成一条路径,但仍不是任意自由文本串;真实工程里全文文本命中通常靠全文检索扩展(如 Jena Text 的 text:query)实现。这里按课件原样保留以还原 QALD 任务定义。

QALD 采用准确率 P、召回率 R 与 F 值作为指标。

(2)WebQuestions 基于 Freebase:通过 Google Suggest API 在「建议词图」上广度优先爬取真实提问,共 5810 问(3778 训练 / 2032 测试),答案经 Amazon Mechanical Turk 众包标注(须能在 Freebase 找到),一问可多答,用 Average F1 评价;升级版 WebQuestionsSP 补 SPARQL 语义解析标注,其中 4737 问带 semantic parse 与 SPARQL、标为 answerable,其余为 not answerable。

(3)Free917 也基于 Freebase,共 917 问(641 训练 / 276 测试),依据 Freebase 已有事实反向构造、表述较模板化;WebQuestions 挖掘真实用户提问,措辞更自由(less formulaic)、难度更高。


2. KBQA 的基本概念、系统流水线与核心挑战

2.1 简单流程与两大表示范式

简单流程是「问句 → 语义匹配 / 推理 → 答案」。按语义表示分两大范式:基于符号表示(传统方法) 含关键词检索、文本蕴含推理、逻辑表达式三条路线,把问题表示成离散符号结构(谓词、逻辑式、查询模板),可解释、能复杂推理但依赖人工规则;基于分布式表示(深度学习方法) 用 LSTM、注意力模型(Attention Model)、记忆网络(Memory Network)把问题与知识映射成稠密向量做匹配,自动化高但可解释性与复杂推理弱。以「姚明的老婆的国籍是?」为例,符号范式(Semantic Parsing,SP)显式构造可执行 SPARQL,IR 与分布式范式更接近「在候选答案上排序」。上位概念 NLI(自然语言接口) 输出分加工过的直接答案(Processed,即 QA)与未加工的记录/片段/文档(Unprocessed),KBQA 追求前者。

2.2 描述问答的七个基本概念

  1. 问句短语:由疑问词定义「问什么」。Wh-words 为 who/what/which/when/where/why/how,可与名/形/副词组合(which actress、how long)。
  2. 问题类型(决定回答策略):事实型 FACTOID 下分谓词型(泰姬陵在哪)、列表型 LIST(列出德国所有城市)、最高级型 SUPERLATIVE、是非型 YES-NO(撒切尔是化学家吗);另有观点型、因果型、方法型、解释型、关联型、比较/评价型。
  3. 答案类型:缩写、实体(事件/颜色/动植物)、描述/解释(对 how/why)、人物(对 who)、地点(对 where)、数值(计数/距离/尺寸,对 how many/how far/how long)、时间(日期/时刻,对 when)。
  4. 问题主题(focus/topic):问题寻求的属性/实体及所属领域,如「珠峰高度」属地理-山脉、「梅尼埃病影响哪个器官」属医学。
  5. 来源类型:按结构分结构化(数据库)、半结构化(XML)、非结构化;按范围分单一数据集、多个分布式数据集、Web 规模。
  6. 领域类型:开放域 vs 特定域;按数据类型分文本/图片/音频/视频,后两者通向多模态问答与视觉问答(Visual QA)。
  7. 答案格式:长答案(定义/论证)、短答案(短语)、精确答案(命名实体、数字、聚合值、yes/no)。

2.3 答案评估、答案处理与任务复杂性

质量六原则:相关度 Relevance、正确度 Correctness、精炼度 Conciseness、完备度 Completeness、简单度 Simplicity、合理度 Justification(提供可核验证据)。单条答案判为四级:Right(正确完整)、Inexact(不完整/不正确)、Unsupported(缺证据支撑)、Wrong(答非所问)。答案五种处理方式:简单抽取(摘片段)、组合(拼合多句/多记录)、摘要(综合大文本)、运算/函数型(计数、求最值等算子)、推理(对数据施加推理)。任务复杂性四维度:语义可处理性(Semantic Tractability,Popescu 等 2003,看问题与答案的词汇重叠/距离)、答案局部性(Answer Locality,Webber 等 2003,答案是否散布于多文档/记录)、可推导性(Derivability,显式/隐式)、语义复杂度(歧义与数据异构程度)。

2.4 问答系统的六个基本组件

自然语言问题
 → [问题分析] 句法分析、NER、答案类型识别
 → [数据匹配] 问题词项对齐到数据实体/谓词
 → [查询构造] 结合映射与语法约束,生成多个结构化查询候选
 → [排序 Scoring] 对候选查询打分排名
 → [结果返回与生成] 执行查询,从结果集抽取/组织自然语言答案
 底座资源:[数据预处理](索引/清洗/特征抽取)+ 本体 ontology + 索引与数据集

各组件职责见图注。三类方法本质上都是对「分析—匹配—构造—排序」这条主干的不同实现。

2.5 KBQA 的六项基本需求

高可用(支持自然语言查询)、高查询表达力(路径、合取、析取、聚合、条件)、准确完备的语义匹配(高精度高召回)、低维护成本(以最小适配代价迁移到新领域数据集)、低查询执行时间(适合交互式)、高可扩展(组织级乃至 Web 级)。

2.6 核心挑战:怎样把问题映射到答案

以「What is the currency of the Czech Republic?」为例,目标查询是 res:Czech_Republic dbo:currency ?uri。鸿沟有两层:把自然语言表达式映射到知识库词汇元素(兼顾词汇与结构差异),以及处理歧义、模糊与指代。展开为七类:

(1)URI 与自然语言只靠 label 相连,label 覆盖不了词汇变体。 URI 语言无关,与自然语言唯一的真实联系是标签,而标签覆盖不了 wife of / husband of / married to 等等价说法:

dbo:spouse rdfs:label "spouse"@en , "echtgenoot"@nl .

(2)概念粒度常与 schema 不吻合。 「德国何时加入欧盟」落到专门属性 dbp:accessioneudate;「孙辈」语言里是一个词、图里却是两跳 dbo:child

PREFIX res: <http://dbpedia.org/resource/>
PREFIX dbp: <http://dbpedia.org/property/>
SELECT DISTINCT ?date WHERE { res:Germany dbp:accessioneudate ?date . }
PREFIX res: <http://dbpedia.org/resource/>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT DISTINCT ?uri WHERE {
  res:Bruce_Lee dbo:child ?c .
  ?c dbo:child ?uri .
}

(3)存在数据集无关的固定表达。 the most、more than 不对应谓词而对应聚合骨架:the most N → ORDER BY DESC(COUNT(?N)) LIMIT 1more than i N → HAVING COUNT(?N) > i

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT ?uri WHERE {
  ?x rdf:type dbo:Film .
  ?x dbo:producer ?uri .
}
GROUP BY ?uri
ORDER BY DESC(COUNT(?x)) OFFSET 0 LIMIT 1

课件把它抽象成 ORDER BY DESC(COUNT(?N)) LIMIT 1 的聚合骨架(省略了 GROUP BY);可执行写法必须像上面这样按答案变量 GROUP BY,3.3 的 Python 代码即给出了带 GROUP BY 的完整可运行版本。

(4)不同数据集知识表示不同。 同样是「市长」,可建 City-mayor->Person、引入 Role 中间节点、或用名为 Mayor 的节点,同一问题要翻译成不同结构(schema 差异)。

(5)关系/属性常被隐含表述。 to be、to have、介词 of/with 高度依赖语境:「Which museum has the most paintings?」的 has 对应 ?museum dbo:exhibits ?painting(博物馆→画);「Which country has the most caves?」的 has 却对应 ?cave dbo:location ?country(洞穴→国家,方向相反)。

(6)多语言与数据质量异构。 URI 语言无关使图谱原则上适合多语言,但带语言标签的 RDF 字面量不到四分之一、且绝大多数是英语。数据侧要求完整性与准确性(错误答案比没有答案更糟):数据集常不完整、不同库用不同词汇重复描述同一事物甚至冲突。

(7)分布式互联与性能。 数据分布在大量互联数据集中(肺结核药物副作用要跨 Diseasome/Drugbank/SIDER 经 owl:sameAs 串联);课件记录当时系统单数据集处理一问约需 20–30 秒,离实时交互尚有距离。

对应课件总结的七条挑战:①弥合自然语言与规范化结构化数据之间的鸿沟;②处理不完整、有噪、异构的数据集;③处理大规模知识图谱;④处理分布式数据集上的问答;⑤融合结构化与非结构化数据;⑥降低维护成本;⑦快速复制、迁移到不同领域。


3. 基于模板的方法:TBSL 与自动模板学习 QUINT

3.1 TBSL 的动机:区分「词」与「语义结构」

模板法以 TBSL(Unger 等,2012) 为代表,它认为理解问题要分清两层:词(数据集相关) 须映射到具体库的 URI(Abraham Lincoln → res:Abraham_Lincolndied in → dbo:deathPlace);语义结构(数据集无关) 对应查询骨架(who → SELECT ?x WHERE{…},the most/more than 等聚合骨架见 2.6)。方法分两步:模板生成(解析问题,产出镜像其语言结构、含过滤聚合、实体/谓词位置留槽的 SPARQL 模板)与模板实例化(用统计式实体识别与谓词检测把自然语言表达与本体概念匹配填槽)。

3.2 模板生成与实例化

以「Who produced the most films?」为例,语言处理(Linguistic processing)流程为:获取词性标注 POS → 基于 POS 与语法规则表示问句 → 用领域相关词(produced/VBD、films/NNS)与领域无关词(who、the most)辅助分析 → 转成 SPARQL 模板,留下类槽 ?c CLASS [films] 与属性槽 ?p PROPERTY [produced](句法歧义可产生多个候选模板):

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT DISTINCT ?x WHERE {
  ?y rdf:type ?c .
  ?y ?p ?x .
}
ORDER BY DESC(COUNT(?y))
OFFSET 0 LIMIT 1
# 槽位:?c = CLASS [films],?p = PROPERTY [produced]

实例化即把短语映射到本体概念:对资源与类用 WordNet 定义标签同义词,并计算 trigram、Levenshtein(编辑距离)、子串 三类字符串相似度;对属性标签还要与 BOA 模式库(远程监督挖掘的谓词自然语言说法,如 born in、spouse of)比较;排位最高者填槽。于是类槽 [films] 召回 dbo:Filmdbo:FilmFestival,属性槽 [produced] 召回 dbo:producerdbo:wineProduced 等候选。

注意答案变量 ?x(制片人)落在宾语位置:查 DBpedia 本体,dbo:producer 的 domain=dbo:Work、range=dbo:Agentdbo:FilmWorkdbo:PersonAgent),注释为 “the producer of the creative work”,即数据三元组方向是「电影/作品 dbo:producer 人」,故模板让电影 ?y 作主语、制片人 ?x 作宾语(与课件 Page 61 的语义结构模板一致)。课件 Page 66 语言处理阶段另给了一个镜像英文语序(Who produced films)的候选 ?x dbo:producer ?y(人作主语、电影作宾语),Page 72 还把它连同 0.76/0.60 分数一起展示;但该方向与真实本体相反、在真实 DBpedia 上匹配不到数据,正是下一步 domain/range 类型检查应淘汰的那一类。本讲义统一采用与本体一致的 Page 61 方向(3.3 代码数据按「电影 producer 人」建模、确实查到答案);引用 0.76/0.60 只用于说明类槽 Film 胜过 FilmFestival,这一排序结论与查询写成哪个方向无关。

3.3 查询排序、选择与执行(含 Python 实现)

排序规则:①每个实体按字符串相似度 + 显著度(prominence,先验流行度/入度)打分;②查询分等于各槽位实体分的均值;③做类型检查——对 ?x rdf:type <class>?x p ee p ?x 三元组检查谓词 domain/range 是否与类一致;④只返回最高分查询(课件中 Film+producer 0.76 高于 FilmFestival+producer 0.60)。下面用 rdflib 跑通全链路:

# -*- coding: utf-8 -*-
# TBSL 模板问答最小实现:模板生成(留槽) -> 槽位实例化(相似度+显著度) -> 类型检查 -> 排序 -> 执行
import re
from rdflib import Graph, Namespace
from rdflib.namespace import RDF, RDFS, OWL

DBO = Namespace("http://dbpedia.org/ontology/")
DBR = Namespace("http://dbpedia.org/resource/")
g = Graph(); g.bind("dbo", DBO); g.bind("dbr", DBR)

# ---- 本体:类、属性及其 domain/range(供类型一致性检查) ----
# 真实 DBpedia:dbo:producer 的 domain=dbo:Work(dbo:Film 是其子类)、range=dbo:Agent
# (dbo:Person 是 Agent 子类;演示数据制片人均为人,range 简化取 Person)
g.add((DBO.Work, RDF.type, OWL.Class))
g.add((DBO.Film, RDF.type, OWL.Class)); g.add((DBO.Film, RDFS.subClassOf, DBO.Work))
g.add((DBO.FilmFestival, RDF.type, OWL.Class))    # 电影节并非 Work 的子类
g.add((DBO.Person, RDF.type, OWL.Class)); g.add((DBO.Winery, RDF.type, OWL.Class))
g.add((DBO.producer, RDF.type, OWL.ObjectProperty))
g.add((DBO.producer, RDFS.domain, DBO.Work))        # 作品(Work) --producer--> 人/Agent
g.add((DBO.producer, RDFS.range, DBO.Person))
g.add((DBO.wineProduced, RDF.type, OWL.ObjectProperty))  # 课件里的干扰属性:酒庄 --wineProduced--> 葡萄酒
g.add((DBO.wineProduced, RDFS.domain, DBO.Winery))

# ---- 内联数据:让 p_spielberg 制片数量最多;电影节是 FilmFestival 而非 Film ----
films = {"Jaws": "p_spielberg", "E.T.": "p_spielberg", "Jurassic_Park": "p_spielberg",
         "Schindlers_List": "p_spielberg", "Lincoln": "p_spielberg",
         "Star_Wars": "p_lucas", "American_Graffiti": "p_lucas", "Taxi_Driver": "p_scorsese"}
for f, p in films.items():
    g.add((DBR[f], RDF.type, DBO.Film)); g.add((DBR[f], DBO.producer, DBR[p]))
    g.add((DBR[p], RDF.type, DBO.Person))
g.add((DBR.Cannes_Film_Festival, RDF.type, DBO.FilmFestival))
g.add((DBR.Cannes_Film_Festival, DBO.producer, DBR.p_organizer))
g.add((DBR.p_organizer, RDF.type, DBO.Person))

# ---- 标签表(模拟 rdfs:label + WordNet 同义词)与显著度先验 ----
LABELS = {DBO.Film: ["film", "movie", "films"], DBO.FilmFestival: ["film festival", "filmfest"],
          DBO.Person: ["person", "people"], DBO.producer: ["producer", "produce", "produced"],
          DBO.wineProduced: ["wine produced", "wineproduced"]}
PROM = {DBO.Film: 0.9, DBO.FilmFestival: 0.5, DBO.Person: 0.8,
        DBO.producer: 0.9, DBO.wineProduced: 0.2}

def trigrams(s):                                  # 字符级 trigram 集合
    s = "  " + re.sub(r"\s+", " ", s.lower()).strip() + "  "
    return set(s[i:i+3] for i in range(len(s) - 2))

def trigram_dice(a, b):                           # trigram Dice 系数(字符串相似度之一)
    A, B = trigrams(a), trigrams(b)
    return 2 * len(A & B) / (len(A) + len(B)) if A and B else 0.0

def slot_score(phrase, uri, alpha=0.75):          # 槽位分 = 0.75*最大标签相似度 + 0.25*显著度
    sim = max(trigram_dice(phrase, lab) for lab in LABELS[uri])
    return round(alpha * sim + (1 - alpha) * PROM.get(uri, 0.3), 3)

def domain_ok(prop, cls):                         # 类型检查:类是否落在谓词 domain 上(相等或是其子类)
    d = g.value(prop, RDFS.domain)
    if d is None:
        return True
    sup, stack = set(), [cls]                     # 沿 rdfs:subClassOf 向上收集祖先类
    while stack:
        c = stack.pop()
        for s in g.objects(c, RDFS.subClassOf):
            if s not in sup:
                sup.add(s); stack.append(s)
    return cls == d or d in sup                   # Film⊑Work 通过;FilmFestival 与 Work 无继承关系→不通过

# Step1 抽象模板留下两个槽:CLASS[films]、PROPERTY[produced]
# Step2 先填 PROPERTY 槽
prop_rank = sorted(((slot_score("produced", p), p) for p in [DBO.producer, DBO.wineProduced]),
                   reverse=True)
print("== PROPERTY 槽 [produced] 候选 ==")
for s, p in prop_rank:
    print(f"  {p.split('/')[-1]:13s} {s}")
BEST_P = prop_rank[0][1]

# Step3 固定谓词,再填 CLASS 槽并做 domain/range 检查(不一致则惩罚),对应课件 0.76 vs 0.60
print("\n== CLASS 槽 [films] 候选(谓词固定为 producer)==")
cls_rank = []
for c in [DBO.Film, DBO.FilmFestival]:
    sc, sp = slot_score("films", c), prop_rank[0][0]
    base = round((sc + sp) / 2, 3)
    ok = domain_ok(BEST_P, c)
    final = round(base if ok else base * 0.8, 3)   # 类型不一致打八折
    cls_rank.append((final, c, ok, sc))
    print(f"  {c.split('/')[-1]:14s} 槽分={sc} 类型一致={ok} 查询分={final}")
cls_rank.sort(reverse=True)
BEST_C = cls_rank[0][1]

# 用最优实例化结果拼出可执行 SPARQL(GROUP BY 聚合,等价课件 ORDER BY DESC(COUNT))
q = f"""
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT ?x (COUNT(?y) AS ?n) WHERE {{
  ?y rdf:type <{BEST_C}> .
  ?y <{BEST_P}> ?x .
}} GROUP BY ?x ORDER BY DESC(?n) LIMIT 1
"""
print("\n== 执行:Who produced the most films? ==")
for r in g.query(q):
    print(f"  答案 {str(r.x).split('/')[-1]},制片 {int(r.n)} 部")

运行结果:

== PROPERTY 槽 [produced] 候选 ==
  producer      0.975
  wineProduced  0.59

== CLASS 槽 [films] 候选(谓词固定为 producer)==
  Film           槽分=0.975 类型一致=True 查询分=0.975
  FilmFestival   槽分=0.478 类型一致=False 查询分=0.581

== 执行:Who produced the most films? ==
  答案 p_spielberg,制片 5 部

属性槽先淘汰形似义异的 wineProduced;类槽里 FilmFestival 不是 producer 的 domain(dbo:Work)的子类、类型不相容被惩罚(FilmWork 的子类故通过),Film+producer 胜出并返回制片最多者,复现课件 0.76 高于 0.60 的排序(数值随相似度与先验取值而变,排序结论不变)。

3.4 TBSL 缺点与自动模板学习 QUINT

TBSL 缺点有二:人工模板结构未必与图谱真实建模契合;一个问题的潜在模板极多、手工准备代价极大。QUINT(Abujabal 等,WWW 2017) 据此实现模板自动生成:根据「话语—答案」对(utterance-answer pair),借依存树自动学习「话语—查询」模板,并用简单问题模板解决复杂问题(依存树能捕捉远距离依赖、可跳过无用 token,容错性好)。

  • 构建骨架查询:输入话语 u 与答案集 Au(如「Which actress played character Amy Squirrel on Bad Teacher?」→ {LucyPunch})。先用实体链接工具 S-MART 做命名实体识别与链接(NERL,链到 Freebase),据话语与答案中的实体取连接它们的最小子图;再把答案节点与 CVT(复合值类型) 节点替换为变量 ?x?cvt,并在答案上补一条 type 边。
  • ILP 对齐与词典 L:用整数线性规划(ILP)学习话语与查询的对齐 m(如把「played on」对齐到 cast.actor)。词典含谓词词典 LP 与类型词典 LC,以 ClueWeb09-FACC1(约 5 亿页标注 Freebase 实体的网页)为语料、远程监督构建:语料「[[Albert Einstein|AlbertEinstein]] was born in [[Ulm|Ulm]]」配合三元组 (AlbertEinstein,birthPlace,Ulm),即把「was born in → birthPlace」加入 LP 并按出现次数赋权(类型同理,如「… and other scientists」→ LC)。ILP 把问题分块成短语集 Ph、查询子图语义项集 Sq,按词典加带权边(权重 W)、用 0/1 变量 X 决定是否保留边并最大化总权重,约束为「每个语义项须有一条边、每个短语只对应一个语义项、type 边只选一条」。
  • 模板三元组 (ut, qt, mt):话语模板 ut、查询模板 qt、映射方法模板 mt。问答阶段对新问题做依存分析与 S-MART 链接,到模板库用子图同构匹配命中 ut、再用词典实例化 mt;模板多命中与实体链接歧义产生多候选,用随机森林(Random Forest)学习候选间偏序排序。
  • 复杂问题处理:先依存树重写(并列连词、关系从句)拆成子句,各自匹配、实例化、排序得 query list;再答案拼接——按位置 r 给子句查询打 1/r 分、对各子问题答案取交集,选交集非空且组合分最高的组合。

3.5 模板方法的优缺点

优点:模板查询响应快、准确率较高,骨架天然带连接/聚合/约束,可答相对复杂的复合问题。缺点:人工模板常无法匹配真实用户问法;为覆盖多种表述需建庞大模板库,耗时耗力且拉低查询效率(QUINT 用自动学习缓解手工成本,但模板结构与图谱建模是否契合的问题仍在)。


4. 基于语义解析的方法:资源映射、Logic Form 与弱监督

4.1 从问句到 Logic Form

语义解析(Semantic Parsing,SP)的核心是把问题转成知识库能执行的逻辑表达式(Logic Form)。「姚明的老婆的国籍是?」先表为 λ 演算、再落成 SPARQL:

λx . 配偶(姚明, y) ∧ 国籍(y, x)
SELECT DISTINCT ?x WHERE {
  res:姚明 配偶 ?y .
  ?y 国籍 ?x .
}

逻辑表达式语言有 Lambda Calculus(λ 演算)、DCS-Tree、Fun-QL,可翻译成 SQL、SPARQL、Prolog、Fun-QL 等可执行语言。逻辑式分一元(unary,对应实体/类型)二元(binary,对应谓词相关的实体对),支持连接 Join、交集 Intersection、聚合 Aggregate(计数/最值)。四个基本步骤为短语检测、资源映射、语义组合、查询生成,用到分词、POS、NER、关系抽取、实体链接、句法分析、语义组合等技术;本例即把 E:姚明 与 R:配偶/R:国籍组合成两条三元组模式再生成 SPARQL。

4.2 关键问题一:资源映射(Alignment 词典)

一是获得短语到资源的映射,靠构造词汇表 Lexicon 完成,分两种:简单映射靠字符串相似度(「Obama」→Barack Obama);复杂映射针对关系短语(「was also born in」→PlaceOfBirth,课件 Berant 例用 Freebase 风格命名,下面 DBpedia 实现用等价的 dbo:birthPlace),用统计/远程监督——若实体对经常出现在短语两侧、且图谱中也存在该谓词三元组,就建立短语→谓词映射并按共现次数赋权:

# -*- coding: utf-8 -*-
# 语义解析-资源映射:简单映射(字符串相似度+显著度) 与 复杂映射(远程监督统计)
import re
from collections import defaultdict

def trigrams(s):
    s = "  " + re.sub(r"\s+", " ", s.lower()).strip() + "  "
    return set(s[i:i+3] for i in range(len(s) - 2))

def trigram_dice(a, b):
    A, B = trigrams(a), trigrams(b)
    return 2 * len(A & B) / (len(A) + len(B)) if A and B else 0.0

# ---- 复杂映射:远程监督构建谓词词典 LP ----
corpus = [
    "[[Barack Obama | BarackObama]] was also born in [[Honolulu | Honolulu]] .",
    "[[Barack Obama | BarackObama]] was also born in [[Honolulu | Honolulu]] , Hawaii .",
    "[[Albert Einstein | AlbertEinstein]] was born in [[Ulm | Ulm]] .",
    "[[Marie Curie | MarieCurie]] was born in [[Warsaw | Warsaw]] .",
]
kb_birthplace = {("BarackObama", "Honolulu"), ("AlbertEinstein", "Ulm"), ("MarieCurie", "Warsaw")}

def annotated_entities(sent):
    # 从 '[[表面形式 | KBid]]' 标注中取出链接到 KB 的实体 id(纯字符串切分,不依赖外部文件)
    ents = []
    for chunk in sent.split("[[")[1:]:
        inner = chunk.split("]]")[0]          # 形如 'Barack Obama | BarackObama'
        if "|" in inner:
            ents.append(inner.split("|")[1].strip())
    return ents

phrase = "was also born in"
pair_freq = defaultdict(int)
for sent in corpus:
    ents = annotated_entities(sent)
    if phrase.lower() in sent.lower() and len(ents) == 2:
        pair_freq[(ents[0], ents[1])] += 1    # 统计短语两侧实体对的出现次数
support = sum(v for pr, v in pair_freq.items() if pr in kb_birthplace)
print("复杂映射(远程监督):")
print(f"  短语 '{phrase}' -> dbo:birthPlace,语料共现权重(出现次数)={support},实体对={dict(pair_freq)}")
print("  原理:实体对既出现在短语两侧、又出现在 KB 的 birthPlace 三元组中,即可建立短语->谓词映射\n")

# ---- 简单映射:实体提及到资源,字符串相似度*0.8 + 显著度*0.2 ----
ENT_PROM = {"Barack_Obama": 0.9, "Michelle_Obama": 0.5, "Honolulu": 0.4}  # 实体先验流行度
def best_sim(mention, name):
    toks = re.split(r"[_\s]+", name.lower()) + [name.replace("_", " ").lower()]
    return max(trigram_dice(mention.lower(), t) for t in toks)

mention = "obama"
print(f"简单映射 mention='{mention}'(字符串相似度*0.8 + 流行度*0.2):")
rank = sorted(((round(0.8 * best_sim(mention, e) + 0.2 * ENT_PROM[e], 3), e)
               for e in ["Barack_Obama", "Honolulu", "Michelle_Obama"]), reverse=True)
for s, e in rank:
    print(f"   {e:16s} {s}")

运行结果:

复杂映射(远程监督):
  短语 'was also born in' -> dbo:birthPlace,语料共现权重(出现次数)=2,实体对={('BarackObama', 'Honolulu'): 2}
  原理:实体对既出现在短语两侧、又出现在 KB 的 birthPlace 三元组中,即可建立短语->谓词映射

简单映射 mention='obama'(字符串相似度*0.8 + 流行度*0.2):
   Barack_Obama     0.98
   Michelle_Obama   0.9
   Honolulu         0.08

「Obama」单靠字符串相似度会在 Barack 与 Michelle 间打平,加显著度先验才正确排序——说明实体链接离不开上下文与先验,单纯字符串匹配既不精确、覆盖也低。

4.3 关键问题二:逻辑表达式消歧、Alignment 与 Bridging

二是解决文本歧义:同一句可组合出多个候选逻辑表达式(课件「Which software … developed by organizations founded in California?」给出 4 种候选、3 种错误),需打分选优。两个互补动作:Alignment(对齐) 构建短语到 KB 谓词的粗映射词典——实体先链到 Freebase,一元(unary)短语用 Hearst 模式挖掘(约 15000 个文本短语);二元(binary) 对应 KB 图中长度 1 或 2 的路径、一元(unary) 对应 Type.x/Profession.x,KB 谓词共约 60000 个;Bridging(桥接补边) 处理未显式出现的谓词(如 What government does Chile have?、What is Italy’s language?、Who did … marry in 1928?),用相邻谓词与类型约束补出缺失的边,可连接两个一元节点、也可处理事件修饰语(Event Modifiers,如 in 1928)。随后用过度泛化文法(over-general grammar)经语义组合(Composition/Derivation)派生候选 Logic Form(可视作图模板 Graph Template),再训练模型估计「给定问句生成某逻辑式」的概率并排序。下面用 rdflib 实现该流程并附 Bridging 补边。

# -*- coding: utf-8 -*-
# 语义解析:候选 Logic Form(图模板) 生成与打分 + Bridging 补边 + 转 SPARQL 执行
from rdflib import Graph, Namespace
from rdflib.namespace import RDF

SP = Namespace("http://example.org/people#")
pg = Graph(); pg.bind("sp", SP)
pg.add((SP.姚明, SP.配偶, SP.叶莉)); pg.add((SP.叶莉, SP.国籍, SP.中国))
pg.add((SP.姚明, SP.国籍, SP.中国))
pg.add((SP.叶莉, RDF.type, SP.Person)); pg.add((SP.姚明, RDF.type, SP.Person))

phrase2pred = {"老婆": "配偶", "妻子": "配偶", "国籍": "国籍"}   # Alignment 词典:短语->谓词
PRED_W = {"配偶": 0.9, "国籍": 0.95}                            # 谓词对齐权重(来自词典统计)
topic = SP.姚明                                                # 实体链接得到的主题实体
rels = [phrase2pred[w] for w in "姚明 的 老婆 的 国籍 是".split() if w in phrase2pred]
print("主题实体:", topic.split('#')[-1], "|核心推断链:", rels)

def grow_paths(g_, start, max_len=2):
    # 围绕主题实体生长长度 1/2 的路径,每条路径即一个候选 Logic Form(图模板)
    res = []
    def dfs(node, chain, depth):
        if chain:
            res.append(tuple(chain))
        if depth >= max_len:
            return
        for _, p, o in g_.triples((node, None, None)):
            dfs(o, chain + [(p, o)], depth + 1)
    dfs(start, [], 0)
    return res

def lf_score(path):
    # 用词典权重给路径打分:路径谓词序列与问题对齐谓词序列的匹配程度
    preds = [str(p).split('#')[-1] for p, _ in path]
    sc = 0.0
    for i, want in enumerate(rels):
        if i < len(preds) and preds[i] == want:
            sc += PRED_W.get(want, 0.5)
    return round(sc / max(len(rels), 1), 3), preds

scored = sorted(((lf_score(p), p) for p in grow_paths(pg, topic)),
                key=lambda t: t[0][0], reverse=True)
print("\n候选 Logic Form(图模板)打分:")
for (sc, preds), path in scored:
    print(f"  分={sc:5.3f} 谓词={preds} 终点={str(path[-1][1]).split('#')[-1]}")

def to_sparql(start, path):                   # 把最优路径翻译成 SPARQL 三元组模式
    lines = []
    for i, (p, o) in enumerate(path):
        lines.append(f"<{start}> <{p}> ?x{i+1} ." if i == 0 else f"?x{i} <{p}> ?x{i+1} .")
    return "\n".join(lines), f"?x{len(path)}"

best = scored[0][1]
where, ans = to_sparql(topic, best)
q = f"SELECT DISTINCT {ans} WHERE {{\n{where}\n}}"
print("\n生成的 SPARQL:\n" + q)
print("执行结果(姚明的老婆的国籍):", [str(r[0]).split('#')[-1] for r in pg.query(q)])

# ---- Bridging:问题省略显式关系时,用类型约束补一条边 ----
BG = Namespace("http://example.org/geo#"); bg = Graph(); bg.bind("bg", BG)
bg.add((BG.Italy, BG.language, BG.Italian)); bg.add((BG.Italy, BG.capital, BG.Rome))
bg.add((BG.Italian, RDF.type, BG.Language)); bg.add((BG.Rome, RDF.type, BG.City))
def bridge(g_, subj, answer_type):
    for _, p, o in g_.triples((subj, None, None)):
        if (o, RDF.type, answer_type) in g_:   # 选宾语类型与答案类型一致的那条边
            return p, o
bp, bo = bridge(bg, BG.Italy, BG.Language)
print("\nBridging('Italy 的语言',问题未给动词)补边:", str(bp).split('#')[-1], "->", str(bo).split('#')[-1])

运行结果:

主题实体: 姚明 |核心推断链: ['配偶', '国籍']

候选 Logic Form(图模板)打分:
  分=0.925 谓词=['配偶', '国籍'] 终点=中国
  分=0.450 谓词=['配偶'] 终点=叶莉
  分=0.450 谓词=['配偶', 'type'] 终点=Person
  分=0.000 谓词=['国籍'] 终点=中国
  分=0.000 谓词=['type'] 终点=Person

生成的 SPARQL:
SELECT DISTINCT ?x2 WHERE {
<http://example.org/people#姚明> <http://example.org/people#配偶> ?x1 .
?x1 <http://example.org/people#国籍> ?x2 .
}
执行结果(姚明的老婆的国籍): ['中国']

Bridging('Italy 的语言',问题未给动词)补边: language -> Italian

两跳路径「配偶→国籍」得分最高并返回「中国」;「Italy 的语言」未出现关系动词,全靠 Bridging 依答案类型(Language)补出未明说的边。

4.4 弱监督语义解析与 Paraphrasing

传统语义解析靠专家手写规则/逻辑式作监督,缓慢昂贵不可扩展。Berant 等《Semantic Parsing on Freebase from Question-Answer Pairs》(EMNLP 2013)转向弱监督:监督数据是易从用户获得的「问题/答案对」。应对大规模 KB 有三条路线:无监督(Unger 2012、Yahya 2012)、远距离监督(Krishnamurthy and Mitchell 2012,适配有限小规模语义关系)、用「问题/逻辑式对」训练 parser(Cai and Yates 2013);文本到 KB 映射有三重困难:字符串匹配不精确、候选穷举不可行、覆盖率低。Berant 2013 的消融结论是 Bridging 对模板化 Free917 更重要、Alignment 对自由措辞 WebQuestions 更重要

针对 KB 不完整(Reverb 语料中仅 2% 关系短语能对齐到 Freebase),Berant 等《Semantic Parsing via Paraphrasing》(ACL 2014)用释义(paraphrasing)绕开:先用一个简单模型在实体周围生长(grow)出候选逻辑式(课件 Page124-125:平均每例约 650 个候选逻辑式、召回约 65%),再为每个候选逻辑式生成规范问句(canonical utterances,课件 Page127:平均每例约 1500 条),释义模型判断原问题与哪个规范问句同义、对其排序,间接选中逻辑式。模型定义在逻辑式 Zx 与规范问句 Cz 的联合分布上,需估计释义参数 θpr 与逻辑式参数 θlf,训练用 AdaGrad(Duchi 等 2010)。两种简易实现:关联模型(Association) 基于 Paralex 数据集(Fader 等 2013,从 WikiAnswers 改写链接得到约 1800 万对词对齐问题对),把同词性或经 WordNet 关联的词建立关联、覆盖有限;向量空间模型(VSM) 在 Wikipedia 上训词向量 v(w)、取问题实词 C,学习矩阵 W 估计相似度(单位阵=点积、对角阵=带缩放点积、满秩阵=刻画维度交互)。同一思路的极致是《Building a Semantic Parser Overnight》:少量人工对齐 + 大规模释义自动扩充。语义解析仍面临开放域解析、词典获取、问答对获取、消歧(PCCG、MLN 等概率建模)、符号匹配等挑战。


5. 基于深度学习的方法:分阶段查询图、图遍历与端到端嵌入

5.1 分阶段查询图生成(Yih 等,2015)

深度学习与 KBQA 结合有两方向:用深度学习改进传统方法各环节(神经网络做关系匹配替换人工特征),以及端到端模型直接学「问题→答案」。《Semantic Parsing via Staged Query Graph Generation》(Yih 等,ACL 2015)属前者,把问答刻画为「基于 λ 演算在图谱上匹配最优子图」,分阶段生成查询图。查询图四种图元:知识库实体中间变量(含 CVT)、聚合函数lambda 变量(答案)。三步:①链接主题实体并识别核心推断链——以「Who first voiced Meg on Family Guy?」为例,确定候选主题实体后提取其周围候选路径(长度 1 的路径,以及长度 2 且含 CVT 的路径,如 cast-actor);②CNN 给候选路径打分——自然语言与候选谓语序列分别过两个卷积网络得 300 维表示、用余弦相似度算匹配分;③施加参数约束(Argument Constraints)——x 是实体则加实体节点,first/latest 等词加为聚合约束。排序用三类特征:主题实体链接分、核心推断链关系匹配分(神经网络)、约束侧关键词与实体匹配,关键是关系分类。转向神经网络是因为传统特征依赖 NLP 预处理与人工设计,难服务缺 NLP 资源的语言、且误差逐级累积。

5.2 图遍历问答(Yao 等,2014)

《A Graph Traversal Based Approach … over DBpedia》(Yao 等,ACL 2014)的图遍历 QA 是端到端方法的前身(区别仅在 ranking 与 mapping 函数实现)。问句理解:实体链接(用 Wikipedia Miner,全局阈值 0.15,检测提及并链到 KB 资源)+拓扑结构抽取(成分分析得成分树、用拓扑模式抽关系)。图遍历:先子图构造(从链接实体迭代向外至多 K 步),再路径查找(从实体 e 取一跳路径,计算 e 周围谓词与拓扑结构边短语的语义相似度,迭代向外得多长度路径,到最外层停止、丢弃不匹配路径)。Focus 约束:focus 是直接描述答案的短语(如「Which television shows were created by John Cleese?」中的 television shows),从疑问词推导(who→person/organization、where→place、when→date)或按 POS 取疑问部分后最长名词短语;路径排序为 path score = predicate score + type score。

5.3 端到端子图嵌入(Bordes 等,2014)与最小实现

《Question Answering with Subgraph Embedding》(Bordes 等,EMNLP 2014)是典型端到端模型,当时只处理单关系(Single Relation)简单问题,分候选生成(实体链接定位主实体、其周围实体均为候选)与候选排序(学问题与候选子图嵌入再算相似度)两步,展开为五步:定位主实体 → 找答案路径 → 生成候选子图 → 问题与子图映射成嵌入 → 归一化点积(即余弦相似度) 得相似度(课件统称点积 dot product,向量归一化后即为余弦)。最小实现如下(用特征空间向量模拟学到的嵌入,固定随机种子):

# -*- coding: utf-8 -*-
# 端到端子图嵌入问答(Bordes 2014 思想)最小实现:候选生成 -> 问题/候选子图向量化 -> 余弦(点积)排序
import re, math, random
random.seed(42)

# Freebase 风格简易 KB:主实体 -> [(关系, 候选邻居)](单关系简单问题,候选=主实体一跳邻居)
KB = {"/m/johnny_cash": [("/people/spouse", "/m/vivian_liberto"),
                         ("/people/spouse", "/m/june_carter"),
                         ("/people/profession", "/m/singer")]}
TYPE = {"/m/vivian_liberto": "wife", "/m/june_carter": "wife", "/m/singer": "profession"}
MARRY_ORDER = ["/m/vivian_liberto", "/m/june_carter"]    # 第一任、第二任(KB 中其实带时序)
VOCAB = {"johnny", "cash", "wife", "first", "spouse", "profession", "singer"}
REL2FEAT = {"/people/spouse": {"wife", "spouse"}, "/people/profession": {"profession", "singer"}}

def sparse_vec(text_or_feats):                   # 把词袋/特征集合映射为稀疏向量(模拟嵌入)
    feats = set(re.findall(r"[a-z_]+", text_or_feats.lower())) if isinstance(text_or_feats, str) \
            else set(text_or_feats)
    return {w: 1.0 for w in feats if w in VOCAB}

def question_vec(q):
    return sparse_vec(q)

def candidate_vec(rel, obj):                    # 候选答案子图向量:关系特征 + 答案类型特征
    f = set(REL2FEAT.get(rel, set())) | {TYPE.get(obj, "")}
    f.discard("")
    return sparse_vec(f)

def cosine(u, v):                               # 点积归一化即余弦相似度
    dot = sum(u.get(k, 0) * v.get(k, 0) for k in set(u) | set(v))
    nu = math.sqrt(sum(x * x for x in u.values())); nv = math.sqrt(sum(x * x for x in v.values()))
    return round(dot / (nu * nv), 3) if nu and nv else 0.0

def rank_answers(q):
    qv = question_vec(q)
    return sorted(((cosine(qv, candidate_vec(rel, obj)), rel, obj)
                   for rel, obj in KB["/m/johnny_cash"]), reverse=True)

print("简单问题 'What is the profession of Johnny Cash?':")
for s, rel, obj in rank_answers("What is the profession of Johnny Cash?"):
    print(f"  {obj:18s} rel={rel:18s} cos={s}")

print("\n时序问题 'Who is Johnny Cash's first wife?':")
rank = rank_answers("Who is Johnny Cash's first wife?")
for s, rel, obj in rank:
    print(f"  {obj:18s} rel={rel:18s} cos={s}")
print("  两任妻子同分:'first' 在候选子图特征里没有对应算子,被模型忽略,无法区分第一/第二任。")
q_tokens = set(re.findall(r"[a-z_]+", "Who is Johnny Cash's first wife?".lower()))
if "first" in q_tokens:                         # 必须为 first 挂接显式时序排序算子才能答对
    wives = [o for r, o in KB["/m/johnny_cash"] if r == "/people/spouse"]
    ordered = [w for w in MARRY_ORDER if w in wives]
    print("  挂接 first->时序排序算子后,答案:", ordered[0])

运行结果:

简单问题 'What is the profession of Johnny Cash?':
  /m/singer          rel=/people/profession cos=0.408
  /m/vivian_liberto  rel=/people/spouse     cos=0.0
  /m/june_carter     rel=/people/spouse     cos=0.0

时序问题 'Who is Johnny Cash's first wife?':
  /m/vivian_liberto  rel=/people/spouse     cos=0.354
  /m/june_carter     rel=/people/spouse     cos=0.354
  /m/singer          rel=/people/profession cos=0.0
  两任妻子同分:'first' 在候选子图特征里没有对应算子,被模型忽略,无法区分第一/第二任。
  挂接 first->时序排序算子后,答案: /m/vivian_liberto

简单问题中正确候选(singer)相似度显著最高;但「first wife」里两任妻子同分——模型只看到 wife、对 first 无能为力,须补显式时序算子。

5.4 Multi-Column CNN、Cross-Attention 与三类方法对比

Multi-Column CNN(Dong 等,ACL 2015) 认为答案有多侧面信息、单一向量不够,用三列卷积网络分别从答案类型(Answer Type)、答案上下文(Answer Context)、答案路径(Answer Path) 学匹配再综合排序。Cross-Attention 模型(Hao 等,ACL 2017) 指出已有方法问句侧多用词向量平均、过于简单(关注答案不同部分时问句表示应不同),知识侧受限于语料、未考虑全局;遂引入双向交叉注意力(A-Q / Q-A Attention)让问题与候选相互聚焦,并用 TransE 训知识嵌入融入全局、结合多任务学习。优缺点:优点是无需人工模板与规则、全过程自动;缺点是当时主要处理简单题与单边关系、复杂问题不如两类传统方法,且通常不含显式的排序限制与聚合机制——前者是 ORDER BY/LIMIT 等 Solution Modifiers(解修饰符/结果修饰符)、后者是 COUNT/MAX 等聚合函数(课件原文作“聚类操作”,按上下文 first/latest 对应 aggregation constraints,应为“聚合”的近音误写),对时序敏感问题(如 first wife,见 5.3)需专门 ad-hoc 算子。

方法代表工作监督/知识来源能处理的问题主要优点主要短板
基于模板TBSL、QUINT手工模板 / 依存树自动学模板相对复杂的复合问题响应快、准确率高模板维护昂贵、易与真实问法和 schema 失配
基于语义解析Berant 2013/2014、Yih 2015问题/答案对、词典、远程监督复杂问题、时序问题(配合约束)可解释、表达力强词典/规则工程量大、受 KB 不完整制约
基于深度学习Bordes 2014、Dong 2015、Hao 2017问答对训练嵌入/注意力当时以单关系简单问题为主端到端自动、免人工模板规则复杂推理/时序弱、可解释性差、需专门聚合算子

演进脉络:模板法把「结构」写死、把「词」对齐;语义解析法把「结构」学成逻辑表达式、用 Alignment/Bridging/释义缓解词典瓶颈;深度学习法把结构与词都压进向量做端到端匹配,再以注意力、全局知识嵌入与显式约束补回表达力。

📝 动手练一练

  1. 概念辨析(判断对错并说明理由): ① 「社区问答和知识库问答都依赖关键词检索,因此没有本质区别。」 ② 「Alignment 和 Bridging 解决同一个问题,可以互相替代。」 ③ 「端到端子图嵌入模型天然就能回答 ‘Johnny Cash 的第一任妻子是谁’。」 ④ 「TBSL 对谓词做 domain/range 类型检查是可有可无的装饰步骤。」
👉 点击查看参考答案

错误。社区问答答案来自网民贡献的问答对、检索停留在关键词匹配;知识库问答以知识库为底座、把问题语义解析成 SPARQL 再到图上查询推理,技术底座不同。

错误。Alignment 对齐问题里显式出现的短语到谓词(解决词汇缺口);Bridging 在谓词未被明说时用相邻谓词与类型约束补边(解决结构缺口)。二者互补、不能互相替代(消融结论见 4.4)。

错误。纯嵌入匹配把问题与候选子图向量化后做点积/余弦,候选特征里没有与 first 对应的时序算子,first 被忽略、两任妻子同分(见 5.3 代码),须为 first/latest 挂接显式排序/聚合算子。

错误。类型检查是排序关键:采用的模板是 ?y rdf:type ?c . ?y dbo:producer ?x,类槽变量 ?y(电影)在主语位置,按 RDFS「domain 约束主语、range 约束宾语」,应拿 dbo:producer 的 domain=dbo:Workdbo:Film 是其子类)去比对类槽,其 range=dbo:Agentdbo:Person 是其子类)约束宾语位置的答案人;类槽填成 FilmFestival(并非 Work 的子类)时与 domain 不相容,应被惩罚淘汰(课件原例 Film 0.76 胜、FilmFestival 0.60;3.3 本地复现因权重/先验取值不同得到 0.975 胜、0.581 淘汰,排序结论一致),否则字符串相似度相近的错误类可能胜出。

  1. 动手:为两跳问题写 SPARQL 并用 rdflib 验证。问题「李小龙的孙辈是谁(Who are the grandchildren of Bruce Lee?)」对应两跳 dbo:child(子女的子女)。请先手写 SPARQL(主题实体 res:Bruce_Lee、中间变量 ?c、答案 ?uri),再用脚本验证。
👉 点击查看参考答案

参考 SPARQL(与课件一致,两跳 child):

PREFIX res: <http://dbpedia.org/resource/>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT DISTINCT ?uri WHERE {
  res:Bruce_Lee dbo:child ?c .
  ?c dbo:child ?uri .
}

最小验证脚本(内联三代家庭图谱):

# -*- coding: utf-8 -*-
# 验证两跳问题 "Bruce Lee 的孙辈" 的 SPARQL(子女的子女)
from rdflib import Graph, Namespace

RES = Namespace("http://dbpedia.org/resource/")
DBO = Namespace("http://dbpedia.org/ontology/")
g = Graph()
# 第一代 Bruce_Lee;第二代 Brandon/Shannon;第三代为孙辈
g.add((RES.Bruce_Lee, DBO.child, RES.Brandon_Lee))
g.add((RES.Bruce_Lee, DBO.child, RES.Shannon_Lee))
g.add((RES.Brandon_Lee, DBO.child, RES.grandchild_1))
g.add((RES.Shannon_Lee, DBO.child, RES.grandchild_2))

q = """
PREFIX res: <http://dbpedia.org/resource/>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT DISTINCT ?uri WHERE {
  res:Bruce_Lee dbo:child ?c .
  ?c dbo:child ?uri .
}"""
print("Bruce Lee 的孙辈:", sorted(str(r[0]).split('/')[-1] for r in g.query(q)))

运行结果:

Bruce Lee 的孙辈: ['grandchild_1', 'grandchild_2']

要点:语言里一个词「grandchildren(孙辈)」在图上是长度 2 的路径,正是「概念粒度与 schema 不吻合」——语义解析须能把单个关系词展开成多跳路径(QUINT/Yih 中长度 2 且含 CVT 的核心推断链同理)。

本章小结

  • 形态与历史:问答系统是下一代搜索引擎基本形态,经历「模板/专家系统(1960)→ 信息检索(1990)→ 社区问答(2000)→ 知识库问答(2010)」四阶段;形式分一问一答、交互式(处理共指)、阅读理解;数据集有 QALD(三任务、P/R/F)、WebQuestions/WebQuestionsSP(Average F1、SPARQL 标注)、Free917。
  • 概念与流水线:用七概念(问句短语、问题/答案类型、问题主题、来源/领域类型、答案格式)刻画问答;六组件流水线为「预处理 → 问题分析 → 数据匹配 → 查询构造 → 排序 → 答案抽取」;答案分 Right/Inexact/Unsupported/Wrong 四级。
  • 挑战本质:鸿沟在词汇(label 覆盖不了同义变体)、结构/粒度(一词对多跳或专门属性)、隐含关系(has/of/with 依赖语境且方向可变)、固定聚合表达、多语言、数据异构不完整、分布式互联与实时性能。
  • 三类方法:模板法(TBSL 两步留槽 + 相似度/显著度/domain-range 检查;QUINT 依存树 + ILP + 远程监督词典学 (ut,qt,mt)、随机森林排序、子问题取交集)快而准但模板昂贵;语义解析法(Alignment 映射、Bridging 补边、over-general 文法派生候选逻辑式、弱监督问答对、Paraphrasing 缓解 KB 不完整)能处理复杂/时序问题但工程量大;深度学习这一章实际含三种定位——Yih 分阶段查询图 + CNN 是用神经网络改进传统(查询图/语义解析)方法、Yao 图遍历是端到端前身(仅换 ranking/mapping),Bordes 子图嵌入点积、Dong 多列 CNN、Hao 交叉注意力 + TransE 才是端到端模型,它们全自动但当时主要解决单关系简单问题、缺显式聚合算子。

📋 行动清单

  • 跑通本节 5 段 Python(TBSL 模板问答、资源映射词典、候选逻辑表达式 + Bridging、端到端子图嵌入、两跳孙辈 SPARQL 验证),确认输出与讲义一致,再换成自己的 3–5 条三元组复现一次。
  • 不看讲义默画并口述两张图:①六组件问答流水线;②TBSL 两步法(模板生成留槽 → trigram/Levenshtein/WordNet/BOA + prominence + domain/range 检查的实例化排序)。
  • 选一个含两跳或最高级的问题,分别写出模板法的带槽 SPARQL 骨架、语义解析法的候选逻辑表达式(图模板)、端到端法需要的候选子图特征,并说明 first/most 在三种方法里各如何被处理。

—— 小象教研组

配套学习资源与课件
  • 第9章课件:知识问答 I
    下载
  • 知识图谱课程思维导图(KG_Centralized.xmind 全课程结构图)
    下载
🎁 免费学习资源

领取《小象 11GB VIP 课件资料包与大厂真题手册》

包含全套实战 Jupyter 源码、清洗后数据集、大厂高频面试真题与专属学员答疑交流群。

  • 完整 Python / 数据分析 Jupyter 实战源码
  • 大厂真实业务数据集与练习题
  • 微信扫码添加顾问免费领取;想学什么,直接告诉顾问
微信二维码:扫码添加课程顾问微信扫码添加顾问