Erlang麾下有一群“大将”,其中一员便是Eresye,专司“专家系统引擎”。
Eresye统帅一支轻骑兵,装备精良,武艺高强。
Eresye导出的内建函数,仅仅10个左右,能以极快速度搭建起专家系统的框架,并凭借Rete算法的优势,使系统高速运行。
最近,领教了Eresye的强大威力,得把感受记下来。一来作个总结,二来想看看是不是真弄明白了。一直以为,明白的标志,一是能把复杂的问题简单说清,二是要白纸黑字用笔来说。
用Eresye做了个专家系统模型,虽然十分简陋,属于玩具演示类,没有实用价值,但从中可见Eresye的工作原理和强大功能。
这个系统模拟著作权专家,对作品是否享有著作权,给出咨询判断。系统由推理机、知识库、推理规则构成。推理机是Eresye内建的,拿来用便可;知识库是断言式的事实集合,源自中国《著作权法》的条文规定;推理规则,用以根据事实知识和用户的回答,得出结论。
这里说的,没有学术理论。编程这事儿,其实无需太多理论,除非要唬人吓已。说编程的事儿,最好直接用程序代码。下面是玩具源码。
%
% c_f_2.erl
%
-module (c_f_2).
-export ([start/0,
determine_legal/2,
determine_applicable1/4,
determine_applicable2/4,
determine_applicable3/4,
determine_works1/4,
determine_works2/4,
determine_works3/4,
determine_works4/4,
determine_works5/4,
determine_works6/4,
no_copyright/2
]).
ask_yn (Prompt) ->
P = Prompt ++"( y / n ): ",
[Response | _] = io:get_line (P),
case Response of
$y -> true;
_ -> false
end.
%%%%%%% 知识(事实)库
add_works(Engine) ->
eresye:assert(Engine,{works,1,"音乐、戏剧、曲艺、舞蹈、杂技艺术作品"}),
eresye:assert(Engine,{works,2,"美术、建筑作品"}),
eresye:assert(Engine,{works,3,"摄影作品"}),
eresye:assert(Engine,{works,4,"电影作品和以类似摄制电影的方法创作的作品"}),
eresye:assert(Engine,{works,5,"工程设计图、产品设计图、地图、示意图等图形作品和模型作品"}),
eresye:assert(Engine,{works,6,"计算机软件"}).
add_not_applicable(Engine) ->
eresye:assert(Engine,{not_applicable,1,"法律、法规,国家机关的决议、决定、命令和其他具有立法、行政、司法性质的文件,及其官方正式译文"}),
eresye:assert(Engine,{not_applicable,2,"时事新闻"}),
eresye:assert(Engine,{not_applicable,3,"历法、通用数表、通用表格和公式"}).
%%%%%% 推理规则
determine_legal(Engine,{start,Pid}) ->
add_not_applicable(Engine),
add_works(Engine),
case ask_yn ("/n作品属于:依法禁止出版、传播的作品吗?") of
true ->
io:format("~n结论:非法作品,没有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
false ->
eresye:assert(Engine,{legal,ok})
end.
determine_applicable1(Engine,{start,Pid},{legal,ok},{not_applicable,1,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品没有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_applicable2(Engine,{start,Pid},{legal,ok},{not_applicable,2,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品没有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_applicable3(Engine,{start,Pid},{legal,ok},{not_applicable,3,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品没有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_works1(Engine,{start,Pid},{legal,ok},{works,1,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_works2(Engine,{start,Pid},{legal,ok},{works,2,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_works3(Engine,{start,Pid},{legal,ok},{works,3,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_works4(Engine,{start,Pid},{legal,ok},{works,4,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_works5(Engine,{start,Pid},{legal,ok},{works,5,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
determine_works6(Engine,{start,Pid},{legal,ok},{works,6,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
no_copyright(Engine,{start,Pid}) ->
eresye:retract(Engine,{start,Pid}),
io:format("~n结论:作品没有著作权~n"),
Pid ! ok.
%%%% 程序启动和停止
start () ->
io:format("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%~n"),
io:format("%% %%~n"),
io:format("%% 判断作品是否有著作权 %%~n"),
io:format("%% %%~n"),
io:format("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%~n~n"),
eresye:start (c_f_2),
[eresye:add_rule (c_f_2, {?MODULE, X},10) ||
X <- [
determine_legal,
determine_applicable1,
determine_applicable2,
determine_applicable3
]],
[eresye:add_rule (c_f_2, {?MODULE, X},0) ||
X <- [
determine_works1,
determine_works2,
determine_works3,
determine_works4,
determine_works5,
determine_works6
]],
eresye:add_rule (c_f_2, {?MODULE, no_copyright},-10),
eresye:assert (c_f_2, {start, self ()}),
receive
_ -> ok
end,
io:format("~n~n…… 咨询结束 ……~n"),
eresye:stop (c_f_2).
下面,做些解说。
一、模块名字c_f_2是指:copyright_forward_chaing_version_2,意思是著作权专家系统,使用前向推理方式的第2版。
二、导出模块:-export ([start/0,……]). 必须写全推理规则函数,否则编译时通不过。
三、知识库和推理规则函数的第一个参数,必须是变量,代表推理机名称,否则运行时出错,如:
add_not_applicable(Engine) ->
eresye:assert(Engine,{not_applicable,1,"法律、法规,国家机关的决议、决定、命令和其他具有立法、行政、司法性质的文件,及其官方正式译文"}),
……
四、系统的启动和停止
1、eresye:start (c_f_2),进程开启,参数是模块名字;
2、eresye:add_rule(……),把规则加入推理机中;
3、eresye:assert(……),把知识(事实)加入推理机中;
4、receive
_ -> ok
end, 相当于可终止的loop,终止的条件是进程发来信号;
5、eresye:stop (c_f_2).进程停止,参数是模块名字;
五、推理机运行机制
专家系统操作任务,在规则体中进行;运行规则首先需要将其激活,激活的条件是规则首部的模式全部匹配。例如:
determine_works6(Engine,{start,Pid},{legal,ok},{works,6,A}) ->
case ask_yn(A) of
true ->
io:format("~n结论:作品有著作权~n"),
eresye:retract(Engine,{start,Pid}),
Pid ! ok;
_ -> ok
end.
要激活这条规则,使其执行,必须先给出三个事实断言{start,Pid},{legal,ok},{works,6,A}。
也就是说,当程序eresye:assert了这三个事实后,它们就进入知识库,推理机便自动激活这条规则。
eresye:assert(……)的任务目的,就是为了激活规则。
本系统规则激活的首要条件,是函数start()中的eresye:assert (c_f_2, {start, self ()}),在此断言之后,推理机开始运行,第一个激活的规则是determine_legal(Engine,{start,Pid})。
在激活的规则执行过程中,又有新的事实断言出现,于是,会有其他规则激活执行。
这种以事实数据(前提)导向的推理方式,叫做“前向链推理”、“正向推理”等。
六、对规则激活顺序的控制
如果事实断言同时激活多个规则,而规则执行必须先后有序时,可以预设规则执行的优先级,以作控制。如函数start()中的:
eresye:add_rule (c_f_2, {?MODULE, X},10),规定规则X的优先级为10。优先级高的规则先激活执行。
七、eresye:retract(……)的重要作用
本系统终止运行,要在规则中执行语句 Pid ! ok
在本例中,这样做了以后,程序并未正常终止,出现一些规则继续激活的异常。这个问题,并非本例程编写有误,而是eresye特点所致。
本例用eresye:retract(Engine,{start,Pid}),消除了各规则激活所需的首要条件,解决了问题。
八、如何做实用化改造
我觉得,正向推理方式不如反向推理,后者是Prolog内建的,其实用性、控制性、扩展性、应用领域、代码规模等优于前者。
其实,在Eresye上我实现的第一个专家系统模型,就是反向推理的。它是对Prolog推理机的模拟。