| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Algorithm::Classifier::IsolationForest::App::Command::info; |
|
2
|
|
|
|
|
|
|
|
|
3
|
81
|
|
|
81
|
|
52931
|
use strict; |
|
|
81
|
|
|
|
|
144
|
|
|
|
81
|
|
|
|
|
2643
|
|
|
4
|
81
|
|
|
81
|
|
286
|
use warnings; |
|
|
81
|
|
|
|
|
137
|
|
|
|
81
|
|
|
|
|
2943
|
|
|
5
|
81
|
|
|
81
|
|
330
|
use Algorithm::Classifier::IsolationForest (); |
|
|
81
|
|
|
|
|
147
|
|
|
|
81
|
|
|
|
|
1491
|
|
|
6
|
81
|
|
|
81
|
|
261
|
use Algorithm::Classifier::IsolationForest::App -command; |
|
|
81
|
|
|
|
|
122
|
|
|
|
81
|
|
|
|
|
538
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
sub opt_spec { |
|
9
|
|
|
|
|
|
|
return ( |
|
10
|
|
|
|
|
|
|
[ |
|
11
|
11
|
|
|
11
|
1
|
286200
|
'm=s', |
|
12
|
|
|
|
|
|
|
'Input model JSON file path/name.', |
|
13
|
|
|
|
|
|
|
{ 'default' => 'iforest_model.json', 'completion' => 'files' } |
|
14
|
|
|
|
|
|
|
], |
|
15
|
|
|
|
|
|
|
[ 'json', 'Emit machine-readable JSON instead of the text table.' ], |
|
16
|
|
|
|
|
|
|
); |
|
17
|
|
|
|
|
|
|
} ## end sub opt_spec |
|
18
|
|
|
|
|
|
|
|
|
19
|
0
|
|
|
0
|
1
|
0
|
sub abstract { 'Show the constructor params, fit-time metadata, and tree stats of a saved model' } |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
sub description { |
|
22
|
0
|
|
|
0
|
1
|
0
|
'Loads a saved Algorithm::Classifier::IsolationForest model and prints the |
|
23
|
|
|
|
|
|
|
constructor params, fit-time metadata, and a handful of derived tree |
|
24
|
|
|
|
|
|
|
statistics (count, average/max depth, total nodes). |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
Use --json for a machine-readable dump suitable for piping into jq. |
|
27
|
|
|
|
|
|
|
' |
|
28
|
|
|
|
|
|
|
} |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub validate { |
|
31
|
11
|
|
|
11
|
0
|
31
|
my ( $self, $opt, $args ) = @_; |
|
32
|
|
|
|
|
|
|
|
|
33
|
11
|
50
|
|
|
|
420
|
if ( !-f $opt->{'m'} ) { |
|
|
|
50
|
|
|
|
|
|
|
34
|
0
|
|
|
|
|
0
|
$self->usage_error( '-m, "' . $opt->{'m'} . '", is not a file or does not exist' ); |
|
35
|
|
|
|
|
|
|
} elsif ( !-r $opt->{'m'} ) { |
|
36
|
0
|
|
|
|
|
0
|
$self->usage_error( '-m, "' . $opt->{'m'} . '", is not readable' ); |
|
37
|
|
|
|
|
|
|
} |
|
38
|
11
|
|
|
|
|
40
|
return 1; |
|
39
|
|
|
|
|
|
|
} ## end sub validate |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# Tree-shape stats are derived once at load time. Each tree is a |
|
42
|
|
|
|
|
|
|
# nested arrayref structure -- leaf [0, size] or interior [1, ...] / |
|
43
|
|
|
|
|
|
|
# [2, ...] with children at fixed slots. |
|
44
|
|
|
|
|
|
|
sub _walk_tree { |
|
45
|
6788
|
|
|
6788
|
|
8482
|
my ( $node, $depth, $acc ) = @_; |
|
46
|
6788
|
|
|
|
|
7467
|
$acc->{nodes}++; |
|
47
|
6788
|
100
|
|
|
|
9321
|
if ( $node->[0] == 0 ) { # leaf |
|
48
|
3549
|
|
|
|
|
3963
|
$acc->{leaves}++; |
|
49
|
3549
|
100
|
|
|
|
4824
|
$acc->{max_depth} = $depth if $depth > $acc->{max_depth}; |
|
50
|
3549
|
|
|
|
|
4006
|
$acc->{depth_sum} += $depth; |
|
51
|
3549
|
|
|
|
|
4582
|
return; |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
# Axis interior nodes have children at slots 3,4; oblique at 4,5. |
|
54
|
3239
|
50
|
|
|
|
4552
|
my ( $li, $ri ) = $node->[0] == 1 ? ( 3, 4 ) : ( 4, 5 ); |
|
55
|
3239
|
|
|
|
|
5490
|
_walk_tree( $node->[$li], $depth + 1, $acc ); |
|
56
|
3239
|
|
|
|
|
4181
|
_walk_tree( $node->[$ri], $depth + 1, $acc ); |
|
57
|
|
|
|
|
|
|
} ## end sub _walk_tree |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub _tree_stats { |
|
60
|
8
|
|
|
8
|
|
26
|
my ($trees) = @_; |
|
61
|
8
|
|
|
|
|
49
|
my $acc = { nodes => 0, leaves => 0, max_depth => 0, depth_sum => 0 }; |
|
62
|
8
|
|
|
|
|
38
|
_walk_tree( $_, 0, $acc ) for @$trees; |
|
63
|
8
|
|
|
|
|
32
|
return $acc; |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# Summary of a model's Algorithm::ToNumberMunger spec as a flat |
|
67
|
|
|
|
|
|
|
# 'key => munger name' map (the full spec can be arbitrarily large -- |
|
68
|
|
|
|
|
|
|
# frozen count tables and the like -- so info only names the mungers). |
|
69
|
|
|
|
|
|
|
# Returns undef when the model carries none. |
|
70
|
|
|
|
|
|
|
sub _munger_summary { |
|
71
|
11
|
|
|
11
|
|
38
|
my ($model) = @_; |
|
72
|
11
|
|
|
|
|
35
|
my $mungers = $model->{mungers}; |
|
73
|
11
|
100
|
66
|
|
|
259
|
return undef unless ref $mungers eq 'HASH' && %$mungers; |
|
74
|
1
|
50
|
|
|
|
5
|
return { map { $_ => ( ref $mungers->{$_} eq 'HASH' ? $mungers->{$_}{munger} : undef ) } keys %$mungers }; |
|
|
2
|
|
|
|
|
31
|
|
|
75
|
|
|
|
|
|
|
} |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# Text-table rendering of the summary, matching the feature_names style. |
|
78
|
|
|
|
|
|
|
sub _print_mungers { |
|
79
|
8
|
|
|
8
|
|
22
|
my ($summary) = @_; |
|
80
|
8
|
100
|
|
|
|
26
|
return unless $summary; |
|
81
|
1
|
|
|
|
|
6
|
printf " %-20s %s\n", 'mungers', scalar( keys %$summary ) . ' configured'; |
|
82
|
1
|
|
|
|
|
4
|
for my $k ( sort keys %$summary ) { |
|
83
|
2
|
50
|
|
|
|
24
|
printf " %-18s %s\n", $k, ( defined $summary->{$k} ? $summary->{$k} : '(?)' ); |
|
84
|
|
|
|
|
|
|
} |
|
85
|
1
|
|
|
|
|
3
|
return; |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Online-model counterpart of _walk_tree: nodes are |
|
89
|
|
|
|
|
|
|
# [0, count, lo, hi] / [1, count, lo, hi, attr, split, left, right], |
|
90
|
|
|
|
|
|
|
# a tree record is { root, count, depth_limit }, and root may be undef |
|
91
|
|
|
|
|
|
|
# on a tree that has not learned anything yet. |
|
92
|
|
|
|
|
|
|
sub _walk_tree_online { |
|
93
|
268
|
|
|
268
|
|
376
|
my ( $node, $depth, $acc ) = @_; |
|
94
|
268
|
|
|
|
|
297
|
$acc->{nodes}++; |
|
95
|
268
|
100
|
|
|
|
387
|
if ( $node->[0] == 0 ) { # leaf |
|
96
|
164
|
|
|
|
|
183
|
$acc->{leaves}++; |
|
97
|
164
|
100
|
|
|
|
243
|
$acc->{max_depth} = $depth if $depth > $acc->{max_depth}; |
|
98
|
164
|
|
|
|
|
191
|
$acc->{depth_sum} += $depth; |
|
99
|
164
|
|
|
|
|
204
|
return; |
|
100
|
|
|
|
|
|
|
} |
|
101
|
104
|
|
|
|
|
193
|
_walk_tree_online( $node->[6], $depth + 1, $acc ); |
|
102
|
104
|
|
|
|
|
145
|
_walk_tree_online( $node->[7], $depth + 1, $acc ); |
|
103
|
|
|
|
|
|
|
} ## end sub _walk_tree_online |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub _tree_stats_online { |
|
106
|
3
|
|
|
3
|
|
8
|
my ($trees) = @_; |
|
107
|
3
|
|
|
|
|
16
|
my $acc = { nodes => 0, leaves => 0, max_depth => 0, depth_sum => 0 }; |
|
108
|
3
|
|
|
|
|
10
|
for my $tree (@$trees) { |
|
109
|
60
|
50
|
|
|
|
108
|
_walk_tree_online( $tree->{root}, 0, $acc ) if defined $tree->{root}; |
|
110
|
|
|
|
|
|
|
} |
|
111
|
3
|
|
|
|
|
16
|
return $acc; |
|
112
|
|
|
|
|
|
|
} |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# Online models have a different parameter set and tree shape; handled |
|
115
|
|
|
|
|
|
|
# in a dedicated path so the batch-model reporting below stays simple. |
|
116
|
|
|
|
|
|
|
sub _execute_online { |
|
117
|
3
|
|
|
3
|
|
7
|
my ( $self, $opt, $model ) = @_; |
|
118
|
|
|
|
|
|
|
|
|
119
|
3
|
|
|
|
|
15
|
my $stats = _tree_stats_online( $model->{trees} ); |
|
120
|
3
|
|
|
|
|
5
|
my $n_trees = scalar @{ $model->{trees} }; |
|
|
3
|
|
|
|
|
9
|
|
|
121
|
3
|
50
|
|
|
|
24
|
my $avg_depth = $stats->{leaves} ? $stats->{depth_sum} / $stats->{leaves} : 0; |
|
122
|
3
|
50
|
|
|
|
13
|
my $avg_nodes = $n_trees ? $stats->{nodes} / $n_trees : 0; |
|
123
|
|
|
|
|
|
|
|
|
124
|
3
|
|
|
|
|
6
|
my $tags = $model->{feature_names}; |
|
125
|
3
|
100
|
66
|
|
|
16
|
my $tagged = ( ref $tags eq 'ARRAY' && @$tags ) ? 1 : 0; |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
my %info = ( |
|
128
|
|
|
|
|
|
|
'file' => $opt->{'m'}, |
|
129
|
|
|
|
|
|
|
'type' => 'online', |
|
130
|
|
|
|
|
|
|
'tagged' => $tagged, |
|
131
|
|
|
|
|
|
|
'feature_names' => $tagged ? $tags : undef, |
|
132
|
|
|
|
|
|
|
'n_trees' => $n_trees, |
|
133
|
|
|
|
|
|
|
'n_features' => $model->{n_features}, |
|
134
|
|
|
|
|
|
|
'window_size' => $model->{window_size}, |
|
135
|
|
|
|
|
|
|
'window_count' => $model->window_count, |
|
136
|
|
|
|
|
|
|
'seen' => $model->{seen}, |
|
137
|
|
|
|
|
|
|
'max_leaf_samples' => $model->{max_leaf_samples}, |
|
138
|
|
|
|
|
|
|
'growth' => $model->{growth}, |
|
139
|
|
|
|
|
|
|
'subsample' => $model->{subsample}, |
|
140
|
|
|
|
|
|
|
'contamination' => $model->{contamination}, |
|
141
|
|
|
|
|
|
|
'threshold' => $model->{threshold}, |
|
142
|
|
|
|
|
|
|
'mungers' => _munger_summary($model), |
|
143
|
|
|
|
|
|
|
'munger_module_version' => $model->{munger_module_version}, |
|
144
|
|
|
|
|
|
|
'schema_version' => $model->{schema_version}, |
|
145
|
|
|
|
|
|
|
'schema_description' => $model->{schema_description}, |
|
146
|
|
|
|
|
|
|
'feature_descriptions' => $model->{feature_descriptions}, |
|
147
|
|
|
|
|
|
|
'tree_total_nodes' => $stats->{nodes}, |
|
148
|
|
|
|
|
|
|
'tree_total_leaves' => $stats->{leaves}, |
|
149
|
|
|
|
|
|
|
'tree_max_depth' => $stats->{max_depth}, |
|
150
|
3
|
100
|
|
|
|
23
|
'tree_avg_depth' => $avg_depth, |
|
151
|
|
|
|
|
|
|
'tree_avg_nodes' => $avg_nodes, |
|
152
|
|
|
|
|
|
|
); |
|
153
|
|
|
|
|
|
|
|
|
154
|
3
|
100
|
|
|
|
24
|
if ( $opt->{'json'} ) { |
|
155
|
1
|
|
|
|
|
14
|
require JSON::PP; |
|
156
|
1
|
|
|
|
|
7
|
print JSON::PP->new->canonical(1)->pretty->encode( \%info ); |
|
157
|
1
|
|
|
|
|
898
|
return 1; |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
|
|
160
|
2
|
|
|
|
|
12
|
my @order = qw( |
|
161
|
|
|
|
|
|
|
file type tagged n_trees n_features window_size window_count seen |
|
162
|
|
|
|
|
|
|
max_leaf_samples growth subsample contamination threshold |
|
163
|
|
|
|
|
|
|
schema_version schema_description munger_module_version |
|
164
|
|
|
|
|
|
|
tree_total_nodes tree_total_leaves tree_max_depth |
|
165
|
|
|
|
|
|
|
tree_avg_depth tree_avg_nodes |
|
166
|
|
|
|
|
|
|
); |
|
167
|
|
|
|
|
|
|
|
|
168
|
2
|
|
|
|
|
6
|
for my $k (@order) { |
|
169
|
42
|
|
|
|
|
59
|
my $v = $info{$k}; |
|
170
|
42
|
100
|
|
|
|
73
|
$v = '(unset)' unless defined $v; |
|
171
|
42
|
50
|
66
|
|
|
139
|
$v = sprintf( '%.4f', $v ) |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
172
|
|
|
|
|
|
|
if defined $v |
|
173
|
|
|
|
|
|
|
&& $v =~ /^-?\d+\.\d+/ |
|
174
|
|
|
|
|
|
|
&& $k !~ /^tree_total_/ |
|
175
|
|
|
|
|
|
|
&& $k !~ /\A(?:munger_module_version|schema_version|schema_description)\z/; |
|
176
|
42
|
|
|
|
|
114
|
printf " %-20s %s\n", $k, $v; |
|
177
|
|
|
|
|
|
|
} ## end for my $k (@order) |
|
178
|
|
|
|
|
|
|
|
|
179
|
2
|
100
|
|
|
|
7
|
if ($tagged) { |
|
180
|
1
|
|
|
|
|
4
|
printf " %-20s %s\n", 'feature_names', join( ', ', @$tags ); |
|
181
|
1
|
50
|
|
|
|
5
|
my $fd = ref $model->{feature_descriptions} eq 'HASH' ? $model->{feature_descriptions} : {}; |
|
182
|
1
|
|
|
|
|
4
|
for my $i ( 0 .. $#$tags ) { |
|
183
|
|
|
|
|
|
|
printf " [%d] %s%s\n", $i, $tags->[$i], |
|
184
|
3
|
50
|
|
|
|
11
|
( defined $fd->{ $tags->[$i] } ? ' -- ' . $fd->{ $tags->[$i] } : '' ); |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
} |
|
187
|
2
|
|
|
|
|
8
|
_print_mungers( $info{mungers} ); |
|
188
|
2
|
|
|
|
|
390
|
return 1; |
|
189
|
|
|
|
|
|
|
} ## end sub _execute_online |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub execute { |
|
192
|
11
|
|
|
11
|
1
|
74
|
my ( $self, $opt, $args ) = @_; |
|
193
|
|
|
|
|
|
|
|
|
194
|
11
|
|
|
|
|
103
|
my $model = Algorithm::Classifier::IsolationForest->load( $opt->{'m'} ); |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# load() dispatches on the stored format tag, so this may be an |
|
197
|
|
|
|
|
|
|
# online model -- those have their own parameter set and tree shape. |
|
198
|
11
|
100
|
|
|
|
55
|
if ( ref $model eq 'Algorithm::Classifier::IsolationForest::Online' ) { |
|
199
|
3
|
|
|
|
|
22
|
return $self->_execute_online( $opt, $model ); |
|
200
|
|
|
|
|
|
|
} |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# Tree stats are not stored on the model -- they're cheap to derive. |
|
203
|
8
|
|
|
|
|
44
|
my $stats = _tree_stats( $model->{trees} ); |
|
204
|
8
|
|
|
|
|
46
|
my $n_trees = scalar @{ $model->{trees} }; |
|
|
8
|
|
|
|
|
25
|
|
|
205
|
8
|
50
|
|
|
|
39
|
my $avg_depth = $stats->{leaves} ? $stats->{depth_sum} / $stats->{leaves} : 0; |
|
206
|
8
|
50
|
|
|
|
40
|
my $avg_nodes = $n_trees ? $stats->{nodes} / $n_trees : 0; |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
# Feature-name tags are stored as an arrayref (via -t at fit time). |
|
209
|
8
|
|
|
|
|
17
|
my $tags = $model->{feature_names}; |
|
210
|
8
|
100
|
66
|
|
|
73
|
my $tagged = ( ref $tags eq 'ARRAY' && @$tags ) ? 1 : 0; |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
my %info = ( |
|
213
|
|
|
|
|
|
|
'file' => $opt->{'m'}, |
|
214
|
|
|
|
|
|
|
'mode' => $model->{mode}, |
|
215
|
|
|
|
|
|
|
'voting' => $model->{voting}, |
|
216
|
|
|
|
|
|
|
'tagged' => $tagged, |
|
217
|
|
|
|
|
|
|
'feature_names' => $tagged ? $tags : undef, |
|
218
|
|
|
|
|
|
|
'n_trees' => $n_trees, |
|
219
|
|
|
|
|
|
|
'n_features' => $model->{n_features}, |
|
220
|
|
|
|
|
|
|
'sample_size' => $model->{sample_size}, |
|
221
|
|
|
|
|
|
|
'psi_used' => $model->{psi_used}, |
|
222
|
|
|
|
|
|
|
'c_psi' => $model->{c_psi}, |
|
223
|
|
|
|
|
|
|
'max_depth_used' => $model->{max_depth_used}, |
|
224
|
|
|
|
|
|
|
'extension_level' => $model->{extension_level_used}, |
|
225
|
|
|
|
|
|
|
'contamination' => $model->{contamination}, |
|
226
|
|
|
|
|
|
|
'threshold' => $model->{threshold}, |
|
227
|
|
|
|
|
|
|
'mungers' => _munger_summary($model), |
|
228
|
|
|
|
|
|
|
'munger_module_version' => $model->{munger_module_version}, |
|
229
|
|
|
|
|
|
|
'schema_version' => $model->{schema_version}, |
|
230
|
|
|
|
|
|
|
'schema_description' => $model->{schema_description}, |
|
231
|
|
|
|
|
|
|
'feature_descriptions' => $model->{feature_descriptions}, |
|
232
|
|
|
|
|
|
|
'tree_total_nodes' => $stats->{nodes}, |
|
233
|
|
|
|
|
|
|
'tree_total_leaves' => $stats->{leaves}, |
|
234
|
|
|
|
|
|
|
'tree_max_depth' => $stats->{max_depth}, |
|
235
|
8
|
100
|
|
|
|
87
|
'tree_avg_depth' => $avg_depth, |
|
236
|
|
|
|
|
|
|
'tree_avg_nodes' => $avg_nodes, |
|
237
|
|
|
|
|
|
|
); |
|
238
|
|
|
|
|
|
|
|
|
239
|
8
|
100
|
|
|
|
53
|
if ( $opt->{'json'} ) { |
|
240
|
2
|
|
|
|
|
23
|
require JSON::PP; |
|
241
|
2
|
|
|
|
|
15
|
print JSON::PP->new->canonical(1)->pretty->encode( \%info ); |
|
242
|
2
|
|
|
|
|
1874
|
return 1; |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# Text-table output, in a stable order, with undef shown as "(unset)". |
|
246
|
|
|
|
|
|
|
# feature_names, feature_descriptions, and mungers are refs -- rendered |
|
247
|
|
|
|
|
|
|
# separately below. |
|
248
|
6
|
|
|
|
|
38
|
my @order = qw( |
|
249
|
|
|
|
|
|
|
file mode voting tagged n_trees n_features sample_size psi_used c_psi |
|
250
|
|
|
|
|
|
|
max_depth_used extension_level contamination threshold |
|
251
|
|
|
|
|
|
|
schema_version schema_description munger_module_version |
|
252
|
|
|
|
|
|
|
tree_total_nodes tree_total_leaves tree_max_depth |
|
253
|
|
|
|
|
|
|
tree_avg_depth tree_avg_nodes |
|
254
|
|
|
|
|
|
|
); |
|
255
|
|
|
|
|
|
|
|
|
256
|
6
|
|
|
|
|
14
|
for my $k (@order) { |
|
257
|
126
|
|
|
|
|
185
|
my $v = $info{$k}; |
|
258
|
126
|
100
|
|
|
|
190
|
$v = '(unset)' unless defined $v; |
|
259
|
|
|
|
|
|
|
# Pretty-print floats with a couple of decimals; leave ints raw. |
|
260
|
126
|
100
|
66
|
|
|
530
|
$v = sprintf( '%.4f', $v ) |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
261
|
|
|
|
|
|
|
if defined $v |
|
262
|
|
|
|
|
|
|
&& $v =~ /^-?\d+\.\d+/ |
|
263
|
|
|
|
|
|
|
&& $k !~ /^tree_total_/ |
|
264
|
|
|
|
|
|
|
&& $k !~ /\A(?:munger_module_version|schema_version|schema_description)\z/; |
|
265
|
126
|
|
|
|
|
328
|
printf " %-20s %s\n", $k, $v; |
|
266
|
|
|
|
|
|
|
} ## end for my $k (@order) |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
# Feature-name tags, one per line, in stored (positional) order. |
|
269
|
6
|
100
|
|
|
|
20
|
if ($tagged) { |
|
270
|
3
|
|
|
|
|
17
|
printf " %-20s %s\n", 'feature_names', join( ', ', @$tags ); |
|
271
|
3
|
100
|
|
|
|
12
|
my $fd = ref $model->{feature_descriptions} eq 'HASH' ? $model->{feature_descriptions} : {}; |
|
272
|
3
|
|
|
|
|
12
|
for my $i ( 0 .. $#$tags ) { |
|
273
|
|
|
|
|
|
|
printf " [%d] %s%s\n", $i, $tags->[$i], |
|
274
|
9
|
100
|
|
|
|
41
|
( defined $fd->{ $tags->[$i] } ? ' -- ' . $fd->{ $tags->[$i] } : '' ); |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
} |
|
277
|
6
|
|
|
|
|
34
|
_print_mungers( $info{mungers} ); |
|
278
|
6
|
|
|
|
|
1600
|
return 1; |
|
279
|
|
|
|
|
|
|
} ## end sub execute |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
return 1; |