Instructivo para la implementación de Edge en iOS

Qué compilarás

En este instructivo, descargarás un modelo personalizado de TensorFlow Lite exportado desde AutoML Vision Edge. Luego, ejecutarás una app para iOS prediseñada que usa el modelo a fin de identificar imágenes de flores.

Captura de pantalla de un dispositivo móvil del producto final
Crédito de la imagen: Felipe Venâncio, “from my mother's garden” (CC BY 2.0, imagen que se muestra en la app).

Objetivos

En esta introducción detallada, usarás el código para lo siguiente:

  • Ejecutar un modelo previamente entrenado en una app para iOS con el intérprete de TFLite

Antes de comenzar

Instala TensorFlow

Antes de comenzar el instructivo, debes instalar varios tipos de software:

Si tienes una instalación de Python que funcione, ejecuta los siguientes comandos para descargar este software:

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

Clona el repositorio de Git

Con la línea de comandos, clona el repositorio de Git mediante el siguiente comando:

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

Navega al directorio del clon local del repositorio (directorio tensorflow-for-poets-2). Ejecutarás todas las siguientes muestras de códigos de este directorio:

cd tensorflow-for-poets-2

Configura la app para iOS

Para la app de demostración de iOS, se requieren varias herramientas adicionales:

  1. Xcode
  2. Herramientas de línea de comandos de Xcode
  3. CocoaPods

Descarga Xcode

Usa el siguiente vínculo para descargar Xcode en tu máquina.

Instala herramientas de línea de comandos de Xcode

Para instalar las herramientas de línea de comandos de Xcode, ejecuta el siguiente comando:

xcode-select --install

Instala CocoaPods

CocoaPods usa Ruby, que está instalado de forma predeterminada en macOS.

Para instalar CocoaPods, ejecuta este comando:

sudo gem install cocoapods
Install TFLite Cocoapod

El resto de este codelab debe ejecutarse directamente en macOS, por lo que debes cerrar Docker ahora (con Ctrl-D).

Usa el siguiente comando para instalar TensorFlow Lite y crear el archivo .xcworkspace mediante CocoaPods:

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

Abre el proyecto con Xcode. Puedes abrir el proyecto a través de la línea de comandos o la IU.

Para abrir el proyecto a través de la línea de comandos, ejecuta el siguiente comando:

open ios/tflite/tflite_photos_example.xcworkspace

Para abrir el proyecto a través de la IU, inicia Xcode y selecciona el botón “Open another Project” (Abrir otro proyecto).

IU de Xcode

Después de abrir el proyecto, ve al archivo .xcworkspace (no a .xcproject).

Ejecuta la app original

La app es un ejemplo sencillo en la que se ejecuta un modelo de reconocimiento de imágenes en el simulador de iOS. La app lee desde la biblioteca de fotos, ya que el simulador no admite la entrada de cámara.

Antes de insertar el modelo personalizado, prueba la versión de modelo de referencia de la app que usa el modelo base entrenado de “mobilenet” en las 1,000 categorías de ImageNet.

Para iniciar la app en el simulador, selecciona el botón reproducir Ícono de reproducción de Xcode en la esquina superior derecha de la ventana de Xcode.

Con el botón “Next Photo” (Foto siguiente), verás las fotos del dispositivo.

Para agregar fotos a la biblioteca de fotos del dispositivo, arrástralas y suéltalas en la ventana del simulador.

El resultado debe mostrar anotaciones similares a esta imagen:

Captura de pantalla de la ejecución de prueba de la app

Ejecuta la app personalizada

La configuración original de la app clasifica las imágenes en una de las 1,000 clases de ImageNet, mediante MobileNet estándar.

Modifica la app para que use el modelo que se volvió a entrenar con categorías de imágenes personalizadas.

Agrega los archivos del modelo al proyecto

El proyecto de demostración está configurado para buscar un archivo labels.txt y graph.lite en el directorio android/tflite/app/src/main/assets/.

Para reemplazar esos dos archivos por tus versiones, ejecuta el siguiente comando:

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

Ejecuta tu app

Para volver a iniciar la app en el simulador, selecciona el botón reproducir Ícono de reproducción de Xcode en la esquina superior derecha de la ventana de Xcode.

Para probar las modificaciones, agrega los archivos de imagen del directorio flower_photos/ y obtén predicciones.

Los resultados deberían ser similares a esto:

Captura de pantalla de un dispositivo móvil del producto final
Crédito de la imagen: Felipe Venâncio, “from my mother's garden” (CC BY 2.0, imagen que se muestra en la app).

Ten en cuenta que las imágenes predeterminadas no son de flores.

Para realizar una prueba real del modelo, agrega algunas de las imágenes de datos de entrenamiento que descargaste antes o descarga algunas imágenes de una Búsqueda de Google para usarlas en la predicción.

¿Cómo funciona?

Ahora que la app está en ejecución, observa el código específico de TensorFlow Lite.

Pod de TensorFlowLite

Esta app usa un CocoaPod de TFLite ya compilado. El Podfile incluye el CocoaPod en el proyecto:

Podfile

platform :ios, '8.0'
inhibit_all_warnings!

target 'tflite_photos_example'
       pod 'TensorFlowLite'

El código que interactúa con TFLite está incluido en el archivo CameraExampleViewController.mm.

Configuración

El primer bloque de interés (después de las importaciones necesarias) es el método 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 línea clave en esta primera mitad del método es la línea model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);. Este código crea un FlatBufferModel a partir del archivo de grafo.

Un FlatBuffer es una estructura de datos que se puede asignar a la memoria. Estas son características clave de TFLite, ya que permiten que el sistema administre mejor la memoria que usa el modelo. El sistema puede intercambiar partes del modelo con transparencia dentro o fuera de la memoria, según sea necesario.

En la segunda parte del método, se crea un intérprete para el modelo y se adjuntan las implementaciones de op a la estructura de datos del grafo que se cargó antes:

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 estás familiarizado con TensorFlow en Python, esto es similar a la creación de un tf.Session().

Ejecuta el modelo

El método UpdatePhoto controla todos los detalles para recuperar la foto siguiente, actualizar la ventana de vista previa y ejecutar el modelo en la 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];
  }
}

Las últimas tres líneas son las que nos interesan.

El método CGImageToPixels convierte la CGImage que se muestra en la biblioteca de fotos de iOS en una estructura simple que contiene los datos de ancho, alto, canales y píxeles.

CameraExampleViewController.h

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

El método inputImageToModel controla la inserción de la imagen en la memoria del intérprete. Esto incluye cambiar el tamaño de la imagen y ajustar los valores de píxeles para que coincidan con lo que espera el modelo.

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

Sabemos que el modelo solo tiene una entrada, por lo que en la línea float* out = interpreter->typed_input_tensor<float>(0); se solicita al intérprete un puntero de la memoria para la entrada 0. El resto del método controla la aritmética del puntero y la escala de píxeles para copiar los datos en ese arreglo de entrada.

Por último, el método runModel ejecuta el modelo:

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

  ...

}

Luego, runModel vuelve a leer los resultados. Para hacerlo, el método solicita al intérprete un puntero de los datos del arreglo de la salida. La salida es un arreglo simple de números de punto flotante. El método GetTopN controla la extracción de los 5 primeros resultados (mediante una cola de prioridad).

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

  ...
}

Las siguientes líneas convierten esos 5 pares (probability, class_id) en pares (probability, label) y, luego, transfieren ese resultado, de manera asíncrona, al método setPredictionValues que actualiza el informe en pantalla:

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

Próximos pasos

Ya completaste un instructivo de una app de clasificación de flores para iOS con un modelo de Edge. Usaste un modelo entrenado de Edge de Tensorflow Lite para probar una app de clasificación de imágenes antes de modificarla y obtener anotaciones de muestra. Luego, examinaste el código específico de TensorFlow Lite para comprender la funcionalidad subyacente.

Los siguientes recursos pueden ayudarte a seguir aprendiendo sobre los modelos de TensorFlow y AutoML Vision Edge: