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;
( run in 1.953 second using v1.01-cache-2.11-cpan-c966e8aa7e8 )