人工智能(蔡自兴)-实验2事实的表示
实验2 Prolog程序事实表示
Visual Prolog是面向对象的、严格类型化的和模式检验的程序设计语言。在编写Visual Prolog程序时,必须掌握这些内容,但是在这里将集中在编写代码这个核心问题上,也就是说,编写这些代码时暂时不考虑类、类型和模式。
为此,将使用包含在Visual Prolog 6 中的PIE例子。PIE是一个经典的Prolog解释器,通过它,可以学会和实现Prolog程序,而不必关心类、类型等方面的知识。
这里的内容是基于使用Build6004或者是以后的Visual Prolog 6版本,否则,PIE应用程序将不会象现在描述的这样工作。这个编译号可以在VDE的About对话框中找到。
1. Horn子句逻辑
Visual Prolog 和其它Prolog用语都是基于Horn子句逻辑的。Horn子句逻辑是对事物及其相互关系进行推理的形式系统。
在自然语言中,可以有这样样的一个陈述句:
John是Bill的父亲。
这里涉及两个实体,John和Bill,以及他们之间的关系,即一个是另
一个的父亲.在Horn子句逻辑中,可以这样形式化地表述上面的陈述
句:
father(“Bill”,”John”).
上面的father是带两个参量的一个谓词或关系,它表示第2个人是
第1个人的父亲。
注意:此处已经选择了第2个人是第1个人的父亲,也可以选择另外
的方式,变量的顺序形式化设计者的选择,然而一旦选定了,就必须
保持一致,在这里的表述中,父亲始终是第2个人。
已经选择用人名来代表人,因为在现实世界中,许多人有相同的名字,
所以这一方法不一定有效。但在这里,用这一简单的形式化表示。
有了上面的形式化方法,可以表示任何人之间的任何类型的家庭关
系,但是,为了让这些表述更为有趣,制定下面的规则。
X是Z的祖父,如果X是Y的父亲且Y是Z的父亲
其中X,Y,Z指人。在Horn子句逻辑中,可以这样表述 grandFathe(Person,GrandFathe):-
father(Person,Father),father(Father,GrandFather).
已经选择使用了比X,Y,Z更容易理解的变量名。另外,还引入了一
个谓词来描述祖父关系。再次选择了祖父作为第二个变量,像这样的
保持一致是明智的,不同谓词的变量可以遵循相同的规则,当解读这
些规则时,可以将:-解释为“如果(if)”,将隔开关系的逗号解释
为“与(and)”.
像“John是Bill的父亲”这样的陈述称为事实,而“X是Z的祖父,
如果X是Y的父亲且Y是Z的父亲“称为规则。
可以用事实和规则来形成定理,一个定理是事实和规则的集合,下面
陈述一个小定理:
father(“Bill”,”John”). fathaer(“Pam””Bill”). grandFather(Person,GrandFather):-
father(Person,Father), father(Father,GrandFather). 这个定理的作用是回答这样的一些问题:
John是Sue的父亲吗? 谁是Pam的父亲?
John是Pam的祖父吗?
^„„ 这些问题称为目标(goal),它们可以这样形式化表示:
?- father(“Sue”,”John”).
?-father(“Pam”,X). ?- grandfather(“Pam”,”John”). 这些问题被称为目标子句(goal clause)或简称为目标。事实(facts),规则(rules)及目标合起来称为Horn子句,因此得名为Horn子句逻辑。
某些目标,如第一个和最后一个目标,可以简单地用“是”或“不是”来回答,其他目标,如第2个目标,需要寻找一个解,例如,X=“Bill”. 一个目标可以有多个解,例如:
?- father(X,Y).
E有两个解:
X=“Bill”,Y=”John”.
X=“Pam”,Y=”Bill”.
一个Prolog程序是一个定理和目标的集合。当程序开始时,它试图使用定理为目标找到一个解。
探索Prolog
Prolog在英语中的意思就是Programming in LOGic(逻辑编程)。它是建立在逻辑学的理论基础之上的, 最初是运用于自然语言的研究领域。然而现在它被广泛的应用在人工智能的研究中,它可以用来建造专家系统、自然语言理解、智能知识库等。同时它对一些通常的应用程序的编写也很有帮助。使用它能够比其他的语言更快速地开发程序,因为它的编程方法更象是使用逻辑的语言来描述程序。 从纯理论的角度来讲,Prolog是一种令人陶醉的编程语言,但是在这本书中还是着重介绍他的实际使用方法。
逻辑编程
什么叫逻辑编程?也许你还没有一个整体的印象,还是让我们首先来研究一个简单的例子吧。运用经典的逻辑理论,我们可以说“所有的人(person)都属于人类(moral)”,如果用Prolog的语言来说就是“对于所有的X,只要X是一个人,它就属于人类。”
moral(X):-person(X) . 同样,我们还可以加入一些简单的事实,比如:苏格拉底(socrates)是一个人。
person(socrates).
有了这两条逻辑声明,Prolog就可以判断苏格拉底是不是属于人类。在Prolog的Listener中键入如下的命令:
?-mortal(socrates). (此句中的'?-'是Listener的提示符,本句表示询问苏格拉底是不是属于人类。)
Linstener将给出答案:
yes
我们还可以询问,“谁属于人类?”
?-mortal(X).
我们会得到如下的答案:
X= socrates
这个简单的例子显示了Prolog的一些强大的功能。它能让程序代码更简洁、更容易编写。在多数情况下Prolog的程序员不需要关心程序的运行流程,这些都由Prolog自动地完成了。
当然,一个完整的程序不能只包括逻辑运算部分,还必须拥有输入输出,乃至用户界面部分。很遗憾,Prolog在这些方面做得不好,或者说很差。不过它还是提供了一些基本的方法的。下面是上述的程序一个完整的例子。
把这个程序调入Listener中,运行
?- mortal_report.
Known mortals are:
socrates
plato mortal_report.。
aristotle
no
以上程序中的一些函数以后还会详细的介绍的。最后的那个no表示没有其他的人了。
进入下一章
从下一章起,就开始正式介绍Prolog的编程方法了。我将用一个实例来介绍Prolog,这是一个文字的冒险游戏,你所扮演的角色是一个三岁的小女孩,你想睡觉了,可是没有毛毯(nani)你就不能安心的睡觉。所以你必须在那个大房子中找到你的毛毯,这就是你的任务。这个游戏能够显示出一些Prolog的独到之处,不过Prolog的功能远不止编个简单的游戏,所以文中还将介绍一些其他的小程序。
事实 (facts) 注:斜粗体字表示Prolog的专有名词
事实(facts)是prolog中最简单的谓词(predicate)。它和关系数据库中的记录十分相似。在下一章中我们会把事实作为数据库来搜索。
事实的语法结构如下:
pred(arg1, arg2, ... argN).
其中pred为谓词的名称。arg1,...为参数,共有N个。‘.’是所有的Prolog子句的结束符。没有参数的谓词形式如下:
pred.
参数可以是以下四种之一:
整数(integer)
绝对值小于某一个数的正数或负数。
原子(atom)
由小写字母开头的字符串。
变量(variable)
由大写字母或下划线(_)开头。
结构(structure)
在以后的章节介绍。
不同的Prolog还增加了一些其他的数据类型,例如浮点数和字符串等。
Prolog字符集包括: 大写字母,A-Z;小写字母,a-z;数字,0-9;+-/\^,.~:.?#$等。
原子通常是字母和数字组成,开头的字符必须是小写字母。例如: hello
twoWordsTogether
x14
为了方便阅读,可以使用下划线把单词分开。例如:
a_long_atom_name
z_23
下面的是不合法的原子,
no-embedded-hyphens
123nodigitsatbeginning
_nounderscorefirst
Nocapsfirst
使用单引号扩起来的字符集都是合法的原子。例如:
'this-hyphen-is-ok'
'UpperCase'
'embedded blanks'
下面的由符号组成的也是合法的原子:
-->
++
变量和原子相似, 但是开头字符四大写字母或是下划线。例如: X
Input_List
_4th_argument
Z56
有了这些基本的知识,我们就可以开始编写事实了。事实通常用来储存程序所需的数据。例如,某次商业买卖中的顾客数据。customer/3。(/3表示customer有三个参数)
customer('John Jones', boston, good_credit).
customer('Sally Smith', chicago, good_credit).
必须使用单引号把顾客名引起来,因为它们是由大写字母开头的,并且中间有空格。
再看一个例子,视窗系统使用事实储存不同的窗口信息。在这个例子中参数有窗口名称和窗口的位置坐标。
window(main, 2, 2, 20, 72).
window(errors, 15, 40, 20, 78).
某个医疗专家系统可能有如下的疾病数据库。
disease(plague, infectious). {疾病(瘟疫,有传染性)}
Prolog的解释器提供了动态储存事实和规则的方法,并且也提供了访问它们的方法。数据库的更新是通过运行‘consult’或
‘reconsult’命令。我们也可以直接在解释器中输入谓词,但是这些谓词不会被储存到硬盘上。
寻找Nani
下面我们正式开始“寻找Nani”游戏的编写。我们从定义基本的事实开始,这些事实是本游戏的基本的数据库。它们包括: 房间和它们的联系
物体和它们的位置 物体的属性 玩家在游戏开始时的位置
图2.1 “寻找Nani”游戏的的房间格局
首先我们使用room/1谓词定义房间,一共有五条子句,它们都是事实,如图2.1。
room(kitchen).
room(office).
room(hall).
room('dining room').
room(cellar).
我们使用具有两个参数的谓词来定义物体的位置。第一个参数代表物体的名称,第二个参数表示物体的位置。开始时,我们加入如下的物体。
location(desk, office).
location(apple, kitchen).
location(flashlight, desk).
location('washing machine', cellar).
location(nani, 'washing machine').
location(broccoli, kitchen).
location(crackers, kitchen).
location(computer, office).
注意:我们定义的那些符号,例如:kitchen、desk等对于我们是有意义的,可是它们对于Prolog是没有任何意义的,完全可以使用任何符号来表示房间的名称。
谓词location/2的意思是“第一个参数所代表的物体位于第二个参数所代表的物体中”。Prolog能够区别location(sink, kitchen)和location(kitchen, sink)。因此,参数的顺序是我们定义事实时需要考虑的一个重要问题。
下面我们来表达房间的联系。使用door/2来表示两个房间有门相连,这里遇到了一个小小的困难:
door(office, hall).
我们想要表达的意思是,office和hall之间有一个门。可是由于Prolog能够区分door(office, hall)和door(hall, office), 所
以如果我们想要表达一种双向的联系,就必须把每种联系都定义一遍。
door(office, hall).
door(hall, office).
参数的顺序对定义物体的位置有帮助,可是在定义房间的联系时却带来了麻烦。我们不得不把每个房门都定义两次!
在这一章里,只定义单向的门,以后会很好地解决此问题的。 door(office, hall).
door(kitchen, office).
door(hall, 'dining room').
door(kitchen, cellar).
door('dining room', kitchen).
下面定义某些物体的属性,
edible(apple).
edible(crackers).
tastes_yucky(broccoli).
最后,定义手电筒(由于是晚上,玩家必须想找到手电筒,并打开它才能到那些关了灯的房间)的状态和玩家的初始位置。
turned_off(flashlight).
here(kitchen).
好了,到此你应该学会了如何使用Prolog的事实来表达数据了。 用事实表达完成以下描述
作业
1
作业2
比如一群年轻人正在恋爱,每个人都有自己心中所追求的对象: 张学友爱王菲
张学友爱周慧敏
王菲爱谢廷峰
周慧敏爱张学友
谢廷峰爱王菲
谢廷峰爱周慧敏
刘德华爱周慧敏
......