Ir a la consola

Usa un modelo de TensorFlow Lite para las inferencias con el Kit de AA en iOS

Puedes usar el Kit de AA para realizar inferencias en el dispositivo con un modelo de TensorFlow Lite.

El Kit de AA únicamente puede usar los modelos de TensorFlow Lite en dispositivos que ejecuten iOS 9 o posterior.

Consulta la muestra de inicio rápido del Kit de AA en GitHub para ver un ejemplo de esta API en uso.

Antes de comenzar

  1. Si aún no agregaste Firebase a tu app, sigue los pasos en la guía de introducción para hacerlo.
  2. Incluye las bibliotecas del Kit de AA en el Podfile:
    pod 'Firebase/Core'
    pod 'Firebase/MLModelInterpreter'
    
    Después de instalar o actualizar los pods de tu proyecto, asegúrate de abrir el proyecto de Xcode a través de su .xcworkspace.
  3. En tu app, importa Firebase:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. Además, importa el módulo FirebaseMLCommon:

    Swift

    import FirebaseMLCommon

    Objective-C

    @import FirebaseMLCommon;
  5. Convierte el modelo de TensorFlow que deseas usar al formato de TensorFlow Lite. Consulta TOCO: Convertidor de optimización de TensorFlow Lite.

Aloja o empaqueta tu modelo

Si quieres usar un modelo de TensorFlow Lite para las inferencias en tu app, primero debes hacer que esté disponible para el Kit de AA. El Kit de AA puede usar modelos de TensorFlow Lite alojados de forma remota con Firebase, empaquetados con el objeto binario de la app o ambas opciones.

Al alojar un modelo en Firebase, puedes actualizarlo sin lanzar una nueva versión de la app, y puedes usar Remote Config y A/B Testing para entregar de forma dinámica diferentes modelos a distintos conjuntos de usuarios.

Si eliges proporcionar el modelo únicamente mediante el alojamiento con Firebase y no empaquetarlo con tu app, puedes reducir el tamaño de descarga inicial de la app. Sin embargo, ten en cuenta que si el modelo no se empaqueta con la app, las funcionalidades relacionadas con el modelo no estarán disponibles hasta que la app descargue el modelo por primera vez.

Si empaquetas el modelo con la app, puedes asegurarte de que las funciones de AA de tu app estén activas cuando el modelo alojado en Firebase no esté disponible.

Cómo alojar modelos en Firebase

Para alojar tu modelo de TensorFlow Lite en Firebase, sigue estos pasos:

  1. Haz clic en la pestaña Personalizado de la sección Kit de AA de Firebase console.
  2. Haz clic en Agregar modelo personalizado (o Agregar otro modelo).
  3. Ingresa el nombre que se usará para identificar el modelo en tu proyecto de Firebase. Luego, sube el archivo del modelo de TensorFlow Lite (generalmente, con la extensión .tflite o .lite).

Después de agregar un modelo personalizado al proyecto de Firebase, podrás usar el nombre que especificaste para hacer referencia al modelo en tus apps. Puedes subir un nuevo modelo de TensorFlow Lite en cualquier momento. Tu app descargará el nuevo modelo y comenzará a usarlo cuando se reinicie. Podrás definir las condiciones del dispositivo que tu app requiera para intentar actualizar el modelo (ver a continuación).

Cómo empaquetar modelos con una app

Para empaquetar el modelo de TensorFlow Lite con tu app, agrega el archivo del modelo (generalmente, con la extensión .tflite o .lite) al proyecto de Xcode. Cuando lo hagas, selecciona Copiar recursos del paquete. El archivo del modelo se incluirá en el paquete de la app y estará disponible para el Kit de AA.

Carga el modelo

Para usar el modelo de TensorFlow Lite en tu app, primero configura el Kit de AA con las ubicaciones donde se encuentra disponible el modelo: de manera remota mediante Firebase, en el almacenamiento local, o en ambos. Si especificas un modelo local y uno remoto, el Kit de AA usará el modelo remoto si está disponible y, en caso de no estarlo, usará el modelo almacenado localmente.

Configura un modelo alojado en Firebase

Si alojaste tu modelo en Firebase, registra un objeto RemoteModel y especifica el nombre que le asignaste al modelo cuando lo subiste, así como las condiciones en las que el Kit de AA debe descargar el modelo de forma inicial y cuando existan actualizaciones disponibles.

Swift

let initialConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)
let updateConditions = ModelDownloadConditions(
  allowsCellularAccess: false,
  allowsBackgroundDownloading: true
)
let remoteModel = RemoteModel(
  name: "my_remote_model",
  allowsModelUpdates: true,
  initialConditions: initialConditions,
  updateConditions: updateConditions
)
let isRegistered = ModelManager.modelManager().register(remoteModel)

Objective-C

FIRModelDownloadConditions *initialConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];
FIRModelDownloadConditions *updateConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:NO
                                         allowsBackgroundDownloading:YES];
FIRRemoteModel *remoteModel = [[FIRRemoteModel alloc] initWithName:@"my_remote_model"
                                                allowsModelUpdates:YES
                                                 initialConditions:conditions
                                                  updateConditions:conditions];
  BOOL isRegistered =
      [[FIRModelManager modelManager] registerRemoteModel:remoteModel];

Configura un modelo local

Si empaquetaste el modelo con tu app, registra un objeto LocalModel especifica el nombre de archivo del modelo de TensorFlow Lite y asigna al modelo el nombre que utilizarás en el próximo paso.

Swift

guard let modelPath = Bundle.main.path(forResource: "my_model", ofType: "tflite")
    else {
        // Invalid model path
        return
}
let localModel = LocalModel(name: "my_local_model", path: modelPath)
let registrationSuccessful = ModelManager.modelManager().register(localModel)

Objective-C

NSString *modelPath = [NSBundle.mainBundle pathForResource:@"my_model"
                                                    ofType:@"tflite"];
FIRLocalModel *localModel = [[FIRLocalModel alloc] initWithName:@"my_local_model"
                                                           path:modelPath];
BOOL registrationSuccess =
    [[FIRModelManager modelManager] registerLocalModel:localModel];

Crea un intérprete a partir de tu modelo

Después de que configures las ubicaciones de tu modelo, crea un objeto ModelOptions con el modelo remoto, el modelo local, o ambos, y úsalo para obtener una instancia de ModelInterpreter. Si solo tienes un modelo, especifica nil para el tipo que no usas.

Swift

let options = ModelOptions(remoteModelName: "my_remote_model",
                           localModelName: "my_local_model")
let interpreter = ModelInterpreter.modelInterpreter(options: options)

Objective-C

FIRModelOptions *options = [[FIRModelOptions alloc] initWithRemoteModelName:@"my_remote_model"
                                                             localModelName:@"my_local_model"];
FIRModelInterpreter *interpreter = [FIRModelInterpreter modelInterpreterWithOptions:options];

Especifica la entrada y salida del modelo

A continuación, configura los formatos de entrada y salida del intérprete de modelo.

Un modelo de TensorFlow Lite toma como entrada y produce como salida una o más matrices multidimensionales. Estas matrices contienen valores byte, int, long o float. Debes configurar el Kit de AA con la cantidad y las dimensiones ("shape") de las matrices que usa tu modelo.

Si no conoces la forma y el tipo de datos de la entrada y la salida del modelo, puedes usar el intérprete Python de TensorFlow Lite para inspeccionar tu modelo. Por ejemplo:

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="my_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
print(interpreter.get_input_details()[0]['shape'])  # Example: [1 224 224 3]
print(interpreter.get_input_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

# Print output shape and type
print(interpreter.get_output_details()[0]['shape'])  # Example: [1 1000]
print(interpreter.get_output_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

Después de determinar el formato de entrada y salida del modelo, crea un objeto ModelInputOutputOptions para configurar el intérprete de modelo de tu app.

Por ejemplo, un modelo de clasificación de imágenes de coma flotante puede tomar como entrada un arreglo Nx224x224x3 de valores Float, que representa un lote de N imágenes de 224 x 224 de tres canales (RGB), y puede producir como salida una lista de 1,000 valores Float, cada uno de los cuales representa la probabilidad de que la imagen pertenezca a una de las 1,000 categorías que predice el modelo.

Para un modelo de ese tipo, debes configurar la entrada y la salida del intérprete de modelo como se muestra a continuación:

Swift

let ioOptions = ModelInputOutputOptions()
do {
    try ioOptions.setInputFormat(index: 0, type: .float32, dimensions: [1, 224, 224, 3])
    try ioOptions.setOutputFormat(index: 0, type: .float32, dimensions: [1, 1000])
} catch let error as NSError {
    print("Failed to set input or output format with error: \(error.localizedDescription)")
}

Objective-C

FIRModelInputOutputOptions *ioOptions = [[FIRModelInputOutputOptions alloc] init];
NSError *error;
[ioOptions setInputFormatForIndex:0
                             type:FIRModelElementTypeFloat32
                       dimensions:@[@1, @224, @224, @3]
                            error:&error];
if (error != nil) { return; }
[ioOptions setOutputFormatForIndex:0
                              type:FIRModelElementTypeFloat32
                        dimensions:@[@1, @1000]
                             error:&error];
if (error != nil) { return; }

Realiza inferencias sobre los datos de entrada

Por último, para realizar inferencias con el modelo, obtén los datos de entrada, realiza todas las transformaciones en los datos que sean necesarias para tu modelo y crea un objeto Data que contenga los datos.

Por ejemplo, si el modelo procesa imágenes y tiene dimensiones de entrada con valores de punto flotante [BATCH_SIZE, 224, 224, 3], es posible que debas escalar los valores de color de la imagen a un rango de puntos flotantes como se muestra en el siguiente ejemplo:

Swift

let image: CGImage = // Your input image
guard let context = CGContext(
  data: nil,
  width: image.width, height: image.height,
  bitsPerComponent: 8, bytesPerRow: image.width * 4,
  space: CGColorSpaceCreateDeviceRGB(),
  bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
) else {
  return false
}

context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
guard let imageData = context.data else { return false }

let inputs = ModelInputs()
var inputData = Data()
do {
  for row in 0 ..< 224 {
    for col in 0 ..< 224 {
      let offset = 4 * (col * context.width + row)
      // (Ignore offset 0, the unused alpha channel)
      let red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
      let green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
      let blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)

      // Normalize channel values to [0.0, 1.0]. This requirement varies
      // by model. For example, some models might require values to be
      // normalized to the range [-1.0, 1.0] instead, and others might
      // require fixed-point values or the original bytes.
      var normalizedRed = Float32(red) / 255.0
      var normalizedGreen = Float32(green) / 255.0
      var normalizedBlue = Float32(blue) / 255.0

      // Append normalized values to Data object in RGB order.
      let elementSize = MemoryLayout.size(ofValue: normalizedRed)
      var bytes = [UInt8](repeating: 0, count: elementSize)
      memcpy(&bytes, &normalizedRed, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&bytes, &normalizedGreen, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&ammp;bytes, &normalizedBlue, elementSize)
      inputData.append(&bytes, count: elementSize)
    }
  }
  try inputs.addInput(inputData)
} catch let error {
  print("Failed to add input: \(error)")
}

Objective-C

CGImageRef image = // Your input image
long imageWidth = CGImageGetWidth(image);
long imageHeight = CGImageGetHeight(image);
CGContextRef context = CGBitmapContextCreate(nil,
                                             imageWidth, imageHeight,
                                             8,
                                             imageWidth * 4,
                                             CGColorSpaceCreateDeviceRGB(),
                                             kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
UInt8 *imageData = CGBitmapContextGetData(context);

FIRModelInputs *inputs = [[FIRModelInputs alloc] init];
NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];

for (int row = 0; row < 224; row++) {
  for (int col = 0; col < 224; col++) {
    long offset = 4 * (col * imageWidth + row);
    // Normalize channel values to [0.0, 1.0]. This requirement varies
    // by model. For example, some models might require values to be
    // normalized to the range [-1.0, 1.0] instead, and others might
    // require fixed-point values or the original bytes.
    // (Ignore offset 0, the unused alpha channel)
    Float32 red = imageData[offset+1] / 255.0f;
    Float32 green = imageData[offset+2] / 255.0f;
    Float32 blue = imageData[offset+3] / 255.0f;

    [inputData appendBytes:&red length:sizeof(red)];
    [inputData appendBytes:&green length:sizeof(green)];
    [inputData appendBytes:&blue length:sizeof(blue)];
  }
}

[inputs addInput:inputData error:&error];
if (error != nil) { return nil; }

Después de preparar la entrada del modelo, pasa las opciones de entrada y entrada/salida al método run(inputs:options:) del intérprete de modelo.

Swift

interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in
    guard error == nil, let outputs = outputs else { return }
    // Process outputs
    // ...
}

Objective-C

[interpreter runWithInputs:inputs
                   options:ioOptions
                completion:^(FIRModelOutputs * _Nullable outputs,
                             NSError * _Nullable error) {
  if (error != nil || outputs == nil) {
    return;
  }
  // Process outputs
  // ...
}];

Para obtener el resultado, llama al método output(index:) del objeto que se muestra. Por ejemplo:

Swift

// Get first and only output of inference with a batch size of 1
let output = try? outputs.output(index: 0) as? [[NSNumber]]
let probabilities = output??[0]

Objective-C

// Get first and only output of inference with a batch size of 1
NSError *outputError;
NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];

La manera de usar el resultado depende del modelo que uses.

Por ejemplo, si realizas una clasificación, el paso siguiente puede ser asignar los índices del resultado a las etiquetas que representan. Imagina que tienes un archivo de texto con cadenas de etiqueta para cada una de las categorías de tu modelo. Para asignar las cadenas de etiqueta a las probabilidades de salida, puedes realizar algo como lo siguiente:

Swift

guard let labelPath = Bundle.main.path(forResource: "retrained_labels", ofType: "txt") else { return }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labels = fileContents?.components(separatedBy: "\n") else { return }

for i in 0 ..< labels.count {
  if let probability = probabilities?[i] {
    print("\(labels[i]): \(probability)")
  }
}

Objective-C

NSError *labelReadError = nil;
NSString *labelPath = [NSBundle.mainBundle pathForResource:@"retrained_labels"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&labelReadError];
if (labelReadError != nil || fileContents == NULL) { return; }
NSArray<NSString *> *labels = [fileContents componentsSeparatedByString:@"\n"];
for (int i = 0; i < labels.count; i++) {
    NSString *label = labels[i];
    NSNumber *probability = probabilites[i];
    NSLog(@"%@: %f", label, probability.floatValue);
}

Apéndice: Seguridad del modelo

Sin importar cómo hagas que estén disponibles tus modelos de TensorFlow Lite para el Kit de AA, este los almacena en el formato estándar de protobuf serializado en el almacenamiento local.

En teoría, eso significa que cualquier persona puede copiar tu modelo. Sin embargo, en la práctica, la mayoría de los modelos son tan específicos para la app, y ofuscados además por las optimizaciones, que el riesgo es comparable a que alguien de la competencia desensamble y vuelva a usar tu código. Con todo, debes estar al tanto de ese riesgo antes de usar un modelo personalizado en tu app.