首页 / 第二章
02知识表示
知识是智能的基础。要让机器拥有智能,首先要解决的问题是:如何把人类的知识"装进"计算机? 本章依次实现谓词逻辑、产生式规则、框架、语义网络、面向对象五种经典表示法, 并延伸到今天的知识图谱——它们是第三章推理与第八章专家系统的地基。
2.1 知识与知识表示的基本概念
◈ 定义
知识是人们在改造客观世界的实践中积累起来的认识和经验的总和, 它把有关信息中两个或多个信息联系在一起,形成新的信息(如"如果乌云密布且空气潮湿,那么很可能要下雨")。 知识具有相对正确性、不确定性、可表示性与可利用性。
2.1.1 知识的分类
| 分类标准 | 类型 | 说明与示例 |
|---|---|---|
| 按作用范围 | 常识性知识 | 人人皆知的普遍知识,如"鸟会飞""水在 100℃ 沸腾"。 |
| 领域性知识 | 特定领域的专业知识,如医学诊断规则、法律条文。 | |
| 按确定性 | 确定性知识 | 真值明确,可用逻辑表示,如"三角形内角和为 180°"。 |
| 不确定性知识 | 含经验、模糊成分,如"发高烧多半是感染"(第 5 章处理)。 | |
| 按内容层次 | 事实知识 | "雪是白的""李白是诗人"——描述对象的属性、状态。 |
| 规则知识 | 事实间的因果关系,形如"若 P 则 Q"。 | |
| 控制知识 | 关于如何运用知识的知识(策略),如搜索顺序。 | |
| 元知识 | "知识的知识",如"这条规则比那条更可靠"。 |
2.1.2 什么是知识表示
知识表示就是将人类知识形式化或模型化,用一组约定的符号结构把知识编码成计算机可以接受的数据结构。 对同一知识,往往有多种可行的表示方法,选择时主要考虑四条标准:
- 表示能力:能否正确、充分地表达领域知识;
- 可利用性:是否便于使用这些知识进行推理(推理效率高不高);
- 可组织性与可维护性:知识是否便于增删改,能否模块化;
- 可理解性与自然性:表示方式是否接近人类表达习惯。
◈ 一条主线
本章五种经典方法的演进,本质是表示粒度越来越大、结构化程度越来越高、越来越接近人类认知: 谓词(原子命题)→ 规则(条件—结论)→ 框架(对象全貌)→ 语义网络(关系网络)→ 知识图谱(大规模实体关系网络)。
2.2 一阶谓词逻辑表示法
2.2.1 命题逻辑
命题是具有真假意义的陈述句,如"雪是白的"(真)、"3 大于 5"(假)。
用大写字母 P, Q, R 表示命题,用联结词组合:
| 联结词 | 含义 | 读法 |
|---|---|---|
| ¬P | 否定 | 非 P |
| P ∧ Q | 合取 | P 且 Q |
| P ∨ Q | 析取 | P 或 Q |
| P → Q | 蕴含 | 若 P 则 Q |
| P ↔ Q | 等价 | P 当且仅当 Q |
但命题逻辑无法刻画命题内部结构。"苏格拉底是哲人"和"柏拉图是哲人"在命题逻辑中只能记成两个无关的 P 和 Q,
无法表达"是哲人"这个共同成分。谓词逻辑把命题进一步分解为谓词 + 个体。
2.2.2 谓词、个体与量词
形如 Man(socrates) 的原子公式中,Man 是谓词(表示性质或关系,首字母大写),
socrates 是个体(客体)。表示 n 个个体间关系的谓词称为 n 元谓词,如
Greater(3, 2)、Love(xiaoming, xiaohong)。
- 全称量词 ∀x:"对所有 x"。例:∀x (Bird(x) → Fly(x)) 表示"所有鸟都会飞"。
- 存在量词 ∃x:"存在某个 x"。例:∃x (Father(x, john)) 表示"存在一个人是 John 的父亲"。
用谓词公式表示知识的步骤
- 定义用哪些谓词及其含义,确定个体与个体域;
- 把事实表示为谓词公式的合取(与);
- 把规则表示为蕴含式;
- 用 ∧、∨、量词等连接成谓词公式集(知识库)。
示例——"凡是人都会死;苏格拉底是人"可表示为:
∀x (Human(x) → Mortal(x)) 与 Human(socrates),
由第三章的归结推理即可推出 Mortal(socrates)。
2.2.3 Python 实现:一个微型谓词知识库
下面把谓词实现为 Python 对象,支持事实存储、带变量的规则、查询与反向链推理。
变量以 ? 开头(如 ?x),与 Prolog 的思想一致,是 2.3 节之前的一个小预告。
"""微型一阶谓词知识库:事实 + 规则 + 反向链查询"""
class Atom:
"""原子谓词,如 Human(?x)、Parent(bob, anna)。"""
def __init__(self, pred, *terms):
self.pred = pred
self.terms = list(terms)
@staticmethod
def parse(expr):
"""把字符串 'Human(?x)' 解析成 Atom 对象。"""
pred, rest = expr.split("(", 1)
terms = rest.rstrip(")").split(",")
return Atom(pred.strip(), *[t.strip() for t in terms])
def is_var(self, term):
return isinstance(term, str) and term.startswith("?")
def match(self, fact, subst):
"""将本模式(可含变量)与事实(不含变量)在已有置换 subst 下匹配。"""
if self.pred != fact.pred or len(self.terms) != len(fact.terms):
return None
for pat_t, fact_t in zip(self.terms, fact.terms):
pat_t = subst.get(pat_t, pat_t) if self.is_var(pat_t) else pat_t
if self.is_var(pat_t):
subst = dict(subst)
subst[pat_t] = fact_t # 变量绑定
elif pat_t != fact_t:
return None
return subst
def substitute(self, subst):
terms = [subst.get(t, t) if self.is_var(t) else t for t in self.terms]
return Atom(self.pred, *terms)
def __repr__(self):
return f"{self.pred}({', '.join(self.terms)})"
class KnowledgeBase:
def __init__(self):
self.facts = [] # 事实:Atom 列表
self.rules = [] # 规则:(前提原子列表, 结论原子)
def add_fact(self, expr):
self.facts.append(Atom.parse(expr))
def add_rule(self, head, *body):
"""head 为结论,body 为若干前提,例如
add_rule('Mortal(?x)', 'Human(?x)') 表示 Human(x) → Mortal(x)"""
self.rules.append((Atom.parse(head), [Atom.parse(b) for b in body]))
def query(self, goal_expr):
"""查询目标,返回所有使目标成立的变量绑定(反向链)。"""
goal = Atom.parse(goal_expr)
answers = []
def prove(goals, subst):
if not goals: # 所有子目标都已证明
answers.append(subst)
return
first, rest = goals[0], goals[1:]
first = first.substitute(subst)
# 路径 A:与已知事实匹配
for fact in self.facts:
new_subst = first.match(fact, subst)
if new_subst is not None:
prove(rest, new_subst)
# 路径 B:使用规则,把目标替换成规则的前提
for head, body in self.rules:
new_subst = first.match(head, subst)
if new_subst is not None:
new_body = [a.substitute(new_subst) for a in body]
prove(new_body + [a.substitute(new_subst) for a in rest], new_subst)
prove([goal], {})
return answers
if __name__ == "__main__":
kb = KnowledgeBase()
# ---- 事实:家庭关系 ----
for f in [
"Human(socrates)", "Human(bob)", "Human(anna)", "Human(tom)",
"Parent(bob, anna)", "Parent(anna, tom)",
]:
kb.add_fact(f)
# ---- 规则 ----
kb.add_rule("Mortal(?x)", "Human(?x)") # 人都会死
kb.add_rule("Ancestor(?x, ?y)", "Parent(?x, ?y)")
kb.add_rule("Ancestor(?x, ?y)", "Parent(?x, ?z)", "Ancestor(?z, ?y)")
# ---- 查询 ----
print("会死者:", kb.query("Mortal(?who)"))
print("Bob 的孩子:", kb.query("Parent(bob, ?c)"))
print("Tom 的祖先:", kb.query("Ancestor(?a, tom)"))
会死者: [{'?who': 'socrates'}, {'?who': 'bob'}, {'?who': 'anna'}, {'?who': 'tom'}]
Bob 的孩子: [{'?c': 'anna'}]
Tom 的祖先: [{'?a': 'anna'}, {'?a': 'bob'}]
系统不仅查到了直接事实,还通过规则推出了"Bob 是 Tom 的祖父,因此也是 Tom 的祖先", 这就是知识表示与推理结合的雏形。谓词逻辑表示法自然、严密、易于理解, 但表达能力有限(不便表达不确定知识与启发式知识),且当事实增多时推理效率会下降。
2.3 产生式表示法
1943 年美国数学家波斯特(Post)首先提出产生式(Production)。
它是目前专家系统中应用最多的一种知识表示方法,形如 P → Q 或
IF P THEN Q:P 是产生式的前提(条件/前件),Q 是结论或动作(后件)。
◈ 结构
一个产生式系统由三部分组成:
① 综合数据库(Context / 工作存储器):存放当前问题的初始事实、中间结果;
② 规则库(Rule Base):存放所有产生式规则;
③ 控制系统(推理机):决定当前哪些规则的前提被满足(匹配)、
当多条规则同时满足时选哪一条(冲突消解)、以及执行后件更新数据库(动作)。
产生式表示的知识示例
- 事实:"雪是白的"——确定性三元组
(雪, 颜色, 白); - 规则:
IF 动物有羽毛 THEN 它是鸟类; - 不确定规则:
IF 发烧 AND 咳嗽 THEN 感冒 (可信度 0.7)。
Python 实现:通用产生式推理引擎
"""产生式系统:综合数据库 + 规则库 + 正向推理机"""
class Rule:
def __init__(self, name, conditions, conclusion, desc=""):
self.name = name
self.conditions = set(conditions) # 前件:需要同时成立的事实集合
self.conclusion = conclusion # 后件:推出的新事实
self.desc = desc
def __repr__(self):
return f"<{self.name}: IF {sorted(self.conditions)} THEN '{self.conclusion}'>"
class ProductionSystem:
def __init__(self):
self.facts = set() # 综合数据库
self.rules = [] # 规则库
def add_fact(self, fact):
self.facts.add(fact)
def add_rule(self, rule):
self.rules.append(rule)
def forward_chaining(self, trace=True):
"""正向推理:从已知事实出发,反复触发规则,直到不再产生新事实。"""
fired = set() # 已触发过的规则,避免重复
log = []
changed = True
while changed:
changed = False
conflict_set = [] # 冲突集:当前所有可触发的规则
for rule in self.rules:
if rule.name in fired:
continue
if rule.conditions.issubset(self.facts): # 前件全部满足
conflict_set.append(rule)
if conflict_set:
# 冲突消解策略:规则按名称排序(也可按优先级、专一性等)
rule = sorted(conflict_set, key=lambda r: r.name)[0]
self.facts.add(rule.conclusion)
fired.add(rule.name)
changed = True
msg = f"触发 {rule.name}:{rule.desc} => 新事实「{rule.conclusion}」"
log.append(msg)
if trace:
print(msg)
return log
if __name__ == "__main__":
# 经典动物识别规则库(第八章还会扩展为完整专家系统)
ps = ProductionSystem()
for f in ["有毛发", "吃肉", "黄褐色", "有黑色条纹"]:
ps.add_fact(f)
ps.add_rule(Rule("R1", ["有毛发"], "是哺乳动物", "有毛发→哺乳动物"))
ps.add_rule(Rule("R2", ["有奶"], "是哺乳动物", "有奶→哺乳动物"))
ps.add_rule(Rule("R3", ["是哺乳动物", "吃肉"], "是食肉动物", "哺乳动物且吃肉→食肉动物"))
ps.add_rule(Rule("R4", ["是哺乳动物", "有蹄"], "是有蹄类动物", "哺乳动物且有蹄→有蹄类"))
ps.add_rule(Rule("R5", ["是食肉动物", "黄褐色", "有黑色条纹"], "是老虎",
"食肉+黄褐色+黑条纹→老虎"))
ps.add_rule(Rule("R6", ["是食肉动物", "黄褐色", "有暗斑点"], "是金钱豹",
"食肉+黄褐色+暗斑点→金钱豹"))
print("初始事实:", sorted(ps.facts))
print("-" * 50)
ps.forward_chaining()
print("-" * 50)
print("最终结论:", "老虎" if "是老虎" in ps.facts else "未能识别")
初始事实: ['吃肉', '有毛发', '有黑色条纹', '黄褐色'] -------------------------------------------------- 触发 R1:有毛发→哺乳动物 => 新事实「是哺乳动物」 触发 R3:哺乳动物且吃肉→食肉动物 => 新事实「是食肉动物」 触发 R5:食肉+黄褐色+黑条纹→老虎 => 新事实「是老虎」 -------------------------------------------------- 最终结论: 老虎
产生式表示法的优点是模块性强(规则之间通过数据库间接联系,可独立增删)、 自然性好(IF-THEN 接近人类经验表达)、推理路径可解释(能回答"为什么")。 缺点是规则间关系隐晦、大型规则库存在组合爆炸与冲突消解困难、效率不高。
2.4 框架表示法
1975 年明斯基(Minsky)在论文《A Framework for Representing Knowledge》中提出框架理论: 人在面对新情境时,会从记忆中选取一个称为"框架"的结构(类似填空表格),根据需要修改其中的细节。 框架表示法正是以这一理论为基础的结构化知识表示方法,擅长表示典型情境、对象的全貌。
◈ 框架的组成
一个框架由框架名和若干槽(slot)组成;每个槽描述对象的一个属性,
槽又可包含若干侧面(facet)和值,常见侧面有 value(值)、
default(默认值)、if-needed(需要时计算的过程)、
if-added(赋值时触发的过程)。槽值可以是具体值、另一框架名,从而构成框架网络,
子框架可通过 AKO / ISA 槽继承父框架的属性。
框架表示示例:"教师"框架
"""框架表示法:槽/侧面、默认值、过程性附件、继承"""
class Frame:
def __init__(self, name):
self.name = name
self.slots = {} # 槽名 -> {侧面名: 值}
def add_slot(self, slot, value=None, default=None,
if_needed=None, if_added=None):
self.slots[slot] = {
"value": value,
"default": default,
"if_needed": if_needed, # 缺值时调用的函数(frame, slot)
"if_added": if_added, # 写入值时触发的函数(frame, slot, val)
}
def get(self, slot, seen=None):
"""取值顺序:本框架值 → 默认值 → if_needed 过程 → 沿 AKO 继承"""
seen = seen or set()
if self.name in seen:
return None
seen.add(self.name)
s = self.slots.get(slot)
if s is not None:
if s["value"] is not None:
return s["value"]
if s["default"] is not None:
return s["default"]
if s["if_needed"] is not None:
return s["if_needed"](self, slot)
# 沿 ISA / AKO 到父框架中继承
for parent_slot in ("AKO", "ISA"):
if parent_slot in self.slots and self.slots[parent_slot]["value"]:
parent = FrameSystem.get_frame(self.slots[parent_slot]["value"])
val = parent.get(slot, seen)
if val is not None:
return val
return None
def setv(self, slot, value):
s = self.slots.setdefault(slot, {"value": None, "default": None,
"if_needed": None, "if_added": None})
s["value"] = value
if s["if_added"]:
s["if_added"](self, slot, value)
def show(self):
print(f"【框架:{self.name}】")
for slot, facets in self.slots.items():
if slot in ("AKO", "ISA"):
continue
print(f" {slot: <6} = {self.get(slot)}")
class FrameSystem:
_frames = {}
@classmethod
def get_frame(cls, name):
return cls._frames.get(name)
@classmethod
def create(cls, name):
f = Frame(name)
cls._frames[name] = f
return f
if __name__ == "__main__":
# ---- 定义父框架:教职工 ----
staff = FrameSystem.create("教职工")
staff.add_slot("类别")
staff.add_slot("工作单位", default="人工智能学院")
staff.add_slot("健康状况", default="良好")
# ---- 子框架:教师,AKO 继承自教职工 ----
teacher = FrameSystem.create("教师")
teacher.add_slot("AKO", value="教职工")
teacher.add_slot("学历", default="博士研究生")
teacher.add_slot("职称")
# if_needed 过程性知识:没有填写职称时按年龄推断
teacher.add_slot("退休年龄",
if_needed=lambda f, s: 65 if f.get("性别") == "男" else 60)
# ---- 实例框架:具体某位教师 ----
wang = FrameSystem.create("王老师")
wang.add_slot("ISA", value="教师")
wang.setv("姓名", "王知行")
wang.setv("性别", "男")
wang.setv("职称", "教授")
wang.setv("所授课程", "人工智能导论")
wang.show()
print("\n—— 继承与推理效果 ——")
print("工作单位(继承自教职工):", wang.get("工作单位"))
print("学历(继承自教师默认值):", wang.get("学历"))
print("退休年龄(if_needed 过程推算):", wang.get("退休年龄"))
【框架:王老师】 姓名 = 王知行 性别 = 男 职称 = 教授 所授课程 = 人工智能导论 退休年龄 = 65 —— 继承与推理效果 —— 工作单位(继承自教职工): 人工智能学院 学历(继承自教师默认值): 博士研究生 退休年龄(if_needed 过程推算): 65
框架法把陈述性知识(属性值)与过程性知识(if_needed)组织在一起, 层次清晰、继承性强,特别适合表达有固定结构的典型对象与场景(如病历、简历、事故情境)。 缺点是不善于表达过程性的流程知识,且多重继承时可能产生属性冲突。
2.5 语义网络表示法
1968 年奎廉(Quillian)在研究人类联想记忆时提出语义网络。 它通过节点和带标注的弧描述事物间的语义关系:节点表示事物、概念、属性值; 弧表示节点间的关系。一个最简单的语义单元是三元组 (节点 A,弧,节点 B)。
常用的基本语义关系
| 关系 | 含义 | 示例 |
|---|---|---|
| ISA / instance-of | 具体与抽象(实例关系) | 小燕子 —ISA→ 鸟 |
| AKO / subclass-of | 子类关系(可继承属性) | 鸟 —AKO→ 动物 |
| part-of | 部分与整体(不继承属性) | 轮子 —part-of→ 汽车 |
| have / can | 属性、能力关系 | 鸟 —have→ 翅膀 |
| located-at | 位置关系 | 书 —located-at→ 图书馆 |
| caused-by | 因果关系 | 下雨 —导致→ 地湿 |
Python 实现:语义网络与继承推理
用语义网络表示知识后,推理的主要形式是匹配与继承:
查询"小燕子有什么器官"时,沿 ISA → AKO 链向上,继承"鸟有翅膀""动物有心脏"等属性。
"""语义网络:有向图(三元组)+ 沿继承关系的属性推理"""
class SemanticNetwork:
def __init__(self):
self.triples = [] # [(主体, 关系, 客体)]
# 可继承、指向"父概念"的关系
self.isa_rels = {"ISA", "AKO", "subclass-of", "instance-of"}
def add(self, subj, rel, obj):
self.triples.append((subj, rel, obj))
def direct(self, subj, rel):
"""直接关系查询。"""
return [o for s, r, o in self.triples if s == subj and r == rel]
def parents(self, node):
"""沿 ISA/AKO 一步可达的父概念。"""
return [o for s, r, o in self.triples
if s == node and r in self.isa_rels]
def inheritable_props(self, node):
"""继承推理:收集节点自身及所有祖先概念的属性(have/can/属性...)。"""
result = {}
visited = set()
stack = [node]
while stack:
cur = stack.pop()
if cur in visited:
continue
visited.add(cur)
for s, r, o in self.triples:
if s == cur and r not in self.isa_rels and r != "part-of":
result.setdefault(r, []).append((o, cur))
stack.extend(self.parents(cur))
return result
def all_nodes(self):
return sorted({s for s, _, _ in self.triples} | {o for _, _, o in self.triples})
def describe(self, node):
print(f"「{node}」可知的属性(含继承):")
for rel, vals in self.inheritable_props(node).items():
for val, source in vals:
tag = "自身" if source == node else f"继承自「{source}」"
print(f" --{rel}--> {val} ({tag})")
if __name__ == "__main__":
net = SemanticNetwork()
# 概念层次
net.add("动物", "AKO", "生物")
net.add("鸟", "AKO", "动物")
net.add("鱼", "AKO", "动物")
net.add("燕子", "ISA", "鸟")
net.add("麻雀", "ISA", "鸟")
# 属性
net.add("生物", "能", "新陈代谢")
net.add("动物", "能", "运动")
net.add("动物", "have", "心脏")
net.add("鸟", "have", "翅膀")
net.add("鸟", "能", "飞行")
net.add("鱼", "have", "鳃")
net.add("鱼", "能", "游泳")
net.add("燕子", "颜色", "黑色")
net.add("燕子", "build", "泥巢")
net.describe("燕子")
print()
print("直接会飞的:", net.direct("鸟", "能"))
print("燕子的父概念链:", net.parents("燕子"))
「燕子」可知的属性(含继承): --颜色--> 黑色 (自身) --build--> 泥巢 (自身) --have--> 翅膀 (继承自「鸟」) --能--> 飞行 (继承自「鸟」) --have--> 心脏 (继承自「动物」) --能--> 运动 (继承自「动物」) --能--> 新陈代谢 (继承自「生物」) 直接会飞的: ['飞行'] 燕子的父概念链: ['鸟']
语义网络的优点是直观、联想方式自然、属性共享(继承)减少冗余; 缺点是推理结论依赖网络结构,没有统一的形式化语义,规模一大就难以管理和保证一致性。 ——这正是现代知识图谱用本体(Ontology)和 RDF/OWL 标准加以规范化的原因。
2.6 面向对象表示法
面向对象(Object-Oriented)表示法与框架法一脉相承,但以更规范的类(class)、对象(object)、 封装、继承、多态机制组织知识:类描述一类事物的共同属性(字段)与行为(方法),对象是类的实例; 子类继承父类并可改写方法。这种表示法天然能被 Java、C++、Python 等 OO 语言直接支持。
封装
把数据(属性)与处理数据的方法捆绑在对象内部,对外隐藏实现细节,仅暴露接口。
继承
子类自动获得父类的属性与方法,实现知识复用与分类层次。
多态
同名方法在不同子类中有不同实现,如 bird.move() 是飞、fish.move() 是游。
"""用面向对象方式表示'动物分类学'知识"""
class Animal:
"""动物基类:所有动物共享的知识。"""
kingdom = "动物界"
def __init__(self, name, age):
self.name = name
self.age = age
def move(self):
raise NotImplementedError # 由具体子类实现(多态)
def info(self):
return f"{self.name},{self.age} 岁,属于{self.kingdom},移动方式:{self.move()}"
class Bird(Animal):
"""鸟类继承动物类,补充鸟的共同知识。"""
def __init__(self, name, age, can_fly=True):
super().__init__(name, age)
self.can_fly = can_fly
self.body_cover = "羽毛"
def move(self):
return "飞行" if self.can_fly else "行走"
def lay_egg(self):
return f"{self.name} 产了一枚卵"
class Fish(Animal):
def move(self):
return "游泳"
def breathe(self):
return f"{self.name} 用鳃呼吸"
class Penguin(Bird):
"""企鹅是鸟但不会飞——继承体系下的特化。"""
def __init__(self, name, age):
super().__init__(name, age, can_fly=False)
zoo = [Bird("白鹭", 3), Fish("鲤鱼", 1), Penguin("QQ", 2)]
for a in zoo:
print(a.info()) # 同一个 info(),行为随对象类型不同(多态)
print(zoo[0].lay_egg())
print(zoo[1].breathe())
白鹭,3 岁,属于动物界,移动方式:飞行 鲤鱼,1 岁,属于动物界,移动方式:游泳 QQ,2 岁,属于动物界,移动方式:行走 白鹭 产了一枚卵 鲤鱼 用鳃呼吸
2.7 知识图谱入门(现代延伸)
知识图谱(Knowledge Graph)是语义网络思想在大数据时代的复兴与工程化, Google 于 2012 年正式提出。它以 RDF 三元组(头实体, 关系, 尾实体) 为基本数据模型描述世界中的实体及其关系,典型项目有 Wikidata、DBpedia、CN-DBpedia 等, 广泛用于搜索问答、推荐、风控、医疗等领域。
2.7.1 构建一个可推理的小型知识图谱
下面实现一个内存知识图谱,支持:三元组增删、多跳关系查询、逆关系、 基于规则的关系推理(如"祖父 = 父亲的父亲""首都的国家即位于"),并演示一个简单问答接口。
"""迷你知识图谱:三元组存储 + 多跳查询 + 规则推理 + 简单问答"""
from collections import defaultdict
class KnowledgeGraph:
def __init__(self):
self.triples = set()
self.spo = defaultdict(list) # (主体, 关系) -> [客体]
self.ops = defaultdict(list) # (客体, 关系) -> [主体]
def add(self, s, p, o):
if (s, p, o) not in self.triples:
self.triples.add((s, p, o))
self.spo[(s, p)].append(o)
self.ops[(o, p)].append(s)
def query(self, s, p, o):
"""三元组中任意一项为 None 表示查询变量,返回所有匹配。"""
ans = []
for (a, b, c) in self.triples:
if (s is None or s == a) and (p is None or p == b) and (o is None or o == c):
ans.append((a, b, c))
return ans
def hop(self, entity, *relations):
"""沿关系链做多跳查询,如 hop('红楼梦', '作者', '国籍')。"""
current = [entity]
for r in relations:
nxt = []
for e in current:
nxt.extend(self.spo.get((e, r), []))
current = nxt
return current
def add_rule(self, new_rel, rel_chain):
"""关系推理规则:若存在 rel_chain 关系链,则蕴含 new_rel 关系。
例:new_rel='祖父', chain=('父亲','父亲')"""
added = []
for s in self.query(None, rel_chain[0], None):
tails = self.hop(s[0], *rel_chain)
for t in tails:
before = len(self.triples)
self.add(s[0], new_rel, t)
if len(self.triples) > before:
added.append((s[0], new_rel, t))
return added
if __name__ == "__main__":
kg = KnowledgeGraph()
# ---- 文学领域知识 ----
data = [
("红楼梦", "作者", "曹雪芹"), ("曹雪芹", "朝代", "清朝"),
("红楼梦", "体裁", "古典小说"), ("西游记", "作者", "吴承恩"),
("吴承恩", "朝代", "明朝"), ("三国演义", "作者", "罗贯中"),
("罗贯中", "朝代", "元末明初"), ("水浒传", "作者", "施耐庵"),
("施耐庵", "朝代", "元末明初"),
("贾宝玉", "登场于", "红楼梦"), ("孙悟空", "登场于", "西游记"),
# ---- 地理知识 ----
("中国", "首都", "北京"), ("北京", "位于", "中国"),
("法国", "首都", "巴黎"), ("巴黎", "位于", "法国"),
("巴黎", "地标", "埃菲尔铁塔"),
]
for t in data:
kg.add(*t)
# 1) 单条件查询:四大名著的作者
print("《红楼梦》的作者:", kg.hop("红楼梦", "作者"))
# 2) 多跳查询:《红楼梦》作者的朝代
print("《红楼梦》作者所处朝代:", kg.hop("红楼梦", "作者", "朝代"))
# 3) 反向查询:谁登场于《西游记》
who = [s for s, p, o in kg.query(None, "登场于", "西游记")]
print("《西游记》中的角色:", who)
# 4) 两跳到达:从《红楼梦》出发,经'作者'能到谁,再经'朝代'
print("全部三元组数量:", len(kg.triples))
# 5) 规则推理:定义'名著作者时代' = 作者→朝代 的压缩关系
for s, _, book in kg.query(None, "登场于", None):
pass
new_facts = kg.add_rule("作者朝代", ("作者", "朝代"))
print("规则推理新增三元组:")
for f in sorted(new_facts):
print(" ", f)
# 6) 埃菲尔铁塔在哪个国家(两跳:地标→位于)
print("埃菲尔铁塔所在国家:", kg.hop("埃菲尔铁塔", "位于"))
# 7) 简单模板问答
def qa(question):
q = question.replace("?", "").replace("?", "")
if "的作者是谁" in q:
book = q.split("的作者是谁")[0].strip("《》")
return "、".join(kg.hop(book, "作者"))
if "在哪个国家" in q:
place = q.split("在哪个国家")[0]
return "、".join(kg.hop(place, "位于"))
return "抱歉,这个问题超出了我的知识范围。"
print(qa("《红楼梦》的作者是谁?"))
print(qa("埃菲尔铁塔在哪个国家?"))
《红楼梦》的作者: ['曹雪芹']
《红楼梦》作者所处朝代: ['清朝']
《西游记》中的角色: ['孙悟空']
全部三元组数量: 15
规则推理新增三元组:
('三国演义', '作者朝代', '元末明初')
('水浒传', '作者朝代', '元末明初')
('西游记', '作者朝代', '明朝')
('红楼梦', '作者朝代', '清朝')
埃菲尔铁塔所在国家: ['法国']
《红楼梦》的作者是谁?
曹雪芹
埃菲尔铁塔在哪个国家?
法国
✦ 工程延伸
真实知识图谱的实体规模可达百亿三元组,工程上常用 Neo4j、JanusGraph 等图数据库存储, 用 SPARQL / Cypher 查询语言,用 OWL 本体 + 描述逻辑推理机 (如 Pellet、HermiT)做一致性检查与推理;实体与关系的自动获取则依赖第 9 章将介绍的 NLP 技术(命名实体识别、关系抽取)。
本章小结
- 知识是智能的基础,按层次可分为事实、规则、控制知识和元知识;知识表示追求表示能力、可利用性与可维护性。
- 一阶谓词逻辑:谓词+个体+量词,严密自然,可用反向链进行逻辑推理。
- 产生式:IF-THEN 规则,由综合数据库、规则库、控制系统构成,是专家系统的主流表示法。
- 框架:槽/侧面结构描述典型对象,支持默认值、过程附件与继承。
- 语义网络:节点—弧—节点的图结构,靠匹配和继承推理,直观自然。
- 面向对象:类与对象、封装、继承、多态,可被现代编程语言直接支持。
- 知识图谱:三元组模型的大规模工程化,支持多跳查询与规则推理,是现代 AI 的重要基础设施。