Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

benchmarking/bench-voting.pl  view on Meta::CPAN

#!/usr/bin/perl
# benchmarking/bench-voting.pl
#
# Head-to-head comparison of mean aggregation (classic IForest) vs
# majority voting (MVIForest -- Chabchoub, Togbe, Boly & Chiky 2022) for
# both speed and detections.
#
# voting => 'majority' is aggregation-only: it builds the exact same
# trees as voting => 'mean' and differs solely in how the per-tree path
# lengths are combined at score/predict time.  So a fair comparison holds
# the trees fixed (same seed + data) and varies only the aggregation.
#
# Two things are measured:
#
#   1. SPEED.  predict() is where majority voting can win: it stops
#      walking a point's trees as soon as the majority outcome is decided
#      (the paper's "stop at majority"), whereas mean aggregation always
#      walks every tree.  score_samples() has no such early exit (the
#      vote fraction needs the full count), so it is shown as a contrast.
#      Timed across n_trees, query-set size, and feature count, under the
#      default backend (C + OpenMP when available).
#
#      How much predict() saves depends on the data: early exit triggers
#      sooner when points are clearly inliers or clearly outliers, later
#      when they sit near the decision boundary.  The gaussian-cluster +
#      planted-outlier data here is fairly separable, so this is closer
#      to a best case than to a worst case.
#
#      The speed timings run on the SERIAL C backend (use_openmp => 0).
#      The majority-vote win is algorithmic -- it walks fewer trees per
#      point -- and forcing serial isolates that from OpenMP scheduling:
#      early exit makes different points finish after different numbers
#      of trees, so an OpenMP `parallel for` sees uneven per-point work
#      and its load-imbalance jitter would otherwise swamp the effect
#      being measured (wall_cmpthese times a single window, unlike
#      wall_rate's windowed median -- see BenchAccel).  The fewer-walks
#      saving carries over to the OpenMP path; it is just far noisier to
#      measure there.
#
#   2. DETECTIONS.  On data with a known planted-outlier block we report,
#      per decision threshold, how many points each mode flags, how many
#      of the true outliers it catches (recall), how many inliers it
#      flags (false alarms), and how often the two modes agree.  The
#      threshold means different things in each mode -- a forest-level
#      score cutoff for mean, a per-tree cutoff for majority -- but the
#      paper compares them at the same nominal value (0.6), so we do too.
#
# Run with:
#   perl -Ilib benchmarking/bench-voting.pl

use strict;
use warnings;
use lib '../lib';
use FindBin;
use lib "$FindBin::Bin";
use BenchAccel qw(wall_cmpthese);
use Algorithm::Classifier::IsolationForest;

use constant PI => 3.14159265358979;

sub gaussian {
	my ( $mu, $sigma ) = @_;
	return $mu + $sigma * sqrt( -2 * log( rand() || 1e-12 ) ) * cos( 2 * PI * rand() );
}

# Returns ($rows, $n_inliers): the first $n_inliers rows are the gaussian
# cluster, the remaining rows are the planted outliers (a 5% block pushed
# 5-8 sigma out).  Keeping the split point lets the detection section
# score recall (true outliers caught) against false alarms (inliers
# flagged).
sub make_data {
	my ( $n, $nf ) = @_;
	my @rows = map {
		[ map { gaussian( 0, 1 ) } 1 .. $nf ]
	} 1 .. $n;
	my $n_inliers = scalar @rows;
	for ( 1 .. int( $n * 0.05 ) ) {
		my $r = 5 + rand() * 3;
		push @rows, [ map { $r * ( rand() > 0.5 ? 1 : -1 ) } 1 .. $nf ];
	}
	return ( \@rows, $n_inliers );
} ## end sub make_data

my $HAS_C      = $Algorithm::Classifier::IsolationForest::HAS_C;
my $HAS_OPENMP = $Algorithm::Classifier::IsolationForest::HAS_OPENMP;

# Build a mean model and a majority model over identical trees (same seed



( run in 0.915 second using v1.01-cache-2.11-cpan-9581c071862 )