首先介绍一下RBS(Rule Based System)。从字面上很容易看出,这是指基于规则的系统,和神经元网络不同,这是人工智能的另一个分支。该系统用来解决问题的方法是基于如下形式的一个过程:
其中当前状态作为输入,兰色区域为人工智能处理的过程,输出为事件,同时事件本身又会影响着状态的变化。这篇文章讲着重讲解关于匹配和结果过滤的算法理论和具体的C/C++实现。
要做匹配,首先就应该确定匹配的格式,那么该如何确定呢,我们下来看一下下面这条逻辑语句:如果 A 并且 B,或者 C 则 D,那么这样一条语句在计算机里是如何体现呢。是:
if (A and B) or C then D,看到这样的结构,是否让我们想起了来描述算术表达式的Expression Tree呢。恩,那么我们来试着用树型结构来保存这样的逻辑:
其中橘黄色的表示匹配的元素,紫红色的表示任意两个元素之间的逻辑关系,最后绿色的表示这条逻辑的结果。似乎看起来没什么问题,而且是很规则的二叉树,当逻辑越来越多的时候,也就是数棵相互之间没有任何关系的森林了。那么实际上呢,实际上很多逻辑的元素子并没有改变,也就是说,同样是ABC三条关系,只是其中的逻辑关系发生变化了,或者有些情况连逻辑关系也没变,只是结果D不同,那么对于这样的情况,我们用什么样的方法将这个森林连成一片呢?
这里将介绍到Rete算法,90%的大型商业RBS系统都是使用的基于Rete算法的模型。那么我们来看看Rete算法究竟是如何定义的。
Rete算法的初衷是在第一次对森林进行搜索之后,将搜索到的结果组织成一棵树,之后每次搜索森林之前先搜索这棵树,看能否找到感兴趣的资料,其宗旨是起到一个缓存的作用。但是呢,实际上我们可以在我们游戏开始的时候,就将整个森林编译成一棵树放在内存中,之后的所有匹配都可以从编译好的树中去搜索。
下面来通过一张图来讲一下Rete算法的原理:
左边的图是之前提到过的一条逻辑:
if (A and B) or C then D
而右边的图是稍微修改过的另一条逻辑:
if (A and B) and E then F
那么我们如何对这两棵树进行编译来形成一棵树呢:
这个时候假如输入是A and B的话,那么精确匹配的结果是D一个结果,而模糊匹配的结果则是所有和带黑色粗边框的那个AND逻辑运算子所有相关联的结果,包括D和F。
接下来看看具体的结构如何在实际的编程语言中实现。之所以我用很分明的颜色将匹配元素,匹配运算符号和匹配结果分开,就是明确的区分这三者之间的关系。从图中不难看出,以上三者的一些特点:
1. 匹配元素没有连入节点
2. 一个匹配元素可能连接到多个匹配运算子或者运算结果上
3. 一个运算子做多只有两个节点连入,接入的节点可能是匹配元素也可能是运算子
4. 一个运算子可以连接到多个运算节点或者运算结果上
5. 一个运算结果可以有多个来自运算子或者运算元素的节点的接入
6. 运算结果没有接出节点
并且由于运算子的接入节点是有限的,所以要求在运算子上保存接入节点的信息。那么在实际搜索匹配的时候,输入是一条逻辑信息,然后在Rete生成树中找到逻辑中的第一个逻辑元素,然后搜索该元素的接出节点中是否有匹配的运算子,如果没有的话,继续搜索到所有跟这个匹配元素相关的所有结果,算为模糊匹配,并将模糊指数设为1(之后的结果过滤阶段会讨论到相关的用处)
那么在进行一次相关的搜索之后,一般会得到多余一个结果,但是最终的决策只能从中选一个,那么如何对结果进行过滤呢。虽然从理论上讲,要根据不同的情况来设置不同的过滤机制,这里就先讲一些比较General一些的过滤算法。
1. 优先级设置法,这里对于开始的模糊指数的设置就有效果了,但是只能过滤掉模糊匹配的,对于精确匹配的仍然需要继续采取其他的过滤措施进行过滤。
2. 最近时间访问法,搜索最近一次访问过的结果,这种方法是针对原始的Rete算法搜索出的结果的,不适合在我们所说的在程序一开始就生成好树的做法。
3. 最小匹配法,某个输入的输出结果最少,表示结果越specific,就在那些结果中继续搜索。
4. 最初匹配法,在搜索树的时候,在精确匹配之后,按照广度优先搜索,返回最先搜索到的结果。
5. 分组法,根据不同的类型将RBS分成不止一个组,从而可以编译成多棵树形成一个森林,然后搜索的时候只在固定的组内进行搜索。
6. 不重复法,将最近访问过的结果排除,然后在剩下的结果中继续搜索。
7. 历史数据排序法,将所有的历史访问信息排序,返回访问数最多的结果。
8. 随机法,听名字就该知道了吧。=)
然后安排上自己在设计规则数据库的时候加入的一些判断优先的元素,可以构造出符合自己要求的结果过滤机制。
下面来看看RBS在某些FPS游戏中的应用:(摘自某些FPS游戏)
Action Selection
IF enemy AND health_low THEN retreat
IF enemy AND NOT distance_close THEN pursue
IF enemy THEN dodge
IF true THEN NOT retreat AND NOT pursue AND NOT dodge
Movement
IF collision AND enemy THEN move_avoid
IF retreat THEN move_flee
IF pursue THEN move_seek
IF dodge THEN move_side
IF true THEN move_forward
Weapon Selection
IF enemy AND distance_far THEN use_rocketlauncher AND use_railgun
IF enemy AND NOT distance_far THEN use_chaingun AND use_hyperblaster
View Control
IF enemy THEN look_enemy AND fire
IF obstacle_front AND obstacle_left AND obstacle_right THEN look_behind
IF obstacle_front AND obstacle_left THEN look_right
IF obstacle_front AND obstacle_right THEN look_left
IF NOT health_full AND health_item THEN look_health
IF NOT armor_full AND armor_item THEN look_armor
IF true THEN look_around