AI-ExpertSystem-Advanced
view release on metacpan or search on metacpan
lib/AI/ExpertSystem/Advanced.pm view on Meta::CPAN
our $VERSION = '0.03';
=head1 Attributes
=over 4
=item B<initial_facts>
A list/set of initial facts the algorithms start using.
During the forward algorithm the task is to find a list of goals caused
by these initial facts (the only data we have in that moment).
Lets imagine your knowledge database is about symptoms and diseases. You need
to find what diseases are caused by the symptoms of a patient, these first
symptons are the initial facts.
Initial facts as also asked and inference facts can be negative or positive. By
default the initial facts are positive.
Keep in mind that the data contained in this array can be the IDs or the name
of the fact.
This array will be converted to L<initial_facts_dict>. And all the data (ids or
or names) will be made of only IDs.
my $ai = AI::ExpertSystem::Advanced->new(
viewer_class => 'terminal',
knowledge_db => $yaml_kdb,
initial_facts => ['I', ['F', '-'], ['G', '+']);
As you can see if you want to provide the sign of a fact, just I<encapsulate>
it in an array, the first item should be the fact and the second one the
sign.
=cut
has 'initial_facts' => (
is => 'rw',
isa => 'ArrayRef[Str]',
default => sub { return []; });
=item B<initial_facts_dict>
This dictionary (see L<AI::ExpertSystem::Advanced::Dictionary> has the sasme
data of L<initial_facts> but with the additional feature(s) of proviing
iterators and a quick way to find elements.
=cut
has 'initial_facts_dict' => (
is => 'ro',
isa => 'AI::ExpertSystem::Advanced::Dictionary');
=item B<goals_to_check>
my $ai = AI::ExpertSystem::Advanced->new(
viewer_class => 'terminal',
knowledge_db => $yaml_kdb,
goals_to_check => ['J']);
When doing the L<backward()> algorithm it's required to have at least one goal
(aka hypothesis).
This could be pretty similar to L<initial_facts>, with the difference that the
initial facts are used more with the causes of the rules and this one with
the goals (usually one in a well defined knowledge database).
The same rule of L<initial_facts> apply here, you can provide the sign of the
facts and you can provide the id or the name of them.
From our example of symptoms and diseases lets imagine we have the hypothesis
that a patient has flu, we don't know the symptoms it has, we want the
expert system to keep asking us for them to make sure that our hypothesis
is correct (or incorrect in case there's not enough information).
=cut
has 'goals_to_check' => (
is => 'rw',
isa => 'ArrayRef[Str]',
default => sub { return []; });
=item B<goals_to_check_dict>
Very similar to L<goals_to_check> (and indeed of L<initial_facts_dict>). We
want to make the job easier.
It will be a dictionary made of the data of L<goals_to_check>.
=cut
has 'goals_to_check_dict' => (
is => 'ro',
isa => 'AI::ExpertSystem::Advanced::Dictionary');
=item B<inference_facts>
Inference facts are basically the core of an expert system. These are facts
that are found and copied when a set of facts (initial, inference or asked)
match with the causes of a goal.
L<inference_facts> is a L<AI::ExpertSystem::Advanced::Dictionary>, it will
store the name of the fact, the rule that caused these facts to be copied to
this dictionary, the sign and the algorithm that triggered it.
=cut
has 'inference_facts' => (
is => 'ro',
isa => 'AI::ExpertSystem::Advanced::Dictionary');
=item B<knowledge_db>
The object reference of the knowledge database L<AI::ExpertSystem::Advanced> is
using.
=cut
has 'knowledge_db' => (
is => 'rw',
isa => 'AI::ExpertSystem::Advanced::KnowledgeDB::Base',
required => 1);
=item B<asked_facts>
During the L<backward()> algorithm there will be cases when there's no clarity
if a fact exists. In these cases the L<backward()> will be asking the user
(via automation or real questions) if a fact exists.
Going back to the L<initial_facts> example of symptoms and diseases. Imagine
the algorithm is checking a rule, some of the facts of the rule make a match
with the ones of L<initial_facts> or L<inference_facts> but some wont, for
these I<unsure> facts the L<backward()> will ask the user if a symptom (a fact)
exists. All these asked facts will be stored here.
=cut
has 'asked_facts' => (
is => 'ro',
isa => 'AI::ExpertSystem::Advanced::Dictionary');
=item B<visited_rules>
Keeps a record of all the rules the algorithms have visited and also the number
of causes each rule has.
=cut
has 'visited_rules' => (
is => 'ro',
isa => 'AI::ExpertSystem::Advanced::Dictionary');
=item B<verbose>
my $ai = AI::ExpertSystem::Advanced->new(
viewer_class => 'terminal',
knowledge_db => $yaml_kdb,
initial_facts => ['I'],
verbose => 1);
By default this is turned off. If you want to know what happens behind the
scenes turn this on.
Everything that needs to be debugged will be passed to the L<debug()> method
of your L<viewer>.
=cut
has 'verbose' => (
is => 'rw',
isa => 'Bool',
default => 0);
=item B<viewer>
Is the object L<AI::ExpertSystem::Advanced> will be using for printing what is
happening and for interacting with the user (such as asking the
L<asked_facts>).
This is practical if you want to use a viewer object that is not provided by
L<AI::ExpertSystem::Advanced::Viewer::Factory>.
=cut
has 'viewer' => (
is => 'rw',
lib/AI/ExpertSystem/Advanced.pm view on Meta::CPAN
if $self->{'verbose'};
next;
}
my $factor = $self->get_causes_match_factor($current_rule);
if ($factor ge $self->{'found_factor'} && $factor lt 1.0) {
# Copy all of the goals (usually only one) of the current rule to
# the intuitive facts
my $goals = $self->get_goals_by_rule($current_rule);
while(my $goal = $goals->iterate_reverse) {
$intuitive_facts->append($goal,
{
name => $goal,
sign => $goals->get_value($goal, 'sign')
});
}
}
}
if ($intuitive_facts->size() eq 0) {
$self->{'viewer'}->debug("Done with intuition") if
$self->{'verbose'};
return 1;
}
$intuitive_facts->populate_iterable_array();
# now each intuitive fact will be a goal
while(my $fact = $intuitive_facts->iterate) {
$self->{'goals_to_check_dict'}->append(
$fact,
{
name => $fact,
sign => $intuitive_facts->get_value($fact, 'sign')
});
$self->{'goals_to_check_dict'}->populate_iterable_array();
print "Running backward for $fact\n";
if (!$self->backward()) {
$self->{'viewer'}->debug("Backward exited");
return 1;
}
# Now we have inference facts, anything positive?
$self->{'inference_facts'}->populate_iterable_array();
while(my $inference_fact = $self->{'inference_facts'}->iterate) {
my $sign = $self->{'inference_facts'}->get_value(
$inference_fact, 'sign');
if ($sign eq FACT_SIGN_POSITIVE) {
$self->{'viewer'}->print(
"Done, a positive inference fact found"
);
return 1;
}
}
}
$self->forward();
}
}
=head2 B<summary($return)>
The main purpose of any expert system is the ability to explain: what is
happening, how it got to a result, what assumption(s) it required to make,
the fatcs that were excluded and the ones that were used.
This method will use the L<viewer> (or return the result) in YAML format of all
the rules that were shot. It will explain how it got to each one of the causes
so a better explanation can be done by the L<viewer>.
If C<$return> is defined (eg, it got any parameter) then the result wont be
passed to the L<viewer>, instead it will be returned as a string.
=cut
sub summary {
my ($self, $return) = @_;
# any facts we found via inference?
if (scalar @{$self->{'inference_facts'}->{'stack'}} eq 0) {
$self->{'viewer'}->print_error("No inference was possible");
} else {
my $summary = {};
# How the rules started being shot?
my $order = 1;
# So, what rules we shot?
foreach my $shot_rule (sort(keys %{$self->{'shot_rules'}})) {
$summary->{'rules'}->{$shot_rule} = {
order => $order,
};
$order++;
# Get the causes and goals of this rule
my $causes = $self->get_causes_by_rule($shot_rule);
$causes->populate_iterable_array();
while(my $cause = $causes->iterate) {
# How we got to this cause? Is it an initial fact,
# an inference fact? or by forward algorithm?
my ($method, $sign, $algorithm);
if ($self->{'asked_facts'}->find($cause)) {
$method = 'Question';
$sign = $self->{'asked_facts'}->get_value($cause, 'sign');
$algorithm = $self->{'asked_facts'}->get_value($cause, 'algorithm');
} elsif ($self->{'inference_facts'}->find($cause)) {
$method = 'Inference';
$sign = $self->{'inference_facts'}->get_value($cause, 'sign');
$algorithm = $self->{'inference_facts'}->get_value($cause, 'algorithm');
} elsif ($self->{'initial_facts_dict'}->find($cause)) {
$method = 'Initial';
$sign = $self->{'initial_facts_dict'}->get_value($cause, 'sign');
} else {
$method = 'Forward';
$sign = $causes->get_value($cause, 'sign');
}
$summary->{'rules'}->{$shot_rule}->{'causes'}->{$cause} = {
method => $method,
sign => $sign,
algorithm => $algorithm,
};
}
my $goals = $self->get_goals_by_rule($shot_rule);
$goals->populate_iterable_array();
while(my $goal = $goals->iterate) {
# We got to this goal by asking the user of it? or by
# "natural" backward algorithm?
( run in 2.851 seconds using v1.01-cache-2.11-cpan-e1769b4cff6 )