Algorithm-Classifier-IsolationForest
view release on metacpan or search on metacpan
lib/Algorithm/Classifier/IsolationForest/Online.pm view on Meta::CPAN
n_trees => $args{n_trees} // 100,
window_size => $window_size,
max_leaf_samples => $args{max_leaf_samples} // 32,
growth => $growth,
subsample => $args{subsample} // 1.0,
seed => $args{seed},
contamination => $args{contamination},
missing => $missing,
feature_names => $args{feature_names},
threshold => undef, # learned lazily if contamination set
n_features => undef, # learned from the first row
seen => 0, # total points learned over the model's lifetime
window => [], # the retained rows, oldest first
trees => [],
mungers => undef, # optional Algorithm::ToNumberMunger spec hash
# Opaque schema metadata, usually set via the parent class's
# new_from_prototype and persisted with the model.
schema_version => $args{schema_version},
schema_description => $args{schema_description},
feature_descriptions => $args{feature_descriptions},
_use_c => $use_c,
_use_openmp => $use_openmp,
};
for my $doc (qw(schema_version schema_description)) {
croak "$doc must be a plain string"
if defined $self->{$doc} && ref $self->{$doc};
}
Algorithm::Classifier::IsolationForest::_validate_feature_descriptions( $self->{feature_names},
$self->{feature_descriptions} )
if defined $self->{feature_descriptions};
# Optional Algorithm::ToNumberMunger integration, identical to the
# parent's: compiled eagerly so spec errors surface here; the module
# is only required when a spec is actually given.
if ( defined $args{mungers} ) {
croak "mungers must be a hashref of 'tag => munger spec'"
unless ref $args{mungers} eq 'HASH';
croak "mungers requires feature_names (the munger plan compiles against them)"
unless ref $self->{feature_names} eq 'ARRAY' && @{ $self->{feature_names} };
$self->{mungers} = $args{mungers};
$self->{_munger_plan}
= Algorithm::Classifier::IsolationForest::_compile_mungers( $self->{feature_names}, $self->{mungers} );
$self->{munger_module_version} = $Algorithm::ToNumberMunger::VERSION;
} ## end if ( defined $args{mungers} )
croak "n_trees must be >= 1" unless $self->{n_trees} >= 1;
croak "max_leaf_samples must be >= 1" unless $self->{max_leaf_samples} >= 1;
croak "window_size must be 0 (unbounded) or >= max_leaf_samples"
if $self->{window_size} && $self->{window_size} < $self->{max_leaf_samples};
croak "subsample must be in (0, 1]"
unless $self->{subsample} > 0 && $self->{subsample} <= 1;
croak "contamination must be a number in (0, 0.5]"
if defined $self->{contamination}
&& !( $self->{contamination} > 0 && $self->{contamination} <= 0.5 );
$self->{trees} = [ map { { root => undef, count => 0, depth_limit => 0 } } 1 .. $self->{n_trees} ];
srand( $self->{seed} ) if defined $self->{seed};
return bless $self, $class;
} ## end sub new
=head2 learn(\@data)
Learns the passed samples, in order, as the next points of the stream.
Once the model has seen more than C<window_size> points, each learned
point also forgets the oldest retained point, so the model tracks the
most recent C<window_size> points.
The data format matches the parent class's C<fit>: an arrayref of
arrayrefs, each inner arrayref one sample of numeric features. All
samples must have the same feature count; the count is locked in by the
first sample ever learned.
Returns C<$self>, so it chains.
$oif->learn(\@rows);
=cut
sub learn {
my ( $self, $data ) = @_;
croak "learn() expects a non-empty arrayref of samples"
unless ref $data eq 'ARRAY' && @$data;
for my $row (@$data) {
$self->_learn_row( $self->_prep_row( $row, 'learn' ) );
}
return $self;
}
=head2 learn_tagged(\%row)
=head2 learn_tagged(\@rows)
Learns one sample supplied as a hashref of named feature values, or a
whole batch supplied as an arrayref of such hashrefs, in stream order.
The model must have C<feature_names> set. Rows go through
L</tagged_row_to_array> (and therefore through the munger plan when
C<mungers> is configured). Returns C<$self>.
$oif->learn_tagged({ cpu => 0.9, mem => 0.4, disk => 0.1 });
$oif->learn_tagged(\@hashref_rows);
Croaks under the same conditions as L</tagged_row_to_array>, naming the
offending row by index in the batch form.
=cut
sub learn_tagged {
my ( $self, $row ) = @_;
if ( ref $row eq 'ARRAY' ) {
my @rows;
for my $i ( 0 .. $#$row ) {
push @rows, $self->tagged_row_to_array( $row->[$i], "learn_tagged (row $i)" );
}
return $self->learn( \@rows );
}
my $vec = $self->tagged_row_to_array( $row, 'learn_tagged' );
return $self->learn( [$vec] );
} ## end sub learn_tagged
lib/Algorithm/Classifier/IsolationForest/Online.pm view on Meta::CPAN
mungers => $self->{mungers},
munger_module_version => $self->{munger_module_version},
schema_version => $self->{schema_version},
schema_description => $self->{schema_description},
feature_descriptions => $self->{feature_descriptions},
},
trees => [ map { { count => $_->{count}, root => $_->{root} } } @{ $self->{trees} } ],
window => $self->{window},
};
return JSON::PP->new->canonical(1)->encode($payload);
} ## end sub to_json
=head2 from_json($json)
Init the object from the model in the specified JSON string.
my $oif = Algorithm::Classifier::IsolationForest::Online->from_json($json);
=cut
sub from_json {
my ( $class, $text ) = @_;
my $payload = JSON::PP->new->decode($text);
croak "not an online IsolationForest model"
unless ref $payload eq 'HASH'
&& defined $payload->{format}
&& $payload->{format} eq 'Algorithm::Classifier::IsolationForest::Online';
my $p = $payload->{params} || {};
my $self = {
n_trees => $p->{n_trees},
window_size => $p->{window_size} // 0,
max_leaf_samples => $p->{max_leaf_samples},
growth => $p->{growth} // 'adaptive',
subsample => $p->{subsample} // 1.0,
seed => undef,
contamination => $p->{contamination},
threshold => $p->{threshold},
n_features => $p->{n_features},
missing => $p->{missing} // 'die',
feature_names => $p->{feature_names},
# Recompiled lazily on first tagged use, like the parent.
mungers => $p->{mungers},
munger_module_version => $p->{munger_module_version},
# Opaque schema metadata; absent in models saved before prototype
# support, which just means "none recorded".
schema_version => $p->{schema_version},
schema_description => $p->{schema_description},
feature_descriptions => $p->{feature_descriptions},
seen => $p->{seen} // 0,
window => $payload->{window} // [],
trees => [],
_use_c => $Algorithm::Classifier::IsolationForest::HAS_C,
_use_openmp => $Algorithm::Classifier::IsolationForest::HAS_OPENMP,
};
my $trees = $payload->{trees};
croak "model contains no trees" unless ref $trees eq 'ARRAY' && @$trees;
my $model = bless $self, $class;
# depth_limit is a pure function of the tree's count, so recompute it
# rather than trusting a stored float.
$self->{trees}
= [ map { { count => $_->{count}, root => $_->{root}, depth_limit => $model->_rpl( $_->{count} ) } } @$trees ];
return $model;
} ## end sub from_json
=head2 save($path)
Saves the model to the specified path.
$oif->save($path);
=cut
sub save {
my ( $self, $path ) = @_;
write_file( $path, { 'atomic' => 1 }, $self->to_json );
}
=head2 load($path)
Init the object from the model in the specified file.
my $oif = Algorithm::Classifier::IsolationForest::Online->load($path);
=cut
sub load {
my ( $class, $path ) = @_;
my $raw_model = read_file($path);
return $class->from_json($raw_model);
}
=head2 to_prototype
Returns a prototype JSON string extracted from this model: its variable
schema (feature_names, feature_descriptions, mungers, missing policy)
plus its current tuning knobs, with C<"class": "online">. Identical
semantics to the parent class's method -- see PROTOTYPES in
L<Algorithm::Classifier::IsolationForest> for the file format and the
croak/placeholder rules. C<seed> is not emitted; pass it as an override
when creating from the prototype.
my $proto_json = $oif->to_prototype;
=cut
sub to_prototype {
my ($self) = @_;
croak "to_prototype: this model has no feature_names; a prototype's variable " . "schema needs named features"
unless ref $self->{feature_names} eq 'ARRAY' && @{ $self->{feature_names} };
my $schema = {
feature_names => $self->{feature_names},
missing => $self->{missing},
};
$schema->{feature_descriptions} = $self->{feature_descriptions}
( run in 0.629 second using v1.01-cache-2.11-cpan-0b5f733616e )