| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Algorithm::Classifier::IsolationForest::App::Command::proto; |
|
2
|
|
|
|
|
|
|
|
|
3
|
81
|
|
|
81
|
|
51418
|
use strict; |
|
|
81
|
|
|
|
|
193
|
|
|
|
81
|
|
|
|
|
2592
|
|
|
4
|
81
|
|
|
81
|
|
305
|
use warnings; |
|
|
81
|
|
|
|
|
133
|
|
|
|
81
|
|
|
|
|
4192
|
|
|
5
|
81
|
|
|
81
|
|
980
|
use Algorithm::Classifier::IsolationForest (); |
|
|
81
|
|
|
|
|
621
|
|
|
|
81
|
|
|
|
|
2467
|
|
|
6
|
81
|
|
|
81
|
|
505
|
use Algorithm::Classifier::IsolationForest::App -command; |
|
|
81
|
|
|
|
|
202
|
|
|
|
81
|
|
|
|
|
728
|
|
|
7
|
81
|
|
|
81
|
|
24374
|
use File::Slurp qw(read_file write_file); |
|
|
81
|
|
|
|
|
145
|
|
|
|
81
|
|
|
|
|
60488
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
sub opt_spec { |
|
10
|
|
|
|
|
|
|
return ( |
|
11
|
|
|
|
|
|
|
[ |
|
12
|
5
|
|
|
5
|
1
|
126237
|
'from-model=s', |
|
13
|
|
|
|
|
|
|
'Extract a prototype from this saved model JSON (batch or online).', |
|
14
|
|
|
|
|
|
|
{ 'completion' => 'files' } |
|
15
|
|
|
|
|
|
|
], |
|
16
|
|
|
|
|
|
|
[ |
|
17
|
|
|
|
|
|
|
'check=s', |
|
18
|
|
|
|
|
|
|
'Validate this prototype file and print a summary of it; exits non-zero when invalid.', |
|
19
|
|
|
|
|
|
|
{ 'completion' => 'files' } |
|
20
|
|
|
|
|
|
|
], |
|
21
|
|
|
|
|
|
|
[ |
|
22
|
|
|
|
|
|
|
'o=s', |
|
23
|
|
|
|
|
|
|
'Output the prototype to this file instead of printing (--from-model only).', |
|
24
|
|
|
|
|
|
|
{ 'completion' => 'files' } |
|
25
|
|
|
|
|
|
|
], |
|
26
|
|
|
|
|
|
|
[ 'w', 'If the file specified via -o exists, over write it.' ], |
|
27
|
|
|
|
|
|
|
); |
|
28
|
|
|
|
|
|
|
} ## end sub opt_spec |
|
29
|
|
|
|
|
|
|
|
|
30
|
0
|
|
|
0
|
1
|
0
|
sub abstract { 'Extract a prototype from a saved model, or validate a prototype file' } |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub description { |
|
33
|
0
|
|
|
0
|
1
|
0
|
'Works with model prototypes: small JSON documents holding the variable |
|
34
|
|
|
|
|
|
|
schema (feature names, per-feature descriptions, munger specs, missing |
|
35
|
|
|
|
|
|
|
policy), a user-owned schema_version and schema_description, and |
|
36
|
|
|
|
|
|
|
optionally the tuning knobs. `iforest fit --prototype` and |
|
37
|
|
|
|
|
|
|
`iforest stream --prototype` create models from one; see PROTOTYPES in |
|
38
|
|
|
|
|
|
|
the Algorithm::Classifier::IsolationForest POD for the file format. |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
--from-model extracts a prototype from a saved model, closing the loop: |
|
41
|
|
|
|
|
|
|
pull the schema and knobs out of a good model, edit the metadata, and |
|
42
|
|
|
|
|
|
|
create fresh models from it. A model with no recorded schema_version / |
|
43
|
|
|
|
|
|
|
schema_description gets placeholder values to edit in. |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
--check validates a prototype file and prints a summary of what it |
|
46
|
|
|
|
|
|
|
describes, exiting non-zero when the file is not a valid prototype. |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
Exactly one of --from-model or --check must be given. |
|
49
|
|
|
|
|
|
|
'; |
|
50
|
|
|
|
|
|
|
} ## end sub description |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub validate { |
|
53
|
5
|
|
|
5
|
0
|
13
|
my ( $self, $opt, $args ) = @_; |
|
54
|
|
|
|
|
|
|
|
|
55
|
5
|
100
|
|
|
|
23
|
my $from = defined $opt->{'from_model'} ? 1 : 0; |
|
56
|
5
|
100
|
|
|
|
14
|
my $check = defined $opt->{'check'} ? 1 : 0; |
|
57
|
5
|
100
|
|
|
|
17
|
if ( $from + $check != 1 ) { |
|
58
|
2
|
|
|
|
|
11
|
$self->usage_error('exactly one of --from-model or --check must be specified'); |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
3
|
100
|
|
|
|
14
|
my ( $switch, $file ) = $from ? ( '--from-model', $opt->{'from_model'} ) : ( '--check', $opt->{'check'} ); |
|
62
|
3
|
50
|
|
|
|
101
|
if ( !-f $file ) { |
|
|
|
50
|
|
|
|
|
|
|
63
|
0
|
|
|
|
|
0
|
$self->usage_error( $switch . ', "' . $file . '", is not a file or does not exist' ); |
|
64
|
|
|
|
|
|
|
} elsif ( !-r $file ) { |
|
65
|
0
|
|
|
|
|
0
|
$self->usage_error( $switch . ', "' . $file . '", is not readable' ); |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
|
|
68
|
3
|
100
|
|
|
|
15
|
if ( defined $opt->{'o'} ) { |
|
69
|
1
|
50
|
|
|
|
2
|
if ($check) { |
|
70
|
0
|
|
|
|
|
0
|
$self->usage_error('-o may only be used with --from-model'); |
|
71
|
|
|
|
|
|
|
} |
|
72
|
1
|
50
|
33
|
|
|
66
|
if ( -e $opt->{'o'} && !$opt->{'w'} ) { |
|
73
|
0
|
|
|
|
|
0
|
$self->usage_error( '-o, "' . $opt->{'o'} . '", already exists and -w is not specified' ); |
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
} |
|
76
|
|
|
|
|
|
|
|
|
77
|
3
|
|
|
|
|
12
|
return 1; |
|
78
|
|
|
|
|
|
|
} ## end sub validate |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub execute { |
|
81
|
3
|
|
|
3
|
1
|
18
|
my ( $self, $opt, $args ) = @_; |
|
82
|
|
|
|
|
|
|
|
|
83
|
3
|
100
|
|
|
|
10
|
if ( defined $opt->{'from_model'} ) { |
|
84
|
1
|
|
|
|
|
10
|
my $model = Algorithm::Classifier::IsolationForest->load( $opt->{'from_model'} ); |
|
85
|
1
|
|
|
|
|
5
|
my $proto_json = $model->to_prototype; |
|
86
|
1
|
50
|
|
|
|
582
|
if ( defined $opt->{'o'} ) { |
|
87
|
1
|
|
|
|
|
9
|
write_file( $opt->{'o'}, { 'atomic' => 1 }, $proto_json . "\n" ); |
|
88
|
|
|
|
|
|
|
} else { |
|
89
|
0
|
|
|
|
|
0
|
print $proto_json. "\n"; |
|
90
|
|
|
|
|
|
|
} |
|
91
|
1
|
|
|
|
|
1202
|
return 1; |
|
92
|
|
|
|
|
|
|
} ## end if ( defined $opt->{'from_model'} ) |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# --check: structural validation plus a human summary of the file. |
|
95
|
2
|
|
|
|
|
16
|
my $raw = read_file( $opt->{'check'} ); |
|
96
|
2
|
|
|
|
|
237
|
my $proto = eval { Algorithm::Classifier::IsolationForest->validate_prototype($raw) }; |
|
|
2
|
|
|
|
|
20
|
|
|
97
|
2
|
100
|
|
|
|
158
|
die( '--check, "' . $opt->{'check'} . '", is not a valid prototype: ' . $@ ) if $@; |
|
98
|
|
|
|
|
|
|
|
|
99
|
1
|
|
|
|
|
2
|
my $schema = $proto->{schema}; |
|
100
|
1
|
|
|
|
|
2
|
my $tags = $schema->{feature_names}; |
|
101
|
1
|
|
|
|
|
7
|
printf " %-20s %s\n", 'file', $opt->{'check'}; |
|
102
|
1
|
|
|
|
|
3
|
printf " %-20s %s\n", 'class', $proto->{class}; |
|
103
|
1
|
|
|
|
|
3
|
printf " %-20s %s\n", 'schema_version', $proto->{schema_version}; |
|
104
|
1
|
|
|
|
|
3
|
printf " %-20s %s\n", 'schema_description', $proto->{schema_description}; |
|
105
|
1
|
50
|
|
|
|
5
|
printf " %-20s %s\n", 'missing', ( defined $schema->{missing} ? $schema->{missing} : '(unset)' ); |
|
106
|
1
|
|
|
|
|
5
|
printf " %-20s %s\n", 'feature_names', join( ', ', @$tags ); |
|
107
|
1
|
50
|
|
|
|
3
|
my $fd = ref $schema->{feature_descriptions} eq 'HASH' ? $schema->{feature_descriptions} : {}; |
|
108
|
|
|
|
|
|
|
|
|
109
|
1
|
|
|
|
|
5
|
for my $i ( 0 .. $#$tags ) { |
|
110
|
|
|
|
|
|
|
printf " [%d] %s%s\n", $i, $tags->[$i], |
|
111
|
3
|
100
|
|
|
|
12
|
( defined $fd->{ $tags->[$i] } ? ' -- ' . $fd->{ $tags->[$i] } : '' ); |
|
112
|
|
|
|
|
|
|
} |
|
113
|
1
|
|
|
|
|
3
|
my $mungers = $schema->{mungers}; |
|
114
|
1
|
50
|
33
|
|
|
4
|
if ( ref $mungers eq 'HASH' && %$mungers ) { |
|
115
|
0
|
|
|
|
|
0
|
printf " %-20s %s\n", 'mungers', scalar( keys %$mungers ) . ' configured'; |
|
116
|
0
|
|
|
|
|
0
|
for my $k ( sort keys %$mungers ) { |
|
117
|
|
|
|
|
|
|
printf " %-18s %s\n", $k, |
|
118
|
0
|
0
|
0
|
|
|
0
|
( ref $mungers->{$k} eq 'HASH' && defined $mungers->{$k}{munger} ? $mungers->{$k}{munger} : '(?)' ); |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
} |
|
121
|
1
|
50
|
|
|
|
4
|
my $params = ref $proto->{params} eq 'HASH' ? $proto->{params} : {}; |
|
122
|
1
|
|
|
|
|
3
|
for my $k ( sort keys %$params ) { |
|
123
|
4
|
50
|
|
|
|
11
|
printf " %-20s %s\n", $k, ( defined $params->{$k} ? $params->{$k} : '(unset)' ); |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
1
|
|
|
|
|
117
|
return 1; |
|
127
|
|
|
|
|
|
|
} ## end sub execute |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
return 1; |