Erlang 与人工智能:亲属关系
出自Erlang社区
本文谈谈如何用Erlang和规则产生式系统,写个人工智能应用程序。
具体地,我们利用Erlang推理机工具ERESYE,写基于规则的系统。
众所周知,基于规则的系统是由知识库和产生式规则组成的。知识库存放着表示特定应用领域的事实集合;产生式规则利用知识推理,并且推断出新的知识。当一个或多个事实匹配上规则模板,规则体中的代码便启动执行。
ERESYE的事实以Erlang的元组或记录表示,规则以Erlang的函数子句书写。子句头部的事实或模板必须匹配,以激活规则执行。
以ERESYE写人工智能程序的步骤如下:
-
确定你的应用领域及其表示的事实;
-
确定你所需要的规则,用一阶逻辑谓词或自然语言写出它们;
-
按照ERESYE要求的方式,以Erlang函数子句的形式,实现这一系统。
运行本文涉及的例程,需要ERESYE最新版本1.2.4。
应用程序:亲属关系
我们将设计一个系统,用少许推理规则,得到新知识。我们选择亲属关系做为样本,出发点是些基本概念,如父母、男性、女性,应用规则之后,我们将得到一些概念:母亲、父亲、姐妹、兄弟、祖母、祖父等。
上述概念可以下列事实表示:
# | Concept | Fact / Erlang tuple |
1 | X is male | {male, X} |
2 | X is female | {female, X} |
3 | X is Y's parent | {parent, X, Y} |
4 | X is Y's mother | {mother, X, Y} |
5 | X is Y's father | {father, X, Y} |
6 | X is Y's sister | {sister, X, Y} |
7 | X is Y's brother | {brother, X, Y} |
8 | X is Y's grandmother | {grandmother, X, Y} |
9 | X is Y's grandfather | {grandfather, X, Y} |
概念1、2、3是派生其他概念的基础。
以规则产生新概念
概念:“母亲”。
产生母亲概念的规则非常直观:如果X是女性,并且X是Y的父母,那么,X是Y的母亲。
以ERESYE的观点看,由于知识存放在知识库,规则就翻译成这样:如果知识库中存在事实
{female, X} 和 {parent, X, Y},我们就断言事实{mother, X, Y}。
关于母亲的规则,可以这样:
%%
%% if (X is female) and (X is Y's parent) then (X is Y's mother)
%%
mother (Engine, {female, X}, {parent, X, Y}) ->
eresye:assert (Engine, {mother, X, Y}).
概念:“父亲”。由以下规则产生:
%%
%% if (X is male) and (X is Y's parent) then (X is Y's father)
%%
father (Engine, {male, X}, {parent, X, Y}) ->
eresye:assert (Engine, {father, X, Y}).
概念:“姐妹”。由以下规则产生:
%%
%% if (Y and Z have the same parent X) and (Z is female)
%% then (Z is Y's sister)
%%
sister (Engine, {parent, X, Y}, {parent, X, Z}, {female, Z}) when Y =/= Z ->
eresye:assert (Engine, {sister, Z, Y}).
请注意子句头中的guard语句,在Y和Z取值相同时,它保证不激活规则。
概念:“兄弟”。由以下规则产生:
%%
%% if (Y and Z have the same parent X) and (Z is male)
%% then (Z is Y's brother)
%%
brother (Engine, {parent, X, Y}, {parent, X, Z}, {male, Z}) when Y =/= Z ->
eresye:assert (Engine, {brother, Z, Y}).
概念:“祖母”和“祖父”。由以下规则产生:
%%
%% if (X is Y's mother) and (Y is Z's parent)
%% then (X is Z's grandmother)
%%
grandmother (Engine, {mother, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandmother, X, Z}).
%%
%% if (X is Y's father) and (Y is Z's parent)
%% then (X is Z's grandfather)
%%
grandfather (Engine, {father, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandfather, X, Z}).
推理机初始化与知识库扩充
写完规则后,我们需要:
1.初始化推理机;
2.将规则加入推理机;
3.以初始化的事实集合,扩展知识库。
我们在函数start中做这些事:
start () ->
eresye:start (relatives),
lists:foreach (fun (X) ->
eresye:add_rule (relatives, {?MODULE, X})
end,
[mother, father, brother, sister,
grandfather, grandmother]),
eresye:assert (relatives,
[{male, bob}, {male, corrado}, {male, mark}, {male, caesar},
{female, alice}, {female, sara}, {female, jane}, {female, anna},
{parent, jane, bob}, {parent, corrado, bob},
{parent, jane, mark}, {parent, corrado, mark},
{parent, jane, alice}, {parent, corrado, alice},
{parent, bob, caesar}, {parent, bob, anna},
{parent, sara, casear}, {parent, sara, anna}]),
ok.
可见,创建RESYE新的推理机,意味着调用函数eresye:start/1,参数是推理机的名称。
接着,我们必须给推理机增加规则。为此要用函数 eresye:add_rule/2。它有2个参数,推理机名称和一个表示函数的元组,形式是 {Module, FuncName}。函数 Module:FuncName明显是必须导出的。推理机必须加入规则,加入规则必须用函数add_rule。为此,用了循环操作处理存放规则的列表。
最后,我们用函数 eresye:assert/2,把存放在列表内的一组样本事实扩充到推理机中。
程序测试
我们最终完整的人工智能程序代码如下:
%
% relatives.erl
%
-module (relatives).
-compile ([export_all]).
%%
%% if (X is female) and (X is Y's parent) then (X is Y's mother)
%%
mother (Engine, {female, X}, {parent, X, Y}) ->
eresye:assert (Engine, {mother, X, Y}).
%%
%% if (X is male) and (X is Y's parent) then (X is Y's father)
%%
father (Engine, {male, X}, {parent, X, Y}) ->
eresye:assert (Engine, {father, X, Y}).
%%
%% if (Y and Z have the same parent X) and (Z is female)
%% then (Z is Y's sister)
%%
sister (Engine, {parent, X, Y}, {parent, X, Z}, {female, Z}) when Y =/= Z ->
eresye:assert (Engine, {sister, Z, Y}).
%%
%% if (Y and Z have the same parent X) and (Z is male)
%% then (Z is Y's brother)
%%
brother (Engine, {parent, X, Y}, {parent, X, Z}, {male, Z}) when Y =/= Z ->
eresye:assert (Engine, {brother, Z, Y}).
%%
%% if (X is Y's father) and (Y is Z's parent)
%% then (X is Z's grandfather)
%%
grandfather (Engine, {father, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandfather, X, Z}).
%%
%% if (X is Y's mother) and (Y is Z's parent)
%% then (X is Z's grandmother)
%%
grandmother (Engine, {mother, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandmother, X, Z}).
start () ->
eresye:start (relatives),
lists:foreach (fun (X) ->
eresye:add_rule (relatives, {?MODULE, X})
end,
[mother, father,
brother, sister,
grandfather, grandmother]),
eresye:assert (relatives,
[{male, bob},
{male, corrado},
{male, mark},
{male, caesar},
{female, alice},
{female, sara},
{female, jane},
{female, anna},
{parent, jane, bob},
{parent, corrado, bob}, {parent, jane, mark}, {parent, corrado, mark}, {parent, jane, alice}, {parent, corrado, alice}, {parent, bob, caesar}, {parent, bob, anna}, {parent, sara, casear}, {parent, sara, anna}]), ok.
现在开始测试我们的程序:
Erlang (BEAM) emulator version 5.5 [source] [async-threads:0] [hipe] Eshell V5.5 (abort with ^G) 1> c(relatives). {ok,relatives} 2> relatives:start(). ok 3> 调用函数 relatives:start/0后,完成推理机的创建和扩充。若未出现错误,规则会执行并产生新的事实。我们可用函数 eresye:get_kb/1去检查上述情况。该函数返回事实列表,事实存放在特定推理机知识库中。 4> eresye:get_kb(relatives). [{brother,bob,mark}, {sister,alice,bob}, {sister,alice,mark}, {brother,bob,alice}, {brother,mark,alice}, {grandmother,jane,caesar}, {grandfather,corrado,caesar}, {grandmother,jane,anna}, {grandfather,corrado,anna}, {sister,anna,caesar}, {brother,caesar,anna}, {sister,anna,casear}, {mother,sara,anna}, {mother,sara,casear}, {parent,sara,anna}, {father,bob,anna}, {parent,sara,casear}, {father,bob,caesar}, {parent,bob,anna}, {father,corrado,alice}, {parent,bob,caesar}, {mother,jane,alice}, {parent,corrado,alice}, {father,corrado,mark}, {parent,jane,alice}, {mother,jane,mark}, {parent,corrado|...}, {brother|...}, {...}|...] 5>
这些事实表示的概念有father,sister,等,它们证明了规则似乎如期执行。
我们也可以特定的事实模板,询问知识库。例如,如果我们要知道谁是Alice的兄弟,可以如下操作:
6> eresye:query_kb(relatives, {brother, '_', alice}). [{brother,bob,alice},{brother,mark,alice}] 7>
如例所示,函数 eresye:query_kb/2 第一参数是推理机名称,第二参数是以元组表示的、用于匹配的事实模板。在元组中,原子'_'扮演的是通配符的角色。不过,为了指定更为复杂的匹配模式,可以使用函数fun返回true或false,明示模板是否得以匹配。例如,为了选择Alice的兄弟和Anna的兄弟,我们可以如下操作:
7> eresye:query_kb(relatives, {brother, '_', fun (X) -> (X == alice) or (X == anna) end}). [{brother,bob,alice},{brother,mark,alice},{brother,caesar,anna}] 8>
结论
本文不仅说明了如何用ERESYE写人工智能程序,并且展示了Erlang语言的强大功能:函数编程、面向符号编程、函数说明的自反执行。这些功能以非常有趣的方式,成功开发崭新的Erlang应用领域。