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

正向推理

规则库激活后,正向推理规则自动启用。

规则库激活后,正向规则执行的顺序,以其在.krb规则库文件中的次序为准。

正向推理的基本情况

为了进行正向推理,Pyke 查看哪个规则的 if 子句,与已知事实相匹配( if 子句可以多次匹配成功,参见“回溯”)。 规则匹配成功后,开始启用它,将其 than 子句中的事实,加入已知事实的列表。

新的事实与其他正向规则 if 子句匹配后,可以将其启用。各种深度的推理过程,都可发生这种匹配。Pyke 把第一个规则的 then 子句,与下一个规则 if 子句,建立链接。

注 意

正向推理的过程,持续到没有规则可供使用。

复习

  • Pyke 正向推理,从第一个规则的 if 子句开始,查看它是否与已知事实匹配。
  • 如果匹配,就进入 then 子句,启用这个规则。
  • 启用的规则可能与其他规则的 if 子句相关联。

Pyke 此时的推理过程,是从规则的 if 子句进到 then 子句,再从下一规则的 if 子句进到 then 子句。这种方式叫做正向推理。

foreachasssert 代替 ifthen

规则的 if 子句中有事实陈述的模式,它们可能与几个事实匹配,于是,规则可能多次匹配启用。

规则 then 子句的事实陈述也有模式。每当规则启用,then 子句中的模式变量,可以约束成不同值,从而断言出不同事实。

为了避免概念冲突,Pyke 在正向推理过程中使用 foreachassert ,取代 if 和 then。在 if 子句的事实陈述第一列表,逐个匹配整合事实陈述。在规则启用后,断言 then 子句事实陈述第二列表中的事实。

注 意

使用 foreachassert 标志着这是个正向推理规则。

示 例

本例以一组父子关系的事实,揭示某人的父系祖先。为了举例的简明,省略了母亲、女儿的关系。这些事实保存在事实库 family 的知识项 son_of(son, father)。

1  son_of(michael, bruce)
2  son_of(bruce, thomas)
3  son_of(thomas, frederik)
4  son_of(frederik, hiram)

以下列方式传承父子关系:

father_son($father, $son, $prefix)

即:

$son:儿子的名字 (例如 michael)
$father:父亲的名字 (例如 bruce)
$prefix:在“father”和“son”前面的标识,表示是第几代的父子关系 (例如 () 直接的父子关系 father_son,(grand), (祖父 great, 曾祖父 grand), 等等)。

这里用了三条正向推理规则,每条都是单独的运用:

  • 第一步: 直接的父子关系
    • 第一步:展示的是,通过运用模式匹配,把值从规则中的一个事实传递到另一个中。
  • 第二步: 祖父辈的父子关系
    • 第二步:展示的是,在正向推理规则的多个前提条件之间回溯。弄懂它有助于理解回溯。
  • 第三步: 曾祖父辈的父子关系
    • 第二步:展示的是,正向推理规则的递归。

下面,看看怎样运行示例。

第一步: 规则 direct_father_son

首先,需要建立直接的父子关系,即事实 father_son 中的模式变量 $prefix 的值是空元组 ():

1  direct_father_son
2      foreach
3          family.son_of($son, $father)
4      assert
5          family.father_son($father, $son, ())

模式变量的用法

它演示了模式变量,怎样把规则中事实的值,传递到另一个事实。

规则的第2行,是个 foreach 子句,其中只有第3行单独的事实陈述。 这个事实陈述,匹配全部4个 son_of 事实,因此,规则可匹配成功4次,但其中的模式变量 $son 和 $father,约束的值不同。 由此,第5行的 assert 子句运行时,有4次引起断言不同的事实。每次事实的断言,模式变量 $son 和 $father 的值,从第3行传递到第5行。

规则启用,第3行匹配成了:

1  son_of(michael, bruce)

运行到第5行,做出断言:

5  father_son(bruce, michael, ())

规则适用,第3行第二次匹配成:

2  son_of(bruce, thomas)

它又运行到第5行,第二次断言:

6  father_son(thomas, bruce, ())

规则接着对其余二个 son_of 事实,再适用两次,再两次断言 father_son 的关系。于是,这一规则产生了四个新事实:

5  father_son(bruce, michael, ())
6  father_son(thomas, bruce, ())
7  father_son(frederik, thomas, ())
8  father_son(hiram, frederik, ())

第二步: 规则 grand_father_son

现在我们要加上祖父辈的父子关系。增加新规则:

 6  grand_father_son
 7      foreach
 8          family.father_son($father, $grand_son, ())
 9          family.father_son($grand_father, $father, ())
10      assert
11          family.father_son($grand_father, $grand_son, (grand))

回溯的用法

规则 grand_father_son,根据 foreach 子句里事实 father_son 的组合情况(第8第9行),适用运行。同时,根据各次组合的情况,断言一个 father_son 事实(第11行,注意值 (grand) )。

这一规则可以较好地说明回溯,帮助你理解。下面,让我们看看这个规则的回溯运行。

foreach 子句里有两个事实(第8行第9行),期待与第三参数为空元组 () 的事实 father_son 相匹配。

8  family1.father_son($father, $grand_son, ())
9  family1.father_son($grand_father, $father, ())

与之匹配的 family1 事实如下(第5至第8):

5  father_son(bruce, david, ())
6  father_son(thomas, bruce, ())
7  father_son(frederik, thomas, ())
8  father_son(hiram, frederik, ())

Pyke 从规则 family1 前提条件列表的顶部,开始为第一个前提(第8行)寻找匹配。匹配合一的是事实5,$father 约束成 bruce。

8  family.father_son($father, $grand_son, ())    => fact 5, SUCCESS
9  family.father_son($grand_father, $father, ())

匹配成功后,Pyke 进到第9行的下个前提。因为 $father 已约束为 bruce,这次成功地与事实6匹配合一。

8  family.father_son($father, $grand_son, ())    => fact 5
9  family.father_son($grand_father, $father, ()) => fact 6, SUCCESS

虽又匹配成功,可 Pyke 已走到前提列表的尽头,于是开始启用规则的断言:

9  father_son(thomas, michael, (grand))

这是个前向推理规则,Pyke 要尽量获得问题的全部答复,所以,它继续运行,仿佛因遇到失败(意思是说,它不喜欢当前的答复)。

注 意

稍后会看到,Pyke 不是以反向推理规则自动运行的。

Pyke 因失败退回到第二个前提条件,寻找以 bruce 为第一参数的其他 father_son 事实。

8  family1.father_son($father, $grand_son, ())    => fact 5
9  family1.father_son($grand_father, $father, ()) => FAILS

失败导致后退,Pyke 退到第一个前提,寻找事实5之外的其他 father_son 事实。结果与事实6匹配,$father 约束成 thomas:

8  family1.father_son($father, $grand_son, ())    => fact 6, SUCCESS
9  family1.father_son($grand_father, $father, ())

成功导致前进,Pyke 进到第二个前提,与事实7匹配:

8  family1.father_son($father, $grand_son, ())    => fact 6
9  family1.father_son($grand_father, $father, ()) => fact 7, SUCCESS

虽又匹配成功,可 Pyke 已走到前提列表的尽头,于是开始启用规则的断言:

10 father_son(frederik, bruce, (grand))

接着,Pyke 败退到第二前提,继续寻找事实7之后的可匹配事实。这失败了。

8  family1.father_son($father, $grand_son, ())    => fact 6
9  family1.father_son($grand_father, $father, ()) => FAILS

失败导致后退,Pyke 退到第一个前提,寻找事实6之后的其他 father_son 事实。因为事实7是最后一个匹配成功的,所以跳过它,直接去试末尾的事实8。结果,与事实8匹配成功,把 $father 约束成 hiram:结果与事实6匹配,$father 约束成 thomas:

8  family1.father_son($father, $grand_son, ())    => fact 8, SUCCESS
9  family1.father_son($grand_father, $father, ())

成功导致前进,Pyke 进到第二个前提,寻找与 hiram 匹配的 father_son for hiram 事实。结果,失败了:

8  family1.father_son($father, $grand_son, ())    => fact 8
9  family1.father_son($grand_father, $father, ()) => FAILS

失败导致后退,Pyke 退到第一个前提,寻找事实8之后的其他可匹配事实。结果没有找到,失败了:

8  family1.father_son($father, $grand_son, ())    => FAILS
9  family1.father_son($grand_father, $father, ())

失败导致后退,但 Pyke 已经退到前提列表的尽头,规则适用失败,Pyke 完成了任务。

重 要

注意,foreach 子句末尾的事实,可以多次匹配成功,由此多次启用 assert 子句。

可是,
foreach 子句起始的事实,仅能匹配失败一次。若匹配失败,整个规则适用失败。

因此,适用规则 grand_father_son,结果会再产生3个事实:

9  father_son(thomas, david, (grand))
10 father_son(frederik, bruce, (grand))
11 father_son(hiram, thomas, (grand))    (this is the one we skipped)

第三步:规则 great_grand_father_son

最后,我们增加曾祖父辈的父子关系。规则是:

12  great_grand_father_son
13      foreach
14          family1.father_son($father, $gg_son, ())
15          family1.father_son($gg_father, $father, ($prefix1, *$rest_prefixes))
16      assert
17          family1.father_son($gg_father, $gg_son,
                                (great, $prefix1, *$rest_prefixes))

注 意

注意,第15行事实中的模式变量 $prefixes,如何指定成 ($prefix1, *$rest_prefixes)的形式。结果是,它与空元组 () 不能匹配。 不过,如果 $rest_prefixes 约束成空元组 (),它还可以匹配 (grand)。

这种规则是唯一可以递归适用的。它断言出新事实后,新事实可为相同(与第15行事实匹配的)规则使用,以产生上溯祖祖辈辈的父子关系。

递归的规则

适用这一规则,通常会断言以下事实:

12 father_son(frederik, david, (great, grand))
13 father_son(hiram, bruce, (great, grand))

可是,因为这些事实也可为第15行的相同规则使用,所以,Pyke 会再次运行规则核查新的事实。

尝试匹配第一个新事实: father_son(frederik, david, (great, grand)),但失败了,因为 david 不是父亲。

尝试匹配第二个新事实:father_son(hiram, bruce, (great, grand)) ,结果得到一个新事实:

14 father_son(hiram, david, (great, great, grand))

再次用此规则试配最后的新事实,但因 david 不是父亲而失败。

Pyke 此时完成了这一规则的适用。它启用了三次,断言的事实如下:

12 father_son(frederik, david, (great, grand))
13 father_son(hiram, bruce, (great, grand))
14 father_son(hiram, david, (great, great, grand))

运行示例

这些规则可以如下运行:

>>> from pyke import knowledge_engine
>>> engine = knowledge_engine.engine(__file__)
>>> engine.activate('fc_related')     # This is where the rules are run!
>>> engine.get_kb('family1').dump_specific_facts()
father_son('bruce', 'david', ())
father_son('thomas', 'bruce', ())
father_son('frederik', 'thomas', ())
father_son('hiram', 'frederik', ())
father_son('thomas', 'david', ('grand',))
father_son('frederik', 'bruce', ('grand',))
father_son('hiram', 'thomas', ('grand',))
father_son('frederik', 'david', ('great', 'grand'))
father_son('hiram', 'bruce', ('great', 'grand'))
father_son('hiram', 'david', ('great', 'great', 'grand'))

    原文作者:lawme
    原文地址: https://blog.csdn.net/lawme/article/details/5378278
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞