反向推理
反向推理规则的启用,发生在你的程序对 Pyke 进行询问时。例如,要求 Pyke 证明某一具体目标。Pyke 的证明活动,只用到激活的规则库。
“反向推理”的概况
为了做反向推理,Pyke 要找出这样的规则:其 then 子句部分,与目标(即程序向Pyke询问的问题)能够匹配。找到匹配的规则后,Pyke 试图通过递归,证明该规则 if 子句的全部子目标是真实的。其中一些子目标是寻求匹配的事实,另一些则是其他的反向推理规则。如果这些子目标全部证明为真,这一规则适用成功,最初的终极目标证明为真。否则,这一规则适用失败,Pyke 尝试寻找其他规则,即其 then 子句与目标匹配的规则。这一过程,会反复进行。
Pyke 最终会形成一条“证据链”,从第一条规则的 if 子句开始,到下一规则的 then 子句结束。
复习
- Pyke 的证明活动,开始于寻找某一规则,其 then 子句与目标匹配。
- Pyke 接着证明规则 if 子句为真。
- 亦即:规则的 if 子句,与另一规则的 then 子句,可以建立连接。
Pyke 的证明活动方式,是从规则的 then 子句开始,到 if 子句结束,与我们通常运用的推理方式相反,因此,叫做“反向推理”。
更具体地说,Pyke 要求书写你的反向推理规则时,首先写出 then 子句,因为它在运行时先受处理。
用 use 和 when 代替 then 和 if
不过,then-if 规则听着别扭,Pyke 就用 use 代替 then,用 when 代替 if。
Pyke 只允许在 use 子句里出现一个事实陈述,这与正向推理的 assert 子句(允许多个事实)不同。
示例
请思考下面的例子:
1 direct_father_son 2 use father_son($father, $son, ()) 3 when 4 family2.son_of($son, $father) 5 grand_father_son 6 use father_son($grand_father, $grand_son, (grand)) 7 when 8 father_son($father, $grand_son, ()) 9 father_son($grand_father, $father, ()) 10 great_grand_father_son 11 use father_son($gg_father, $gg_son, (great, $prefix1, *$rest_prefixes)) 12 when 13 father_son($father, $gg_son, ()) 14 father_son($gg_father, $father, ($prefix1, *$rest_prefixes)) 15 brothers 16 use brothers($brother1, $brother2) 17 when 18 father_son($father, $brother1, ()) 19 father_son($father, $brother2, ()) 20 check $brother1 != $brother2 21 uncle_nephew 22 use uncle_nephew($uncle, $nephew, $prefix) 23 when 24 brothers($uncle, $father) 25 father_son($father, $nephew, $prefix1) 26 $prefix = ('great',) * len($prefix1) 27 cousins 28 use cousins($cousin1, $cousin2, $distance, $removed) 29 when 30 uncle_nephew($uncle, $cousin1, $prefix1) 31 father_son($uncle, $cousin2, $prefix2) 32 $distance = min(len($prefixes1), len($prefixes2)) + 1 33 $removed = abs(len($prefixes1) - len($prefixes2))
这些规则得出的结论,与使用正向推理方式的结果,完全相同。只是增加了一些亲属关系的规则,即:brothers, uncle_nephew, cousins (兄弟、叔侄、堂兄弟之类)
可以画出类似正向推理的、适用规则产生的函数调用流程图:

规则调用示例
在向 Pyke 求证某个目标的要求之后,这些规则才被调用。
提出求证要求的最简方式,是用函数 some_engine.prove_1_goal 或者 some_engine.prove_goal。函数 prove_1_goal 返回找到的第一个证据后,便停止运行(或者引起意外:pyke.knowledge_engine.CanNotProve)。函数 prove_goal 返回的是求证过程中产生的全部证据的“管理器”。产生的证据可能多得没有上限。
这两个函数,按照“方案流程图”确定的顺序,返回模式变量。
反向推理规则的回溯调用
下面的例子,求证过程始于一组事实 family2 的匹配:
1 son_of(tim, thomas) 2 son_of(fred, thomas) 3 son_of(bruce, thomas) 4 son_of(david, bruce)
我们想知道 fred 的侄子是谁,便询问 uncle_nephew(fred, $nephew, $prefix) 。
程序代码中小括号里的数字,是求证成功时的步骤。例程中的行号,表示了求证失败时的位置。
(1) 22 use uncle_nephew(fred, $nephew, $prefix) 24 brothers(fred, $father) (2) 16 use brothers(fred, $brother2) 18 father_son($father, fred, ()) (3) 2 use father_son($father, fred, ()) 4 family2.son_of(fred, $father) matches fact 2: son_of(fred, thomas) 19 father_son(thomas, $brother2, ()) (4) 2 use father_son(thomas, $son, ()) 4 family2.son_of($son, thomas) matches fact 1: son_of(tim, thomas) 20 check fred != tim 25 father_son(tim, $nephew, $prefix1) (5.1) 2 use father_son(tim, $son, ()) 4 family2.son_of($son, tim) => FAILS (5.2) 6 use father_son(tim, $grand_son, (grand)) 8 father_son(tim, $grand_son, ()) 2 use father_son(tim, $son, ()) 4 family2.son_of($son, tim) => FAILS (5.3) 11 use father_son(tim, $gg_son, (great, $prefix1, *$rest_prefixes)) 13 father_son(tim, $gg_son, ()) 2 use father_son(tim, $son, ()) 4 family2.son_of($son, tim) => FAILS
各规则的调用顺序,编号在小括号中。第 5 步时,试用了 3 个不同规则(5.1, 5.2 和 5.3),但都失败了。
可以把这些规则看成函数,适用规则求证的过程,可以画成流程图,证明成功的以黑圈表示,失败的以红圈表示:

我们需要回溯!
此时,Pyke 求证过程无法继续,必须回溯。回溯的方式,是从运行过的规则列表中向后退,把它们全部组合到一个列表中。比如,在第 5 步失败后,Pyke 退回到第 4 步,试图寻找到达下一步的其他办法。
如果找到了其他办法,Pyke 开始下一步的求证;否则,Pyke 回退到上一步。
Pyke 退回到第 4 步,把模式变量 $son 约束成 fred。在适用规则 brothers 时,匹配失败:
20 check $brother1 != $brother2
Pyke 只好再次退回第 4 步。下一个办法,是把模式变量 $son 约束成 bruce。规则 brothers 这次适用成功了,运行到调用规则 father_son,返回了 fred 的侄子 david。
再次回溯,但没有其他办法前行。
“回溯”的小结
我们看到了:
- 回溯算法:“成进败退”。回溯不仅出现在单一规则的 when 子句里,而且存在于求证目标的整个过程之中。
- This execution model is not available within traditional programming languages like Python. 这种程序运行方式,并不适用于像 Python 这样传统的编程语言。
- 在运算过程中,随时后退寻找其他办法的能力,是反向推理系统功能强大的原因。
运行示例
>>> from pyke import knowledge_engine >>> engine = knowledge_engine.engine(__file__) >>> engine.activate('bc_related')
因为不是正向推理规则,即便激活了规则库,却没有规则运行。
我们要询问:“谁是 Fred 的侄子?”。问题译成 Pyke 的事实陈述:
bc_related.uncle_nephew(fred, $v1, $v2)。
我们用的是规则库的名字 bc_related ,不是事实库的名字 family2。这是因为,预期的答案来自 bc_related。
规则库是'bc_related',事实陈述是'uncle_nephew',参数是'fred'和 2 个模式变量:
>>> from __future__ import with_statement >>> with engine.prove_goal('bc_related.uncle_nephew(fred, $nephew, $distance)') as gen: ... for vars, no_plan in gen: ... print vars['nephew'], vars['distance'] david ()