首页 / 第三章
03确定性推理
有了知识表示,下一步是运用知识进行推理——从已知判断推出新判断。 本章研究前提与结论之间因果关系确定的演绎推理:从假言推理的直觉出发, 深入到置换合一、子句化简与 Robinson 归结原理,并实现两个真正的逻辑定理证明器。
3.1 推理的基本概念
◈ 定义
推理是按照某种策略,从已有判断(前提)出发推出新判断(结论)的思维过程。 在 AI 系统中,推理机利用知识库中的知识去求解问题。确定性推理所依据的证据与知识都是确定的, 结论的真值也唯一确定(真或假)。
3.1.1 推理的分类
| 分类标准 | 类型 | 特点 |
|---|---|---|
| 逻辑基础 | 演绎推理 | 由一般到个别,前提真则结论必然真(三段论)。 |
| 归纳推理 | 由个别到一般,结论具有或然性(是机器学习的思想源头)。 | |
| 默认推理 | 知识不完备时,在没有反证的情况下假设某些条件成立。 | |
| 知识确定性 | 确定性推理 | 本章:结论非真即假。 |
| 不确定性推理 | 第 5 章:证据或规则带可信度/概率。 | |
| 结论单调性 | 单调推理 | 随知识增加,结论只增不减(经典逻辑)。 |
| 非单调推理 | 新证据可能推翻旧结论(常识推理、失效性)。 | |
| 推理方向 | 正向推理 | 事实 → 结论(数据驱动,前向链)。 |
| 反向推理 | 假设目标 → 验证前提(目标驱动,后向链)。 | |
| 混合推理 | 正反向结合。 |
3.1.2 推理机的三个核心问题
- 匹配:知识库中的哪条规则的前提与当前综合数据库相符合?简单情形是精确匹配,复杂时需要合一(见 3.3)。
- 冲突消解:当多条规则同时匹配成功,选择哪一条?常见策略:按优先级、按条件的专一性(特殊者优先)、按新鲜度(新事实优先)、按出现顺序。
- 执行与终止:执行选中规则的后件,更新数据库;当目标被证实(反向)或无新事实产生(正向)时终止。
3.2 自然演绎推理
自然演绎推理从一组已知为真的事实出发,直接运用经典逻辑的推理规则推出结论。最核心的规则有:
拒取式(MT):¬Q,P → Q ⊢ ¬P
假言三段论:P → Q,Q → R ⊢ P → R
经典三段论:"凡人都会死(P→Q);苏格拉底是人(P);所以苏格拉底会死(Q)"就是假言推理。 下面用 Python 实现一个自然演绎风格的前向链证明器:给定事实和蕴含规则,逐步推出所有可达结论并给出证明链。
"""自然演绎:前向链(反复应用假言推理),并记录每条结论的证明链"""
class ProofEngine:
def __init__(self):
self.facts = {} # 命题 -> 证明依据
self.rules = [] # (前提集合, 结论, 规则名)
def add_fact(self, p):
self.facts.setdefault(p, ("公理", []))
def add_rule(self, premises, conclusion, name):
self.rules.append((set(premises), conclusion, name))
def forward(self, target):
"""返回能否推出 target;推理过程中保留证明树。"""
changed = True
while changed:
changed = False
for premises, concl, name in self.rules:
if concl in self.facts:
continue
if premises.issubset(self.facts): # 前件全部已知为真
self.facts[concl] = (name, sorted(premises))
changed = True
print(f"应用 {name}:由 {sorted(premises)} 推出 「{concl}」")
return target in self.facts
def explain(self, p, depth=0):
"""递归打印证明树。"""
if p not in self.facts:
return
reason, deps = self.facts[p]
print(" " * depth + f"└─ {p} [{reason}]")
for d in deps:
self.explain(d, depth + 1)
if __name__ == "__main__":
eng = ProofEngine()
# 事实
for f in ["苏格拉底是人", "下雨"]:
eng.add_fact(f)
# 蕴含规则(前提 -> 结论)
eng.add_rule(["苏格拉底是人"], "苏格拉底会死", "假言推理:凡人皆有死")
eng.add_rule(["下雨"], "天空有云", "规则:下雨则天阴")
eng.add_rule(["天空有云"], "可能需要带伞", "规则:阴天备伞")
eng.add_rule(["苏格拉底会死"], "苏格拉底不是神", "规则:神不死")
print("=== 前向推理过程 ===")
ok = eng.forward("苏格拉底不是神")
print("\n能否证明'苏格拉底不是神':", ok)
print("\n=== 证明树 ===")
eng.explain("苏格拉底不是神")
=== 前向推理过程 ===
应用 假言推理:凡人皆有死:由 ['苏格拉底是人'] 推出 「苏格拉底会死」
应用 规则:下雨则天阴:由 ['下雨'] 推出 「天空有云」
应用 规则:阴天备伞:由 ['天空有云'] 推出 「可能需要带伞」
应用 规则:神不死:由 ['苏格拉底会死'] 推出 「苏格拉底不是神」
能否证明'苏格拉底不是神': True
=== 证明树 ===
└─ 苏格拉底不是神 [规则:神不死]
└─ 苏格拉底会死 [假言推理:凡人皆有死]
└─ 苏格拉底是人 [公理]
自然演绎符合人的思维习惯,但存在两个问题:① 推理规则众多,机器不知道该选哪条; ② 容易在无关的推理路径上组合爆炸。Robinson 于 1965 年提出的归结原理, 把推理规则统一为唯一一条"归结式",使机器定理证明发生了根本性突破。
3.3 置换与合一
谓词推理中,规则与事实往往含变量,匹配不再是字符串相等,而需要置换与合一。
◈ 置换(Substitution)
置换是形如 {t₁/x₁, t₂/x₂, …} 的有限集合,表示把公式中的变量 xᵢ 替换为项 tᵢ。
例如对 P(x, f(y)) 施加置换 {a/x, b/y} 得到 P(a, f(b))。
两个置换可合成(复合):先 θ₁ 后 θ₂。
◈ 合一(Unification)
寻找一个置换,使两个谓词公式变得相同的过程叫合一,该置换叫合一子;
其中最一般的合一子记为 mgu(most general unifier)。
例:P(x, b) 与 P(a, y) 的 mgu 是 {a/x, b/y}。
3.3.1 最一般合一算法(MGU)的 Python 实现
下面实现经典的递归下降合一算法(支持常量、变量、函数项),
并包含 occur-check(检查变量是否循环出现在项中,防止 x → f(x) 式的无限递归)。
"""一阶谓词项的表示与最一般合一算法(MGU)
项的表示约定:
变量:以 '?' 开头的字符串,如 '?x'
常量:普通字符串,如 'a'、'john'
函数项:('f', ['?x', 'a']) 元组 (函数名, 项列表)
"""
def is_var(t):
return isinstance(t, str) and t.startswith("?")
def walk(t, subst):
"""沿置换链解析变量的当前绑定。"""
while is_var(t) and t in subst:
t = subst[t]
return t
def occur_check(var, term, subst):
"""var 是否出现在 term 中(防止循环置换)。"""
term = walk(term, subst)
if term == var:
return True
if isinstance(term, tuple):
return any(occur_check(var, arg, subst) for arg in term[1])
return False
def extend(var, term, subst):
"""把 var := term 加入置换(含 occur 检查)。"""
if occur_check(var, term, subst):
return None # 无法合一,如 ?x 与 f(?x)
new_subst = dict(subst)
new_subst[var] = term
return new_subst
def unify(t1, t2, subst=None):
"""返回 t1 与 t2 的 mgu(字典);不可合一返回 None。"""
if subst is None:
subst = {}
t1, t2 = walk(t1, subst), walk(t2, subst)
if t1 == t2:
return subst
if is_var(t1):
return extend(t1, t2, subst)
if is_var(t2):
return extend(t2, t1, subst)
if isinstance(t1, tuple) and isinstance(t2, tuple):
if t1[0] != t2[0] or len(t1[1]) != len(t2[1]):
return None # 函数符/参数个数不同
for a, b in zip(t1[1], t2[1]):
subst = unify(a, b, subst)
if subst is None:
return None
return subst
return None # 两个不同常量,不可合一
def apply_subst(term, subst):
"""把置换作用于一个项/原子。"""
term = walk(term, subst)
if is_var(term) or isinstance(term, str):
return term
if isinstance(term, tuple):
return (term[0], [apply_subst(a, subst) for a in term[1]])
return term
if __name__ == "__main__":
# 原子 P(term...) 用 ('P', [项...]) 表示
tests = [
(("P", ["?x", "b"]), ("P", ["a", "?y"])),
(("Q", ["?x", "?x"]), ("Q", ["a", "b"])), # 失败:?x 不能同时是 a 和 b
(("R", ["?x"]), ("R", [("f", ["?y"])]),),
(("R", ["?x"]), ("R", [("f", ["?x"])])), # 失败:occur check
(("T", ["?x", ("f", ["?y"])]), ("T", ["a", ("f", ["b"])])),
]
for t1, t2 in tests:
mgu = unify(t1, t2)
print(f"合一 {t1} 与 {t2}")
if mgu is None:
print(" => 不可合一\n")
else:
print(f" mgu = {mgu}")
print(f" 合一后 = {apply_subst(t1, mgu)}\n")
合一 ('P', ['?x', 'b']) 与 ('P', ['a', '?y'])
mgu = {'?x': 'a', '?y': 'b'}
合一后 = ('P', ['a', 'b'])
合一 ('Q', ['?x', '?x']) 与 ('Q', ['a', 'b'])
=> 不可合一
合一 ('R', ['?x']) 与 ('R', [('f', ['?y'])])
mgu = {'?x': ('f', ['?y'])}
合一后 = ('R', [('f', ['?y'])])
合一 ('R', ['?x']) 与 ('R', [('f', ['?x'])])
=> 不可合一
合一 ('T', ['?x', ('f', ['?y'])]) 与 ('T', ['a', ('f', ['b'])])
mgu = {'?x': 'a', '?y': 'b'}
合一后 = ('T', ['a', ('f', ['b'])])
3.4 子句集及其化简
归结原理要求把任意谓词公式化成子句集。
子句是若干文字的析取:L₁ ∨ L₂ ∨ … ∨ Lₙ,其中文字是谓词公式或其否定;
不含任何文字的子句叫空子句 □(NIL),它是永假的——推出空子句就意味着矛盾。
谓词公式化为子句集的九个步骤
- 消去蕴含符:用
P→Q ⇔ ¬P∨Q替换所有 →、↔; - 否定符内移:用德摩根律把 ¬ 移到紧靠谓词的位置;
- 变量标准化:使不同量词约束的变量有不同名字;
- 消去存在量词:若在全称量词辖域内,用 Skolem 函数替换,否则用 Skolem 常量;
- 化为前束范式:把全称量词全部移到公式左边;
- 消去全称量词:此时所有变量都默认全称约束;
- 化为合取范式:反复使用分配律
A∨(B∧C) ⇔ (A∨B)∧(A∨C); - 消去合取词:每个析取式成为一个子句;
- 子句变量更名:使不同子句中的变量互不同名。
◈ 关键定理
公式 F 与其子句集 S 在"不可满足性"上保持一致:
F 不可满足 当且仅当 S 不可满足。
因此可以用归结子句集来代替对原公式的证明。
3.5 命题逻辑的归结方法
则归结式 R(C₁, C₂) = C₁′ ∨ C₂′
若 C₁、C₂ 为真,则归结式必为真(归结是保真的)。
特别地,P 与 ¬P 归结得到空子句 □。归结反演证明步骤:
- 把前提知识全部化为子句集
S; - 把目标结论
G否定,即加入¬G到子句集; - 对子句反复归结,把归结式加入集合;
- 若归结出空子句 □,说明
S ∪ {¬G}矛盾,反证G成立。
3.5.1 Python 实现命题逻辑归结证明器
下面实现一个完整证明器:先把人类可读的逻辑表达式(支持 ~ & | -> <->)
解析成语法树,自动化为 CNF,再进行归结反演。
"""命题逻辑归结反演证明器
表达式语法:~P 非;P&Q 与;P|Q 或;P->Q 蕴含;P<->Q 等价
"""
# ---------- 1. 递归下降解析 ----------
def parse(expr):
tokens = expr.replace("(", " ( ").replace(")", " ) ").split()
pos = 0
def parse_iff():
nonlocal pos
left = parse_imp()
while pos < len(tokens) and tokens[pos] == "<->":
pos += 1
right = parse_imp()
left = ("iff", left, right)
return left
def parse_imp():
nonlocal pos
left = parse_or()
while pos < len(tokens) and tokens[pos] == "->":
pos += 1
right = parse_or()
left = ("imp", left, right)
return left
def parse_or():
nonlocal pos
left = parse_and()
while pos < len(tokens) and tokens[pos] == "|":
pos += 1
left = ("or", left, parse_and())
return left
def parse_and():
nonlocal pos
left = parse_not()
while pos < len(tokens) and tokens[pos] == "&":
pos += 1
left = ("and", left, parse_not())
return left
def parse_not():
nonlocal pos
if tokens[pos] == "~":
pos += 1
return ("not", parse_not())
if tokens[pos] == "(":
pos += 1
node = parse_iff()
pos += 1 # ')'
return node
tok = tokens[pos]
pos += 1
return ("lit", tok)
tree = parse_iff()
assert pos == len(tokens), f"无法解析的记号:{tokens[pos:]}"
return tree
# ---------- 2. 化为 CNF ----------
def eliminate(node):
"""消去 -> 与 <->。"""
t = node[0]
if t == "lit":
return node
if t == "not":
return ("not", eliminate(node[1]))
a, b = eliminate(node[1]), eliminate(node[2])
if t == "imp": # A->B ≡ ~A|B
return ("or", ("not", a), b)
if t == "iff": # A<->B ≡ (~A|B)&(~B|A)
return ("and", ("or", ("not", a), b), ("or", ("not", b), a))
return (t, a, b)
def nnf(node):
"""否定内移,得到否定范式。"""
t = node[0]
if t == "lit":
return node
if t == "not":
sub = node[1]
if sub[0] == "not": # 双重否定
return nnf(sub[1])
if sub[0] == "and": # ~(A&B) ≡ ~A|~B
return ("or", nnf(("not", sub[1])), nnf(("not", sub[2])))
if sub[0] == "or": # ~(A|B) ≡ ~A&~B
return ("and", nnf(("not", sub[1])), nnf(("not", sub[2])))
return node
return (t, nnf(node[1]), nnf(node[2])) if t in ("and", "or") else node
def distr(a, b):
"""分配律:A | (B & C) = (A | B) & (A | C)。"""
if a[0] == "and":
return ("and", distr(a[1], b), distr(a[2], b))
if b[0] == "and":
return ("and", distr(a, b[1]), distr(a, b[2]))
return ("or", a, b)
def cnf(node):
if node[0] == "lit" or node[0] == "not":
return node
a, b = cnf(node[1]), cnf(node[2])
if node[0] == "and":
return ("and", a, b)
return distr(a, b) # or
def to_clauses(node):
"""CNF 树 -> 子句集合;子句用 frozenset 表示,
文字:命题名为正,('~', 名) 为负。"""
clauses = set()
def walk(n):
if n[0] == "and":
walk(n[1]); walk(n[2])
else:
lits = set()
def collect(o):
if o[0] == "or":
collect(o[1]); collect(o[2])
elif o[0] == "not":
lits.add(("~", o[1][1]))
else:
lits.add(o[1])
collect(n)
clauses.add(frozenset(lits))
walk(node)
return clauses
# ---------- 3. 归结 ----------
def resolve(c1, c2):
"""两个子句的所有可能归结式。"""
results = []
for lit in c1:
neg = ("~", lit[1]) if isinstance(lit, tuple) else ("~", lit)
comp = lit[1] if isinstance(lit, tuple) else None
# c1 中的正文字 lit,找 c2 中 ('~', lit);反之亦然
if isinstance(lit, tuple): # 负文字
if comp in c2:
merged = (c1 - {lit}) | (c2 - {comp})
results.append(frozenset(merged))
else: # 正文字
if neg in c2:
merged = (c1 - {lit}) | (c2 - {neg})
results.append(frozenset(merged))
return results
def resolution_prove(kb_exprs, goal_expr):
"""归结反演:kb_exprs 前提列表,goal_expr 待证结论。"""
clauses = set()
for e in kb_exprs:
clauses |= to_clauses(cnf(nnf(eliminate(parse(e)))))
# 目标取否定
clauses |= to_clauses(cnf(nnf(eliminate(parse("~(" + goal_expr + ")")))))
print("子句集:")
for c in sorted(clauses, key=str):
print(" ", show_clause(c))
new = set()
while True:
pairs = [(c1, c2) for c1 in clauses for c2 in clauses if c1 != c2]
for c1, c2 in pairs:
for r in resolve(c1, c2):
if len(r) == 0:
print(f"归结 {show_clause(c1)} 与 {show_clause(c2)} => □ 空子句")
return True # 矛盾!目标成立
if not tautology(r):
new.add(r)
if new <= clauses:
return False # 不再产生新子句,目标不可证
clauses |= new
new = set()
def tautology(c):
"""重言式子句(同时含 P 与 ~P)可直接删除。"""
for lit in c:
if not isinstance(lit, tuple):
if ("~", lit) in c:
return True
return False
def show_clause(c):
if not c:
return "□"
return " ∨ ".join(l if not isinstance(l, tuple) else "¬" + l[1] for l in sorted(c, key=str))
if __name__ == "__main__":
# 例题:若下雨则地湿 (R->W);下雨了 (R);证明 地是湿的 (W)
print("【例1】R->W, R ⊢ W ?")
print("结论:", resolution_prove(["R->W", "R"], "W"), "(True 表示得证)\n")
# 经典:(P->Q) & (Q->R) & P ⊢ R
print("【例2】P->Q, Q->R, P ⊢ R ?")
print("结论:", resolution_prove(["P->Q", "Q->R", "P"], "R"))
【例1】R->W, R ⊢ W ?
子句集:
R
W ∨ ¬R
¬W
归结 R 与 W ∨ ¬R => W
归结 ¬W 与 W => □ 空子句
结论: True (True 表示得证)
【例2】P->Q, Q->R, P ⊢ R ?
子句集:
P
Q ∨ ¬P
R ∨ ¬Q
¬R
结论: True
3.6 谓词逻辑的归结反演
谓词逻辑归结比命题逻辑多一步:先对两个亲本子句做最一般合一,再消去互补文字。
设 C₁ = L₁ ∨ C₁′,C₂ = ¬L₂ ∨ C₂′,若 L₁ 与 L₂ 的 mgu 为 σ,
则二元归结式为 (C₁′ ∨ C₂′)σ。下面把 3.3 的 MGU 与归结结合,实现一个 FOL 定理证明器。
"""一阶谓词逻辑归结反演(子句已手工化为标准形式)
文字表示: ('+', ('谓词', [项...])) 正文字
('-', ('谓词', [项...])) 负文字
子句:frozenset(文字);变量以 ?开头;函数项 ('f',[...])
"""
from unify import unify, apply_subst # 复用 3.3 节的合一算法
def negate(lit):
sign, atom = lit
return ("-" if sign == "+" else "+", atom)
def resolve_fol(c1, c2):
"""返回两个谓词子句的所有归结式(已实例化)。"""
results = []
for l1 in c1:
for l2 in c2:
if l1[0] == l2[0]:
continue # 同号不能互补
mgu = unify(l1[1], l2[1])
if mgu is None:
continue
rest1 = [apply_subst_lit(x, mgu) for x in c1 - {l1}]
rest2 = [apply_subst_lit(x, mgu) for x in c2 - {l2}]
resolvent = frozenset(rest1 + rest2)
if not is_tautology(resolvent):
results.append(resolvent)
return results
def apply_subst_lit(lit, subst):
sign, atom = lit
return (sign, apply_subst(atom, subst))
def is_tautology(clause):
for lit in clause:
if negate(lit) in clause:
return True
return False
def rename(clause, k):
"""子句变量标准化改名:?x -> ?x_k,避免不同子句变量重名。"""
def rn(term):
if isinstance(term, str) and term.startswith("?"):
return term + "_" + str(k)
if isinstance(term, tuple):
return (term[0], [rn(a) for a in term[1]])
return term
return frozenset((s, rn(atom)) for s, atom in clause)
def fol_prove(clauses):
"""对子句集(含目标之否定)做水平饱和归结,出现 □ 即不可满足。"""
clauses = set(clauses)
level = 0
while True:
new = set()
items = list(clauses)
for i in range(len(items)):
for j in range(i + 1, len(items)):
c1 = rename(items[i], level)
c2 = rename(items[j], level)
for r in resolve_fol(c1, c2):
if len(r) == 0:
print(f"第 {level} 层归结得到空子句 □")
return True
if r not in clauses:
new.add(r)
if new <= clauses:
return False
clauses |= new
level += 1
print(f"第 {level} 层:新增 {len(new)} 个子句,总计 {len(clauses)}")
def L(sign, pred, *terms):
"""快速构造文字。"""
return (sign, (pred, list(terms)))
if __name__ == "__main__":
# ===== 例1:苏格拉底三段论 =====
# ∀x Human(x)->Mortal(x) 化为子句: ¬Human(?x) ∨ Mortal(?x)
# Human(socrates);目标否定:¬Mortal(socrates)
kb = [
frozenset([L("-", "Human", "?x"), L("+", "Mortal", "?x")]),
frozenset([L("+", "Human", "socrates")]),
frozenset([L("-", "Mortal", "socrates")]), # 目标之否定
]
print("【苏格拉底会死?】", fol_prove(kb), "\n")
# ===== 例2:家庭关系 =====
# ∀x,y Parent(x,y)-> Ancestor(x,y)
# ∀x,y,z Parent(x,z) ∧ Ancestor(z,y) -> Ancestor(x,y)
# 事实:Parent(bob,anna), Parent(anna,tom)
# 目标:Ancestor(bob,tom),其否定加入
kb2 = [
frozenset([L("-", "Parent", "?x", "?y"), L("+", "Ancestor", "?x", "?y")]),
frozenset([L("-", "Parent", "?x", "?z"),
L("-", "Ancestor", "?z", "?y"),
L("+", "Ancestor", "?x", "?y")]),
frozenset([L("+", "Parent", "bob", "anna")]),
frozenset([L("+", "Parent", "anna", "tom")]),
frozenset([L("-", "Ancestor", "bob", "tom")]),
]
print("【Bob 是 Tom 的祖先?】", fol_prove(kb2))
【苏格拉底会死?】第 0 层归结得到空子句 □ True 【Bob 是 Tom 的祖先?】第 0 层:新增 2 个子句,总计 7 第 1 层:新增 3 个子句,总计 10 第 2 层归结得到空子句 □ True
3.7 归结反演求解问题
归结反演不仅能证明命题,还能求取答案(回答"是谁""是什么")。技巧是:
把目标构造为带待求变量的重言式(回答谓词),例如目标为
∃y Ancestor(bob, y),则加入子句 ¬Ancestor(bob, ?y) ∨ ANSWER(?y)。
归结结束时,ANSWER 中变量被绑定的值就是答案。下面在上面证明器基础上稍作扩展:
"""归结反演求取答案:ANSWER 谓词记录变量绑定"""
from fol_resolution import L, rename, is_tautology
from unify import unify, apply_subst
def resolve_with_trace(c1, c2):
results = []
for l1 in c1:
for l2 in c2:
if l1[0] == l2[0] and l1[1][0] != "ANSWER":
continue
if l1[0] != l2[0]:
mgu = unify(l1[1], l2[1])
if mgu is None:
continue
rest1 = [(s, apply_subst(a, mgu)) for s, a in c1 - {l1}]
rest2 = [(s, apply_subst(a, mgu)) for s, a in c2 - {l2}]
r = frozenset(rest1 + rest2)
if not is_tautology(r):
results.append(r)
return results
def fol_query(clauses, answer_lit, max_level=8):
"""answer_lit 形如 ANSWER(?y) 的正文字。"""
clauses = set(clauses)
for level in range(max_level):
new = set()
items = list(clauses)
for i in range(len(items)):
for j in range(i + 1, len(items)):
for r in resolve_with_trace(rename(items[i], level),
rename(items[j], level)):
only_answer = all(lit[1][0] == "ANSWER" for lit in r)
if only_answer and r:
return r # 只剩 ANSWER 文字,读出答案
new.add(r)
if new <= clauses:
return None
clauses |= new
return None
if __name__ == "__main__":
# 求:Bob 的孙辈是谁?(即 Parent(bob,?z) 且 Parent(?z,?y))
kb = [
frozenset([L("-", "Parent", "?x", "?y"), L("+", "Ancestor", "?x", "?y")]),
frozenset([L("-", "Parent", "?x", "?z"),
L("-", "Ancestor", "?z", "?y"),
L("+", "Ancestor", "?x", "?y")]),
frozenset([L("+", "Parent", "bob", "anna")]),
frozenset([L("+", "Parent", "anna", "tom")]),
frozenset([L("+", "Parent", "anna", "lily")]),
# 目标重言式:¬Ancestor(bob,?y) ∨ ANSWER(?y)
frozenset([L("-", "Ancestor", "bob", "?y"),
("+", ("ANSWER", ["?y"]))]),
]
ans = fol_query(kb, None)
if ans:
for lit in ans:
print("答案:Bob 的后代包括 =", lit[1][1])
else:
print("无解")
答案:Bob 的后代包括 = ['anna'] 答案:Bob 的后代包括 = ['tom'] 答案:Bob 的后代包括 = ['lily']
⚠ 归结策略与效率
无引导的归结会产生大量无用子句,造成组合爆炸。实用系统采用删除策略(重言式、被归类子句删除)、 支持集策略(每次归结至少有一个子句来自目标或其后裔,保证完备性)、 线性输入策略(至少一个亲本子句来自原始子句集)、单文字子句策略等缩小搜索规模。
3.8 基于规则的演绎推理
归结反演需要把所有知识化成子句,破坏了规则的领域语义("IF-THEN"结构丢失),不便于构建实用系统。 基于规则的演绎推理直接保留蕴含式,按推理方向分两类:
正向规则演绎(F 规则)
从事实表达式出发,用形如 L → W 的规则对事实进行变换,直到目标表达式成立。数据驱动,适合预测类任务。
反向规则演绎(B 规则)
从目标出发,用规则 W → L 把目标分解为子目标,递归验证。目标驱动,适合诊断类任务,第二章谓词知识库即此法。
无论正向还是反向,实际系统(如第 8 章的专家系统)都会用合一处理变量, 并用冲突消解策略控制推理过程。这正是本章理论走向工程的桥梁。
本章小结
- 确定性推理是前提与结论因果确定的演绎推理,核心问题是匹配、冲突消解与推理方向。
- 自然演绎用假言推理等规则从前提出发,符合直觉但规则多、易爆炸。
- 置换是变量替换,合一是寻找使两式相同的置换,MGU 是最一般合一子,需做 occur-check。
- 谓词公式经九步化为子句集,并保持不可满足性等价;空子句 □ 表示矛盾。
- Robinson 归结原理:互补文字消去;加入目标之否定,归结出 □ 即反证目标成立。
- 谓词归结 = 合一 + 归结;借助 ANSWER 谓词,归结反演还能求取问题的答案。
- 实用中需结合支持集、删除策略等控制归结规模;规则演绎保留 IF-THEN 语义,是专家系统的推理基础。