在本章以及下面几章,我将详细介绍一下使用prolog设计专家系统的技术。首先让我们来看看什么是专家系统,以及它的基本设计方法和技术,然后再使用prolog设计几个微型专家系统。 什么是专家系统 专家系统是人工智能最重要的应用之一,它的目的是让电脑在某种程度上帮助或者替代某个领域的专家解决问题。例如医疗诊断系统、投资风险分析系统、家居设计系统等等。 一个典型的专家系统的构成方式如下图所示:
Domain Expert就是某个领域的专家,他提供原始的知识。Knowledge Engineer是把专家的知识翻译成电脑所能够识别的知识的工程师。某领域的专家把他所知道的知识告诉knowlegde engineer以后,由knowlegde engineer对这些知识进行处理,最后做成知识库knowledge base。System Engineer是设计专家系统的程序员,他的主要任务是编写专家系统的推理机构inferface engine,和用户界面user interface。用户使用用户界面和专家系统打交道,他和专家系统之间的交流的一些信息由工作空间working storage储存。推理机构根据用户信息和知识库中的信息为用户提供服务。 在设计专家系统时候有目标驱动和数据驱动两种方式。下面我们通过实例来说明一下如何使用prolog编写目标驱动的专家系统。 一个可以识别鸟类的专家系统 这个系统的目的是通过用户对某种鸟类的描述,推断出用户描述的是何种鸟。由于prolog的规则就是一种非常好的表达知识的方法,而其内建的回溯功能和模式匹配功能则是很好的推理机构,所以使用prolog来编写这样的专家系统是再容易不过的了。 首先让我们来看看如何是使用prolog的规则来表达知识吧。我们可以从鸟类专家那里得到如下的知识: 如果某种鸟是属于信天翁科,并且其颜色是白色的,那么这种叫就是laysan信天翁。(我是翻译的外文教材,所以这里关于鸟类的知识翻译的并不是太准确) 当然我们要用英语来表这个规则: IF 如果使用prolog的规则来表达就是: bird(laysan_albatross) :- 同样我们还可以加入下面的规则: bird(laysan_albatross):- bird(black_footed_albatross):- bird(whistling_swan) :- bird(trumpeter_swan) :- 为了能够让这些规则能够分辨不同的鸟类,我们必须储存关于某种鸟的特定的信息。例如,如果我们加入下面两个事实的话: family(albatross). 然后在解释器中进行如下的询问: - bird(X). 很自然的我们就得到了答案。 现在我们看到了一个再简单不过的专家系统了。他具备了前面所说的专家系统的几个构造部分。 四条关于识别鸟的规则就是知识库knowledge base。 两条关于某种鸟的特性的事实就是工作空间working storage中存储的信息。 prolog的内建的模式匹配和回溯功能就是推理机构。 prolog的解释器就是用户界面。 当然,上面的这四个部分都还只是雏形,下面我们就要分解介绍如何慢慢的添加功能。 增加层次关系 仅仅使用上面的四条知识构成专家系统的知识结构是远远不够的。下面我们就来添加一些新的知识。动物界为动物分了科、目、属、种、类等几个层次,这些层次构成一个树状结构,下面的几条规则就描述了其中的一些层次结构。 order(tubenose) :- order(waterfowl) :- family(albatross) :- family(swan) :-
其中的第二个规则:order的中文意思是动物学的目,waterfowl的意思是水鸟,所以第二个规则描述的就是,水鸟这个目的动物具有“脚有蹼”“bill扁平”这两个特点。 再解释一下最后一个规则,他的意思是天鹅这个科的的特点是:“属于水鸟目”“颈部很长”“颜色是白色”“飞行起来比较沉重”。 注意前面我们bird的规则中使用了family,而在此对family的规则进行了定义,定义family的过程中,又使用了order。作为有强大回溯功能的prolog可以很容易的把这些规则串起来,而不需要我们做更多的工作。下面我们来看一个例子。 假如我们在系统中添加了如下的描述鸟的事实: nostrils(external_tubular). 那么我们就可以用来识别具有这些特征的是什么鸟了。 - bird(X). 更加复杂的规则 要识别加拿大鹅就需要比较复杂的规则。因为加拿大鹅夏天在加拿大度过,而冬天在美国度过。我们在识别的时候需要知道是在什么地方,什么时候看到这种鸟的。在此我们仅需要加入两个规则来描述它就行了。 bird(canada_goose):- bird(canada_goose):- 我们再加入一些关于地方的谓词: country(united_states):- country(united_states):- country(united_states):- country(united_states):- country(canada):- province(ontario). country(canada):- province(quebec). region(new_england):- region(south_east):- state(X), member(X, [florida, mississippi, ....]). 还有许多需要多个规则的鸟类。例如雄野鸭的头是绿色的,而雌野鸭是杂褐色的。 bird(mallard):- family(duck), voice(quack), head(green). bird(mallard):- family(duck), voice(quack), color(mottled_brown). 好了,作为一个例子,我们的关于鸟类的知识够多的了。只要你手上有一本这方面的专业书籍,你应该很容易的把书上的知识翻译成prolog的规则吧。 用户界面 前面我们一直是使用prolog的事实来储存有关用户掌握的信息,作为一个真正的专家系统,系统应该主动的向用户提问以收集信息,而不是让用户自己把所有的信息手工输入。 所以这里当我们遇到需要向用户收集信息的时候,就应该向用户提出问题。 前面的有关鸟的属性都是需要从用户那里获得的信息。所以我们就把他们改写成下面这种规则: eats(X):- ask(eats, X). ask(Attr, Val):- write(Attr:Val), write(‘? ‘), read(yes). 有了上面的这些规则,当系统需要寻找color(white)这个目标的时候,他就会调用ask规则,如果ask(color,white)目标成立的话,那么color(white)就成功了。 ask/2中的最后一个目标read(yes),只有当用户输入yes的时候才成功,而用户输入别的东西都会是失败。 现在我们不需要事先添加用户信息就可以运行了,系统会在需要的时候对用户进行询问。下面是一次对话的例子: - bird(X). 这里还有一个问题,如果用户最后一个问题回答no的话,那么bird(laysan_albatross)规则就会失败,然后回溯,将会测试下一个规则bird(black_footed_albatross),它的第一个子目标又会引起系统询问一些以前曾经询问过的问题。这的确是个比较笨的系统,下面我们将介绍如何保存询问的信息,以免重复询问。 记住答案 我们使用新的谓词known/3来记住用户对问题的回答。这个谓词的子句不是直接写在程序中的,而是通过assert动态的加入到系统中的,这也就是专家系统的工作空间working storage。 每次调用ask谓词要做的第一件事情,就是检查known谓词是否已经保存了这个问题的信息。如果还没有保存过这样的问题的信息,就会在用户回答以后,把回答的答案加入到系统中。known/3的三个参数分别是yes/no,属性,属性值。 新版本的ask谓词如下: ask(A, V):- ask(A, V):- ask(A, V):- 具有多值的回答 我们还可以对known谓词进行改进。到目前为止,ask谓词所询问的问题都是是非问题。这意味着用户可能会对color-white和color-black这样的问题同时回答yes,这显然是不合理的,当系统知道color-white是真的时候,就应该同时知道color-black是假。这就是说color属性只能是单值的,不过对于voice这样的属性就可以多值的。所以我们在专家系统中应该对这些属性进行说明。 我们使用最简单的方法来解决这个问题---加一个用来描述属性的谓词。 multivalued(voice). 这两个句子说明voice和feed属性可以是多值的。 同时我们需要对ask谓词进行修改: ask(A, V):- 我们添加上面一条ask子句,它应该放在其他ask子句之前。这个子句首先检查属性A是否为多值属性,如果不是,就在已知的属性值中查找是否已经有其他的值, 如果有的话,就不需要询问用户,而直接失败了。 例如,如果用户已经对size-large这个问题回答了yes,系统就不会询问size-small这样的问题了。 用户菜单 我们可以进一步的改进用户的界面,例如可以添加让用户选择答案的菜单功能。 我们使用menuask谓词来完成这个功能。它和ask很相像,不过多了一个用来保存所有可能的属性值的列表参数。把原来的ask谓词出现的地方用menuask来替代: size(X):- menuask(size, X, [large, plump, medium, small]). menuask的具体程序如下: menuask(A, V, MenuList) :- check_val(X, A, V, MenuList) :- %如果用户输入的值在列表中可以找到。 check_val(X, A, V, MenuList) :- %如果找不到。 这是一个最最简单的menuask的例子,用户需要输入属性值的内容,而不能通过输入1、2、3或者是a、b、c来选择属性值。 一个简单的外壳程序 很明显这个识别鸟类的专家系统由两个完全不同的部分组成,一个是知识库,另一个是控制用户界面的。 我们应该尽量把这两部分分离开来,这样我们就可以制作出一个外壳程序,使用这个外壳程序和不同的知识库联接,就可以快速的开发不同的专家系统了。 为了能够把这两部分分开,就要定义统一的接口。由于不同的专家系统要完成的目标不一样,例如识别鸟的系统的目标是bird(X),而识别鱼的系统则是fish(X),因此在每个知识库中都定一个相同的最顶层目标top_goal(X)。对于识别鸟的系统这个目标为: top_goal(X):-bird(X). 在外壳程序中我们可以定义solve谓词做一些初始化工作,以及调用知识库的顶层目标top_goal: solve :- solve :- 内部谓词abolish是用来删除系统中所有的known子句的。这样用户就可以使用solve来解决不同的问题,而不至于把两次对话的内容搞混淆。 由于一开始系统中没有known子句,所以需要使用define来定义一下,这样系统再找不到known子句的时候就不会报告错误了。不同的prolog系统的这种内部谓词不一定相同。 现在总结一下: solve ask menuask谓词都是外壳程序中的谓词,也就是说它们对于所有的类似的专家系统都是相同的。 top_goal bird order family region size color eats wings multivalue这样的谓词都是属于知识库的谓词,它们是和具体的知识挂钩的。top_goal是外壳程序和知识库之间的桥梁。 如果我们把这些谓词分别保存在不同的文件中,例如外壳程序保存在文件native中,而鸟类的知识库保存在birds.kb中的话,我们只要在解释器中进行如下的对话就可以使用专家系统了。 - consult(native). - consult(‘birds.kb‘). - solve. 命令循环 我们还可以增强外壳程序的功能,增加一个叫做go的谓词,go可以识别三种命令:load consult和quit。 load命令用来调入某个知识库,consult命令用来调用知识库的top_goal从而开始对话,quit用来退出外壳程序。 下面是go谓词的具体程序: go :- greeting :- do(load) :- load_kb, !. do(consult) :- solve, !. do(quit). do(X) :- go谓词使用repeat循环不停的从用户取得命令。do谓词用来识别用户的命令。这里的load_kb谓词的具体程序如下: load_kb :- 我们还可以增加一些其他的外壳命令,例如:help,用来显示所有的命令的使用方法,list用来列出所有的known子句以供调试之用。 最后我们的专家系统和用户之间的对话如下: - consult(native). - go. 通过使用外壳程序,我们可以屏蔽一些prolog编程的难点。让开发人员在编写专家系统的时候只需要把注意力集中在知识库的表达上面,而使用prolog表达知识库是很容易的,就和我们平时说话没有什么两样。 到这里,你应该对专家系统的编写有个初步的了解了吧,下一章我们将研究一些比较深的专家系统的知识了。 (由于本章介绍的专家系统还只是雏形,所以没有提供完整的程序,你可以自己把上面的程序片断拼凑起来,在解释器中测试一下。) |
|