File Coverage

blib/lib/Algorithm/Classifier/IsolationForest/App/Command/gblob.pm
Criterion Covered Total %
statement 18 58 31.0
branch 0 14 0.0
condition 0 13 0.0
subroutine 6 12 50.0
pod 4 6 66.6
total 28 103 27.1


line stmt bran cond sub pod time code
1             package Algorithm::Classifier::IsolationForest::App::Command::gblob;
2              
3 81     81   51458 use strict;
  81         144  
  81         2721  
4 81     81   303 use warnings;
  81         127  
  81         3130  
5 81     81   378 use Algorithm::Classifier::IsolationForest;
  81         247  
  81         4342  
6 81     81   373 use Algorithm::Classifier::IsolationForest::App -command;
  81         139  
  81         522  
7 81     81   24337 use File::Slurp qw(write_file);
  81         164  
  81         4267  
8              
9 81     81   378 use constant PI => 3.14159265358979;
  81         148  
  81         51011  
10              
11             sub opt_spec {
12             return (
13 0     0 1   [ 'o=s', 'Output file path/name.', { 'default' => 'blob.csv', 'completion' => 'files' } ],
14             [ 's=i', 'Seed int' ],
15             [ 'p', 'Print the output instead of writing it a file.' ],
16             [ 'w', 'If the file already exists, overwrite it.' ],
17             [ 'n=i', 'Number of normal points to generate.', { 'default' => '500' } ],
18             [
19             'a=i',
20             'Number of abnormal points to generate. If less than 1, none will be generated.',
21             { 'default' => '20' }
22             ],
23             [ 'd=i', 'Number of dimensions (features) per point.', { 'default' => '2' } ],
24             );
25             } ## end sub opt_spec
26              
27 0     0 1   sub abstract { 'Generates a gaussian blob of points.' }
28              
29             sub description {
30 0     0 1   'Generates a gaussian blob of points.
31              
32             The output format is as below...
33              
34             $feat1,...,$featN,$truth
35              
36             $truth is a 0/1 with 1 meaning it is a abnormal value.
37              
38             Normal points are drawn from N(0,1) in each dimension. Anomalous points are
39             placed on a hyperspherical shell at radius 5-8 from the origin.
40              
41             Use -D to control the number of dimensions (default: 2).
42             ';
43             } ## end sub description
44              
45             sub validate {
46 0     0 0   my ( $self, $opt, $args ) = @_;
47              
48 0 0 0       if ( defined( $opt->{'s'} ) && $opt->{'s'} <= 0 ) {
49 0           $self->usage_error( '-s, "' . $opt->{'s'} . '", is less than or equal to 0, should be a positive int' );
50             }
51              
52 0 0 0       if ( !$opt->{'p'} && -e $opt->{'o'} && !$opt->{'w'} ) {
      0        
53             $self->usage_error(
54 0           '-o "' . $opt->{'o'} . '", already exists. Specify -w to overwrite it or use a different value.' );
55             }
56              
57 0 0         if ( $opt->{'n'} < 1 ) {
58 0           $self->usage_error( '-n, "' . $opt->{'n'} . '", must be be 1 or greater' );
59             }
60              
61 0 0         if ( $opt->{'d'} < 1 ) {
62 0           $self->usage_error( '-D, "' . $opt->{'d'} . '", must be 1 or greater' );
63             }
64              
65 0           return 1;
66             } ## end sub validate
67              
68             sub gaussian {
69 0     0 0   my ( $mu, $sigma ) = @_;
70 0   0       my $u1 = rand() || 1e-12;
71 0           my $u2 = rand();
72 0           return $mu + $sigma * sqrt( -2 * log($u1) ) * cos( 2 * PI * $u2 );
73             }
74              
75             sub execute {
76 0     0 1   my ( $self, $opt, $args ) = @_;
77              
78 0           my $dims = $opt->{'d'};
79 0 0         srand( $opt->{'s'} ) if defined $opt->{'s'};
80              
81 0           my $data = '';
82              
83             # Normal points: each feature is drawn from N(0,1)
84 0           for ( 1 .. $opt->{'n'} ) {
85 0           my @feats = map { gaussian( 0, 1 ) } 1 .. $dims;
  0            
86 0           $data = $data . join( ',', @feats ) . ",0\n";
87             }
88              
89             # Anomalous points: random direction in D-space scaled to radius 5-8.
90             # Direction is a normalised vector of D Gaussian draws.
91 0 0         if ( $opt->{'a'} >= 1 ) {
92 0           for ( 1 .. $opt->{'a'} ) {
93 0           my $radius = 5 + rand() * 3;
94 0           my @raw = map { gaussian( 0, 1 ) } 1 .. $dims;
  0            
95 0           my $norm = 0;
96 0           $norm += $_ * $_ for @raw;
97 0   0       $norm = sqrt($norm) || 1;
98 0           my @feats = map { $_ / $norm * $radius } @raw;
  0            
99 0           $data = $data . join( ',', @feats ) . ",1\n";
100             }
101             } ## end if ( $opt->{'a'} >= 1 )
102              
103 0 0         if ( $opt->{'p'} ) {
104 0           print $data;
105 0           exit 0;
106             }
107              
108 0           write_file( $opt->{'o'}, $data );
109              
110             } ## end sub execute
111              
112             return 1;