Tutoriel de déploiement d'Edge sur iOS

Objectifs du tutoriel

Dans ce tutoriel, vous allez télécharger un modèle TensorFlow Lite personnalisé et exporté à partir d'AutoML Vision Edge. Vous exécuterez ensuite une application iOS préconfigurée permettant d'identifier des images de fleurs à l'aide de ce modèle.

capture d'écran du produit final sur mobile
Crédit image : Felipe Venâncio, "du jardin de ma mère" (CC BY 2.0, image affichée dans l'application).

Objectifs

Dans ce tutoriel de présentation détaillé, vous allez utiliser du code pour effectuer les opérations suivantes :

  • Exécuter un modèle pré-entraîné dans une application iOS à l'aide de l'interpréteur TFLite

Avant de commencer

Installer TensorFlow

Avant de commencer le tutoriel, vous devez installer plusieurs éléments logiciels :

Si vous disposez d'une installation Python opérationnelle, exécutez les commandes suivantes pour télécharger le logiciel :

pip install --upgrade  "tensorflow==1.7.*"
pip install PILLOW

Cloner le dépôt Git

À l'aide de la ligne de commande, exécutez la commande suivante pour cloner le dépôt Git :

git clone https://github.com/googlecodelabs/tensorflow-for-poets-2

Accédez au répertoire du clone local du dépôt (répertoire tensorflow-for-poets-2). Vous exécuterez tous les exemples de code suivants à partir de ce répertoire :

cd tensorflow-for-poets-2

Configurer l'application iOS

L'application de démonstration iOS nécessite plusieurs outils supplémentaires :

  1. Xcode
  2. Outils de ligne de commande Xcode
  3. CocoaPods

Télécharger Xcode

Utilisez ce lien pour télécharger Xcode sur votre ordinateur.

Installer les outils de ligne de commande Xcode

Exécutez la commande suivante pour installer les outils de ligne de commande Xcode :

xcode-select --install

Installer CocoaPods

CocoaPods utilise Ruby, qui est installé par défaut sur macOS.

Pour installer CocoaPods, exécutez la commande suivante :

sudo gem install cocoapods
Install TFLite Cocoapod

Le reste de cet atelier de programmation doit s'exécuter directement dans macOS. Vous devez donc fermer Docker dès maintenant. (Vous pouvez appuyer sur Ctrl+D.)

Utilisez la commande suivante pour installer TensorFlow Lite et créer le fichier .xcworkspace à l'aide de CocoaPods :

pod install --project-directory=ios/tflite/

Ouvrez le projet avec Xcode. Vous pouvez ouvrir le projet via la ligne de commande ou via l'UI.

Pour ouvrir le projet via la ligne de commande, exécutez la commande suivante :

open ios/tflite/tflite_photos_example.xcworkspace

Pour ouvrir le projet via l'UI, lancez Xcode, puis cliquez sur le bouton "Open another project" (Ouvrir un autre projet).

UI Xcode

Une fois le projet ouvert, accédez au fichier .xcworkspace (et non au fichier .xcproject).

Exécuter l'application d'origine

L'application est un exemple simple qui exécute un modèle de reconnaissance d'image dans le simulateur iOS. Elle lit la photothèque, car le simulateur n'accepte pas d'entrée de l'appareil photo.

Avant d'insérer votre modèle personnalisé, testez la version de référence de l'application qui utilise la version de base de MobileNet, entraînée sur les 1 000 catégories ImageNet.

Pour lancer l'application dans le simulateur, sélectionnez le bouton de lecture icône de lecture Xcode dans l'angle supérieur droit de la fenêtre Xcode.

Le bouton Next Photo (Photo suivante) fait défiler les photos de l'appareil.

Vous pouvez ajouter des photos à la photothèque de l'appareil en faisant un glisser-déposer dans la fenêtre du simulateur.

Le résultat doit afficher des annotations semblables à ce qui suit :

capture d'écran du test de l'application

Exécuter l'application personnalisée

La configuration initiale de l'application permet de classer des images dans l'une des 1 000 classes ImageNet à l'aide de la version standard de MobileNet.

Modifiez l'application de sorte qu'elle utilise votre modèle réentraîné avec des catégories d'image personnalisées.

Ajouter les fichiers de modèle au projet

Le projet de démonstration est configuré pour rechercher un fichier graph.lite et un fichier labels.txt dans le répertoire android/tflite/app/src/main/assets/.

Pour remplacer ces deux fichiers par vos versions, exécutez la commande suivante :

cp tf_files/optimized_graph.lite ios/tflite/data/graph.lite
cp tf_files/retrained_labels.txt ios/tflite/data/labels.txt

Exécuter l'application

Pour relancer l'application dans le simulateur, sélectionnez le bouton de lecture icône de lecture Xcode dans l'angle supérieur droit de la fenêtre Xcode.

Pour tester les modifications, ajoutez des fichiers image à partir du répertoire flower_photos/ et obtenez des prédictions.

Les résultats doivent ressembler à ce qui suit :

capture d'écran du produit final sur mobile
Crédit image : Felipe Venâncio, "du jardin de ma mère" (CC BY 2.0, image affichée dans l'application).

Notez que les images par défaut ne représentent pas des fleurs.

Pour réellement tester le modèle, vous pouvez ajouter quelques-unes des images des données de formation que vous avez téléchargées précédemment ou effectuer une recherche Google et télécharger les images obtenues afin de les utiliser pour la prédiction.

Fonctionnement

Maintenant que l'application est en cours d'exécution, examinez le code propre à TensorFlow Lite.

Pod TensorFlow Lite

Cette application utilise un CocoaPod TFLite précompilé. Le fichier Podfile inclut le CocoaPod dans le projet :

Podfile

platform :ios, '8.0'
inhibit_all_warnings!

target 'tflite_photos_example'
       pod 'TensorFlowLite'

L'intégralité du code d'interface avec TFLite est contenu dans le fichier CameraExampleViewController.mm.

Prérequis

Le premier bloc de code présentant un certain intérêt (après les importations nécessaires) est la méthode viewDidLoad :

CameraExampleViewController.mm

#include "tensorflow/contrib/lite/kernels/register.h"
#include "tensorflow/contrib/lite/model.h"
#include "tensorflow/contrib/lite/string_util.h"
#include "tensorflow/contrib/lite/tools/mutable_op_resolver.h"

...

- (void)viewDidLoad {
  [super viewDidLoad];
  labelLayers = [[NSMutableArray alloc] init];

  NSString* graph_path = FilePathForResourceName(model_file_name, model_file_type);
  model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);
  if (!model) {
    LOG(FATAL) << "Failed to mmap model " << graph_path;
  }
  LOG(INFO) << "Loaded model " << graph_path;
  model->error_reporter();
  LOG(INFO) << "resolved reporter";

  ...

La ligne clé de la première moitié de la méthode est la suivante : model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);. Ce code crée un FlatBufferModel à partir du fichier graphique.

Un élément FlatBuffer est une structure de données mappable en mémoire. Il s'agit d'une fonctionnalité clé de TFLite, car il permet au système de mieux gérer la mémoire utilisée par le modèle. Le système peut faire basculer certaines parties du modèle dans la mémoire ou à l'extérieur de celle-ci, de manière transparente et selon les besoins.

La deuxième partie de la méthode crée un interpréteur pour le modèle et joint des implémentations d'opérations à la structure de données graphique chargée précédemment :

CameraExampleViewController.mm

- (void)viewDidLoad {
  ...

  tflite::ops::builtin::BuiltinOpResolver resolver;
  LoadLabels(labels_file_name, labels_file_type, &labels);

  tflite::InterpreterBuilder(*model, resolver)(&interpreter);
  if (!interpreter) {
    LOG(FATAL) << "Failed to construct interpreter";
  }
  if (interpreter->AllocateTensors() != kTfLiteOk) {
    LOG(FATAL) << "Failed to allocate tensors!";
  }

  [self attachPreviewLayer];
}

Si vous connaissez bien TensorFlow en Python, cela équivaut à peu près à la création d'un tf.Session().

Exécuter le modèle

La méthode UpdatePhoto gère entièrement l'extraction de la photo suivante, la mise à jour de la fenêtre d'aperçu et l'exécution du modèle sur la photo.

CameraExampleViewController.mm

- (void)UpdatePhoto{
  PHAsset* asset;
  if (photos==nil || photos_index >= photos.count){
    [self updatePhotosLibrary];
    photos_index=0;
  }
  if (photos.count){
    asset = photos[photos_index];
    photos_index += 1;
    input_image = [self convertImageFromAsset:asset
                                   targetSize:CGSizeMake(wanted_input_width, wanted_input_height)
                                         mode:PHImageContentModeAspectFill];
    display_image = [self convertImageFromAsset:asset
                                     targetSize:CGSizeMake(asset.pixelWidth,asset.pixelHeight)
                                           mode:PHImageContentModeAspectFit];
    [self DrawImage];
  }

  if (input_image != nil){
    image_data image = [self CGImageToPixels:input_image.CGImage];
    [self inputImageToModel:image];
    [self runModel];
  }
}

Ce sont les trois dernières lignes qui nous intéressent.

La méthode CGImageToPixels convertit la CGImage renvoyée par la photothèque iOS en une structure simple contenant la largeur, la hauteur, les canaux et les données de pixel.

CameraExampleViewController.h

typedef struct {
  int width;
  int height;
  int channels;
  std::vector<uint8_t> data;
} image_data;

La méthode inputImageToModel gère l'insertion de l'image dans la mémoire de l'interpréteur. Cela inclut le redimensionnement de l'image et l'ajustement des valeurs de pixel de sorte qu'elles correspondent aux attentes du modèle.

CameraExampleViewController.mm

- (void)inputImageToModel:(image_data)image{
  float* out = interpreter->typed_input_tensor<float>(0);

  const float input_mean = 127.5f;
  const float input_std = 127.5f;
  assert(image.channels >= wanted_input_channels);
  uint8_t* in = image.data.data();

  for (int y = 0; y < wanted_input_height; ++y) {
    const int in_y = (y * image.height) / wanted_input_height;
    uint8_t* in_row = in + (in_y * image.width * image.channels);
    float* out_row = out + (y * wanted_input_width * wanted_input_channels);
    for (int x = 0; x < wanted_input_width; ++x) {
      const int in_x = (x * image.width) / wanted_input_width;
      uint8_t* in_pixel = in_row + (in_x * image.channels);
      float* out_pixel = out_row + (x * wanted_input_channels);
      for (int c = 0; c < wanted_input_channels; ++c) {
        out_pixel[c] = (in_pixel[c] - input_mean) / input_std;
      }
    }
  }
}

Nous savons que le modèle ne comporte qu'une seule entrée. Par conséquent, la ligne float* out = interpreter->typed_input_tensor<float>(0); demande à l'interpréteur de pointer vers la mémoire pour l'entrée 0. Le reste de la méthode gère l'arithmétique de pointage et la mise à l'échelle des pixels pour copier les données dans le tableau d'entrée.

Enfin, la méthode runModel exécute le modèle :

CameraExampleViewController.mm

- (void)runModel {
  double startTimestamp = [[NSDate new] timeIntervalSince1970];
  if (interpreter->Invoke() != kTfLiteOk) {
    LOG(FATAL) << "Failed to invoke!";
  }
  double endTimestamp = [[NSDate new] timeIntervalSince1970];
  total_latency += (endTimestamp - startTimestamp);
  total_count += 1;
  NSLog(@"Time: %.4lf, avg: %.4lf, count: %d", endTimestamp - startTimestamp,
        total_latency / total_count,  total_count);

  ...

}

runModel renvoie ensuite les résultats. Pour ce faire, il demande à l'interpréteur de pointer vers les données du tableau de sortie. Le résultat est un simple tableau de valeurs de type "float". La méthode GetTopN gère l'extraction des cinq premiers résultats (grâce à une file d'attente de priorité).

CameraExampleViewController.mm

- (void)runModel {
  ...

  const int output_size = (int)labels.size();
  const int kNumResults = 5;
  const float kThreshold = 0.1f;

  std::vector<std::pair<float, int>> top_results;

  float* output = interpreter->typed_output_tensor<float>(0);
  GetTopN(output, output_size, kNumResults, kThreshold, &top_results);

  ...
}

Les quelques lignes suivantes convertissent simplement ces cinq paires (probability, class_id) en paires (probability, label), puis transmettent ce résultat, de manière asynchrone, à la méthode setPredictionValues qui met à jour le rapport à l'écran :

CameraExampleViewController.mm

- (void)runModel {
  ...

  std::vector<std::pair<float, std::string>> newValues;
  for (const auto& result : top_results) {
    std::pair<float, std::string> item;
    item.first = result.first;
    item.second = labels[result.second];

    newValues.push_back(item);
  }
  dispatch_async(dispatch_get_main_queue(), ^(void) {
    [self setPredictionValues:newValues];
  });
}

Étapes suivantes

Vous avez à présent terminé cette procédure d'utilisation d'un modèle Edge pour une application iOS de classification de fleurs. Vous avez testé une application de classification d'images à l'aide d'un modèle Edge Tensorflow Lite entraîné, vous avez modifié cette application, puis vous avez obtenu des exemples d'annotations. Vous avez ensuite examiné le code propre à TensorFlow Lite afin d'en comprendre les fonctionnalités sous-jacentes.

Pour continuer à découvrir les modèles TensorFlow et AutoML Vision Edge, consultez les ressources suivantes :