正向推理
规则库激活后,正向推理规则自动启用。
规则库激活后,正向规则执行的顺序,以其在.krb规则库文件中的次序为准。
正向推理的基本情况
为了进行正向推理,Pyke 查看哪个规则的 if 子句,与已知事实相匹配( if 子句可以多次匹配成功,参见“回溯”)。 规则匹配成功后,开始启用它,将其 than 子句中的事实,加入已知事实的列表。
新的事实与其他正向规则 if 子句匹配后,可以将其启用。各种深度的推理过程,都可发生这种匹配。Pyke 把第一个规则的 then 子句,与下一个规则 if 子句,建立链接。
注 意
正向推理的过程,持续到没有规则可供使用。
复习
- Pyke 正向推理,从第一个规则的 if 子句开始,查看它是否与已知事实匹配。
- 如果匹配,就进入 then 子句,启用这个规则。
- 启用的规则可能与其他规则的 if 子句相关联。
Pyke 此时的推理过程,是从规则的 if 子句进到 then 子句,再从下一规则的 if 子句进到 then 子句。这种方式叫做正向推理。
用 foreach 和 asssert 代替 if 和 then
规则的 if 子句中有事实陈述的模式,它们可能与几个事实匹配,于是,规则可能多次匹配启用。
规则 then 子句的事实陈述也有模式。每当规则启用,then 子句中的模式变量,可以约束成不同值,从而断言出不同事实。
为了避免概念冲突,Pyke 在正向推理过程中使用 foreach 和 assert ,取代 if 和 then。在 if 子句的事实陈述第一列表,逐个匹配整合事实陈述。在规则启用后,断言 then 子句事实陈述第二列表中的事实。
注 意
使用 foreach 和 assert 标志着这是个正向推理规则。
示 例
本例以一组父子关系的事实,揭示某人的父系祖先。为了举例的简明,省略了母亲、女儿的关系。这些事实保存在事实库 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'))