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 )