view release on metacpan or search on metacpan
Makefile.PL view on Meta::CPAN
# what Inline::MakeMaker generates, with two differences: only the one
# module that actually embeds C gets a rule (Inline::MakeMaker emits one
# per .pm in lib/), and install mode is signalled to the module via
# IF_INSTALL_BUILD=1 in the rule's environment instead of a global
# -MInline=_INSTALL_ import, so the module can pass Inline's _INSTALL_
# config itself alongside NAME/VERSION. Inline's install mode reads the
# version and blib/arch destination from @ARGV. The trailing -e writes a
# stub .inl file satisfying the make dependency (also what
# Inline::MakeMaker does); the compiled object itself lands under
# blib/arch/auto/ and is picked up by `make install`.
sub MY::postamble {
return '' unless $prebuilt;
return <<'POSTAMBLE';
# --- Inline::C install-time build (generated by Makefile.PL):
Algorithm-Classifier-IsolationForest.inl : pm_to_blib
IF_INSTALL_BUILD=1 $(PERL) "-Mblib" "-MAlgorithm::Classifier::IsolationForest" -e"require Inline; my %A = (modinlname => 'Algorithm-Classifier-IsolationForest.inl', module => 'Algorithm::Classifier::IsolationForest'); my %S = (API => \%A); Inline::s...
dynamic :: Algorithm-Classifier-IsolationForest.inl
POSTAMBLE
} ## end sub MY::postamble
my %WriteMakefileArgs = (
NAME => 'Algorithm::Classifier::IsolationForest',
AUTHOR => q{Zane C. Bowers-Hadley <vvelox@vvelox.net>},
VERSION_FROM => 'lib/Algorithm/Classifier/IsolationForest.pm',
ABSTRACT_FROM => 'lib/Algorithm/Classifier/IsolationForest.pm',
LICENSE => 'lgpl_2_1',
MIN_PERL_VERSION => '5.006',
INST_SCRIPT => 'bin',
EXE_FILES => ['src_bin/iforest'],
lib/Algorithm/Classifier/IsolationForest.pm view on Meta::CPAN
* call in this function happens before the parallel region starts
* (single-threaded), so the result still varies with the model's
* `seed` the way every other code path does; it isn't used inside the
* parallel loop.
*
* Each tree is built entirely with plain C data (row-index int arrays,
* a growable TreeBuf of packed doubles/ints) -- no Perl API call
* happens anywhere inside the parallel region. Each node record in
* TreeBuf uses _pack_tree's 6-double SoA layout (see the file-top
* comment), but the node ORDER differs: records are appended
* post-order (a node is pushed after both its children, since child
* indices must be known first), so the root is the last record --
* _pack_tree's pre-order puts it at 0. _unpack_forest accounts for
* this. Oblique coefficients are also always stored sparse (in the
* random pool's order) -- the dense-pack fast path is skipped because
* its only purpose is speeding up score_all_xs, and _rebuild_c_trees
* reapplies it anyway once the caller unpacks these buffers back into
* the standard Perl tree shape and re-derives the scoring buffers.
*
* After the parallel region, each tree's TreeBuf is copied into a Perl
* string SV (one memcpy each, serially) and stored into nodes_rv /
lib/Algorithm/Classifier/IsolationForest.pm view on Meta::CPAN
int p = _partition_lomuto(a, lo, hi);
if (p == k) return a[p];
if (p < k) lo = p + 1; else hi = p - 1;
}
return a[lo];
}
/* Median of a[0..n-1] (reorders a[]). Odd n: the single middle order
* statistic. Even n: quickselect finds the lower-median at k = n/2-1,
* which leaves every a[i > k] >= a[k] (the standard quickselect
* post-condition) -- so the upper-median is just the min of that
* remaining slice, one more linear scan instead of a second full
* selection pass. */
static double _median_select(double *a, int n) {
if (n % 2 == 1) {
return _kth_smallest(a, n, n / 2);
} else {
int k = n / 2 - 1;
double lower = _kth_smallest(a, n, k);
double upper = a[k + 1];
int i;
lib/Algorithm/Classifier/IsolationForest.pm view on Meta::CPAN
return _unpack_forest( \@nodes, \@idx, \@val );
} ## end sub _build_forest_openmp
# Inverse of _pack_tree's SoA layout: given one tree's packed node
# buffer plus the shared idx/val coefficient buffers, reconstructs the
# ordinary nested-arrayref tree structure _build_tree/_build_node_c
# produce. li/ri fields hold the child's absolute node index, so this
# just follows them recursively from whatever index the caller says the
# root lives at. NOTE: _pack_tree numbers nodes DFS pre-order (root at
# 0), but build_forest_openmp_xs appends nodes post-order (children
# before parent), putting the root LAST -- the caller must pass the
# right root index for the buffer's origin.
sub _unpack_node {
my ( $nodes, $idx, $val, $node_i ) = @_;
my $off = $node_i * 6;
my $type = $nodes->[$off];
if ( $type == 0 ) {
return [ _NODE_LEAF, int( $nodes->[ $off + 1 ] ) ];
} elsif ( $type == 1 ) {
lib/Algorithm/Classifier/IsolationForest.pm view on Meta::CPAN
[ @{$val}[ $coff .. $coff + $num - 1 ] ],
$b,
_unpack_node( $nodes, $idx, $val, int($li) ),
_unpack_node( $nodes, $idx, $val, int($ri) ),
];
} ## end else [ if ( $type == 0 ) ]
} ## end sub _unpack_node
# Unpacks every tree in the three per-tree packed-buffer arrayrefs
# build_forest_openmp_xs returns into the ordinary nested tree shape.
# The C builder pushes nodes post-order (a node is recorded after both
# of its children), so each tree's root is the LAST node record, not
# index 0 as in _pack_tree's pre-order layout.
sub _unpack_forest {
my ( $nodes_list, $idx_list, $val_list ) = @_;
my @trees;
for my $i ( 0 .. $#$nodes_list ) {
my @nodes = unpack( 'd*', $nodes_list->[$i] );
my @idx = unpack( 'l*', $idx_list->[$i] );
my @val = unpack( 'd*', $val_list->[$i] );
my $root = @nodes / 6 - 1;
lib/Algorithm/Classifier/IsolationForest/App/Command/fit.pm view on Meta::CPAN
'extension_level' => $opt->{'e'},
'contamination' => $opt->{'c'},
'feature_names' => $opt->{'t'},
'voting' => $opt->{'voting'},
'mungers' => $mungers,
);
} ## end if ( !$iforest )
# Munge the raw rows into numbers, then run the numeric validation
# that was skipped at read time -- an unmunged column holding a
# string is still an error, just reported post-munge.
if ($has_mungers) {
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' )
lib/Algorithm/Classifier/IsolationForest/App/Command/stream.pm view on Meta::CPAN
for my $i ( 0 .. $#$scores ) {
my $label = $scores->[$i] >= $threshold ? 1 : 0;
if ( $opt->{'d'} ) {
$results_string .= join( ',', @{ $data[$i] } ) . ',' . $scores->[$i] . ',' . $label . "\n";
} else {
$results_string .= $scores->[$i] . ',' . $label . "\n";
}
}
} ## end else [ if ( $opt->{'learn_only'} ) ]
# Refresh the contamination threshold against the post-stream window so
# the saved model's default cutoff tracks the stream.
if ( !$opt->{'score_only'} && defined $oif->{contamination} && $oif->window_count ) {
$oif->relearn_threshold;
}
if ( $opt->{'save'} && !$opt->{'score_only'} ) {
$oif->save( $opt->{'m'} );
}
if ( length $results_string ) {
t/35-online-accel.t view on Meta::CPAN
# Learn a drifted cluster; the stream length also forces window
# evictions, so both learn and unlearn mutations are in play.
srand(8);
$m->learn( cluster( 300, 3 ) );
ok( !$m->{_c_nodes}, 'snapshot dropped by learning' );
my $c_after = with_knobs( $m, 1, 0, sub { $m->score_samples( \@eval ) } );
my $perl_after = with_knobs( $m, 0, 0, sub { $m->score_samples( \@eval ) } );
for my $i ( 0 .. $#eval ) {
cmp_ok( $c_after->[$i], '==', $perl_after->[$i], "post-mutation row $i matches fresh pure Perl" );
}
isnt( $c_after->[0], $before->[0], 'and the scores really did move with the drift' );
}; ## end 'mutation invalidates the packed snapshot' => sub
SKIP: {
skip 'OpenMP not linked in', 1
unless $Algorithm::Classifier::IsolationForest::HAS_OPENMP;
subtest 'OpenMP on/off parity' => sub {
my $m = make_model();
t/39-online-stream.t view on Meta::CPAN
$rate /= scalar @$flags;
cmp_ok( $rate, '>=', 0.02, 'window flag rate not far below contamination' );
cmp_ok( $rate, '<=', 0.09, 'window flag rate not far above contamination' );
# relearn_threshold tracks drift.
my $old_thr = $m->decision_threshold;
$m->learn( cluster( 600, 6 ) );
my $ret = $m->relearn_threshold;
is( $ret, $m, 'relearn_threshold chains' );
isnt( $m->decision_threshold, $old_thr, 'threshold moved with the stream' );
is( $m->predict( [ [ 6, 6 ] ] )->[0], 0, 'post-drift centre passes at the refreshed threshold' );
ok(
!eval { $class->new( n_trees => 5 )->relearn_threshold; 1 },
'relearn_threshold croaks without contamination'
);
}; ## end 'contamination threshold' => sub
subtest 'window_size 0 disables forgetting' => sub {
srand(10);
my $m = $class->new( seed => 13, n_trees => 20, window_size => 0, max_leaf_samples => 16 );