Algorithm-Classifier-IsolationForest
view release on metacpan or search on metacpan
lib/Algorithm/Classifier/IsolationForest/App/Command/predict.pm view on Meta::CPAN
package Algorithm::Classifier::IsolationForest::App::Command::predict;
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest ();
use Algorithm::Classifier::IsolationForest::App -command;
use Algorithm::Classifier::IsolationForest::App::Command::pack ();
use File::Slurp qw(read_file write_file);
use Scalar::Util qw(looks_like_number);
sub opt_spec {
return (
[
'm=s',
'Input model JSON file path/name.',
{ 'default' => 'iforest_model.json', 'completion' => 'files' }
],
[ 'i=s', 'Input CSV for processing.', { 'completion' => 'files' } ],
[ 'o=s', 'Output to this file instead of printing.', { 'completion' => 'files' } ],
[ 'w', 'If the file specified via -o exists, over write it.', { 'completion' => 'files' } ],
[ 't=f', 'Alternative threshold value to use. 0 < $val < 1' ],
[ 'd', 'Include the input data in the output.' ],
);
} ## end sub opt_spec
sub abstract { 'Processes the data using the score_predict_samples using the specified model' }
sub description {
'Processes the data using the score_predict_samples using the specified model.
The input may be either a CSV (one row of features per line) or a
.iforest-packed binary produced by `iforest pack` (auto-detected via
its magic bytes; cuts the CSV parse + pack_input_xs cost on repeated
runs against the same dataset).
The input CSV may have any number of feature columns; every row must have the
same column count and every value must be numeric.
Output format is as below per line.
$score,$predict
If -d is specified all input feature columns are prepended. When the
input is a .iforest-packed file the columns come from unpacking the
stored doubles.
$feat1,...,$featN,$score,$predict
';
} ## end sub description
sub validate {
my ( $self, $opt, $args ) = @_;
if ( !defined( $opt->{'i'} ) ) {
$self->usage_error('-i has not been specified for a file to process');
} elsif ( !-f $opt->{'i'} ) {
$self->usage_error( '-i, "' . $opt->{'i'} . '", is not a file or does not exist' );
} elsif ( !-r $opt->{'i'} ) {
$self->usage_error( '-i, "' . $opt->{'i'} . '", is not readable' );
}
if ( !-f $opt->{'m'} ) {
$self->usage_error( '-m, "' . $opt->{'m'} . '", is not a file or does not exist' );
} elsif ( !-r $opt->{'m'} ) {
$self->usage_error( '-m, "' . $opt->{'m'} . '", is not readable' );
}
if ( defined( $opt->{'o'} ) && !$opt->{'w'} && -e $opt->{'o'} ) {
$self->usage_error( '-o, "' . $opt->{'o'} . '", already exists and -w is not specified' );
}
if ( defined( $opt->{'t'} ) && $opt->{'t'} <= 0 ) {
$self->usage_error( '-t, "' . $opt->{'t'} . '", needs to be greater than 0 and less than 1' );
} elsif ( defined( $opt->{'t'} ) && $opt->{'t'} >= 1 ) {
$self->usage_error( '-t, "' . $opt->{'t'} . '", needs to be greater than 0 and less than 1' );
}
return 1;
} ## end sub validate
sub execute {
my ( $self, $opt, $args ) = @_;
my $iforest = Algorithm::Classifier::IsolationForest->load( $opt->{'m'} );
# A model carrying Algorithm::ToNumberMunger specs takes raw values in
# its munged CSV columns: skip the per-field numeric check at read
# time and munge the rows before scoring (re-checking numerics after).
# Packed input is never munged -- it is already doubles.
my $has_mungers = ref $iforest->{mungers} eq 'HASH' && %{ $iforest->{mungers} } ? 1 : 0;
my @data; # arrayref-of-arrayrefs OR re-derived on demand from $packed
my $score_input; # what we hand to score_predict_samples
if ( Algorithm::Classifier::IsolationForest::App::Command::pack::is_packed_file( $opt->{'i'} ) ) {
my ( $n_pts, $n_feats, $bytes )
= Algorithm::Classifier::IsolationForest::App::Command::pack::read_packed_file( $opt->{'i'} );
die "packed input has $n_feats features but model expects " . $iforest->{n_features} . "\n"
if $n_feats != $iforest->{n_features};
# Build a PackedData wrapper directly from the on-disk bytes --
# no CSV parse, no pack_input_xs.
$score_input = bless {
packed => $bytes,
n_pts => $n_pts,
n_feats => $n_feats,
},
'Algorithm::Classifier::IsolationForest::PackedData';
# Only unpack to per-row arrayrefs when -d asks for it, since
# that work undoes the whole point of using a packed file.
if ( $opt->{'d'} ) {
my @doubles = unpack( 'd*', $bytes );
for my $i ( 0 .. $n_pts - 1 ) {
push @data, [ @doubles[ $i * $n_feats .. ( $i + 1 ) * $n_feats - 1 ] ];
( run in 1.754 second using v1.01-cache-2.11-cpan-995e09ba956 )