AI-DecisionTree
view release on metacpan or search on metacpan
- DecisionTree.pm is now pure-perl again (though Instance.pm still
has an XS component).
- Fixed a one-off bug in the Instance.xs code that could create
garbage data.
- Handles "sparse" data better. Sparse data means that every
attribute doesn't have to be defined for every training/test
instance. This can now be a meaningful property - the absence of a
value is currently equivalent to a special "<undef>" value.
- Don't trigger warnings when undefined attribute values are
encountered (as happens with sparse data).
- Added documentation for the 'prune' parameter to new()
- More consistent with memory allocation in Instance.xs - uses the
perl memory macros/functions from `perldoc perlclib` instead of raw
malloc/realloc/free.
- Catches possible infinite loop situations when growing the tree
(which shouldn't usually happen, but other mistakes can cause it)
Instance/Instance.xs view on Meta::CPAN
SV * instances_r
SV * tallies_r
SV * totals_r
int attr
CODE:
{
AV *instances = (AV*) SvRV(instances_r);
HV *tallies = (HV*) SvRV(tallies_r);
HV *totals = (HV*) SvRV(totals_r);
I32 top = av_len(instances);
int num_undef = 0;
I32 i, v;
SV **instance_r, **hash_entry, **sub_hash_entry;
Instance *instance;
for (i=0; i<=top; i++) {
instance_r = av_fetch(instances, i, 0);
instance = (Instance *) SvIV(SvRV(*instance_r));
v = attr < instance->num_values ? instance->values[attr] : 0;
/* if (!v) { num_undef++; continue; } */
/* $totals{$v}++ */
hash_entry = hv_fetch(totals, (char *)&v, sizeof(I32), 1);
if (!SvIOK(*hash_entry)) sv_setiv(*hash_entry, 0);
sv_setiv( *hash_entry, 1+SvIV(*hash_entry) );
/* $tallies{$v}{$_->result_int}++ */
hash_entry = hv_fetch(tallies, (char *)&v, sizeof(I32), 0);
if (!hash_entry) {
hash_entry = hv_store(tallies, (char *)&v, sizeof(I32), newRV_noinc((SV*) newHV()), 0);
}
sub_hash_entry = hv_fetch((HV*) SvRV(*hash_entry), (char *)&(instance->result), sizeof(int), 1);
if (!SvIOK(*sub_hash_entry)) sv_setiv(*sub_hash_entry, 0);
sv_setiv( *sub_hash_entry, 1+SvIV(*sub_hash_entry) );
}
RETVAL = num_undef;
/* Old code:
foreach (@$instances) {
my $v = $_->value_int($all_attr->{$attr});
next unless $v;
$totals{ $v }++;
$tallies{ $v }{ $_->result_int }++;
}
*/
}
lib/AI/DecisionTree.pm view on Meta::CPAN
my ($hash, $key) = @_;
unless (exists $hash->{$key}) {
$hash->{$key} = 1 + keys %$hash;
}
return $hash->{$key};
}
sub _create_lookup_hashes {
my $self = shift;
my $h = $self->{results};
$self->{results_reverse} = [ undef, sort {$h->{$a} <=> $h->{$b}} keys %$h ];
foreach my $attr (keys %{$self->{attribute_values}}) {
my $h = $self->{attribute_values}{$attr};
$self->{attribute_values_reverse}{$attr} = [ undef, sort {$h->{$a} <=> $h->{$b}} keys %$h ];
}
}
sub train {
my ($self, %args) = @_;
if (not @{ $self->{instances} }) {
croak "Training data has been purged, can't re-train" if $self->{tree};
croak "Must add training instances before calling train()";
}
lib/AI/DecisionTree.pm view on Meta::CPAN
# Pick the most frequent result for this leaf
$node{result} = (sort {$results{$b} <=> $results{$a}} keys %results)[0];
return \%node;
}
$node{split_on} = $best_attr;
my %split;
foreach my $i (@$instances) {
my $v = $self->_value($i, $best_attr);
push @{$split{ defined($v) ? $v : '<undef>' }}, $i;
}
die ("Something's wrong: attribute '$best_attr' didn't split ",
scalar @$instances, " instances into multiple buckets (@{[ keys %split ]})")
unless keys %split > 1;
foreach my $value (keys %split) {
$node{children}{$value} = $self->_expand_node( instances => $split{$value} );
}
return \%node;
}
sub best_attr {
my ($self, $instances) = @_;
# 0 is a perfect score, entropy(#instances) is the worst possible score
my ($best_score, $best_attr) = (@$instances * $self->entropy( map $_->result_int, @$instances ), undef);
my $all_attr = $self->{attributes};
foreach my $attr (keys %$all_attr) {
# %tallies is correlation between each attr value and result
# %total is number of instances with each attr value
my (%totals, %tallies);
my $num_undef = AI::DecisionTree::Instance::->tally($instances, \%tallies, \%totals, $all_attr->{$attr});
next unless keys %totals; # Make sure at least one instance defines this attribute
my $score = 0;
while (my ($opt, $vals) = each %tallies) {
$score += $totals{$opt} * $self->entropy2( $vals, $totals{$opt} );
}
($best_attr, $best_score) = ($attr, $score) if $score < $best_score;
}
lib/AI/DecisionTree.pm view on Meta::CPAN
# my $confidence = P(H|D) = [P(D|H)P(H)]/[P(D|H)P(H)+P(D|H')P(H')]
# = [P(D|H)P(H)]/P(D);
# my $confidence =
# $confidence *= $self->{prior_freqs}{$r} / $self->{total_instances};
return ($r, $confidence, \%dist);
}
my $instance_val = (exists $args{callback} ? $args{callback}->($tree->{split_on}) :
exists $args{attributes}{$tree->{split_on}} ? $args{attributes}{$tree->{split_on}} :
'<undef>');
## no critic (ProhibitExplicitReturnUndef)
$tree = $tree->{children}{ $instance_val }
or return undef;
}
}
sub as_graphviz {
my ($self, %args) = @_;
my $colors = delete $args{leaf_colors} || {};
require GraphViz;
my $g = GraphViz->new(%args);
my $id = 1;
lib/AI/DecisionTree.pm view on Meta::CPAN
Builds the decision tree from the list of training instances. If a
numeric C<max_depth> parameter is supplied, the maximum tree depth can
be controlled (see also the C<new()> method).
=item get_result(attributes => \%hash)
Returns the most likely result (from the set of all results given to
C<add_instance()>) for the set of attribute values given. An
C<attributes> parameter specifies a hash of attribute-value pairs for
the instance. If the decision tree doesn't have enough information to
find a result, it will return C<undef>.
=item do_purge()
Purges training instances and their associated information from the
DecisionTree object. This can save memory after training, and since
the training instances are implemented as C structs, this turns the
DecisionTree object into a pure-perl data structure that can be more
easily saved with C<Storable.pm>, for instance.
=item purge()
( run in 4.492 seconds using v1.01-cache-2.11-cpan-d80b1682f3f )