现在的位置: 首页 > 综合 > 正文

Pyke 逻辑编程入门(11):规则之“反向推理”

2013年10月03日 ⁄ 综合 ⁄ 共 4077字 ⁄ 字号 评论关闭

反向推理

反向推理规则的启用,发生在你的程序对 Pyke 进行询问时。例如,要求 Pyke 证明某一具体目标。Pyke 的证明活动,只用到激活的规则库。

“反向推理”的概况

为了做反向推理,Pyke 要找出这样的规则:其 then 子句部分,与目标(即程序向Pyke询问的问题)能够匹配。找到匹配的规则后,Pyke 试图通过递归,证明该规则 if 子句的全部子目标是真实的。其中一些子目标是寻求匹配的事实,另一些则是其他的反向推理规则。如果这些子目标全部证明为真,这一规则适用成功,最初的终极目标证明为真。否则,这一规则适用失败,Pyke 尝试寻找其他规则,即其 then 子句与目标匹配的规则。这一过程,会反复进行。

Pyke 最终会形成一条“证据链”,从第一条规则的 if 子句开始,到下一规则的 then 子句结束。

复习

  • Pyke 的证明活动,开始于寻找某一规则,其 then 子句与目标匹配。
  • Pyke 接着证明规则 if 子句为真。
  • 亦即:规则的 if 子句,与另一规则的 then 子句,可以建立连接。

Pyke 的证明活动方式,是从规则的 then 子句开始,到 if 子句结束,与我们通常运用的推理方式相反,因此,叫做“反向推理”。

更具体地说,Pyke 要求书写你的反向推理规则时,首先写出 then 子句,因为它在运行时先受处理。

usewhen 代替 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 ()

抱歉!评论已关闭.