Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

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

    }

The fields, top to bottom...

  - format :: required, always the string
        'Algorithm::Classifier::IsolationForest::Prototype'.  A prototype
        handed to load() (or a model handed to the prototype methods)
        dies with a clear message instead of half-working.

  - version :: the prototype format version; this release reads version 1.
      default :: 1

  - class :: required, 'batch' (this class) or 'online'
        (L<Algorithm::Classifier::IsolationForest::Online>).  Prototypes
        are self-describing; `iforest fit` refuses an online prototype
        and `iforest stream` refuses a batch one.  Two model types with
        the same variables means two prototype files.

  - schema_version :: required opaque string, never parsed or compared
        numerically.  User-owned: bump it when the variable schema
        changes.

  - schema_description :: required opaque free-text string describing
        what this variable schema is, so a model file explains itself
        months later.

  - schema :: required object holding the variable schema.
        feature_names is required (order = CSV column order); the
        optional keys are feature_descriptions ('feature name => free
        text', every key must name an entry in feature_names, partial
        coverage fine), mungers (see L</MUNGERS>), missing, and -- batch
        prototypes only -- impute_with.  Unknown keys croak.

  - params :: optional object of tuning knobs, whitelisted per class.
        Batch: n_trees, sample_size, max_depth, mode, extension_level,
        contamination, voting, seed.  Online: n_trees, window_size,
        max_leaf_samples, growth, subsample, contamination, seed.
        Unknown keys croak -- a typo'd knob silently falling back to its
        default is exactly the failure mode a prototype exists to
        prevent.  Machine-local knobs (use_c, use_openmp, use_openmp_fit,
        parallel_fit) are rejected: they describe the box the model runs
        on, not the model.

=cut

# Per-class whitelists for a prototype's params block (and
# new_from_prototype's %overrides) and its schema block.  Machine-local
# knobs are deliberately absent from the params lists.
my %PROTO_PARAM_KEYS = (
	batch  => { map { $_ => 1 } qw(n_trees sample_size max_depth mode extension_level contamination voting seed) },
	online => { map { $_ => 1 } qw(n_trees window_size max_leaf_samples growth subsample contamination seed) },
);
my %PROTO_SCHEMA_KEYS = (
	batch  => { map { $_ => 1 } qw(feature_names feature_descriptions mungers missing impute_with) },
	online => { map { $_ => 1 } qw(feature_names feature_descriptions mungers missing) },
);

=head2 validate_prototype($proto)

Structurally validates a prototype -- a hashref or a JSON string -- and
returns the decoded hashref; croaks describing the first problem found.
Validation is structural only (no munger compilation), so it does not
require Algorithm::ToNumberMunger even for a munger-bearing prototype.

    my $proto = Algorithm::Classifier::IsolationForest->validate_prototype($json);

=cut

sub validate_prototype {
	my ( $class, $proto ) = @_;

	if ( !ref $proto ) {
		my $decoded = eval { JSON::PP->new->decode($proto) };
		croak "prototype did not parse as JSON: $@" if $@;
		$proto = $decoded;
	}
	croak "not an IsolationForest prototype (expected a JSON object)"
		unless ref $proto eq 'HASH';
	croak "not an IsolationForest prototype (format is not " . "'Algorithm::Classifier::IsolationForest::Prototype')"
		unless defined $proto->{format}
		&& !ref $proto->{format}
		&& $proto->{format} eq 'Algorithm::Classifier::IsolationForest::Prototype';

	my $version = $proto->{version} // 1;
	croak "prototype format version '$version' is newer than this module understands (max 1)"
		if !ref $version && $version =~ /^\d+$/ && $version > 1;

	for my $k ( sort keys %$proto ) {
		croak "prototype has unknown top-level key '$k'"
			unless $k =~ /\A(?:format|version|class|schema_version|schema_description|schema|params)\z/;
	}

	my $which = $proto->{class};
	croak "prototype needs a class of 'batch' or 'online'"
		unless defined $which && !ref $which && $which =~ /\A(?:batch|online)\z/;

	for my $req (qw(schema_version schema_description)) {
		croak "prototype needs a non-empty $req string"
			unless defined $proto->{$req} && !ref $proto->{$req} && length $proto->{$req};
	}

	my $schema = $proto->{schema};
	croak "prototype needs a schema object" unless ref $schema eq 'HASH';
	for my $k ( sort keys %$schema ) {
		croak "prototype schema has unknown key '$k' for a $which prototype (allowed: "
			. join( ', ', sort keys %{ $PROTO_SCHEMA_KEYS{$which} } ) . ')'
			unless $PROTO_SCHEMA_KEYS{$which}{$k};
	}
	my $tags = $schema->{feature_names};
	croak "prototype schema needs a non-empty feature_names array"
		unless ref $tags eq 'ARRAY' && @$tags;
	for my $t (@$tags) {
		croak "prototype feature_names entries must be non-empty strings"
			unless defined $t && !ref $t && length $t;
	}
	_validate_feature_descriptions( $tags, $schema->{feature_descriptions} )
		if defined $schema->{feature_descriptions};
	croak "prototype schema mungers must be an object of 'tag => munger spec'"
		if defined $schema->{mungers} && ref $schema->{mungers} ne 'HASH';
	for my $str (qw(missing impute_with)) {
		croak "prototype schema $str must be a plain string"
			if defined $schema->{$str} && ref $schema->{$str};
	}

	my $params = $proto->{params};
	croak "prototype params must be an object of tuning knobs"
		if defined $params && ref $params ne 'HASH';
	for my $k ( sort keys %{ $params || {} } ) {
		croak "prototype params has unknown key '$k' for a $which prototype (allowed: "
			. join( ', ', sort keys %{ $PROTO_PARAM_KEYS{$which} } )
			. '; machine-local knobs like use_c are deliberately not allowed)'
			unless $PROTO_PARAM_KEYS{$which}{$k};
	}

	return $proto;



( run in 1.772 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )