transfer learning – tflite

Afin de faciliter la lecture du code, il en organisé sous forme d’onglets, à lire de gauche à droite !

Imports & Sélection du modèleData load & PreprocessingDéfinition du modèleApprentissageSauvegardeConversionTest du modèle

Imports

On importe aussi tensorflow_hub car on effectue du transfer learning à partir d’un modèle disponible sur le Hub.

import os
import matplotlib.pylab as plt
import numpy as np
import tensorflow as tf
import tensorflow_hub as hub
print("Version: ", tf.__version__)
print("Eager mode: ", tf.executing_eagerly())
print("Hub version: ", hub.__version__)
print("GPU is", "available" if tf.config.list_physical_devices('GPU') else "NOT AVAILABLE")

On se met en mode GPU, sur Google Colab, car le modèle est complexe et à base de convolutions.

Version:  2.3.0
Eager mode:  True
Hub version:  0.9.0
GPU is available

Un widget propose de choisir son modèle.

Gardons celui proposé par défaut : mobilenet_v2.

Il est décrit de la façon suivante :

  • tf2-preview/mobilenet_v2/feature_vector
  • Feature vectors of images with MobileNet V2 trained on ImageNet (ILSVRC-2012-CLS)

Le modèle a été entraîné sur ImageNet, avec des images au format 224*224.

Il fait 9,9K. Il est au format SavedModel.

FV_SIZE est la Feature Vectors Size.

The output of the module is a batch of feature vectors. For each input image, the feature vector has size num_features = 1280. The feature vectors can then be used further, e.g., for classification as above.

https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/4

module_selection = ("mobilenet_v2", 224, 1280) #@param ["(\"mobilenet_v2\", 224, 1280)", "(\"inception_v3\", 299, 2048)"] {type:"raw", allow-input: true}
handle_base, pixels, FV_SIZE = module_selection
MODULE_HANDLE ="https://tfhub.dev/google/tf2-preview/{}/feature_vector/4".format(handle_base)
IMAGE_SIZE = (pixels, pixels)
print("Using {} with input size {} and output dimension {}".format(
  MODULE_HANDLE, IMAGE_SIZE, FV_SIZE))

Data Load

Les données sont importées de tfds.

import tensorflow_datasets as tfds
tfds.disable_progress_bar()

Un split est effectué pour séparer les données en train/validation/test

(train_examples, validation_examples, test_examples), info = tfds.load(
    'cats_vs_dogs',
    split=['train[80%:]', 'train[80%:90%]', 'train[90%:]'],
    with_info=True, 
    as_supervised=True, 
)

num_examples = info.splits['train'].num_examples
num_classes = info.features['label'].num_classes
print (num_examples, num_classes)
23262 2

Preprocessing

Ensuite on shuffle et batch les datasets.

def format_image(image, label):
  image = tf.image.resize(image, IMAGE_SIZE) / 255.0
  return  image, label

BATCH_SIZE = 32 #@param {type:"integer"}

train_batches = train_examples.shuffle(num_examples // 4).map(format_image).batch(BATCH_SIZE).prefetch(1)
validation_batches = validation_examples.map(format_image).batch(BATCH_SIZE).prefetch(1)
test_batches = test_examples.map(format_image).batch(1)

Le modèle

Commençons avec un freeze de toutes les couches.

do_fine_tuning = False #@param {type:"boolean"}
feature_extractor = hub.KerasLayer(MODULE_HANDLE,
                                   input_shape=IMAGE_SIZE + (3,), 
                                   output_shape=[FV_SIZE],
                                   trainable=do_fine_tuning)
print("Building model with", MODULE_HANDLE)
model = tf.keras.Sequential([
    feature_extractor,
    tf.keras.layers.Dense(num_classes)
])
model.summary()
Building model with https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/4
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
keras_layer_1 (KerasLayer)   (None, 1280)              2257984   
_________________________________________________________________
dense (Dense)                (None, 2)                 2562      
=================================================================
Total params: 2,260,546
Trainable params: 2,562
Non-trainable params: 2,257,984

Notre modèle est exactement celui défini par mobilenet_v2 auquel on ajoute une couche dense avec 2 neurones.

Apprentissage

On commence sans fine tuning. En cas de fine tuning, on utilise SGD comme optimizer, sinon c’est Adam.

if do_fine_tuning:
  model.compile(
    optimizer=tf.keras.optimizers.SGD(lr=0.002, momentum=0.9), 
    loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'])
else:
  model.compile(
    optimizer='adam', 
    loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'])
EPOCHS = 5
hist = model.fit(train_batches,
                    epochs=EPOCHS,
                    validation_data=validation_batches)
Epoch 1/5
146/146 [==============================] - 8s 53ms/step - loss: 0.0859 - accuracy: 0.9714 - val_loss: 0.0421 - val_accuracy: 0.9854
Epoch 2/5
146/146 [==============================] - 7s 50ms/step - loss: 0.0354 - accuracy: 0.9877 - val_loss: 0.0263 - val_accuracy: 0.9944
Epoch 3/5
146/146 [==============================] - 7s 51ms/step - loss: 0.0257 - accuracy: 0.9923 - val_loss: 0.0196 - val_accuracy: 0.9944
Epoch 4/5
146/146 [==============================] - 7s 50ms/step - loss: 0.0202 - accuracy: 0.9944 - val_loss: 0.0145 - val_accuracy: 0.9970
Epoch 5/5
146/146 [==============================] - 7s 51ms/step - loss: 0.0159 - accuracy: 0.9959 - val_loss: 0.0123 - val_accuracy: 0.9974

Inutile de se faire, pour l’instant, du fine tuning avec 99,6% d’accuracy.

Sauvegarde du modèle

CATS_VS_DOGS_SAVED_MODEL = "exp_saved_model"
tf.saved_model.save(model, CATS_VS_DOGS_SAVED_MODEL)
!saved_model_cli show --dir $CATS_VS_DOGS_SAVED_MODEL --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
  inputs['keras_layer_1_input'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 224, 224, 3)
      name: serving_default_keras_layer_1_input:0
The given SavedModel SignatureDef contains the following output(s):
  outputs['dense'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 2)
      name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict
loaded = tf.saved_model.load(CATS_VS_DOGS_SAVED_MODEL)
print(list(loaded.signatures.keys()))
infer = loaded.signatures["serving_default"]
print(infer.structured_input_signature)
print(infer.structured_outputs)
['serving_default']
((), {'keras_layer_1_input': TensorSpec(shape=(None, 224, 224, 3), dtype=tf.float32, name='keras_layer_1_input')})
{'dense': TensorSpec(shape=(None, 2), dtype=tf.float32, name='dense')}
converter = tf.lite.TFLiteConverter.from_saved_model(CATS_VS_DOGS_SAVED_MODEL)
converter.optimizations = [tf.lite.Optimize.DEFAULT]

Création d’un jeu de données représentatif

def representative_data_gen():
  for input_value, _ in test_batches.take(100):
    yield [input_value]
converter.representative_dataset = representative_data_gen

Quantization

converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]

Conversion du modèle

tflite_model = converter.convert()
tflite_model_file = 'converted_model.tflite'

with open(tflite_model_file, "wb") as f:
  f.write(tflite_model)
# Load TFLite model and allocate tensors.
  
interpreter = tf.lite.Interpreter(model_path=tflite_model_file)
interpreter.allocate_tensors()

input_index = interpreter.get_input_details()[0]["index"]
output_index = interpreter.get_output_details()[0]["index"]
from tqdm import tqdm

# Gather results for the randomly sampled test images
predictions = []

test_labels, test_imgs = [], []
for img, label in tqdm(test_batches.take(10)):
  interpreter.set_tensor(input_index, img)
  interpreter.invoke()
  predictions.append(interpreter.get_tensor(output_index))
  
  test_labels.append(label.numpy()[0])
  test_imgs.append(img)
#@title Utility functions for plotting
# Utilities for plotting

class_names = ['cat', 'dog']

def plot_image(i, predictions_array, true_label, img):
  predictions_array, true_label, img = predictions_array[i], true_label[i], img[i]
  plt.grid(False)
  plt.xticks([])
  plt.yticks([])
    
  img = np.squeeze(img)

  plt.imshow(img, cmap=plt.cm.binary)

  predicted_label = np.argmax(predictions_array)
  if predicted_label == true_label:
    color = 'green'
  else:
    color = 'red'
  
  plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
                                100*np.max(predictions_array),
                                class_names[true_label]),
                                color=color)

#@title Visualize the outputs { run: "auto" }
index = 0 #@param {type:"slider", min:0, max:9, step:1}
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(index, predictions, test_labels, test_imgs)
plt.show()