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.
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:
- instala tensorflow versión 1.7
- instala PILLOW
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:
- Xcode
- Herramientas de línea de comandos de Xcode
- 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
Navega al archivo .xcworkspace
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).
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 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:
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 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:
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:
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.
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:
- Obtén más información sobre TFLite en la documentación oficial y el repositorio de códigos.
- Prueba la versión de la cámara de esta app de demostración, que usa una versión cuantizada del modelo. Esto proporciona la misma potencia en un paquete más pequeño y eficiente.
- Prueba otros modelos preparados para TFLite en casos prácticos específicos.
- Obtén más información sobre TensorFlow en general con la documentación de introducción.