Tutorial sul deployment di Edge per iOS

Cosa creerai

In questo tutorial scaricherai un modello TensorFlow Lite personalizzato esportato da AutoML Vision Edge. Eseguirai quindi un'app per iOS predefinita che utilizza il modello per identificare le immagini di fiori.

Screenshot per dispositivo mobile del prodotto finale
Immagine di credito: Felipe Venâncio, "dal giardino di mia madre" (CC BY 2.0, immagine mostrata nell'app).

Obiettivi

In questa procedura dettagliata introduttiva end-to-end, utilizzerai il codice per:

  • Esegui un modello preaddestrato in un'app per iOS utilizzando l'interprete TFLite.

Prima di iniziare

Installa TensorFlow

Prima di iniziare il tutorial, devi installare diversi software:

Se hai un'installazione Python funzionante, esegui questi comandi per scaricare questo software:

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

Clona il repository Git

Utilizzando la riga di comando, clona il repository Git con il seguente comando:

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

Vai alla directory del clone locale del repository (directory tensorflow-for-poets-2). Da questa directory eseguirai tutti i seguenti esempi di codice:

cd tensorflow-for-poets-2

Configura l'app per iOS

L'app demo per iOS richiede diversi strumenti aggiuntivi:

  1. Xcode
  2. Strumenti a riga di comando Xcode
  3. Cocoapodi

Scarica Xcode

Utilizza il seguente link per scaricare Xcode sulla tua macchina.

Installa strumenti a riga di comando Xcode

Installa gli strumenti a riga di comando Xcode eseguendo questo comando:

xcode-select --install

Installazione di Cocoapods

Cocoapods utilizza Ruby, che è installato per impostazione predefinita su macOS.

Per installare cocoapods, esegui questo comando:

sudo gem install cocoapods
Install TFLite Cocoapod

Il resto di questo codelab deve essere eseguito direttamente in macOS, quindi chiudi Docker ora (Ctrl-D chiuderà Docker).

Utilizza questo comando per installare TensorFlow Lite e creare il file .xcworkspace utilizzando cocoapods:

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

Apri il progetto con Xcode. Puoi aprire il progetto tramite riga di comando o UI.

Per aprire il progetto dalla riga di comando, esegui questo comando:

open ios/tflite/tflite_photos_example.xcworkspace

Per aprire il progetto tramite l'interfaccia utente, avvia Xcode e seleziona il pulsante "Apri un altro progetto".

UI Xcode

Dopo aver aperto il progetto, vai al file .xcworkspace (non al file .xcproject).

Esegui l'app originale

L'app è un semplice esempio su cui viene eseguito un modello di riconoscimento delle immagini nel simulatore iOS. L'app legge dalla raccolta fotografica, poiché il simulatore non supporta l'input della fotocamera.

Prima di inserire il tuo modello personalizzato, testa la versione di base dell'app che utilizza la base "mobilenet" addestrata sulle 1000 categorie ImageNet.

Per avviare l'app nel simulatore, seleziona il pulsante di riproduzione Icona di riproduzione xcode nell'angolo in alto a destra della finestra di Xcode.

Il pulsante "Foto successiva" fa avanzare tra le foto sul dispositivo.

Puoi aggiungere foto alla raccolta fotografica del dispositivo trascinandole e rilasciandole nella finestra del simulatore.

Il risultato dovrebbe visualizzare annotazioni simili a questa immagine:

screenshot dell'app di esecuzione di test

Esegui l'app personalizzata

La configurazione originale dell'app classifica le immagini in una delle 1000 classi ImageNet, utilizzando il MobileNet standard.

Modifica l'app in modo che utilizzi il modello riaddestrato con categorie di immagini personalizzate.

Aggiungi i file del modello al progetto

Il progetto dimostrativo è configurato per cercare file graph.lite e labels.txt nella directory android/tflite/app/src/main/assets/.

Per sostituire questi due file con le tue versioni, esegui questo comando:

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

Esegui l'app

Per riavviare l'app nel simulatore, seleziona il pulsante di riproduzione Icona di riproduzione xcode nell'angolo in alto a destra della finestra di Xcode.

Per testare le modifiche, aggiungi file immagine dalla directory flower_photos/ e ottieni previsioni.

I risultati dovrebbero essere simili a questi:

Screenshot per dispositivo mobile del prodotto finale
Immagine di credito: Felipe Venâncio, "dal giardino di mia madre" (CC BY 2.0, immagine mostrata nell'app).

Tieni presente che le immagini predefinite non riguardano fiori.

Per provare davvero il modello, aggiungi alcune delle immagini dei dati di addestramento scaricate in precedenza oppure scarica alcune immagini da una ricerca Google da usare per la previsione.

Come funziona?

Ora che l'app è in esecuzione, controlla il codice specifico di TensorFlow Lite.

Pod TensorFlowLite

Questa app utilizza un TFLite Cocoapod precompilato. Il Podfile include cocoapod nel progetto:

Podfile

platform :ios, '8.0'
inhibit_all_warnings!

target 'tflite_photos_example'
       pod 'TensorFlowLite'

Il codice che interfaccia con TFLite è tutto contenuto nel file CameraExampleViewController.mm.

Configurazione

Il primo blocco di interesse (dopo le importazioni necessarie) è il metodo 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 riga chiave in questa prima metà del metodo è la riga model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);. Questo codice crea un FlatBufferModel dal file del grafico.

Un FlatBuffer è una struttura di dati mappabile in memoria. Queste sono una funzionalità chiave TFLite in quanto consentono al sistema di gestire meglio la memoria utilizzata dal modello. Il sistema può trasferire in modo trasparente parti del modello all'interno o all'esterno della memoria in base alle esigenze.

La seconda parte del metodo crea un interprete per il modello, collegando le implementazioni dell'operatore alla struttura dei dati del grafico che abbiamo caricato in precedenza:

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];
}

Se conosci TensorFlow in Python, grosso modo equivale alla creazione di un elemento tf.Session().

Esegui il modello

Il metodo UpdatePhoto gestisce tutti i dettagli relativi al recupero della foto successiva, all'aggiornamento della finestra di anteprima e all'esecuzione del modello sulla foto.

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];
  }
}

Sono le ultime tre righe che ci interessano.

Il metodo CGImageToPixels converte il valore CGImage restituito dalla libreria Foto di iOS in una struttura semplice contenente larghezza, altezza, canali e dati dei pixel.

CameraExampleViewController.h

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

Il metodo inputImageToModel gestisce l'inserimento dell'immagine nella memoria dell'interprete. Ciò include il ridimensionamento dell'immagine e la regolazione dei valori dei pixel in modo che corrispondano a quanto previsto dal modello.

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;
      }
    }
  }
}

Sappiamo che il modello ha un solo input, quindi la riga float* out = interpreter->typed_input_tensor<float>(0); chiede all'interprete di un puntatore alla memoria per l'input 0. Il resto del metodo gestisce l'aritmetica del puntatore e il ridimensionamento dei pixel per copiare i dati nell'array di input.

Infine, il metodo runModel esegue il modello:

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);

  ...

}

Il prossimo runModel rilegge i risultati. Per farlo, viene chiesto all'interprete un puntatore ai dati dell'array di output. L'output è un semplice array di float. Il metodo GetTopN gestisce l'estrazione dei primi 5 risultati (utilizzando una coda di 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);

  ...
}

Le righe successive convertono semplicemente le prime 5 coppie (probability, class_id) in coppie (probability, label) e poi passano il risultato, in modo asincrono, al metodo setPredictionValues, che aggiorna il report sullo schermo:

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];
  });
}

Passaggio successivo

Hai completato la procedura dettagliata di un'app di classificazione dei fiori per iOS utilizzando un modello Edge. Hai utilizzato un modello Edge Tensorflow Lite addestrato per testare un'app di classificazione delle immagini prima di apportarvi modifiche e ottenere annotazioni di esempio. Hai quindi esaminato il codice specifico di TensorFlow Lite per comprenderne la funzionalità.

Le seguenti risorse possono aiutarti a continuare a conoscere i modelli TensorFlow e AutoML Vision Edge: