add comprehensive readme to the system
Signed-off-by: Jordan Wilberding <jwilberding@gmail.com>
This commit is contained in:
parent
ff3557d883
commit
2b320a34a5
1 changed files with 534 additions and 0 deletions
534
README.md
Normal file
534
README.md
Normal file
|
@ -0,0 +1,534 @@
|
|||
ERESYE - ERlang Expert SYstem Engine
|
||||
========================================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This article describe how to write a simple Artificial Intelligence
|
||||
application with Erlang, by using a *rule production system*.
|
||||
|
||||
To reach this objective, we will exploit the **ERESYE** tool, an
|
||||
Erlang Inference Engine that allows rule-based systems to be written
|
||||
directly in Erlang.
|
||||
|
||||
As it is widely known, a rule-based system is composed by a
|
||||
**knowledge base**, which stores a set of *facts* representing the
|
||||
'universe of discourse' of a given application, and a set of
|
||||
**production rules**, which are used to infer knowledge and/or reason
|
||||
about the knowledge. A rule is activated when one or more facts match
|
||||
the template(s) given in the rule declaration: in such a case, the
|
||||
body of the rule contains a code that is thus executed
|
||||
|
||||
In ERESYE, *facts* are expressed by means of Erlang tuples or records,
|
||||
while rules are written using standard Erlang function clauses, whose
|
||||
declaration reports, in the clause head, the facts or fact templates
|
||||
that have to be matched for the rule to be activated and executed.
|
||||
|
||||
For more information about ERESYE please refer to the paper docs directory.
|
||||
|
||||
For more information about rule-based inference engines and expert
|
||||
systems, you can refer to the book: *S. Russell and
|
||||
P. Norvig. **Artificial Intelligence: A Modern Approach/2E.** Prentice
|
||||
Hall, 2003.*
|
||||
|
||||
To write an AI application with ERESYE the following steps have to be
|
||||
performed:
|
||||
|
||||
1. Indentify your universe of discourse and determine the facts that
|
||||
have to be used to represent such a world;
|
||||
|
||||
2. Indentify the rules that you need and write them by using, e.g.
|
||||
first-order-logic predicates or even natural language;
|
||||
|
||||
3. Implement the system by writing your rules as Erlang function
|
||||
clauses, according to the modality required by ERESYE.
|
||||
|
||||
|
||||
The Application: the Domain of Relatives
|
||||
----------------------------------------
|
||||
|
||||
We will design a system able to derive new knowledge using some
|
||||
inference rules and starting from a small set; as a sample
|
||||
application, we chose the domain of relatives: we will start from some
|
||||
base concepts, such as *parent*, *male* and *female*, and then, by
|
||||
means of a proper set of rules, we will derive the concepts of
|
||||
*mother*, *father*, *sister*, *brother*, *grandmother* and
|
||||
*grandfather*.
|
||||
|
||||
According to the list above, we will first derive the facts that will be
|
||||
used to represent our concepts. Given the set of relationships above, they
|
||||
will be represented by means of the following facts:
|
||||
|
||||
<table border="1" align="center">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>#</td>
|
||||
<td>Concept</td>
|
||||
<td>Fact / Erlang tuple</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>X is male</td>
|
||||
<td><tt>{male, X}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>X is female</td>
|
||||
<td><tt>{female, X}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>X is Y's parent</td>
|
||||
<td><tt>{parent, X, Y}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>X is Y's mother</td>
|
||||
<td><tt>{mother, X, Y}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>X is Y's father</td>
|
||||
<td><tt>{father, X, Y}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>X is Y's sister</td>
|
||||
<td><tt>{sister, X, Y}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>X is Y's brother</td>
|
||||
<td><tt>{brother, X, Y}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>X is Y's grandmother</td>
|
||||
<td><tt>{grandmother, X, Y}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>X is Y's grandfather</td>
|
||||
<td><tt>{grandfather, X, Y}</tt></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Concepts 1, 2 and 3 will be used as a base to derive the other ones.
|
||||
|
||||
Deriving new concepts by means of rules
|
||||
---------------------------------------
|
||||
|
||||
#### Concept: mother
|
||||
|
||||
The rule to derive the concept of mother is quite
|
||||
straightforward:
|
||||
|
||||
if X is female and X is Y's parent then X is Y's mother.
|
||||
|
||||
From the point of view of ERESYE, since knowledge is stored in the
|
||||
*knowledge base* of the engine, the rule above is translated into the
|
||||
following one: *if the facts {female, X} and {parent, X, Y} are
|
||||
*asserted* in the knowledge base, then we assert the fact {mother, X,
|
||||
Y}.
|
||||
|
||||
The rule *mother* can be thus written as follows:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
#### Concept: father
|
||||
|
||||
This concept can be easily derived by means of the following rule:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
#### Concept: sister
|
||||
|
||||
This concept can be expressed by the following rule:
|
||||
|
||||
if Y and Z have the same parent and Z is female, then Z
|
||||
is the Y's sister.
|
||||
|
||||
The ERESYE rule used to map this concept is:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
Please note the guard, which is needed to ensure that when Y and Z are
|
||||
bound to the same value, the rule is not activated (indeed this is
|
||||
possible since the same fact can match both the first and second
|
||||
'parent' pattern).
|
||||
|
||||
#### Concept: brother
|
||||
|
||||
Given the previous one, this concept is now quite simple to
|
||||
implement:
|
||||
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
#### Concepts: grandmother and grandfather
|
||||
|
||||
The former concept can be expressed by means of the rule:
|
||||
|
||||
if X is Y's mother and Y is Z's parent, then X is Z's
|
||||
grandmother.</u>* The latter concept is now obvious.
|
||||
|
||||
Both can be implemented using the following ERESYE rules:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
Instantiating the Engine and Populating the Knowledge Base
|
||||
----------------------------------------------------------
|
||||
|
||||
After writing the rules, we need to:
|
||||
|
||||
- instantiate the engine;
|
||||
- add the rules above to the engine;
|
||||
- populate the knowledge base with a set of initial facts.
|
||||
|
||||
We do this in the function *start* below:
|
||||
|
||||
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.
|
||||
|
||||
As the listing reports, creating a new ERESYE engine implies to call
|
||||
the function *eresye:start/1*, giving the name of the engine to be
|
||||
created
|
||||
|
||||
Then, we have to add the rules to the engine by using the function
|
||||
*eresye:add_rule/2*: it takes two arguments, the name of the engine
|
||||
and a tuple representing the function in the form *{Module,
|
||||
FuncName}*; obviously the function *Module:FuncName* must be
|
||||
exported. Function *add_rule* has to be called for each rule that has
|
||||
to be added; for this reason, the code above has an iteration over the
|
||||
list of rules written before.
|
||||
|
||||
Finally, we populate the inference engine with a set of sample facts
|
||||
by giving them, in a list, to the function *eresye:assert/2*. To test
|
||||
our rules, we considered the relationships in the Figure below and
|
||||
assert only the facts for *male*, *female* and *parent*.
|
||||
|
||||
Testing the application
|
||||
-----------------------
|
||||
|
||||
The final complete code of our AI application is thus the following:
|
||||
|
||||
|
||||
%%%
|
||||
%%% 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.
|
||||
|
||||
Now it's time to test our application:
|
||||
|
||||
|
||||
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>
|
||||
|
||||
Following the call to function *relatives:start/0*, the engine is
|
||||
created and populated; if no errors occurred, the rules should have
|
||||
been processed and the new facts derived. To check this, we can use
|
||||
the function *eresye:get_kb/1*, which returns the list of facts
|
||||
asserted into the knowledge base of a given engine:
|
||||
|
||||
|
||||
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>
|
||||
|
||||
The presence of facts representing concepts like *father*, *sister*,
|
||||
etc., proves that the rules seems to be working as expected.
|
||||
|
||||
We can however query the knowledge base using specific fact templates.
|
||||
For example, if we want to know who are Alice's brothers, we can use
|
||||
the function *eresye:query_kb/2* as follows:
|
||||
|
||||
|
||||
6> eresye:query_kb(relatives, {brother, '_', alice}).
|
||||
[{brother,bob,alice},{brother,mark,alice}]
|
||||
7>
|
||||
|
||||
The facts returned conform to the relationships depicted in the figure
|
||||
above, thus proving that the rules written are really working.
|
||||
|
||||
As the example shows, function *eresye:query_kb/2* takes the engine
|
||||
name as the first argument, while, for the second parameter, a tuple
|
||||
has to be specified, representing the fact template to be matched; in
|
||||
such a tuple, the atom *'_'* plays the role of a wildcard. However, to
|
||||
specify a more complex matching, a *fun* can be used as a tuple
|
||||
element; this *fun* has to return a boolean value which indicates if
|
||||
the element matches the template. For example, to select both Alice's
|
||||
and Anna's brothers, we can use the following function call:
|
||||
|
||||
|
||||
7> eresye:query_kb(relatives, {brother, '_', fun (X) -> (X == alice) or (X == anna) end}).
|
||||
[{brother,bob,alice},{brother,mark,alice},{brother,caesar,anna}]
|
||||
8>
|
||||
|
||||
Deriving new concepts by means of rules
|
||||
---------------------------------------
|
||||
|
||||
#### Concept: 'mother'
|
||||
|
||||
The rule to derive the concept of mother is quite straightforward:
|
||||
|
||||
if X is female and X is Y's parent then X is Y's mother.
|
||||
|
||||
From the point of view of ERESYE, since knowledge is stored in the
|
||||
*knowledge base* of the engine, the rule above is translated into the
|
||||
following one: *<u>if the facts *{female, X}* and *{parent, X, Y}* are
|
||||
**asserted** in the knowledge base, then we assert the fact *{mother,
|
||||
X, Y}*.</u>*
|
||||
|
||||
The rule *mother* can be thus written as follows:
|
||||
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
#### Concept: 'father'
|
||||
|
||||
This concept can be easily derived by means of the following rule:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
#### Concept: 'sister'
|
||||
|
||||
This concept can be expressed by the following rule:
|
||||
|
||||
if Y and Z have the same parent and Z is female, then Z is the Y's
|
||||
sister.
|
||||
|
||||
The ERESYE rule used to map this concept is:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
Please note the guard, which is needed to ensure that when Y and Z are
|
||||
bound to the same value, the rule is not activated (indeed this is possible
|
||||
since the same fact can match both the first and second
|
||||
'parent' pattern).
|
||||
|
||||
#### Concept: 'brother'
|
||||
|
||||
Given the previous one, this concept is now quite simple to
|
||||
implement:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
#### Concepts: 'grandmother' and 'grandfather'
|
||||
|
||||
The former concept can be expressed by means of the rule:
|
||||
|
||||
if X is Y's mother and Y is Z's parent, then X is Z's grandmother.
|
||||
|
||||
The latter concept is now obvious. Both can be implemented using the
|
||||
following ERESYE rules:
|
||||
|
||||
%%
|
||||
%% 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}).
|
||||
|
||||
|
||||
Conclusions
|
||||
-----------
|
||||
|
||||
This HowTo not only shows how to use the ERESYE engine to write an AI
|
||||
application, but also highlights the versatility of the Erlang language:
|
||||
the characteristics of functional and symbolic programming, together with
|
||||
the possibility of performing *introspection* of function declaration,
|
||||
can be successfully exploited for application domains which are completely
|
||||
new for Erlang but can surely be very interesting.
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue