AI-TensorFlow-Libtensorflow
view release on metacpan or search on metacpan
lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod view on Meta::CPAN
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;
my @labels = path($labels_path)->lines( { chomp => 1 });
die "Labels should have @{[ IMAGENET_LABEL_COUNT_WITH_BG ]} items"
unless @labels == IMAGENET_LABEL_COUNT_WITH_BG;
say "Got labels: ", join( ", ", List::Util::head(5, @labels) ), ", etc.";
my @tags = ( 'serve' );
if( File::Which::which('saved_model_cli')) {
local $ENV{TF_CPP_MIN_LOG_LEVEL} = 3; # quiet the TensorFlow logger for the following command
system(qw(saved_model_cli show),
qw(--dir) => $model_base,
qw(--tag_set) => join(',', @tags),
qw(--signature_def) => 'serving_default'
) == 0 or die "Could not run saved_model_cli";
} else {
say "Install the tensorflow Python package to get the `saved_model_cli` command.";
}
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);
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];
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%',
})
),
)
})
);
}
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 {
lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod view on Meta::CPAN
( $pdl_channels / float(255) )
);
## flip vertically to see image right way up
#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;
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));
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;
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";
}
}
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;
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;
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;
__END__
=pod
=encoding UTF-8
=head1 NAME
AI::TensorFlow::Libtensorflow::Manual::Notebook::InferenceUsingTFHubMobileNetV2Model - Using TensorFlow to do image classification using a pre-trained model
=head1 SYNOPSIS
The following tutorial is based on the L<Image Classification with TensorFlow Hub notebook|https://github.com/tensorflow/docs/blob/master/site/en/hub/tutorials/image_classification.ipynb>. It uses a pre-trained model based on the I<MobileNet V2> arch...
Please look at the L<SECURITY note|https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md> regarding running models as models are programs. You can also used C<saved_model_cli scan> to check for L<security-sensitive "denylisted ops"|https:/...
If you would like to visualise a model, you can use L<Netron|https://github.com/lutzroeder/netron> on the C<.pb> file.
=head1 COLOPHON
The following document is either a POD file which can additionally be run as a Perl script or a Jupyter Notebook which can be run in L<IPerl|https://p3rl.org/Devel::IPerl> (viewable online at L<nbviewer|https://nbviewer.org/github/EntropyOrg/perl-AI-...
If you are running the code, you may optionally install the L<C<tensorflow> Python package|https://www.tensorflow.org/install/pip> in order to access the C<saved_model_cli> command, but this is only used for informational purposes.
=head1 TUTORIAL
=head2 Load the library
First, we need to load the C<AI::TensorFlow::Libtensorflow> library and more helpers. We then create an C<AI::TensorFlow::Libtensorflow::Status> object and helper function to make sure that the calls to the C<libtensorflow> C library are working prop...
lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod view on Meta::CPAN
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;
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;
my @labels = path($labels_path)->lines( { chomp => 1 });
die "Labels should have @{[ IMAGENET_LABEL_COUNT_WITH_BG ]} items"
unless @labels == IMAGENET_LABEL_COUNT_WITH_BG;
say "Got labels: ", join( ", ", List::Util::head(5, @labels) ), ", etc.";
B<STREAM (STDOUT)>:
Downloading https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/classification/5?tf-hub-format=compressed to google_imagenet_mobilenet_v2_100_224_classification_5.tar.gz
Downloading https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt to ImageNetLabels.txt
Saved model is in google_imagenet_mobilenet_v2_100_224_classification_5/saved_model.pb
Got labels: background, tench, goldfish, great white shark, tiger shark, etc.
B<RESULT>:
1
=head2 Load the model and session
We define the tag set C<[ 'serve' ]> which we will use to load the model.
my @tags = ( 'serve' );
B<RESULT>:
serve
We can examine what computations are contained in the graph in terms of the names of the inputs and outputs of an operation found in the graph by running C<saved_model_cli>.
if( File::Which::which('saved_model_cli')) {
local $ENV{TF_CPP_MIN_LOG_LEVEL} = 3; # quiet the TensorFlow logger for the following command
system(qw(saved_model_cli show),
qw(--dir) => $model_base,
qw(--tag_set) => join(',', @tags),
qw(--signature_def) => 'serving_default'
) == 0 or die "Could not run saved_model_cli";
} else {
say "Install the tensorflow Python package to get the `saved_model_cli` command.";
}
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(
lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod view on Meta::CPAN
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;">"</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;">"</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;">"</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;">"</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;
# $data is packed as PDL->dims == [w,h] with ARGB pixels
# $ PDL::howbig(ulong) # 4
my $pdl_raw = zeros(ulong, $padded->getwidth, $padded->getheight);
${ $pdl_raw->get_dataref } = $data;
$pdl_raw->upd_data;
# Split uint32_t pixels into first dimension with 3 channels (R,G,B) with values 0-255.
my @shifts = map 8*$_, 0..2;
my $pdl_channels = $pdl_raw->dummy(0)
->and2(ulong(map 0xFF << $_, @shifts)->slice(':,*,*') )
->shiftright( ulong(@shifts)->slice(':,*,*') )
->byte;
my $pdl_scaled = (
# Scale to [ 0, 1 ].
( $pdl_channels / float(255) )
);
## flip vertically to see image right way up
#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 {
lib/AI/TensorFlow/Libtensorflow/Manual/Notebook/InferenceUsingTFHubMobileNetV2Model.pod view on Meta::CPAN
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';
( run in 2.090 seconds using v1.01-cache-2.11-cpan-d8267643d1d )