Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

lib/Algorithm/Classifier/IsolationForest/Online.pm  view on Meta::CPAN

		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 or \@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|/tagged_row_to_array(\%row, $caller)> (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|/tagged_row_to_array(\%row, $caller)>, 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

=head2 score_learn(\@data)

Prequential (test-then-train) operation, the usual way to run a streaming
detector: each sample is scored against the model as it stood I<before>
that sample was learned, then learned.  Returns an arrayref of anomaly
scores, one per sample, in input order.

Unlike the pure scoring methods this works on a brand-new model too (the
first points of a stream simply score 1.0, as nothing is known yet).

    my $scores = $oif->score_learn(\@rows);



( run in 1.434 second using v1.01-cache-2.11-cpan-800906f7e73 )