Note
Go to the end to download the full example code
YOLO/PASCAL-VOC detection tutorial
This tutorial demonstrates that Akida can perform object detection. This is illustrated using a subset of the PASCAL-VOC 2007 dataset which contains 20 classes. The YOLOv2 architecture from Redmon et al (2016) has been chosen to tackle this object detection problem.
1. Introduction
1.1 Object detection
Object detection is a computer vision task that combines two elemental tasks:
object classification that consists in assigning a class label to an image like shown in the AkidaNet/ImageNet inference example
object localization that consists of drawing a bounding box around one or several objects in an image
One can learn more about the subject by reading this introduction to object detection blog article.
1.2 YOLO key concepts
You Only Look Once (YOLO) is a deep neural network architecture dedicated to object detection.
As opposed to classic networks that handle object detection, YOLO predicts bounding boxes (localization task) and class probabilities (classification task) from a single neural network in a single evaluation. The object detection task is reduced to a regression problem to spatially separated boxes and associated class probabilities.
YOLO base concept is to divide an input image into regions, forming a grid, and to predict bounding boxes and probabilities for each region. The bounding boxes are weighted by the prediction probabilities.
YOLO also uses the concept of “anchors boxes” or “prior boxes”. The network does not actually predict the actual bounding boxes but offsets from anchors boxes which are templates (width/height ratio) computed by clustering the dimensions of the ground truth boxes from the training dataset. The anchors then represent the average shape and size of the objects to detect. More details on the anchors boxes concept are given in this blog article.
Additional information about YOLO can be found on the Darknet website and source code for the preprocessing and postprocessing functions that are included in akida_models package (see the processing section in the model zoo) is largely inspired from experiencor github.
2. Preprocessing tools
A subset of VOC has been prepared with test images from VOC2007 that contains 5 examples of each class. The dataset is represented as a tfrecord file, containing images, labels, and bounding boxes.
The load_tf_dataset function is a helper function that facilitates the loading and parsing of the tfrecord file.
The YOLO toolkit offers several methods to prepare data for processing, see load_image, preprocess_image.
import tensorflow as tf
from akida_models import fetch_file
# Download TFrecords test set from Brainchip data server
data_path = fetch_file(
fname="voc_test_20_classes.tfrecord",
origin="https://data.brainchip.com/dataset-mirror/voc/test_20_classes.tfrecord",
cache_subdir='datasets/voc',
extract=True)
# Helper function to load and parse the Tfrecord file.
def load_tf_dataset(tf_record_file_path):
tfrecord_files = [tf_record_file_path]
# Feature description for parsing the TFRecord
feature_description = {
'image': tf.io.FixedLenFeature([], tf.string),
'objects/bbox': tf.io.VarLenFeature(tf.float32),
'objects/label': tf.io.VarLenFeature(tf.int64),
}
def _count_tfrecord_examples(dataset):
return len(list(dataset.as_numpy_iterator()))
def _parse_tfrecord_fn(example_proto):
example = tf.io.parse_single_example(example_proto, feature_description)
# Decode the image from bytes
example['image'] = tf.io.decode_jpeg(example['image'], channels=3)
# Convert the VarLenFeature to a dense tensor
example['objects/label'] = tf.sparse.to_dense(example['objects/label'], default_value=0)
example['objects/bbox'] = tf.sparse.to_dense(example['objects/bbox'])
# Boxes were flattenned that's why we need to reshape them
example['objects/bbox'] = tf.reshape(example['objects/bbox'],
(tf.shape(example['objects/label'])[0], 4))
# Create a new dictionary structure
objects = {
'label': example['objects/label'],
'bbox': example['objects/bbox'],
}
# Remove unnecessary keys
example.pop('objects/label')
example.pop('objects/bbox')
# Add 'objects' key to the main dictionary
example['objects'] = objects
return example
# Create a TFRecordDataset
dataset = tf.data.TFRecordDataset(tfrecord_files)
len_dataset = _count_tfrecord_examples(dataset)
parsed_dataset = dataset.map(_parse_tfrecord_fn)
return parsed_dataset, len_dataset
labels = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus',
'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant', 'sheep', 'sofa',
'train', 'tvmonitor']
val_dataset, len_val_dataset = load_tf_dataset(data_path)
print(f"Loaded VOC2007 sample test data: {len_val_dataset} images.")
Downloading data from https://data.brainchip.com/dataset-mirror/voc/test_20_classes.tfrecord.
0/8399422 [..............................] - ETA: 0s
106496/8399422 [..............................] - ETA: 4s
663552/8399422 [=>............................] - ETA: 1s
1597440/8399422 [====>.........................] - ETA: 0s
2654208/8399422 [========>.....................] - ETA: 0s
3686400/8399422 [============>.................] - ETA: 0s
4849664/8399422 [================>.............] - ETA: 0s
6078464/8399422 [====================>.........] - ETA: 0s
7397376/8399422 [=========================>....] - ETA: 0s
8399422/8399422 [==============================] - 0s 0us/step
Download complete.
Loaded VOC2007 sample test data: 100 images.
Anchors can also be computed easily using YOLO toolkit.
Note
The following code is given as an example. In a real use case scenario, anchors are computed on the training dataset.
from akida_models.detection.generate_anchors import generate_anchors
num_anchors = 5
grid_size = (7, 7)
anchors_example = generate_anchors(val_dataset, num_anchors, grid_size)
Average IOU for 5 anchors: 0.68
Anchors: [[1.09978, 1.95618], [2.28376, 2.34115], [3.54805, 3.30278], [4.554, 4.15158], [5.47376, 5.79116]]
3. Model architecture
The model zoo contains a YOLO model that is built upon the AkidaNet architecture and 3 separable convolutional layers at the top for bounding box and class estimation followed by a final separable convolutional which is the detection layer. Note that for efficiency, the alpha parameter in AkidaNet (network width or number of filter in each layer) is set to 0.5.
from akida_models import yolo_base
# Create a yolo model for 20 classes with 5 anchors and grid size of 7
classes = len(labels)
model = yolo_base(input_shape=(224, 224, 3),
classes=classes,
nb_box=num_anchors,
alpha=0.5)
model.summary()
Model: "yolo_base"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input (InputLayer) [(None, 224, 224, 3)] 0
rescaling (Rescaling) (None, 224, 224, 3) 0
conv_0 (Conv2D) (None, 112, 112, 16) 432
conv_0/BN (BatchNormalizati (None, 112, 112, 16) 64
on)
conv_0/relu (ReLU) (None, 112, 112, 16) 0
conv_1 (Conv2D) (None, 112, 112, 32) 4608
conv_1/BN (BatchNormalizati (None, 112, 112, 32) 128
on)
conv_1/relu (ReLU) (None, 112, 112, 32) 0
conv_2 (Conv2D) (None, 56, 56, 64) 18432
conv_2/BN (BatchNormalizati (None, 56, 56, 64) 256
on)
conv_2/relu (ReLU) (None, 56, 56, 64) 0
conv_3 (Conv2D) (None, 56, 56, 64) 36864
conv_3/BN (BatchNormalizati (None, 56, 56, 64) 256
on)
conv_3/relu (ReLU) (None, 56, 56, 64) 0
dw_separable_4 (DepthwiseCo (None, 28, 28, 64) 576
nv2D)
pw_separable_4 (Conv2D) (None, 28, 28, 128) 8192
pw_separable_4/BN (BatchNor (None, 28, 28, 128) 512
malization)
pw_separable_4/relu (ReLU) (None, 28, 28, 128) 0
dw_separable_5 (DepthwiseCo (None, 28, 28, 128) 1152
nv2D)
pw_separable_5 (Conv2D) (None, 28, 28, 128) 16384
pw_separable_5/BN (BatchNor (None, 28, 28, 128) 512
malization)
pw_separable_5/relu (ReLU) (None, 28, 28, 128) 0
dw_separable_6 (DepthwiseCo (None, 14, 14, 128) 1152
nv2D)
pw_separable_6 (Conv2D) (None, 14, 14, 256) 32768
pw_separable_6/BN (BatchNor (None, 14, 14, 256) 1024
malization)
pw_separable_6/relu (ReLU) (None, 14, 14, 256) 0
dw_separable_7 (DepthwiseCo (None, 14, 14, 256) 2304
nv2D)
pw_separable_7 (Conv2D) (None, 14, 14, 256) 65536
pw_separable_7/BN (BatchNor (None, 14, 14, 256) 1024
malization)
pw_separable_7/relu (ReLU) (None, 14, 14, 256) 0
dw_separable_8 (DepthwiseCo (None, 14, 14, 256) 2304
nv2D)
pw_separable_8 (Conv2D) (None, 14, 14, 256) 65536
pw_separable_8/BN (BatchNor (None, 14, 14, 256) 1024
malization)
pw_separable_8/relu (ReLU) (None, 14, 14, 256) 0
dw_separable_9 (DepthwiseCo (None, 14, 14, 256) 2304
nv2D)
pw_separable_9 (Conv2D) (None, 14, 14, 256) 65536
pw_separable_9/BN (BatchNor (None, 14, 14, 256) 1024
malization)
pw_separable_9/relu (ReLU) (None, 14, 14, 256) 0
dw_separable_10 (DepthwiseC (None, 14, 14, 256) 2304
onv2D)
pw_separable_10 (Conv2D) (None, 14, 14, 256) 65536
pw_separable_10/BN (BatchNo (None, 14, 14, 256) 1024
rmalization)
pw_separable_10/relu (ReLU) (None, 14, 14, 256) 0
dw_separable_11 (DepthwiseC (None, 14, 14, 256) 2304
onv2D)
pw_separable_11 (Conv2D) (None, 14, 14, 256) 65536
pw_separable_11/BN (BatchNo (None, 14, 14, 256) 1024
rmalization)
pw_separable_11/relu (ReLU) (None, 14, 14, 256) 0
dw_separable_12 (DepthwiseC (None, 7, 7, 256) 2304
onv2D)
pw_separable_12 (Conv2D) (None, 7, 7, 512) 131072
pw_separable_12/BN (BatchNo (None, 7, 7, 512) 2048
rmalization)
pw_separable_12/relu (ReLU) (None, 7, 7, 512) 0
dw_separable_13 (DepthwiseC (None, 7, 7, 512) 4608
onv2D)
pw_separable_13 (Conv2D) (None, 7, 7, 512) 262144
pw_separable_13/BN (BatchNo (None, 7, 7, 512) 2048
rmalization)
pw_separable_13/relu (ReLU) (None, 7, 7, 512) 0
dw_1conv (DepthwiseConv2D) (None, 7, 7, 512) 4608
pw_1conv (Conv2D) (None, 7, 7, 1024) 524288
pw_1conv/BN (BatchNormaliza (None, 7, 7, 1024) 4096
tion)
pw_1conv/relu (ReLU) (None, 7, 7, 1024) 0
dw_2conv (DepthwiseConv2D) (None, 7, 7, 1024) 9216
pw_2conv (Conv2D) (None, 7, 7, 1024) 1048576
pw_2conv/BN (BatchNormaliza (None, 7, 7, 1024) 4096
tion)
pw_2conv/relu (ReLU) (None, 7, 7, 1024) 0
dw_3conv (DepthwiseConv2D) (None, 7, 7, 1024) 9216
pw_3conv (Conv2D) (None, 7, 7, 1024) 1048576
pw_3conv/BN (BatchNormaliza (None, 7, 7, 1024) 4096
tion)
pw_3conv/relu (ReLU) (None, 7, 7, 1024) 0
dw_detection_layer (Depthwi (None, 7, 7, 1024) 9216
seConv2D)
pw_detection_layer (Conv2D) (None, 7, 7, 125) 128125
=================================================================
Total params: 3,665,965
Trainable params: 3,653,837
Non-trainable params: 12,128
_________________________________________________________________
The model output can be reshaped to a more natural shape of:
(grid_height, grid_width, anchors_box, 4 + 1 + num_classes)
where the “4 + 1” term represents the coordinates of the estimated bounding boxes (top left x, top left y, width and height) and a confidence score. In other words, the output channels are actually grouped by anchor boxes, and in each group one channel provides either a coordinate, a global confidence score or a class confidence score. This process is done automatically in the decode_output function.
from tensorflow.keras import Model
from tensorflow.keras.layers import Reshape
# Define a reshape output to be added to the YOLO model
output = Reshape((grid_size[1], grid_size[0], num_anchors, 4 + 1 + classes),
name="YOLO_output")(model.output)
# Build the complete model
full_model = Model(model.input, output)
full_model.output
<KerasTensor: shape=(None, 7, 7, 5, 25) dtype=float32 (created by layer 'YOLO_output')>
4. Training
As the YOLO model relies on Brainchip AkidaNet/ImageNet network, it is possible to perform transfer learning from ImageNet pretrained weights when training a YOLO model. See the PlantVillage transfer learning example for a detail explanation on transfer learning principles. Additionally, for achieving optimal results, consider the following approach:
1. Initially, train the model on the COCO dataset. This process helps in learning general object detection features and improves the model’s ability to detect various objects across different contexts.
2. After training on COCO, transfer the learned weights to a model equipped with a VOC head.
3. Fine-tune the transferred weights on the VOC dataset. This step allows the model to adapt to the specific characteristics and nuances of the VOC dataset, further enhancing its performance on VOC-related tasks.
5. Performance
The model zoo also contains an helper method that allows to create a YOLO model for VOC and load pretrained weights for the detection task and the corresponding anchors. The anchors are used to interpret the model outputs.
The metric used to evaluate YOLO is the mean average precision (mAP) which is the percentage of correct prediction and is given for an intersection over union (IoU) ratio. Scores in this example are given for the standard IoU of 0.5, 0.75 and the mean across IoU thresholds ranging from 0.5 to 0.95, meaning that a detection is considered valid if the intersection over union ratio with its ground truth equivalent is above 0.5 for mAP 50 or above 0.75 for mAP 75.
Note
A call to evaluate_map will preprocess the images, make the call to
Model.predict
and use decode_output before computing precision for all classes.
from timeit import default_timer as timer
from akida_models import yolo_voc_pretrained
from akida_models.detection.map_evaluation import MapEvaluation
# Load the pretrained model along with anchors
model_keras, anchors = yolo_voc_pretrained()
model_keras.summary()
Downloading data from https://data.brainchip.com/dataset-mirror/coco/coco_anchors.pkl.
0/126 [..............................] - ETA: 0s
126/126 [==============================] - 0s 2us/step
Download complete.
Downloading data from https://data.brainchip.com/models/AkidaV2/yolo/yolo_akidanet_voc_i8_w4_a4.h5.
0/14926320 [..............................] - ETA: 0s
114688/14926320 [..............................] - ETA: 6s
892928/14926320 [>.............................] - ETA: 1s
2170880/14926320 [===>..........................] - ETA: 0s
3448832/14926320 [=====>........................] - ETA: 0s
4669440/14926320 [========>.....................] - ETA: 0s
5914624/14926320 [==========>...................] - ETA: 0s
7233536/14926320 [=============>................] - ETA: 0s
8593408/14926320 [================>.............] - ETA: 0s
9977856/14926320 [===================>..........] - ETA: 0s
11214848/14926320 [=====================>........] - ETA: 0s
12541952/14926320 [========================>.....] - ETA: 0s
13983744/14926320 [===========================>..] - ETA: 0s
14926320/14926320 [==============================] - 1s 0us/step
Download complete.
Model: "model_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input (InputLayer) [(None, 224, 224, 3)] 0
rescaling (QuantizedRescali (None, 224, 224, 3) 0
ng)
conv_0 (QuantizedConv2D) (None, 112, 112, 16) 448
conv_0/relu (QuantizedReLU) (None, 112, 112, 16) 32
conv_1 (QuantizedConv2D) (None, 112, 112, 32) 4640
conv_1/relu (QuantizedReLU) (None, 112, 112, 32) 64
conv_2 (QuantizedConv2D) (None, 56, 56, 64) 18496
conv_2/relu (QuantizedReLU) (None, 56, 56, 64) 128
conv_3 (QuantizedConv2D) (None, 56, 56, 64) 36928
conv_3/relu (QuantizedReLU) (None, 56, 56, 64) 128
dw_separable_4 (QuantizedDe (None, 28, 28, 64) 704
pthwiseConv2D)
pw_separable_4 (QuantizedCo (None, 28, 28, 128) 8320
nv2D)
pw_separable_4/relu (Quanti (None, 28, 28, 128) 256
zedReLU)
dw_separable_5 (QuantizedDe (None, 28, 28, 128) 1408
pthwiseConv2D)
pw_separable_5 (QuantizedCo (None, 28, 28, 128) 16512
nv2D)
pw_separable_5/relu (Quanti (None, 28, 28, 128) 256
zedReLU)
dw_separable_6 (QuantizedDe (None, 14, 14, 128) 1408
pthwiseConv2D)
pw_separable_6 (QuantizedCo (None, 14, 14, 256) 33024
nv2D)
pw_separable_6/relu (Quanti (None, 14, 14, 256) 512
zedReLU)
dw_separable_7 (QuantizedDe (None, 14, 14, 256) 2816
pthwiseConv2D)
pw_separable_7 (QuantizedCo (None, 14, 14, 256) 65792
nv2D)
pw_separable_7/relu (Quanti (None, 14, 14, 256) 512
zedReLU)
dw_separable_8 (QuantizedDe (None, 14, 14, 256) 2816
pthwiseConv2D)
pw_separable_8 (QuantizedCo (None, 14, 14, 256) 65792
nv2D)
pw_separable_8/relu (Quanti (None, 14, 14, 256) 512
zedReLU)
dw_separable_9 (QuantizedDe (None, 14, 14, 256) 2816
pthwiseConv2D)
pw_separable_9 (QuantizedCo (None, 14, 14, 256) 65792
nv2D)
pw_separable_9/relu (Quanti (None, 14, 14, 256) 512
zedReLU)
dw_separable_10 (QuantizedD (None, 14, 14, 256) 2816
epthwiseConv2D)
pw_separable_10 (QuantizedC (None, 14, 14, 256) 65792
onv2D)
pw_separable_10/relu (Quant (None, 14, 14, 256) 512
izedReLU)
dw_separable_11 (QuantizedD (None, 14, 14, 256) 2816
epthwiseConv2D)
pw_separable_11 (QuantizedC (None, 14, 14, 256) 65792
onv2D)
pw_separable_11/relu (Quant (None, 14, 14, 256) 512
izedReLU)
dw_separable_12 (QuantizedD (None, 7, 7, 256) 2816
epthwiseConv2D)
pw_separable_12 (QuantizedC (None, 7, 7, 512) 131584
onv2D)
pw_separable_12/relu (Quant (None, 7, 7, 512) 1024
izedReLU)
dw_separable_13 (QuantizedD (None, 7, 7, 512) 5632
epthwiseConv2D)
pw_separable_13 (QuantizedC (None, 7, 7, 512) 262656
onv2D)
pw_separable_13/relu (Quant (None, 7, 7, 512) 1024
izedReLU)
dw_1conv (QuantizedDepthwis (None, 7, 7, 512) 5632
eConv2D)
pw_1conv (QuantizedConv2D) (None, 7, 7, 1024) 525312
pw_1conv/relu (QuantizedReL (None, 7, 7, 1024) 2048
U)
dw_2conv (QuantizedDepthwis (None, 7, 7, 1024) 11264
eConv2D)
pw_2conv (QuantizedConv2D) (None, 7, 7, 1024) 1049600
pw_2conv/relu (QuantizedReL (None, 7, 7, 1024) 2048
U)
dw_3conv (QuantizedDepthwis (None, 7, 7, 1024) 11264
eConv2D)
pw_3conv (QuantizedConv2D) (None, 7, 7, 1024) 1049600
pw_3conv/relu (QuantizedReL (None, 7, 7, 1024) 2048
U)
dw_detection_layer (Quantiz (None, 7, 7, 1024) 11264
edDepthwiseConv2D)
voc_classifier (QuantizedCo (None, 7, 7, 125) 128125
nv2D)
dequantizer (Dequantizer) (None, 7, 7, 125) 0
=================================================================
Total params: 3,671,805
Trainable params: 3,647,773
Non-trainable params: 24,032
_________________________________________________________________
# Define the final reshape and build the model
output = Reshape((grid_size[1], grid_size[0], num_anchors, 4 + 1 + classes),
name="YOLO_output")(model_keras.output)
model_keras = Model(model_keras.input, output)
# Create the mAP evaluator object
map_evaluator = MapEvaluation(model_keras, val_dataset,
len_val_dataset, labels, anchors)
# Compute the scores for all validation images
start = timer()
map_dict, average_precisions = map_evaluator.evaluate_map()
mAP = sum(map_dict.values()) / len(map_dict)
end = timer()
for label, average_precision in average_precisions.items():
print(labels[label], '{:.4f}'.format(average_precision))
print('mAP 50: {:.4f}'.format(map_dict[0.5]))
print('mAP 75: {:.4f}'.format(map_dict[0.75]))
print('mAP: {:.4f}'.format(mAP))
print(f'Keras inference on {len_val_dataset} images took {end-start:.2f} s.\n')
0%| | 0/130 [00:00<?, ?it/s]
Getting predictions: 0%| | 0/130 [00:00<?, ?it/s]
Getting predictions: 1%| | 1/130 [00:07<16:59, 7.91s/it]
Getting predictions: 2%|▏ | 3/130 [00:08<04:26, 2.10s/it]
Getting predictions: 4%|▍ | 5/130 [00:08<02:11, 1.05s/it]
Getting predictions: 5%|▌ | 7/130 [00:08<01:18, 1.56it/s]
Getting predictions: 7%|▋ | 9/130 [00:08<00:51, 2.37it/s]
Getting predictions: 8%|▊ | 11/130 [00:08<00:35, 3.37it/s]
Getting predictions: 10%|█ | 13/130 [00:08<00:25, 4.58it/s]
Getting predictions: 12%|█▏ | 15/130 [00:08<00:19, 5.98it/s]
Getting predictions: 13%|█▎ | 17/130 [00:08<00:15, 7.45it/s]
Getting predictions: 15%|█▍ | 19/130 [00:09<00:12, 8.96it/s]
Getting predictions: 16%|█▌ | 21/130 [00:09<00:10, 10.49it/s]
Getting predictions: 18%|█▊ | 23/130 [00:09<00:09, 11.10it/s]
Getting predictions: 19%|█▉ | 25/130 [00:09<00:08, 11.89it/s]
Getting predictions: 21%|██ | 27/130 [00:09<00:07, 13.10it/s]
Getting predictions: 22%|██▏ | 29/130 [00:09<00:07, 14.03it/s]
Getting predictions: 24%|██▍ | 31/130 [00:09<00:06, 14.60it/s]
Getting predictions: 25%|██▌ | 33/130 [00:09<00:06, 15.23it/s]
Getting predictions: 27%|██▋ | 35/130 [00:10<00:06, 15.61it/s]
Getting predictions: 28%|██▊ | 37/130 [00:10<00:05, 15.86it/s]
Getting predictions: 30%|███ | 39/130 [00:10<00:05, 16.06it/s]
Getting predictions: 32%|███▏ | 41/130 [00:10<00:05, 16.35it/s]
Getting predictions: 33%|███▎ | 43/130 [00:10<00:05, 15.97it/s]
Getting predictions: 35%|███▍ | 45/130 [00:10<00:05, 15.74it/s]
Getting predictions: 36%|███▌ | 47/130 [00:10<00:05, 15.77it/s]
Getting predictions: 38%|███▊ | 49/130 [00:10<00:05, 15.91it/s]
Getting predictions: 39%|███▉ | 51/130 [00:11<00:04, 15.87it/s]
Getting predictions: 41%|████ | 53/130 [00:11<00:05, 14.88it/s]
Getting predictions: 42%|████▏ | 55/130 [00:11<00:04, 15.14it/s]
Getting predictions: 44%|████▍ | 57/130 [00:11<00:04, 15.64it/s]
Getting predictions: 45%|████▌ | 59/130 [00:11<00:04, 15.92it/s]
Getting predictions: 47%|████▋ | 61/130 [00:11<00:04, 15.71it/s]
Getting predictions: 48%|████▊ | 63/130 [00:11<00:04, 15.50it/s]
Getting predictions: 50%|█████ | 65/130 [00:12<00:04, 15.00it/s]
Getting predictions: 52%|█████▏ | 67/130 [00:12<00:04, 14.55it/s]
Getting predictions: 53%|█████▎ | 69/130 [00:12<00:04, 14.18it/s]
Getting predictions: 55%|█████▍ | 71/130 [00:12<00:04, 13.90it/s]
Getting predictions: 56%|█████▌ | 73/130 [00:12<00:03, 14.62it/s]
Getting predictions: 58%|█████▊ | 75/130 [00:12<00:04, 13.66it/s]
Getting predictions: 59%|█████▉ | 77/130 [00:12<00:04, 12.41it/s]
Getting predictions: 61%|██████ | 79/130 [00:13<00:03, 13.33it/s]
Getting predictions: 62%|██████▏ | 81/130 [00:13<00:03, 13.83it/s]
Getting predictions: 64%|██████▍ | 83/130 [00:13<00:03, 14.52it/s]
Getting predictions: 65%|██████▌ | 85/130 [00:13<00:03, 13.56it/s]
Getting predictions: 67%|██████▋ | 87/130 [00:13<00:02, 14.47it/s]
Getting predictions: 68%|██████▊ | 89/130 [00:13<00:02, 14.90it/s]
Getting predictions: 70%|███████ | 91/130 [00:13<00:02, 15.38it/s]
Getting predictions: 72%|███████▏ | 93/130 [00:13<00:02, 15.80it/s]
Getting predictions: 73%|███████▎ | 95/130 [00:14<00:02, 15.01it/s]
Getting predictions: 75%|███████▍ | 97/130 [00:14<00:02, 15.35it/s]
Getting predictions: 76%|███████▌ | 99/130 [00:14<00:02, 13.67it/s]
Computing overlaps: 77%|███████▋ | 100/130 [00:14<00:02, 13.67it/s]
Computing overlaps: 81%|████████ | 105/130 [00:14<00:01, 24.10it/s]
Computing overlaps: 88%|████████▊ | 115/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.50: 92%|█████████▏| 120/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.55: 93%|█████████▎| 121/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.60: 94%|█████████▍| 122/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.65: 95%|█████████▍| 123/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.70: 95%|█████████▌| 124/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.75: 96%|█████████▌| 125/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.80: 97%|█████████▋| 126/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.85: 98%|█████████▊| 127/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.90: 98%|█████████▊| 128/130 [00:14<00:00, 42.22it/s]
Computing average precisions th = 0.95: 99%|█████████▉| 129/130 [00:14<00:00, 42.22it/s]
aeroplane 0.7500
bicycle 0.3917
bird 0.4972
boat 0.3567
bottle 0.3063
bus 0.7027
car 0.7333
cat 0.7650
chair 0.2825
cow 0.3700
diningtable 0.5389
dog 0.5643
horse 0.6150
motorbike 0.4875
person 0.4114
pottedplant 0.1000
sheep 0.3460
sofa 0.5889
train 0.6194
tvmonitor 0.5625
mAP 50: 0.8640
mAP 75: 0.5037
mAP: 0.4995
Keras inference on 100 images took 14.83 s.
6. Conversion to Akida
6.1 Convert to Akida model
The last YOLO_output layer that was added for splitting channels into values for each box must be removed before Akida conversion.
# Rebuild a model without the last layer
compatible_model = Model(model_keras.input, model_keras.layers[-2].output)
When converting to an Akida model, we just need to pass the Keras model to cnn2snn.convert.
from cnn2snn import convert
model_akida = convert(compatible_model)
model_akida.summary()
Model Summary
________________________________________________
Input shape Output shape Sequences Layers
================================================
[224, 224, 3] [7, 7, 125] 1 33
________________________________________________
__________________________________________________________________________
Layer (type) Output shape Kernel shape
==================== SW/conv_0-dequantizer (Software) ====================
conv_0 (InputConv2D) [112, 112, 16] (3, 3, 3, 16)
__________________________________________________________________________
conv_1 (Conv2D) [112, 112, 32] (3, 3, 16, 32)
__________________________________________________________________________
conv_2 (Conv2D) [56, 56, 64] (3, 3, 32, 64)
__________________________________________________________________________
conv_3 (Conv2D) [56, 56, 64] (3, 3, 64, 64)
__________________________________________________________________________
dw_separable_4 (DepthwiseConv2D) [28, 28, 64] (3, 3, 64, 1)
__________________________________________________________________________
pw_separable_4 (Conv2D) [28, 28, 128] (1, 1, 64, 128)
__________________________________________________________________________
dw_separable_5 (DepthwiseConv2D) [28, 28, 128] (3, 3, 128, 1)
__________________________________________________________________________
pw_separable_5 (Conv2D) [28, 28, 128] (1, 1, 128, 128)
__________________________________________________________________________
dw_separable_6 (DepthwiseConv2D) [14, 14, 128] (3, 3, 128, 1)
__________________________________________________________________________
pw_separable_6 (Conv2D) [14, 14, 256] (1, 1, 128, 256)
__________________________________________________________________________
dw_separable_7 (DepthwiseConv2D) [14, 14, 256] (3, 3, 256, 1)
__________________________________________________________________________
pw_separable_7 (Conv2D) [14, 14, 256] (1, 1, 256, 256)
__________________________________________________________________________
dw_separable_8 (DepthwiseConv2D) [14, 14, 256] (3, 3, 256, 1)
__________________________________________________________________________
pw_separable_8 (Conv2D) [14, 14, 256] (1, 1, 256, 256)
__________________________________________________________________________
dw_separable_9 (DepthwiseConv2D) [14, 14, 256] (3, 3, 256, 1)
__________________________________________________________________________
pw_separable_9 (Conv2D) [14, 14, 256] (1, 1, 256, 256)
__________________________________________________________________________
dw_separable_10 (DepthwiseConv2D) [14, 14, 256] (3, 3, 256, 1)
__________________________________________________________________________
pw_separable_10 (Conv2D) [14, 14, 256] (1, 1, 256, 256)
__________________________________________________________________________
dw_separable_11 (DepthwiseConv2D) [14, 14, 256] (3, 3, 256, 1)
__________________________________________________________________________
pw_separable_11 (Conv2D) [14, 14, 256] (1, 1, 256, 256)
__________________________________________________________________________
dw_separable_12 (DepthwiseConv2D) [7, 7, 256] (3, 3, 256, 1)
__________________________________________________________________________
pw_separable_12 (Conv2D) [7, 7, 512] (1, 1, 256, 512)
__________________________________________________________________________
dw_separable_13 (DepthwiseConv2D) [7, 7, 512] (3, 3, 512, 1)
__________________________________________________________________________
pw_separable_13 (Conv2D) [7, 7, 512] (1, 1, 512, 512)
__________________________________________________________________________
dw_1conv (DepthwiseConv2D) [7, 7, 512] (3, 3, 512, 1)
__________________________________________________________________________
pw_1conv (Conv2D) [7, 7, 1024] (1, 1, 512, 1024)
__________________________________________________________________________
dw_2conv (DepthwiseConv2D) [7, 7, 1024] (3, 3, 1024, 1)
__________________________________________________________________________
pw_2conv (Conv2D) [7, 7, 1024] (1, 1, 1024, 1024)
__________________________________________________________________________
dw_3conv (DepthwiseConv2D) [7, 7, 1024] (3, 3, 1024, 1)
__________________________________________________________________________
pw_3conv (Conv2D) [7, 7, 1024] (1, 1, 1024, 1024)
__________________________________________________________________________
dw_detection_layer (DepthwiseConv2D) [7, 7, 1024] (3, 3, 1024, 1)
__________________________________________________________________________
voc_classifier (Conv2D) [7, 7, 125] (1, 1, 1024, 125)
__________________________________________________________________________
dequantizer (Dequantizer) [7, 7, 125] N/A
__________________________________________________________________________
6.1 Check performance
Akida model accuracy is tested on the first n images of the validation set.
# Create the mAP evaluator object
map_evaluator_ak = MapEvaluation(model_akida,
val_dataset,
len_val_dataset,
labels,
anchors,
is_keras_model=False)
# Compute the scores for all validation images
start = timer()
map_ak_dict, average_precisions_ak = map_evaluator_ak.evaluate_map()
mAP_ak = sum(map_ak_dict.values()) / len(map_ak_dict)
end = timer()
for label, average_precision in average_precisions_ak.items():
print(labels[label], '{:.4f}'.format(average_precision))
print('mAP 50: {:.4f}'.format(map_ak_dict[0.5]))
print('mAP 75: {:.4f}'.format(map_ak_dict[0.75]))
print('mAP: {:.4f}'.format(mAP_ak))
print(f'Akida inference on {len_val_dataset} images took {end-start:.2f} s.\n')
0%| | 0/130 [00:00<?, ?it/s]
Getting predictions: 0%| | 0/130 [00:00<?, ?it/s]
Getting predictions: 1%| | 1/130 [00:00<00:17, 7.37it/s]
Getting predictions: 2%|▏ | 2/130 [00:00<00:16, 7.91it/s]
Getting predictions: 2%|▏ | 3/130 [00:00<00:15, 8.04it/s]
Getting predictions: 3%|▎ | 4/130 [00:00<00:15, 8.13it/s]
Getting predictions: 4%|▍ | 5/130 [00:00<00:15, 8.28it/s]
Getting predictions: 5%|▍ | 6/130 [00:00<00:15, 8.21it/s]
Getting predictions: 5%|▌ | 7/130 [00:00<00:16, 7.67it/s]
Getting predictions: 6%|▌ | 8/130 [00:01<00:15, 7.68it/s]
Getting predictions: 7%|▋ | 9/130 [00:01<00:15, 7.90it/s]
Getting predictions: 8%|▊ | 10/130 [00:01<00:15, 7.95it/s]
Getting predictions: 8%|▊ | 11/130 [00:01<00:14, 8.02it/s]
Getting predictions: 9%|▉ | 12/130 [00:01<00:14, 8.08it/s]
Getting predictions: 10%|█ | 13/130 [00:01<00:14, 8.08it/s]
Getting predictions: 11%|█ | 14/130 [00:01<00:14, 8.08it/s]
Getting predictions: 12%|█▏ | 15/130 [00:01<00:13, 8.24it/s]
Getting predictions: 12%|█▏ | 16/130 [00:01<00:13, 8.20it/s]
Getting predictions: 13%|█▎ | 17/130 [00:02<00:13, 8.35it/s]
Getting predictions: 14%|█▍ | 18/130 [00:02<00:13, 8.46it/s]
Getting predictions: 15%|█▍ | 19/130 [00:02<00:13, 8.19it/s]
Getting predictions: 15%|█▌ | 20/130 [00:02<00:13, 8.36it/s]
Getting predictions: 16%|█▌ | 21/130 [00:02<00:12, 8.39it/s]
Getting predictions: 17%|█▋ | 22/130 [00:02<00:13, 7.96it/s]
Getting predictions: 18%|█▊ | 23/130 [00:02<00:13, 7.69it/s]
Getting predictions: 18%|█▊ | 24/130 [00:02<00:13, 7.74it/s]
Getting predictions: 19%|█▉ | 25/130 [00:03<00:13, 7.53it/s]
Getting predictions: 20%|██ | 26/130 [00:03<00:13, 7.79it/s]
Getting predictions: 21%|██ | 27/130 [00:03<00:12, 7.95it/s]
Getting predictions: 22%|██▏ | 28/130 [00:03<00:12, 8.09it/s]
Getting predictions: 22%|██▏ | 29/130 [00:03<00:12, 8.14it/s]
Getting predictions: 23%|██▎ | 30/130 [00:03<00:12, 8.31it/s]
Getting predictions: 24%|██▍ | 31/130 [00:03<00:12, 8.16it/s]
Getting predictions: 25%|██▍ | 32/130 [00:03<00:11, 8.27it/s]
Getting predictions: 25%|██▌ | 33/130 [00:04<00:11, 8.35it/s]
Getting predictions: 26%|██▌ | 34/130 [00:04<00:11, 8.41it/s]
Getting predictions: 27%|██▋ | 35/130 [00:04<00:11, 8.26it/s]
Getting predictions: 28%|██▊ | 36/130 [00:04<00:11, 8.16it/s]
Getting predictions: 28%|██▊ | 37/130 [00:04<00:11, 8.18it/s]
Getting predictions: 29%|██▉ | 38/130 [00:04<00:11, 8.07it/s]
Getting predictions: 30%|███ | 39/130 [00:04<00:11, 8.16it/s]
Getting predictions: 31%|███ | 40/130 [00:04<00:11, 8.14it/s]
Getting predictions: 32%|███▏ | 41/130 [00:05<00:10, 8.25it/s]
Getting predictions: 32%|███▏ | 42/130 [00:05<00:10, 8.04it/s]
Getting predictions: 33%|███▎ | 43/130 [00:05<00:11, 7.89it/s]
Getting predictions: 34%|███▍ | 44/130 [00:05<00:11, 7.75it/s]
Getting predictions: 35%|███▍ | 45/130 [00:05<00:10, 7.83it/s]
Getting predictions: 35%|███▌ | 46/130 [00:05<00:10, 8.10it/s]
Getting predictions: 36%|███▌ | 47/130 [00:05<00:10, 7.96it/s]
Getting predictions: 37%|███▋ | 48/130 [00:05<00:10, 8.10it/s]
Getting predictions: 38%|███▊ | 49/130 [00:06<00:09, 8.14it/s]
Getting predictions: 38%|███▊ | 50/130 [00:06<00:09, 8.24it/s]
Getting predictions: 39%|███▉ | 51/130 [00:06<00:09, 8.05it/s]
Getting predictions: 40%|████ | 52/130 [00:06<00:09, 8.05it/s]
Getting predictions: 41%|████ | 53/130 [00:06<00:10, 7.66it/s]
Getting predictions: 42%|████▏ | 54/130 [00:06<00:09, 7.94it/s]
Getting predictions: 42%|████▏ | 55/130 [00:06<00:09, 7.89it/s]
Getting predictions: 43%|████▎ | 56/130 [00:06<00:09, 8.06it/s]
Getting predictions: 44%|████▍ | 57/130 [00:07<00:09, 8.01it/s]
Getting predictions: 45%|████▍ | 58/130 [00:07<00:08, 8.11it/s]
Getting predictions: 45%|████▌ | 59/130 [00:07<00:08, 8.16it/s]
Getting predictions: 46%|████▌ | 60/130 [00:07<00:08, 8.20it/s]
Getting predictions: 47%|████▋ | 61/130 [00:07<00:08, 7.95it/s]
Getting predictions: 48%|████▊ | 62/130 [00:07<00:08, 7.92it/s]
Getting predictions: 48%|████▊ | 63/130 [00:07<00:08, 7.95it/s]
Getting predictions: 49%|████▉ | 64/130 [00:07<00:08, 7.83it/s]
Getting predictions: 50%|█████ | 65/130 [00:08<00:08, 7.62it/s]
Getting predictions: 51%|█████ | 66/130 [00:08<00:08, 7.59it/s]
Getting predictions: 52%|█████▏ | 67/130 [00:08<00:08, 7.46it/s]
Getting predictions: 52%|█████▏ | 68/130 [00:08<00:08, 7.48it/s]
Getting predictions: 53%|█████▎ | 69/130 [00:08<00:08, 7.41it/s]
Getting predictions: 54%|█████▍ | 70/130 [00:08<00:08, 7.43it/s]
Getting predictions: 55%|█████▍ | 71/130 [00:08<00:08, 7.22it/s]
Getting predictions: 55%|█████▌ | 72/130 [00:09<00:07, 7.50it/s]
Getting predictions: 56%|█████▌ | 73/130 [00:09<00:07, 7.77it/s]
Getting predictions: 57%|█████▋ | 74/130 [00:09<00:07, 7.73it/s]
Getting predictions: 58%|█████▊ | 75/130 [00:09<00:07, 7.11it/s]
Getting predictions: 58%|█████▊ | 76/130 [00:09<00:07, 7.26it/s]
Getting predictions: 59%|█████▉ | 77/130 [00:09<00:07, 6.77it/s]
Getting predictions: 60%|██████ | 78/130 [00:09<00:07, 7.12it/s]
Getting predictions: 61%|██████ | 79/130 [00:10<00:06, 7.47it/s]
Getting predictions: 62%|██████▏ | 80/130 [00:10<00:06, 7.53it/s]
Getting predictions: 62%|██████▏ | 81/130 [00:10<00:06, 7.73it/s]
Getting predictions: 63%|██████▎ | 82/130 [00:10<00:06, 7.96it/s]
Getting predictions: 64%|██████▍ | 83/130 [00:10<00:05, 7.86it/s]
Getting predictions: 65%|██████▍ | 84/130 [00:10<00:05, 7.77it/s]
Getting predictions: 65%|██████▌ | 85/130 [00:10<00:06, 7.31it/s]
Getting predictions: 66%|██████▌ | 86/130 [00:10<00:05, 7.56it/s]
Getting predictions: 67%|██████▋ | 87/130 [00:11<00:05, 7.84it/s]
Getting predictions: 68%|██████▊ | 88/130 [00:11<00:05, 7.97it/s]
Getting predictions: 68%|██████▊ | 89/130 [00:11<00:05, 8.00it/s]
Getting predictions: 69%|██████▉ | 90/130 [00:11<00:04, 8.01it/s]
Getting predictions: 70%|███████ | 91/130 [00:11<00:04, 8.12it/s]
Getting predictions: 71%|███████ | 92/130 [00:11<00:04, 8.12it/s]
Getting predictions: 72%|███████▏ | 93/130 [00:11<00:04, 8.20it/s]
Getting predictions: 72%|███████▏ | 94/130 [00:11<00:04, 8.30it/s]
Getting predictions: 73%|███████▎ | 95/130 [00:12<00:04, 7.72it/s]
Getting predictions: 74%|███████▍ | 96/130 [00:12<00:04, 7.84it/s]
Getting predictions: 75%|███████▍ | 97/130 [00:12<00:04, 7.99it/s]
Getting predictions: 75%|███████▌ | 98/130 [00:12<00:04, 7.98it/s]
Getting predictions: 76%|███████▌ | 99/130 [00:12<00:04, 7.00it/s]
Getting predictions: 77%|███████▋ | 100/130 [00:12<00:04, 7.35it/s]
Computing overlaps: 77%|███████▋ | 100/130 [00:12<00:04, 7.35it/s]
Computing overlaps: 87%|████████▋ | 113/130 [00:12<00:00, 36.27it/s]
Computing average precisions th = 0.50: 92%|█████████▏| 120/130 [00:12<00:00, 36.27it/s]
Computing average precisions th = 0.55: 93%|█████████▎| 121/130 [00:12<00:00, 36.27it/s]
Computing average precisions th = 0.60: 94%|█████████▍| 122/130 [00:12<00:00, 36.27it/s]
Computing average precisions th = 0.65: 95%|█████████▍| 123/130 [00:12<00:00, 36.27it/s]
Computing average precisions th = 0.65: 95%|█████████▌| 124/130 [00:12<00:00, 54.32it/s]
Computing average precisions th = 0.70: 95%|█████████▌| 124/130 [00:12<00:00, 54.32it/s]
Computing average precisions th = 0.75: 96%|█████████▌| 125/130 [00:12<00:00, 54.32it/s]
Computing average precisions th = 0.80: 97%|█████████▋| 126/130 [00:12<00:00, 54.32it/s]
Computing average precisions th = 0.85: 98%|█████████▊| 127/130 [00:12<00:00, 54.32it/s]
Computing average precisions th = 0.90: 98%|█████████▊| 128/130 [00:12<00:00, 54.32it/s]
Computing average precisions th = 0.95: 99%|█████████▉| 129/130 [00:12<00:00, 54.32it/s]
aeroplane 0.7300
bicycle 0.4417
bird 0.4833
boat 0.3070
bottle 0.2627
bus 0.7147
car 0.6889
cat 0.7670
chair 0.2726
cow 0.3700
diningtable 0.4711
dog 0.5736
horse 0.6147
motorbike 0.5083
person 0.4021
pottedplant 0.1094
sheep 0.2976
sofa 0.6283
train 0.6042
tvmonitor 0.5643
mAP 50: 0.8409
mAP 75: 0.5086
mAP: 0.4906
Akida inference on 100 images took 12.96 s.
6.2 Show predictions for a random image
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from akida_models.detection.processing import preprocess_image, decode_output
# Shuffle the data to take a random test image
val_dataset = val_dataset.shuffle(buffer_size=len_val_dataset)
input_shape = model_akida.layers[0].input_dims
# Load the image
raw_image = next(iter(val_dataset))['image']
# Keep the original image size for later bounding boxes rescaling
raw_height, raw_width, _ = raw_image.shape
# Pre-process the image
image = preprocess_image(raw_image, input_shape)
input_image = image[np.newaxis, :].astype(np.uint8)
# Call evaluate on the image
pots = model_akida.predict(input_image)[0]
# Reshape the potentials to prepare for decoding
h, w, c = pots.shape
pots = pots.reshape((h, w, len(anchors), 4 + 1 + len(labels)))
# Decode potentials into bounding boxes
raw_boxes = decode_output(pots, anchors, len(labels))
# Rescale boxes to the original image size
pred_boxes = np.array([[
box.x1 * raw_width, box.y1 * raw_height, box.x2 * raw_width,
box.y2 * raw_height,
box.get_label(),
box.get_score()
] for box in raw_boxes])
fig = plt.figure(num='VOC detection by Akida')
ax = fig.subplots(1)
img_plot = ax.imshow(np.zeros(raw_image.shape, dtype=np.uint8))
img_plot.set_data(raw_image)
for box in pred_boxes:
rect = patches.Rectangle((box[0], box[1]),
box[2] - box[0],
box[3] - box[1],
linewidth=1,
edgecolor='r',
facecolor='none')
ax.add_patch(rect)
class_score = ax.text(box[0],
box[1] - 5,
f"{labels[int(box[4])]} - {box[5]:.2f}",
color='red')
plt.axis('off')
plt.show()
Total running time of the script: (0 minutes 46.359 seconds)