File Coverage

blib/lib/Algorithm/Classifier/NaiveBayes/App/Command/explain.pm
Criterion Covered Total %
statement 42 49 85.7
branch 6 10 60.0
condition n/a
subroutine 9 11 81.8
pod 3 5 60.0
total 60 75 80.0


line stmt bran cond sub pod time code
1             package Algorithm::Classifier::NaiveBayes::App::Command::explain;
2              
3 1     1   947 use 5.006;
  1         2  
4 1     1   5 use strict;
  1         1  
  1         24  
5 1     1   4 use warnings;
  1         1  
  1         74  
6 1     1   6 use Algorithm::Classifier::NaiveBayes ();
  1         2  
  1         18  
7 1     1   3 use Algorithm::Classifier::NaiveBayes::App -command;
  1         7  
  1         8  
8 1     1   270 use JSON::PP ();
  1         3  
  1         496  
9              
10             sub options {
11             return (
12 1     1 0 10 [ 'm=s', 'Model JSON file path/name.', { 'default' => 'nb_model.json', 'completion' => 'files' } ],
13             [ 'json', 'Print the raw explanation as JSON instead.' ],
14             );
15             }
16              
17 0     0 1 0 sub abstract { 'Classify the specified text and explain why' }
18              
19             sub description {
20 0     0 1 0 return 'Classify the specified text and show which tokens pushed it towards the class.
21              
22             The text is taken from the remaining args joined by a space, or from
23             stdin if no args are given. Prints the class, its probability, and
24             every token sorted by how hard it pushed towards the winning class
25             over the runner up.
26              
27             nb_tool explain -m model.json you have won a free cruise
28             ';
29             } ## end sub description
30              
31             sub validate {
32 1     1 0 7 my ( $self, $opt, $args ) = @_;
33              
34 1 50       31 if ( !-f $opt->{'m'} ) {
35 0         0 $self->usage_error( '-m, "' . $opt->{'m'} . '", is not a file or does not exist' );
36             }
37              
38 1         4 return 1;
39             }
40              
41             sub execute {
42 1     1 1 6 my ( $self, $opt, $args ) = @_;
43              
44 1         10 my $nb = Algorithm::Classifier::NaiveBayes->new;
45 1         4 $nb->load( $opt->{'m'} );
46              
47 1         10 my $explanation = $nb->explain( $self->text_from($args) );
48 1 50       4 if ( !defined($explanation) ) {
49 0         0 die('The model has not been trained yet');
50             }
51              
52 1 50       3 if ( $opt->{'json'} ) {
53 0         0 print JSON::PP->new->canonical->pretty->encode($explanation);
54 0         0 return;
55             }
56              
57 1         2 my $class = $explanation->{'class'};
58 1         12 print $class. ', probability ' . sprintf( '%.3f', $explanation->{'probs'}{$class} ) . "\n";
59              
60             my ( $first, $second )
61 1         14 = sort { $explanation->{'scores'}{$b} <=> $explanation->{'scores'}{$a} } keys %{ $explanation->{'scores'} };
  1         3  
  1         4  
62 1 50       3 if ( !defined($second) ) {
63 0         0 return;
64             }
65              
66 1         1 my %pull;
67 1         2 foreach my $token ( keys %{ $explanation->{'tokens'} } ) {
  1         2  
68 3         5 my $contribs = $explanation->{'tokens'}{$token}{'contributions'};
69 3         7 $pull{$token} = ( $contribs->{$first} - $contribs->{$second} ) * $explanation->{'tokens'}{$token}{'count'};
70             }
71 1         3 foreach my $token ( sort { $pull{$b} <=> $pull{$a} } keys %pull ) {
  3         6  
72 3 100       25 my $towards = $pull{$token} > 0 ? $first : $second;
73 3         12 print ' ' . $token . ' pushed towards ' . $towards . ' by ' . sprintf( '%.3f', abs( $pull{$token} ) ) . "\n";
74             }
75             } ## end sub execute
76              
77             1;