view release on metacpan or search on metacpan
lib/Algorithm/Classifier/IsolationForest/App/Command/bench.pm view on Meta::CPAN
package Algorithm::Classifier::IsolationForest::App::Command::bench;
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest ();
use Algorithm::Classifier::IsolationForest::App -command;
use File::Slurp qw(read_file);
use Scalar::Util qw(looks_like_number);
use Time::HiRes qw(time);
sub opt_spec {
return (
[
'm=s',
'Input model JSON file path/name.',
{ 'default' => 'iforest_model.json', 'completion' => 'files' }
],
[ 'i=s', 'Input CSV (rows of features to score).', { 'completion' => 'files' } ],
lib/Algorithm/Classifier/IsolationForest/App/Command/bench.pm view on Meta::CPAN
for my $row ( read_file($path) ) {
$line++;
chomp $row;
next if $row =~ /^\s*$/;
my @f = split /,/, $row, -1;
$expected //= scalar @f;
die "line $line of '$path' has $row but expected $expected columns\n"
if scalar @f != $expected;
for my $v (@f) {
die "line $line of '$path' value '$v' is not numeric\n"
unless looks_like_number($v);
}
push @data, \@f;
} ## end for my $row ( read_file($path) )
return ( \@data, $expected );
} ## end sub _read_csv
sub execute {
my ( $self, $opt, $args ) = @_;
my $model = Algorithm::Classifier::IsolationForest->load( $opt->{'m'} );
lib/Algorithm/Classifier/IsolationForest/App/Command/fit.pm view on Meta::CPAN
package Algorithm::Classifier::IsolationForest::App::Command::fit;
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest ();
use Algorithm::Classifier::IsolationForest::App -command;
use File::Slurp qw(read_file write_file);
use Scalar::Util qw(looks_like_number);
sub opt_spec {
return (
[ 'i=s', 'CSV to use.', { completion => 'files' } ],
[ 'o=s', 'Output JSON file path/name.', { 'default' => 'iforest_model.json', 'completion' => 'files' } ],
[ 'p', 'Print the results instead of saving it.' ],
[ 'w', 'Overwrite the file if it already exists.' ],
[ 's=i', 'Seed int' ],
[ 'extended', 'Use EIF instead of IF.' ],
[ 'n=i', 'Number of isolation trees in the ensemble' ],
lib/Algorithm/Classifier/IsolationForest/App/Command/fit.pm view on Meta::CPAN
if ( !$has_mungers ) {
my $col_int = 1;
for my $field (@fields) {
die( 'Line '
. $line_int . ' of "'
. $opt->{'i'}
. '" value for column '
. $col_int . ',"'
. $field
. '", does not appear to be a number' )
unless looks_like_number($field);
$col_int++;
} ## end for my $field (@fields)
} ## end if ( !$has_mungers )
push @data, \@fields;
$line_int++;
} ## end foreach my $line ( read_file( $opt->{'i'} ) )
# The tag count must match the CSV width whether the tags came from -t
lib/Algorithm/Classifier/IsolationForest/App/Command/fit.pm view on Meta::CPAN
my $munged = $iforest->munge_rows( \@data );
for my $i ( 0 .. $#$munged ) {
for my $col ( 0 .. $#{ $munged->[$i] } ) {
die( 'Line '
. ( $i + 1 ) . ' of "'
. $opt->{'i'}
. '" value for column '
. ( $col + 1 ) . ',"'
. ( defined $munged->[$i][$col] ? $munged->[$i][$col] : 'undef' )
. '", is not a number after munging' )
unless looks_like_number( $munged->[$i][$col] );
} ## end for my $col ( 0 .. $#{ $munged->[$i] } )
} ## end for my $i ( 0 .. $#$munged )
@data = @$munged;
} ## end if ($has_mungers)
$iforest->fit( \@data );
my $model = $iforest->to_json;
if ( $opt->{'p'} ) {
lib/Algorithm/Classifier/IsolationForest/App/Command/pack.pm view on Meta::CPAN
package Algorithm::Classifier::IsolationForest::App::Command::pack;
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest ();
use Algorithm::Classifier::IsolationForest::App -command;
use File::Slurp qw(read_file write_file);
use Scalar::Util qw(looks_like_number);
# .iforest-packed v1 file layout (all little-endian):
#
# offset size field
# -----------------------------------------------------------
# 0 8 magic -- ASCII "IFPKD\0\0\0"
# 8 2 version (uint16, currently 1)
# 10 2 reserved (uint16, must be 0)
# 12 4 n_pts (uint32)
# 16 4 n_feats (uint32)
lib/Algorithm/Classifier/IsolationForest/App/Command/pack.pm view on Meta::CPAN
my $line = 0;
for my $row ( read_file( $opt->{'i'} ) ) {
$line++;
chomp $row;
next if $row =~ /^\s*$/;
my @f = split /,/, $row, -1;
die "line $line of '$opt->{i}' has " . scalar(@f) . " columns but model has $nf features\n"
unless scalar @f == $nf;
for my $v (@f) {
die "line $line of '$opt->{i}' value '$v' is not numeric\n"
unless looks_like_number($v);
}
push @data, \@f;
} ## end for my $row ( read_file( $opt->{'i'} ) )
die "input '$opt->{i}' contains no rows\n" unless @data;
my $packed = $model->pack_data( \@data );
_write_packed( $opt->{'o'}, $packed->n_pts, $packed->n_feats, $packed->{packed} );
printf "wrote %s (%d rows, %d features, %d bytes payload)\n",
$opt->{'o'}, $packed->n_pts, $packed->n_feats,
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' } ],
lib/Algorithm/Classifier/IsolationForest/App/Command/predict.pm view on Meta::CPAN
if ( !$has_mungers ) {
my $col_int = 1;
for my $field (@fields) {
die( 'Line '
. $line_int . ' of "'
. $opt->{'i'}
. '" value for column '
. $col_int . ',"'
. $field
. '", does not appear to be a number' )
unless looks_like_number($field);
$col_int++;
} ## end for my $field (@fields)
} ## end if ( !$has_mungers )
push @data, \@fields;
$line_int++;
} ## end foreach my $line ( read_file( $opt->{'i'} ) )
if ($has_mungers) {
lib/Algorithm/Classifier/IsolationForest/App/Command/predict.pm view on Meta::CPAN
my $munged = $iforest->munge_rows( \@data );
for my $i ( 0 .. $#$munged ) {
for my $col ( 0 .. $#{ $munged->[$i] } ) {
die( 'Line '
. ( $i + 1 ) . ' of "'
. $opt->{'i'}
. '" value for column '
. ( $col + 1 ) . ',"'
. ( defined $munged->[$i][$col] ? $munged->[$i][$col] : 'undef' )
. '", is not a number after munging' )
unless looks_like_number( $munged->[$i][$col] );
} ## end for my $col ( 0 .. $#{ $munged->[$i] } )
} ## end for my $i ( 0 .. $#$munged )
$score_input = $munged;
} else {
$score_input = \@data;
}
} ## end else [ if ( Algorithm::Classifier::IsolationForest::App::Command::pack::is_packed_file...)]
my $results = $iforest->score_predict_samples( $score_input, $opt->{'t'} );
lib/Algorithm/Classifier/IsolationForest/App/Command/set_voting.pm view on Meta::CPAN
package Algorithm::Classifier::IsolationForest::App::Command::set_voting;
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest ();
use Algorithm::Classifier::IsolationForest::App -command;
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' }
],
[
'voting=s',
lib/Algorithm/Classifier/IsolationForest/App/Command/set_voting.pm view on Meta::CPAN
my $col_int = 1;
for my $field (@fields) {
die( 'Line '
. $line_int . ' of "'
. $opt->{'i'}
. '" value for column '
. $col_int . ',"'
. $field
. '", does not appear to be a number' )
unless looks_like_number($field);
$col_int++;
} ## end for my $field (@fields)
push @data, \@fields;
$line_int++;
} ## end foreach my $line ( read_file( $opt->{'i'} ) )
} ## end if ( defined( $opt->{'i'} ) )
# set_voting ignores the data argument unless it actually recalibrates, so
lib/Algorithm/Classifier/IsolationForest/App/Command/stream.pm view on Meta::CPAN
package Algorithm::Classifier::IsolationForest::App::Command::stream;
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest ();
use Algorithm::Classifier::IsolationForest::Online ();
use Algorithm::Classifier::IsolationForest::App -command;
use File::Slurp qw(read_file write_file);
use Scalar::Util qw(looks_like_number);
sub opt_spec {
return (
[
'm=s',
'Online model JSON file path/name. Created if it does not exist; resumed and updated if it does.',
{ 'default' => 'oiforest_model.json', 'completion' => 'files' }
],
[ 'i=s', 'Input CSV to stream through the model, in row order.', { 'completion' => 'files' } ],
[ 'o=s', 'Output the scores to this file instead of printing.', { 'completion' => 'files' } ],
lib/Algorithm/Classifier/IsolationForest/App/Command/stream.pm view on Meta::CPAN
if ( !$has_mungers ) {
my $col_int = 1;
for my $field (@fields) {
die( 'Line '
. $line_int . ' of "'
. $opt->{'i'}
. '" value for column '
. $col_int . ',"'
. $field
. '", does not appear to be a number' )
unless looks_like_number($field);
$col_int++;
} ## end for my $field (@fields)
} ## end if ( !$has_mungers )
push @data, \@fields;
$line_int++;
} ## end foreach my $line ( read_file( $opt->{'i'} ) )
# A prototype-created model already carries its tags; hold them to the
lib/Algorithm/Classifier/IsolationForest/App/Command/stream.pm view on Meta::CPAN
my $munged = $oif->munge_rows( \@data );
for my $i ( 0 .. $#$munged ) {
for my $col ( 0 .. $#{ $munged->[$i] } ) {
die( 'Line '
. ( $i + 1 ) . ' of "'
. $opt->{'i'}
. '" value for column '
. ( $col + 1 ) . ',"'
. ( defined $munged->[$i][$col] ? $munged->[$i][$col] : 'undef' )
. '", is not a number after munging' )
unless looks_like_number( $munged->[$i][$col] );
} ## end for my $col ( 0 .. $#{ $munged->[$i] } )
} ## end for my $i ( 0 .. $#$munged )
$stream_rows = $munged;
} ## end if ($has_mungers)
# --- stream ------------------------------------------------------------
my $results_string = '';
if ( $opt->{'learn_only'} ) {
$oif->learn($stream_rows);
} else {
lib/Algorithm/Classifier/IsolationForest/App/Command/streamc.pm view on Meta::CPAN
package Algorithm::Classifier::IsolationForest::App::Command::streamc;
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest::App -command;
use File::Slurp qw(write_file);
use File::Spec ();
use Scalar::Util qw(looks_like_number);
use IO::Socket::UNIX ();
use IO::Select ();
# JSON::MaybeXS codec and the connected socket, set up in execute.
my $JSON;
my $SOCK;
my $TIMEOUT;
my $READ_BUF = '';
sub opt_spec {
lib/Algorithm/Classifier/IsolationForest/App/Command/streamc.pm view on Meta::CPAN
. $line_int
. ' of input has '
. scalar(@fields)
. ' columns but expected '
. $expected_cols );
}
# Numeric-looking fields travel as JSON numbers, everything
# else as strings for the daemon's munger plan to handle; the
# daemon owns validation either way.
push @rows, [ map { looks_like_number($_) ? 0 + $_ : $_ } @fields ];
push @raw, $line;
} ## end else [ if ( $opt->{'jsonl'} ) ]
$flush->() if scalar @rows >= $opt->{'batch'};
} ## end while ( my $line = <$in_fh> )
$flush->();
if ( defined $opt->{'o'} ) {
write_file( $opt->{'o'}, { 'atomic' => 1 }, $results );
}
lib/Algorithm/Classifier/IsolationForest/App/Command/streamd.pm view on Meta::CPAN
use strict;
use warnings;
use Algorithm::Classifier::IsolationForest ();
use Algorithm::Classifier::IsolationForest::Online ();
use Algorithm::Classifier::IsolationForest::App -command;
use File::Slurp qw(read_file write_file);
use File::Path qw(make_path);
use File::Basename qw(dirname);
use File::Spec ();
use Scalar::Util qw(looks_like_number);
use IO::Socket::UNIX ();
use IO::Select ();
use POSIX qw(setsid strftime);
use Errno qw();
# The daemon is a singleton per process, so its runtime state lives in
# file-scoped lexicals rather than being threaded through every helper.
my $JSON; # JSON::MaybeXS codec (required at runtime, see execute)
my $OIF; # the online model
my %OPT; # resolved options
lib/Algorithm/Classifier/IsolationForest/App/Command/streamd.pm view on Meta::CPAN
if ( ref $OIF->{mungers} eq 'HASH' && %{ $OIF->{mungers} } ) {
$vec = $OIF->munge_rows( [$row] )->[0];
}
} else {
die 'row must be a JSON array (positional) or object (tagged)' . "\n";
}
for my $col ( 0 .. $#$vec ) {
next if !defined $vec->[$col]; # undef defers to the model's missing policy
die 'column ' . ( $col + 1 ) . ' is not a number after munging' . "\n"
unless looks_like_number( $vec->[$col] );
}
if ( $mode eq 'learn' ) {
$OIF->learn( [$vec] );
$DIRTY = 1;
return undef;
}
if ( $mode eq 'score' ) {
return $OIF->score_samples( [$vec] )->[0];
}
t/33-parallel-fit.t view on Meta::CPAN
#!perl
# 33-parallel-fit.t
#
# Verifies the parallel_fit option:
# 1. Produces a fitted model with the requested number of trees.
# 2. score_samples on a held-out point looks like a normal score
# (between 0 and 1, and clearly separates an obvious outlier).
# 3. Re-running the parallel fit with the same seed and worker count
# gives bit-identical scores (cross-run reproducibility, which is
# the parallel_fit contract -- serial-vs-parallel differs but
# parallel-vs-parallel does not).
# 4. parallel_fit on a no-fork platform falls back silently to serial.
use strict;
use warnings;
use Test::More;