Control 版 (精华区)

发信人: dagam (断情), 信区: Control
标  题: [转载]专家系统解释[4] 
发信站: 哈工大紫丁香 (2001年07月01日11:50:53 星期天), 站内信件


有了上面的知识,我们就很容易编写出递归处理每个子目标的程序了。
recurse(FirstGoal & RemainingGoals) :-
process(FirstGoal),
recurse(RemainingGoals).
recurse(SingleGoal) :-
process(SingleGoal).
这里使用&是为了读者不把prolog中的两种逗号搞混淆,一种逗号是用来把谓词的参数分
开的例如:father(a,b). 另外一种则是用来连接规则中的两个子目标的, 表示并且的
意思,这种逗号实际上是谓词。
有了可以存取prolog的事实和规则的方法以后,我们就可以很容易的编写出处理这些事
实和规则的谓词了。
prove(true) :- !.
prove((Goal, Rest)) :-
clause(Goal, Body), %找到和目标匹配的子句
prove(Body), %证明Body部分。
prove(Rest). %证明上一层目标的剩余部分。
prove(Goal) :-
clause(Goal, Body), %找到和目标匹配的子句。
prove(Body). %证明这个子句的Body部分。
注意,prove谓词正好模拟了prolog的解题过程。首先他找到头部和第一个目标匹配某个
子句。然后试图证明这个子句的目标列表。
上面的这个解释器只能够处理纯prolog子句。对于prolog的内部谓词无能为力。因此最
后我们加上一条:
prove(X):-call(X).
用来调用内部谓词。
在我们的这个外壳程序中并不打算使用prolog的内部谓词,不过因为需要调用ask和men
uask这样的谓词来和用户对话,这些谓词对于上面的解释器就可以被认为是内部谓词了

和前面的Clam一样,我们加入参数Hist来回答用户的why提问。
prove(true, _) :- !.
prove((Goal, Rest), Hist) :-
prov(Goal, (Goal, Rest)),
prove(Rest, Hist).
prov(true, _) :- !.
prov(menuask(X, Y, Z), Hist) :- menuask(X, Y, Z, Hist), !.
prov(ask(X, Y), Hist) :- ask(X, Y, Hist), !.
prov(Goal, Hist) :-
clause(Goal, List),
prove(List, [Goal|Hist]).
注意这里的历史记录保存的是目标列表,而不是规则名。
下面来修改顶层的谓词。
solve :-
abolish(known, 3),
define(known, 3),
prove(top_goal(X), []),
write('The answer is '), write(X), nl.
solve :-
write('No answer found'), nl.
处理why提问的程序和clam类似。
get_user(X, Hist) :-
repeat,
read(X),
process_ans(X, Hist), !.
process_ans(why, Hist) :-
write(Hist), !, fail.
process_ans(X, _).
最后的对话形式如下:
?- identify.
nostrils : external_tubular? why.
[nostrils(external_tubular), order(tubenose), family(albatross), bird(laysan
_albatross)]
nostrils : external_tubular?
在Clam外壳程序中,为了回答用户的how提问,我们使用了在工作空间中保存相应的信息
的方法,在这里就简单多了, 只需要重新求解某个目标,并且把中间的证明过程都显示
出来:
how(Goal) :-
clause(Goal, List),
prove(List, []),
write(List).
我们还可以让系统来回答whynot的提问,也就是说系统可以告诉用户为什么某个结论不
成立。这段程序和前面的解释器类似,不过它能够显示出目标是在什么地方失败的。
whynot(Goal) :-
clause(Goal, List),
write_line([Goal, 'fails because: ']),
explain(List).
whynot(_).
explain( (H, T) ) :-
check(H),
explain(T).
explain(H) :- check(H).
check(H) :- prove(H, _), write_line([H, succeeds]), !.
check(H) :- write_line([H, fails]), fail.
whynot和how一样存在一个问题。是自动的给出整棵树,从而找到失败的根源,还是让用
check(H) :- write_line([H, fails]), fai从不喜欢光光一个,可惜偏偏光光一个
whynot和how一样存在一个问题。是自动的给出整棵树,从而找到失败的根源,还是让用
户自己不停的询问呢?上面的这段程序只能够给出最近的失败的原因,如果在第二个ch
eck子句中再调用whynot就可以显示出整个树了。我们举一个例子说明一下。
假设:
a:-b,c.
b:-d.
c:-e.
而e不成立,所以最终的结果是a也不成立。用户询问whynot a,系统只能够告诉用户是
因为c不成立。当用户问whynot c的时候,系统才会显示出是因为e不成立。这就是让用
户不停的询问的方法。
如果当用户询问whynot a,系统能够自动的递归寻找失败的原因,它就会直接告诉用户
是因为e不成立导致c不成立,从而导致a不成立的。
加入解释的部分就介绍完了,下一章介绍数据驱动的专家系统的设计方法。
                                                (原文转自“垂钓听竹轩”--

--

--
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: heart.hit.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:5.435毫秒