人工智能实验指导书.docx
《人工智能实验指导书.docx》由会员分享,可在线阅读,更多相关《人工智能实验指导书.docx(34页珍藏版)》请在冰豆网上搜索。
人工智能实验指导书
人工智能实验指导书
软件工程教研室
辽宁工业大学
2008年9月
目录
实验一Prolog初识1
实验二数据结构与列表4
实验三经典问题举例12
实验四专家系统16
实验五五子棋游戏22
实验一Prolog初识
一、实验目的
1、熟悉AmziProlog运行环境;
2、掌握PROLOG语言中常量、变量的表示方法;
3、掌握利用PROLOG进行事实库、规则库的编写方法;
4、掌握利用PROLOG中的谓词assert和retract进行数据管理。
二、相关知识
1、Prolog简介
Prolog在英语中的意思就是ProgramminginLogic(逻辑编程)。
它是建立在逻辑学的理论基础之上的,最初是运用于自然语言的研究领域。
然而现在它被广泛的应用在人工智能的研究中,它可以用来建造专家系统、自然语言理解、智能知识库等。
同时它对一些通常的应用程序的编写也很有帮助。
使用它能够比其他的语言更快速地开发程序,因为它的编程方法更象是使用逻辑的语言来描述程序。
本门课程实验程序将在AmziProlog上实现。
2、AmziProlog的使用
1)在桌面上为AmziProlog建一快捷方式,执行文件为d:
\AmziProlog\bin\wideW.exe。
2)在d盘建一prologxx(xx为学号后两位)文件夹,用以保存同学们自己的程序。
3)编辑AmziProlog程序:
执行桌面上AmziProlog快捷方式,在AmziProlog集成环境中新建(或打开)一Prolog程序,并保存到d:
\prologxx文件夹中。
4)运行程序:
选择菜单项Listener—Start(打开“解释器”),之后选择菜单Listener—Consult,在打开窗口中打开保存过的文件,然后就可以在“解释器”的“?
-”提示符后输入要查询的问题了。
5)如需修改程序,应先关闭“解释器”,修改完程序并保存后,再执行4)步进入。
三、实验内容及步骤
新建一文件:
like.pro,保存至d:
\prologxx
在该文件中建立如下事实数据库:
like(ellen,tennis).
like(john,football).
like(tom,baseball).
like(eric,swimming).
like(mark,tennis).
like(tom,tennis).
注:
谓词名(如:
like)和常量(如:
mark)用小写字母,每条语句以点‘.’结束。
请进入解释器询问吧。
查询1:
?
-like(mark,tennis).{查询的目标(goal)}
yes.{有某个事实与目标匹配,查询成功,回显'yes.'}
查询2:
?
-like(mark,football).
no.{没有与目标匹配的事实,查询失败,回显'no.'}
查询3:
?
-like(X,tennis).{X为变量(大写),匹配中X与ellen被绑定}
X=ellen{按回车键}
yes.{表示还有答案}
如果用户输入分号(;),Prolog就开始寻找其他的答案。
首先它必须释放(unbinds)变量X。
然后从上一次成功的位置的下一条子句开始继续搜索。
这个过程叫做回溯(backtracking)。
?
-like(X,tennis).
X=ellen;
X=mark;
X=tom;
no{表示没有答案了}
查询4:
?
-like(X,tennis),!
.
X=ellen;
no
截断谓词cut,prolog内部谓词,使用符号!
来表示。
cut能够有效地抑制其左边的子目标与其父目标的回溯,而它右边的目标则不受影响,如下面查询:
?
-like(X,tennis),!
like(tom,Y),
查询5:
让我们来控制一下输出吧,退出解释器,在数据库中加入如下规则,并保存。
do:
-like(X,tennis),write(X),tab
(2),write('liketennis.').
重新进入解释器。
?
-do.
ellenliketennis.
yes
注:
:
-即为←。
谓词之间的逗号表示‘与’的关系。
write()为Prolog实现输出内部谓词,每次输出一项内容。
tab
(2)Prolog内部谓词,实现输出两个空格。
查询6:
让我们调用debug来观察一下查询的过程。
?
-debug.
?
?
-do.{进入debug窗口,用creep逐步追踪}
注:
Prolog的目标有四个端口用来控制运行的流程,每个端口的功能如下:
call(调用)开始使用目标搜寻子句。
exit(退出)目标匹配成功,在成功的子句上作记号,并绑定变量。
redo(重试)试图重新满足目标,首先释放变量,并从上次的记号开始搜索。
fail(失败)表示再找不到更多的满足目标的子句了。
查询7:
前面这个查询只输出一个查询结果,让我们再来修改一下刚加入的规则do。
其中nl表示换行,fail强制回溯,直到失败为止。
do:
-like(X,tennis),write(X),tab
(2),write('liketennis.'),nl,.fail.
?
-do.
ellenliketennis
markliketennis
tomliketennis
no
?
-debug.{再debug看看吧}
?
?
-do.
查询8:
动态数据库管理。
?
-listing(like).{listing(谓词名)列出所有该名谓词}
?
-assert((like(ellen,football)).{assert或assertz在所有like谓词后动态加入新谓词}
?
-listing(like).
?
-asserta((like(ellen,swimming)).{在所有like谓词前动态加入新谓词}
?
-listing(like).
?
-retract(like(ellen,football)).{删除动态加入的新谓词}
?
-listing(like).
注:
assert只动态地将谓词加入到内存数据库中,并未加入到为件中
四、思考题
1、试分析Prolog的推理方式(正向/逆向)。
2、试分析Prolog推理时,目标与子句的匹配顺序。
实验二数据结构与列表
一、实验目的
1.了解PROLOG中数据结构和列表的设计与使用。
2.了解PROLOG中利用列表实现递归方法。
二、实验内容及步骤
1、数据结构
除了前面实验用到的事实、查询以及规则中的简单的数据结构外,Prolog还可以通过把这些简单的数据组合起来,生成复杂的数据类型,我们称之为结构。
结构由结构名和一定数量的参数组成,结构的参数可以是简单的数据类型或者是另一个结构。
如:
结构名(参数1,参数2,...)
例如,下面的结构描述了物品的颜色、大小以及数量。
object(apple,red,small,1).
我们可以把结构用作谓词的参数,我们定义一个谓词locat:
******************struct.pro*********************
locat(object(apple,red,small,1),kitchen).
locat(object(cabbage,green,small,1),kitchen).
locat(object(radish,red,small,1),kitchen).
locat(object(table,blue,big,1),kitchen).
locat(object(chair,blue,small,6),kitchen).
locat(object(computer,while,small,2),office).
locat(object(book,various,small,many),office).
对于这段描述我们可以进行如下询问:
?
-locat(X,kitchen).
X=object(apple,red,small,1);
X=object(cabbage,green,small,1);
X=object(radish,red,small,1);
X=object(table,blue,big,1);
X=object(chair,blue,small,6);
no
Prolog变量是没有数据类型之分的,所以它可以很容易的绑定为结构,如同它绑定为原
子一样。
事实上,原子就是没有参数的最简单的结构。
我们还可以让变量绑定为结构中的某些参数,如下面的询问可以找出厨房中所有红色的东西。
?
-locat(object(X,red,S,W),kitchen).
X=apple
S=small
W=1;
X=radish
S=small
W=1;
no
如果不关心大小和数量,可以使用下面的询问,其中变量‘_’是匿名变量。
?
-locat(object(X,red,_,_),kitchen).
X=apple;
X=radish;
no
让我们添加如下谓词,使得只有在物品所在的房间且物品较轻的才能被拿起。
here(kitchen).
cantake(Thing):
-
here(Room),
locat(object(Thing,_,small,_),Room).
看一看如下查询的结果吧。
?
-cantake(apple).
?
-cantake(table).
?
-cantake(book).
同时,也可以把不能拿取某物品的原因说得更详细一些,例如添加如下规则:
cantake(Thing):
-
here(Room),
locat(object(Thing,_,big,_),Room),
write('The'),write(Thing),
write('istoobigtocarry.'),nl,fail.
cantake(Thing):
-
here(Room),
not(locat(object(Thing,_,_,_),Room)),
write('Thereisno'),write(Thing),write('here.'),nl,fail.
下面来试试功能。
?
-cantake(table).
?
-cantake(book).
?
-cantakes(desk).
结构可以任意的嵌套,例如:
locat(object(desk,color(brown),size(large),amount
(1)),office).
下面是针对它的一条查询。
?
-locat(object(X,_,size(large),_),office).
2.列表
为了能够更好地表达一组数据,简化程序,Prolog引入了列表这种数据结构。
列表是一组项目的集合,此项目可以是Prolog的任何数据类型,包括结构和列表。
列表的元素由方括号括起来,
项目中间使用逗号分隔。
例如下面的列表列出了厨房中的物品。
[apple,cabbage,radish,table]
我们可以使用列表来代替多个子句。
例如:
locate(apple,kitchen).
locate(cabbage,kitchen).
locate(radish,kitchen).
locate(table,kitchen).
可表示成:
****************list1.pro****************
loclist([apple,cabbage,radish,table],kitchen).
当某个列表中没有项目时我们称之为空表,使用“[]”表示。
也可以使用nil来表示。
下面
的句子表示hall中没有东西。
loclist([],hall)
变量也可以与列表联合,就像它与其他的数据结构联合一样。
假如数据库中有了上面的子句
,就可以进行如下的询问。
?
-loclist(X,kitchen).
X=[[apple,cabbage,radish,table]
?
-[_,X,_]=[apples,cabbage,radish].
X=cabbage
Prolog提供了两个特性,可以方便的实现对列表元素的访问。
首先,Prolog提供了把表头项目与列表剩下部分分离的方法。
其次,Prolog强大的递归功能可以方便地访问除去表头项目后的列表。
首先,列表的一般形式为:
[X|Y]
使用此列表可以与任意的列表匹配,匹配成功后,X绑定为列表的第一个项目的值,我们称之为表头(head)。
而Y则绑定为剩下的列表,我们称之为表尾(tail)。
下面我们看几个把表头项目与列表剩下部分分离例子:
?
-[a|[b,c,d]]=[a,b,c,d].{等号两边的列表是等价的}
yes
?
-[a|b,c,d]=[a,b,c,d].{在“|”之后只能是一个列表,而不能是多个项目。
}
no
注意:
表尾一定是列表,而表头则是一个项目,即可以是表,也可以是其他的任何数据结构。
下面是其它的一些列表的例子。
?
-[H|T]=[apple,cabbage,radish,table].
H=apple
T=[cabbage,radish,table]
?
-[H|T]=[a,b,c,d,e].
H=a
T=[b,c,d,e]
?
-[H|T]=[a,b].
H=a
T=[b]
?
-[H|T]=[a,[b,c,d]].{这个例子中的第一层列表有两个项目a和[b,c,d]。
}
H=a
T=[[b,c,d]]
?
-[H|T]=[a].{列表中只有一个项目的情况}
H=a
T=[]
?
-[H|T]=[].{空表不能与[H|T]匹配,因为它没有表头。
}
no
注意:
最后这个匹配失败非常重要,在递归过程中经常使用它作为边界检测。
即只要表不
为空,那么它就能与[X|Y]匹配,当表为空时,就不能匹配,表示已经到达边界条件。
我们还可以在第二个项目后面使用“|”,事实上,“|”前面的都是项目,后面的是一个表。
?
-[One,Two|T]=[a,b,c,d].
One=a
Two=b
T=[c,d]
表可以看作是表头项目与表尾列表组合而成。
而表尾列表又是由同样的方式组成的。
所以
表的定义本质上是递归定义。
我们来看看下面的例子。
?
-[a|[b|[c|[d|[]]]]]=[a,b,c,d].
Yes
?
-X=[a,b,c,d],write(X),nl,display(X),nl.{内部谓词display以递归的方式显示列表}
[a,b,c,d]
.(a,.(b,.(c,.d(,[]))))
?
-X=[a,b,[c,d],e],write(X),nl,display(X),nl.
[a,b,[c,d],e]
.(a,.(b,.(.(c,.(d,[])),.(e,[]))))
3.用递归处理列表
首先我们来编写谓词member,它能够判断某个项目是否在列表中。
***************List2.pro****************
member(H,[H|T]).
member(X,[H|T]):
-member(X,T).
第一个子句是递归的边界条件,即如果H表示的项目是列表的表头则匹配成功,递归过程结束。
第二个子句表示:
如果X表示的项目不是列表的表头,则让X与列表的的表尾匹配。
把上面的两个子句放入一prolog文件中,让我们来询问一下吧。
?
-member(apple,[apple,cabbage,radish]).
yes
?
-member(cabbage,[apple,cabbage,radish]).
yes
?
-member(banana,[apple,cabbage,radish]).
no
?
-debug.{请观察一下单步运行的结果吧}
?
?
-member(b,[a,b,c]).
如果询问的第一参数是变量,member/2可以把列表中所有的项目找出来。
?
-member(X,[apple,broccoli,crackers]).
X=apple;
X=broccoli;
X=crackers;
No
打印表中元素的例子:
****************list3.pro***************
write_a_list([]).
write_a_list([H|T]):
-write(H),nl,write_a_list(T).
?
-write_a_list([1,2,3]).
我们再来编写能够把两个列表连接成一个列表的谓词append/3。
**************list4.pro***************
append([],X,X).
append([H|L1],L2,[H|L3]):
-append(L1,L2,L3).
?
-append([a,b,c],[d,e,f],New).{第一个参数和第二个参数连接的表为第三个参数}
New=[a,b,c,d,e,f]
和member/2一样,append/3还有别的使用方法。
下面这个例子显示了append/3是如何把一
个表分解的。
?
-append(X,Y,[a,b,c]).
X=[]
Y=[a,b,c];
X=[a]
Y=[b,c];
X=[a,b]
Y=[c];
X=[a,b,c]
Y=[];
no
3.使用列表
现在有了能够处理列表的谓词,我们可以完成一个小游戏。
在厨房、书房等处分别有一些物品,并可以向这些地方添加物品。
******************list5.pro**********************
loclist([apple,cabbage,radish,table],kitchen).
loclist([desk,computer,book],office).
loclist([flashlight,envelope],desk).
loclist([stamp,key],envelope).
loclist(['washingmachine'],cellar).
loclist([nani],'washingmachine').
member(H,[H|T]).
member(X,[H|T]):
-member(X,T).
location(X,Y):
-loclist(List,Y),member(X,List).{判断X物品是否在Y中}
append([],X,X).
append([H|L1],L2,[H|L3]):
-append(L1,L2,L3).
add_thing(New,Container,NewList):
-
loclist(OldList,Container),
append([New],OldList,NewList).{向Container中添加新物品New,形成新列表NewList}
put_thing(Thing,Place):
-
retract(loclist(OldList,Place)),
asserta(loclist([Thing|List],Place)).{动态修改数据库}
我们可以进行询问了。
?
-add_thing(plum,kitchen,X).
X=[plum,apple,broccoli,crackers]
当然,也可以直接使用[Head|Tail]这种列表结构来编写add_thing/3。
add_thing2(NewThing,Container,NewList):
-
loc_list(OldList,Container),
NewList=[NewThing|OldList].
它和前面的add_thing/3功能相同。
?
-add_thing2(plum,kitchen,X).
X=[plum,apple,broccoli,crackers]
我们还可以对add_thing2/3进行简化,不是用显式的联合,而改为在子句头部的隐式联合。
add_thing3(NewTh,Container,[NewTh|OldList]):
-
loclist(OldList,Container).
它同样能完成我们的任务。
?
-add_thing3(plum,kitchen,X).
X=[plum,apple,broccoli,crackers]
下面的put_thing/2,能够直接修改动态数据库,请自己研究一下。
三、思考题
1、Prolog变量是否有数据类型之分。
2、Prolog中的变量和常量是如何区分的。
实验三经典问题举例
一、实验目的
1、理解PROLOG编制递归程序的方法:
边界条件与递归部分的设计;
2、加深理解人工智能中通过搜索解决问题的方法;
3、熟悉分析、运用递归方法解决问题。
二、实验内容及步骤
(一)Hanoi塔问题:
如上图,目的是把左边的所有盘子移到右边的杆子上。
一次只能移动一个盘子,可以使用中间的杆子作为临时存放盘子的地方。
在移动的过程中,小盘子必须放在大盘子之上。
分析:
用递归来解决这个问题。
如果只有一个盘子,直接移过去就行了,这是递归的边界条件。
如果要移动N个盘子,就要分三步走:
1、把N-1个盘子移动到中间的杆子上(把右边的杆子作为临时存放盘子的位置)
2、把最后一个盘子直接移到右边的杆子上。
3、最后把中间杆子上的盘子移到右边的杆子上(把左边的杆子作为临时存放盘子的位置)。
上面第一、三步用到了递归。
我们看到,通过递归把N个盘子的问题变成了两个N-1个盘子的问题。
如此下去,最后就变成了2个一个盘子的问题了,这也就是说问题被解决了。
1)Hanoi塔的Prolog代码:
hanoi(N):
-move(N,left,middle,right).
move(1,A,_,C):
-inform(A,C),!
.{!
为cut操作,截断进一步搜索}
move(N,A,B,C):
-N1isN-1,
move(N1,A,C,B),inform(A,C),
move(N1,B,A,C).
inform(Lo