AI-TensorFlow-Libtensorflow

 view release on metacpan or  search on metacpan

lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod  view on Meta::CPAN

BEGIN {
    if( IN_IPERL ) {
        $ENV{TF_CPP_MIN_LOG_LEVEL} = 3;
    }
    require AI::TensorFlow::Libtensorflow;
}

use URI ();
use HTTP::Tiny ();
use Path::Tiny qw(path);

use File::Which ();

use List::Util ();

use Data::Printer ( output => 'stderr', return_value => 'void', filters => ['PDL'] );
use Data::Printer::Filter::PDL ();
use Text::Table::Tiny qw(generate_table);

use Imager;

my $s = AI::TensorFlow::Libtensorflow::Status->New;
sub AssertOK {
    die "Status $_[0]: " . $_[0]->Message
        unless $_[0]->GetCode == AI::TensorFlow::Libtensorflow::Status::OK;
    return;
}
AssertOK($s);

use PDL;
use AI::TensorFlow::Libtensorflow::DataType qw(FLOAT);

use FFI::Platypus::Memory qw(memcpy);
use FFI::Platypus::Buffer qw(scalar_to_pointer);

sub FloatPDLTOTFTensor {
    my ($p) = @_;
    return AI::TensorFlow::Libtensorflow::Tensor->New(
        FLOAT, [ reverse $p->dims ], $p->get_dataref, sub { undef $p }
    );
}

sub FloatTFTensorToPDL {
    my ($t) = @_;

    my $pdl = zeros(float,reverse( map $t->Dim($_), 0..$t->NumDims-1 ) );

    memcpy scalar_to_pointer( ${$pdl->get_dataref} ),
        scalar_to_pointer( ${$t->Data} ),
        $t->ByteSize;
    $pdl->upd_data;

    $pdl;
}

use HTML::Tiny;

sub my_table {
    my ($data, $cb) = @_;
    my $h = HTML::Tiny->new;
    $h->table( { style => 'width: 100%' },
        [
            $h->tr(
                map {
                    [
                        $h->td( $cb->($_, $h) )
                    ]
                } @$data
            )
        ]
    )
}

sub show_in_gnuplot {
    my ($p) = @_;
    require PDL::Graphics::Gnuplot;
    PDL::Graphics::Gnuplot::image( square => 1, $p );
}

# image_size => [width, height] (but usually square images)
my %model_name_to_params = (
    mobilenet_v2_100_224 => {
        handle     => 'https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/classification/5',
        image_size => [ 224, 224 ],
    },
    mobilenet_v2_140_224 => {
        handle => "https://tfhub.dev/google/imagenet/mobilenet_v2_140_224/classification/5",
        image_size => [ 224, 224 ],
    },
);

my $model_name = 'mobilenet_v2_100_224';

say "Selected model: $model_name : $model_name_to_params{$model_name}{handle}";

my $model_uri = URI->new( $model_name_to_params{$model_name}{handle} );
$model_uri->query_form( 'tf-hub-format' => 'compressed' );
my $model_base = substr( $model_uri->path, 1 ) =~ s,/,_,gr;
my $model_archive_path = "${model_base}.tar.gz";

use constant IMAGENET_LABEL_COUNT_WITH_BG => 1001;
my $labels_uri = URI->new('https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt');
my $labels_path = ($labels_uri->path_segments)[-1];

my $http = HTTP::Tiny->new;

for my $download ( [ $model_uri  => $model_archive_path ],
                   [ $labels_uri => $labels_path        ]) {
    my ($uri, $path) = @$download;
    say "Downloading $uri to $path";
    next if -e $path;
    $http->mirror( $uri, $path );
}

use Archive::Extract;
my $ae = Archive::Extract->new( archive => $model_archive_path );
die "Could not extract archive" unless $ae->extract( to => $model_base );

my $saved_model = path($model_base)->child('saved_model.pb');
say "Saved model is in $saved_model" if -f $saved_model;

lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod  view on Meta::CPAN

  
  use File::Which ();
  
  use List::Util ();
  
  use Data::Printer ( output => 'stderr', return_value => 'void', filters => ['PDL'] );
  use Data::Printer::Filter::PDL ();
  use Text::Table::Tiny qw(generate_table);
  
  use Imager;
  
  my $s = AI::TensorFlow::Libtensorflow::Status->New;
  sub AssertOK {
      die "Status $_[0]: " . $_[0]->Message
          unless $_[0]->GetCode == AI::TensorFlow::Libtensorflow::Status::OK;
      return;
  }
  AssertOK($s);

In this notebook, we will use C<PDL> to store and manipulate the ndarray data before converting it to a C<TFTensor>. The following functions help with copying the data back and forth between the two object types.

An important thing to note about the dimensions used by TensorFlow's TFTensors when compared with PDL is that the dimension lists are reversed. Consider a binary raster image with width W and height H stored in L<row-major format|https://en.wikipedia...

This difference will be explained more concretely further in the tutorial.

Future work will provide an API for more convenient wrappers which will provide an option to either copy or share the same data (if possible).

  use PDL;
  use AI::TensorFlow::Libtensorflow::DataType qw(FLOAT);
  
  use FFI::Platypus::Memory qw(memcpy);
  use FFI::Platypus::Buffer qw(scalar_to_pointer);
  
  sub FloatPDLTOTFTensor {
      my ($p) = @_;
      return AI::TensorFlow::Libtensorflow::Tensor->New(
          FLOAT, [ reverse $p->dims ], $p->get_dataref, sub { undef $p }
      );
  }
  
  sub FloatTFTensorToPDL {
      my ($t) = @_;
  
      my $pdl = zeros(float,reverse( map $t->Dim($_), 0..$t->NumDims-1 ) );
  
      memcpy scalar_to_pointer( ${$pdl->get_dataref} ),
          scalar_to_pointer( ${$t->Data} ),
          $t->ByteSize;
      $pdl->upd_data;
  
      $pdl;
  }

The following is just a small helper to generate an HTML C<<< <table> >>> for output in C<IPerl>.

  use HTML::Tiny;
  
  sub my_table {
      my ($data, $cb) = @_;
      my $h = HTML::Tiny->new;
      $h->table( { style => 'width: 100%' },
          [
              $h->tr(
                  map {
                      [
                          $h->td( $cb->($_, $h) )
                      ]
                  } @$data
              )
          ]
      )
  }

This is a helper to display images in Gnuplot for debugging, but those debugging lines are commented out.

  sub show_in_gnuplot {
      my ($p) = @_;
      require PDL::Graphics::Gnuplot;
      PDL::Graphics::Gnuplot::image( square => 1, $p );
  }

=head2 Fetch the model and labels

We are going to use an L<image classification model|https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/classification/5> from TensorFlow Hub based on the MobileNet V2 architecture. We download both the model and ImageNet classification labels.

  # image_size => [width, height] (but usually square images)
  my %model_name_to_params = (
      mobilenet_v2_100_224 => {
          handle     => 'https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/classification/5',
          image_size => [ 224, 224 ],
      },
      mobilenet_v2_140_224 => {
          handle => "https://tfhub.dev/google/imagenet/mobilenet_v2_140_224/classification/5",
          image_size => [ 224, 224 ],
      },
  );
  
  my $model_name = 'mobilenet_v2_100_224';
  
  say "Selected model: $model_name : $model_name_to_params{$model_name}{handle}";

B<STREAM (STDOUT)>:

  Selected model: mobilenet_v2_100_224 : https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/classification/5

B<RESULT>:

  1

We download the model and labels to the current directory then extract the model to a folder with the name given in C<$model_base>.

  my $model_uri = URI->new( $model_name_to_params{$model_name}{handle} );
  $model_uri->query_form( 'tf-hub-format' => 'compressed' );
  my $model_base = substr( $model_uri->path, 1 ) =~ s,/,_,gr;
  my $model_archive_path = "${model_base}.tar.gz";
  
  use constant IMAGENET_LABEL_COUNT_WITH_BG => 1001;
  my $labels_uri = URI->new('https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt');
  my $labels_path = ($labels_uri->path_segments)[-1];
  
  my $http = HTTP::Tiny->new;

lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod  view on Meta::CPAN

B<STREAM (STDOUT)>:

  The given SavedModel SignatureDef contains the following input(s):
    inputs['inputs'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 224, 224, 3)
        name: serving_default_inputs:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['logits'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1001)
        name: StatefulPartitionedCall:0
  Method name is: tensorflow/serving/predict

B<RESULT>:

  1

The above C<saved_model_cli> output shows that the model input is at C<serving_default_inputs:0> which means the operation named C<serving_default_inputs> at index C<0> and the output is at C<StatefulPartitionedCall:0> which means the operation named...

It also shows the type and shape of the C<TFTensor>s for those inputs and outputs. Together this is known as a signature.

For the C<input>, we have C<(-1, 224, 224, 3)> which is a L<common input image specification for TensorFlow Hub|https://www.tensorflow.org/hub/common_signatures/images#input>. This is known as C<channels_last> (or C<NHWC>) layout where the TensorFlow...

For the C<output>, we have C<(-1, 1001)> which is C<[batch_size, num_classes]> where the elements are scores that the image received for that ImageNet class.

Now we can load the model from that folder with the tag set C<[ 'serve' ]> by using the C<LoadFromSavedModel> constructor to create a C<::Graph> and a C<::Session> for that graph.

  my $opt = AI::TensorFlow::Libtensorflow::SessionOptions->New;
  
  my $graph = AI::TensorFlow::Libtensorflow::Graph->New;
  my $session = AI::TensorFlow::Libtensorflow::Session->LoadFromSavedModel(
      $opt, undef, $model_base, \@tags, $graph, undef, $s
  );
  AssertOK($s);

So let's use the names from the C<saved_model_cli> output to create our C<::Output> C<ArrayRef>s.

  my %ops = (
      in  => $graph->OperationByName('serving_default_inputs'),
      out => $graph->OperationByName('StatefulPartitionedCall'),
  );
  
  die "Could not get all operations" unless List::Util::all(sub { defined }, values %ops);
  
  my %outputs = map { $_ => [ AI::TensorFlow::Libtensorflow::Output->New( { oper => $ops{$_}, index => 0 } ) ] }
      keys %ops;
  
  p %outputs;
  
  say "Input: " , $outputs{in}[0];
  say "Output: ", $outputs{out}[0];

B<STREAM (STDOUT)>:

  Input: serving_default_inputs:0
  Output: StatefulPartitionedCall:0

B<STREAM (STDERR)>:

=for html <span style="display:inline-block;margin-left:1em;"><pre style="display: block"><code><span style="color: #33ccff;">{</span><span style="">
    </span><span style="color: #6666cc;">in</span><span style=""> </span><span style="color: #33ccff;">   </span><span style="color: #33ccff;">[</span><span style="">
        </span><span style="color: #9999cc;">[0] </span><span style="color: #cc66cc;">AI::TensorFlow::Libtensorflow::Output</span><span style=""> </span><span style="color: #33ccff;">{</span><span style="">
                </span><span style="color: #6666cc;">index</span><span style="color: #33ccff;">   </span><span style="color: #ff6633;">0</span><span style="color: #33ccff;">,</span><span style="">
                </span><span style="color: #6666cc;">oper</span><span style=""> </span><span style="color: #33ccff;">   </span><span style="color: #cc66cc;">AI::TensorFlow::Libtensorflow::Operation</span><span style=""> </span><span style="color: #33...
                    </span><span style="color: #6666cc;">Name</span><span style="">      </span><span style="color: #33ccff;">   </span><span style="color: #33ccff;">&quot;</span><span style="color: #669933;">serving_default_inputs</span><span style=...
                    </span><span style="color: #6666cc;">NumInputs</span><span style=""> </span><span style="color: #33ccff;">   </span><span style="color: #ff6633;">0</span><span style="color: #33ccff;">,</span><span style="">
                    </span><span style="color: #6666cc;">NumOutputs</span><span style="color: #33ccff;">   </span><span style="color: #ff6633;">1</span><span style="color: #33ccff;">,</span><span style="">
                    </span><span style="color: #6666cc;">OpType</span><span style="">    </span><span style="color: #33ccff;">   </span><span style="color: #33ccff;">&quot;</span><span style="color: #669933;">Placeholder</span><span style="color: #33...
                </span><span style="color: #33ccff;">}</span><span style="">
            </span><span style="color: #33ccff;">}</span><span style="">
    </span><span style="color: #33ccff;">]</span><span style="color: #33ccff;">,</span><span style="">
    </span><span style="color: #6666cc;">out</span><span style="color: #33ccff;">   </span><span style="color: #33ccff;">[</span><span style="">
        </span><span style="color: #9999cc;">[0] </span><span style="color: #cc66cc;">AI::TensorFlow::Libtensorflow::Output</span><span style=""> </span><span style="color: #33ccff;">{</span><span style="">
                </span><span style="color: #6666cc;">index</span><span style="color: #33ccff;">   </span><span style="color: #ff6633;">0</span><span style="color: #33ccff;">,</span><span style="">
                </span><span style="color: #6666cc;">oper</span><span style=""> </span><span style="color: #33ccff;">   </span><span style="color: #cc66cc;">AI::TensorFlow::Libtensorflow::Operation</span><span style=""> </span><span style="color: #33...
                    </span><span style="color: #6666cc;">Name</span><span style="">      </span><span style="color: #33ccff;">   </span><span style="color: #33ccff;">&quot;</span><span style="color: #669933;">StatefulPartitionedCall</span><span style...
                    </span><span style="color: #6666cc;">NumInputs</span><span style=""> </span><span style="color: #33ccff;">   </span><span style="color: #ff6633;">263</span><span style="color: #33ccff;">,</span><span style="">
                    </span><span style="color: #6666cc;">NumOutputs</span><span style="color: #33ccff;">   </span><span style="color: #ff6633;">1</span><span style="color: #33ccff;">,</span><span style="">
                    </span><span style="color: #6666cc;">OpType</span><span style="">    </span><span style="color: #33ccff;">   </span><span style="color: #33ccff;">&quot;</span><span style="color: #669933;">StatefulPartitionedCall</span><span style...
                </span><span style="color: #33ccff;">}</span><span style="">
            </span><span style="color: #33ccff;">}</span><span style="">
    </span><span style="color: #33ccff;">]</span><span style="">
</span><span style="color: #33ccff;">}</span><span style="">
</span></code></pre></span>

B<RESULT>:

  1

Now we can get the following testing images from Wikimedia.

  my %images_for_test_to_uri = (
      "tiger" => "https://upload.wikimedia.org/wikipedia/commons/b/b0/Bengal_tiger_%28Panthera_tigris_tigris%29_female_3_crop.jpg",
      #by Charles James Sharp, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons
      "bus" => "https://upload.wikimedia.org/wikipedia/commons/6/63/LT_471_%28LTZ_1471%29_Arriva_London_New_Routemaster_%2819522859218%29.jpg",
      #by Martin49 from London, England, CC BY 2.0 <https://creativecommons.org/licenses/by/2.0>, via Wikimedia Commons
      "car" => "https://upload.wikimedia.org/wikipedia/commons/4/49/2013-2016_Toyota_Corolla_%28ZRE172R%29_SX_sedan_%282018-09-17%29_01.jpg",
      #by EurovisionNim, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons
      "cat" => "https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg",
      #by Alvesgaspar, CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
      "dog" => "https://upload.wikimedia.org/wikipedia/commons/archive/a/a9/20090914031557%21Saluki_dog_breed.jpg",
      #by Craig Pemberton, CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
      "apple" => "https://upload.wikimedia.org/wikipedia/commons/1/15/Red_Apple.jpg",
      #by Abhijit Tembhekar from Mumbai, India, CC BY 2.0 <https://creativecommons.org/licenses/by/2.0>, via Wikimedia Commons
      "banana" => "https://upload.wikimedia.org/wikipedia/commons/1/1c/Bananas_white_background.jpg",
      #by fir0002  flagstaffotos [at] gmail.com		Canon 20D + Tamron 28-75mm f/2.8, GFDL 1.2 <http://www.gnu.org/licenses/old-licenses/fdl-1.2.html>, via Wikimedia Commons
      "turtle" => "https://upload.wikimedia.org/wikipedia/commons/8/80/Turtle_golfina_escobilla_oaxaca_mexico_claudio_giovenzana_2010.jpg",
      #by Claudio Giovenzana, CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
      "flamingo" => "https://upload.wikimedia.org/wikipedia/commons/b/b8/James_Flamingos_MC.jpg",
      #by Christian Mehlführer, User:Chmehl, CC BY 3.0 <https://creativecommons.org/licenses/by/3.0>, via Wikimedia Commons
      "piano" => "https://upload.wikimedia.org/wikipedia/commons/d/da/Steinway_%26_Sons_upright_piano%2C_model_K-132%2C_manufactured_at_Steinway%27s_factory_in_Hamburg%2C_Germany.png",
      #by "Photo: © Copyright Steinway & Sons", CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
      "honeycomb" => "https://upload.wikimedia.org/wikipedia/commons/f/f7/Honey_comb.jpg",
      #by Merdal, CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>, via Wikimedia Commons
      "teapot" => "https://upload.wikimedia.org/wikipedia/commons/4/44/Black_tea_pot_cropped.jpg",
      #by Mendhak, CC BY-SA 2.0 <https://creativecommons.org/licenses/by-sa/2.0>, via Wikimedia Commons
  );
  
  my @image_names = sort keys %images_for_test_to_uri;
  
  
  if( IN_IPERL ) {
      IPerl->html(
          my_table( \@image_names, sub {
              my ($image_name, $h) = @_;
              (
                  $h->tt($image_name),
                  $h->a( { href => $images_for_test_to_uri{$image_name} },
                      $h->img({
                          src => $images_for_test_to_uri{$image_name},
                          alt => $image_name,
                          width => '50%',
                      })
                  ),
              )
          })
      );
  }

B<DISPLAY>:

=for html <span style="display:inline-block;margin-left:1em;"><p><table style="width: 100%"><tr><td><tt>apple</tt></td><td><a href="https://upload.wikimedia.org/wikipedia/commons/1/15/Red_Apple.jpg"><img alt="apple" src="https://upload.wikimedia.org/...

=head2 Download the test images and transform them into suitable input data

We now fetch these images and prepare them to be the in the needed format by using C<Imager> to resize and add padding. Then we turn the C<Imager> data into a C<PDL> ndarray. Since the C<Imager> data is stored as 32-bits with 4 channels in the order ...

We then take all the PDL ndarrays and concatenate them. Again, note that the dimension lists for the PDL ndarray and the TFTensor are reversed.

  sub imager_paste_center_pad {
      my ($inner, $padded_sz, @rest) = @_;
  
      my $outer = Imager->new( List::Util::mesh( [qw(xsize ysize)], $padded_sz ),
          @rest
      );
  
      $outer->paste(
          left => int( ($outer->getwidth  - $inner->getwidth ) / 2 ),
          top  => int( ($outer->getheight - $inner->getheight) / 2 ),
          src  => $inner,
      );
  
      $outer;
  }
  
  sub imager_scale_to {
      my ($img, $image_size) = @_;
      my $rescaled = $img->scale(
          List::Util::mesh( [qw(xpixels ypixels)], $image_size ),
          type => 'min',
          qtype => 'mixing', # 'mixing' seems to work better than 'normal'
      );
  }
  
  sub load_image_to_pdl {
      my ($uri, $image_size) = @_;
  
      my $http = HTTP::Tiny->new;
      my $response = $http->get( $uri );
      die "Could not fetch image from $uri" unless $response->{success};
      say "Downloaded $uri";
  
      my $img = Imager->new;
      $img->read( data => $response->{content} );
  
      my $rescaled = imager_scale_to($img, $image_size);
  
      say sprintf "Rescaled image from [ %d x %d ] to [ %d x %d ]",
          $img->getwidth, $img->getheight,
          $rescaled->getwidth, $rescaled->getheight;
  
      my $padded = imager_paste_center_pad($rescaled, $image_size,
          # ARGB fits in 32-bits (uint32_t)
          channels => 4
      );
  
      say sprintf "Padded to [ %d x %d ]", $padded->getwidth, $padded->getheight;
  
      # Create PDL ndarray from Imager data in-memory.
      my $data;
      $padded->write( data => \$data, type => 'raw' )
          or die "could not write ". $padded->errstr;

lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod  view on Meta::CPAN

      #show_in_gnuplot( $pdl_channels->slice(':,:,-1:0')         ); #DEBUG
      #show_in_gnuplot(   $pdl_scaled->slice(':,:,-1:0') * 255.0 ); #DEBUG
  
      $pdl_scaled;
  }
  
  my @pdl_images = map {
      load_image_to_pdl(
          $images_for_test_to_uri{$_},
          $model_name_to_params{$model_name}{image_size}
      );
  } @image_names;
  
  my $pdl_image_batched = cat(@pdl_images);
  my $t = FloatPDLTOTFTensor($pdl_image_batched);
  
  p $pdl_image_batched;
  p $t;

B<STREAM (STDOUT)>:

  Downloaded https://upload.wikimedia.org/wikipedia/commons/1/15/Red_Apple.jpg
  Rescaled image from [ 2418 x 2192 ] to [ 224 x 203 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/1/1c/Bananas_white_background.jpg
  Rescaled image from [ 1600 x 1067 ] to [ 224 x 149 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/6/63/LT_471_%28LTZ_1471%29_Arriva_London_New_Routemaster_%2819522859218%29.jpg
  Rescaled image from [ 3840 x 2560 ] to [ 224 x 149 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/4/49/2013-2016_Toyota_Corolla_%28ZRE172R%29_SX_sedan_%282018-09-17%29_01.jpg
  Rescaled image from [ 4152 x 2252 ] to [ 224 x 121 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg
  Rescaled image from [ 1795 x 2397 ] to [ 168 x 224 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/archive/a/a9/20090914031557%21Saluki_dog_breed.jpg
  Rescaled image from [ 543 x 523 ] to [ 224 x 216 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/b/b8/James_Flamingos_MC.jpg
  Rescaled image from [ 3000 x 1999 ] to [ 224 x 149 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/f/f7/Honey_comb.jpg
  Rescaled image from [ 800 x 600 ] to [ 224 x 168 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/d/da/Steinway_%26_Sons_upright_piano%2C_model_K-132%2C_manufactured_at_Steinway%27s_factory_in_Hamburg%2C_Germany.png
  Rescaled image from [ 2059 x 2080 ] to [ 222 x 224 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/4/44/Black_tea_pot_cropped.jpg
  Rescaled image from [ 900 x 838 ] to [ 224 x 209 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/b/b0/Bengal_tiger_%28Panthera_tigris_tigris%29_female_3_crop.jpg
  Rescaled image from [ 4500 x 3000 ] to [ 224 x 149 ]
  Padded to [ 224 x 224 ]
  Downloaded https://upload.wikimedia.org/wikipedia/commons/8/80/Turtle_golfina_escobilla_oaxaca_mexico_claudio_giovenzana_2010.jpg
  Rescaled image from [ 2000 x 1329 ] to [ 224 x 149 ]
  Padded to [ 224 x 224 ]

B<STREAM (STDERR)>:

=for html <span style="display:inline-block;margin-left:1em;"><pre style="display: block"><code><span style="color: #cc66cc;">PDL</span><span style="color: #33ccff;"> {</span><span style="">
    </span><span style="color: #6666cc;">Data    </span><span style=""> : </span><span style="color: #669933;">too long to print</span><span style="">
    </span><span style="color: #6666cc;">Type    </span><span style=""> : </span><span style="color: #cc66cc;">float</span><span style="">
    </span><span style="color: #6666cc;">Shape   </span><span style=""> : </span><span style="color: #33ccff;">[</span><span style="color: #9999cc;">3 224 224 12</span><span style="color: #33ccff;">]</span><span style="">
    </span><span style="color: #6666cc;">Nelem   </span><span style=""> : </span><span style="color: #dd6;">1806336</span><span style="">
    </span><span style="color: #6666cc;">Min     </span><span style=""> : </span><span style="color: #f66;">0</span><span style="">
    </span><span style="color: #6666cc;">Max     </span><span style=""> : </span><span style="color: #99f;">1</span><span style="">
    </span><span style="color: #6666cc;">Badflag </span><span style=""> : </span><span style="color: #2c2;">No</span><span style="">
    </span><span style="color: #6666cc;">Has Bads</span><span style=""> : </span><span style="color: #2c2;">No</span><span style="">
</span><span style="color: #33ccff;">}</span><span style="">
</span><span style="color: #cc66cc;">AI::TensorFlow::Libtensorflow::Tensor</span><span style=""> </span><span style="color: #33ccff;">{</span><span style="">
    </span><span style="color: #6666cc;">Type           </span><span style=""> </span><span style="color: #cc66cc;">FLOAT</span><span style="">
    </span><span style="color: #6666cc;">Dims           </span><span style=""> </span><span style="color: #33ccff;">[</span><span style=""> </span><span style="color: #ff6633;">12</span><span style=""> </span><span style="color: #ff6633;">224</span><...
    </span><span style="color: #6666cc;">NumDims        </span><span style=""> </span><span style="color: #ff6633;">4</span><span style="">
    </span><span style="color: #6666cc;">ElementCount   </span><span style=""> </span><span style="color: #ff6633;">1806336</span><span style="">
</span><span style="color: #33ccff;">}</span><span style="">
</span></code></pre></span>

=head2 Run the model for inference

We can use the C<Run> method to run the session and get the output C<TFTensor>.

First, we send a single random input to warm up the model.

  my $RunSession = sub {
      my ($session, $t) = @_;
      my @outputs_t;
  
      $session->Run(
          undef,
          $outputs{in}, [$t],
          $outputs{out}, \@outputs_t,
          undef,
          undef,
          $s
      );
      AssertOK($s);
  
      return $outputs_t[0];
  };
  
  say "Warming up the model";
  use PDL::GSL::RNG;
  my $rng = PDL::GSL::RNG->new('default');
  my $image_size = $model_name_to_params{$model_name}{image_size};
  my $warmup_input = zeros(float, 3, @$image_size, 1 );
  $rng->get_uniform($warmup_input);
  
  p $RunSession->($session, FloatPDLTOTFTensor($warmup_input));

B<STREAM (STDOUT)>:

  Warming up the model

B<STREAM (STDERR)>:

=for html <span style="display:inline-block;margin-left:1em;"><pre style="display: block"><code><span style="color: #cc66cc;">AI::TensorFlow::Libtensorflow::Tensor</span><span style=""> </span><span style="color: #33ccff;">{</span><span style="">
    </span><span style="color: #6666cc;">Type           </span><span style=""> </span><span style="color: #cc66cc;">FLOAT</span><span style="">
    </span><span style="color: #6666cc;">Dims           </span><span style=""> </span><span style="color: #33ccff;">[</span><span style=""> </span><span style="color: #ff6633;">1</span><span style=""> </span><span style="color: #ff6633;">1001</span><...
    </span><span style="color: #6666cc;">NumDims        </span><span style=""> </span><span style="color: #ff6633;">2</span><span style="">
    </span><span style="color: #6666cc;">ElementCount   </span><span style=""> </span><span style="color: #ff6633;">1001</span><span style="">
</span><span style="color: #33ccff;">}</span><span style="">
</span></code></pre></span>

Then we send the batched image data. The returned scores need to by normalised using the L<softmax function|https://en.wikipedia.org/wiki/Softmax_function> with the following formula (taken from Wikipedia):

$$ {\displaystyle \sigma (\mathbf {z} )I<{i}={\frac {e^{z>{i}}}{\sum I<{j=1}^{K}e^{z>{j}}}}\ \ {\text{ for }}i=1,\dotsc ,K{\text{ and }}\mathbf {z} =(zI<{1},\dotsc ,z>{K})\in \mathbb {R} ^{K}.} $$

  my $output_pdl_batched = FloatTFTensorToPDL($RunSession->($session, $t));
  my $softmax = sub { ( map $_/sumover($_)->dummy(0), exp($_[0]) )[0] };
  my $probabilities_batched = $softmax->($output_pdl_batched);
  p $probabilities_batched;

B<STREAM (STDERR)>:

=for html <span style="display:inline-block;margin-left:1em;"><pre style="display: block"><code><span style="color: #cc66cc;">PDL</span><span style="color: #33ccff;"> {</span><span style="">
    </span><span style="color: #6666cc;">Data    </span><span style=""> : </span><span style="color: #669933;">too long to print</span><span style="">
    </span><span style="color: #6666cc;">Type    </span><span style=""> : </span><span style="color: #cc66cc;">float</span><span style="">
    </span><span style="color: #6666cc;">Shape   </span><span style=""> : </span><span style="color: #33ccff;">[</span><span style="color: #9999cc;">1001 12</span><span style="color: #33ccff;">]</span><span style="">
    </span><span style="color: #6666cc;">Nelem   </span><span style=""> : </span><span style="color: #dd6;">12012</span><span style="">
    </span><span style="color: #6666cc;">Min     </span><span style=""> : </span><span style="color: #f66;">2.73727380317723e-07</span><span style="">
    </span><span style="color: #6666cc;">Max     </span><span style=""> : </span><span style="color: #99f;">0.980696022510529</span><span style="">
    </span><span style="color: #6666cc;">Badflag </span><span style=""> : </span><span style="color: #2c2;">No</span><span style="">
    </span><span style="color: #6666cc;">Has Bads</span><span style=""> : </span><span style="color: #2c2;">No</span><span style="">
</span><span style="color: #33ccff;">}</span><span style="">
</span></code></pre></span>

=head2 Results summary

Then select the top 5 of those and find their class labels.

  my $N = 5; # number to select
  
  my $top_batched = $probabilities_batched->qsorti->slice([-1, -$N]);
  
  my @top_lists   = dog($top_batched);
  
  my $includes_background_class = $probabilities_batched->dim(0) == IMAGENET_LABEL_COUNT_WITH_BG;
  
  if( IN_IPERL ) {
      my $html = IPerl->html(
          my_table( [0..$#image_names], sub {
              my ($batch_idx, $h) = @_;
              my $image_name = $image_names[$batch_idx];
              my @top_for_image = $top_lists[$batch_idx]->list;
              (
                      $h->tt($image_name),
                      $h->a( { href => $images_for_test_to_uri{$image_name} },
                          $h->img({
                              src => $images_for_test_to_uri{$image_name},
                              alt => $image_name,
                              width => '50%',
                          })
                      ),
                      do {
                          my @tr;
                          push @tr, [ $h->th('Rank', 'Label No', 'Label', 'Prob') ];
                          while( my ($i, $label_index) = each @top_for_image ) {
                              my $class_index = $includes_background_class ? $label_index : $label_index + 1;
                              push @tr, [ $h->td(
                                      $i + 1,
                                      $class_index,
                                      $labels[$class_index],
                                      $probabilities_batched->at($label_index,$batch_idx),
                              ) ];
  
                          }
                          $h->table([$h->tr(@tr)])
                      },
                  )
          })
      );
      IPerl->display($html);
  } else {
      for my $batch_idx (0..$#image_names) {
          my $image_name = $image_names[$batch_idx];
          my @top_for_image = $top_lists[$batch_idx]->list;
          my @td;
          say "Image name: `$image_name`";
          my $header = [ ('Rank', 'Label No', 'Label', 'Prob') ];
          my @rows;
          while( my ($i, $label_index) = each @top_for_image ) {
              my $class_index = $includes_background_class ? $label_index : $label_index + 1;
              push @rows, [ (
                      $i + 1,
                      $class_index,
                      $labels[$class_index],
                      $probabilities_batched->at($label_index,$batch_idx),
              ) ];
          }
          say generate_table( rows => [ $header, @rows ], header_row => 1 );
          print "\n";
      }
  }

B<DISPLAY>:

=for html <span style="display:inline-block;margin-left:1em;"><p><table style="width: 100%"><tr><td><tt>apple</tt></td><td><a href="https://upload.wikimedia.org/wikipedia/commons/1/15/Red_Apple.jpg"><img alt="apple" src="https://upload.wikimedia.org/...

  my $p_approx_batched = $probabilities_batched->sumover->approx(1, 1e-5);
  p $p_approx_batched;
  say "All probabilities sum up to approximately 1" if $p_approx_batched->all->sclr;

B<STREAM (STDOUT)>:

  All probabilities sum up to approximately 1

B<STREAM (STDERR)>:

=for html <span style="display:inline-block;margin-left:1em;"><pre style="display: block"><code><span style="color: #cc66cc;">PDL</span><span style="color: #33ccff;"> {</span><span style="">
    </span><span style="color: #6666cc;">Data    </span><span style=""> : </span><span style="color: #33ccff;">[</span><span style="color: #ff6633;">1 1 1 1 1 1 1 1 1 1 1 1</span><span style="color: #33ccff;">]</span><span style="">
    </span><span style="color: #6666cc;">Type    </span><span style=""> : </span><span style="color: #cc66cc;">double</span><span style="">
    </span><span style="color: #6666cc;">Shape   </span><span style=""> : </span><span style="color: #33ccff;">[</span><span style="color: #9999cc;">12</span><span style="color: #33ccff;">]</span><span style="">
    </span><span style="color: #6666cc;">Nelem   </span><span style=""> : </span><span style="color: #dd6;">12</span><span style="">
    </span><span style="color: #6666cc;">Min     </span><span style=""> : </span><span style="color: #f66;">1</span><span style="">
    </span><span style="color: #6666cc;">Max     </span><span style=""> : </span><span style="color: #99f;">1</span><span style="">
    </span><span style="color: #6666cc;">Badflag </span><span style=""> : </span><span style="color: #2c2;">No</span><span style="">
    </span><span style="color: #6666cc;">Has Bads</span><span style=""> : </span><span style="color: #2c2;">No</span><span style="">
</span><span style="color: #33ccff;">}</span><span style="">
</span></code></pre></span>

B<RESULT>:

  1

=head1 RESOURCE USAGE

  use Filesys::DiskUsage qw/du/;
  
  my $total = du( { 'human-readable' => 1, dereference => 1 },
      $model_archive_path, $model_base, $labels_path );
  
  say "Disk space usage: $total"; undef;

B<STREAM (STDOUT)>:

  Disk space usage: 27.45M

=head1 DEBUGGING

The following images can be used to test the C<load_image_to_pdl> function.

  my @solid_channel_uris = (
      'https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Solid_red.svg/480px-Solid_red.svg.png',
      'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1d/Green_00FF00_9x9.svg/480px-Green_00FF00_9x9.svg.png',
      'https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Solid_blue.svg/480px-Solid_blue.svg.png',
  );
  undef;

=head1 CPANFILE

  requires 'AI::TensorFlow::Libtensorflow';
  requires 'AI::TensorFlow::Libtensorflow::DataType';
  requires 'Archive::Extract';
  requires 'Data::Printer';
  requires 'Data::Printer::Filter::PDL';
  requires 'FFI::Platypus::Buffer';
  requires 'FFI::Platypus::Memory';
  requires 'File::Which';
  requires 'Filesys::DiskUsage';
  requires 'HTML::Tiny';
  requires 'HTTP::Tiny';
  requires 'Imager';
  requires 'List::Util';
  requires 'PDL';
  requires 'PDL::GSL::RNG';
  requires 'Path::Tiny';
  requires 'Syntax::Construct';
  requires 'Text::Table::Tiny';
  requires 'URI';
  requires 'constant';
  requires 'feature';
  requires 'lib::projectroot';
  requires 'strict';
  requires 'utf8';
  requires 'warnings';

=head1 AUTHOR



( run in 0.728 second using v1.01-cache-2.11-cpan-39bf76dae61 )