Usa un modelo personalizado de TensorFlow Lite en plataformas de Apple

Si tu app usa modelos personalizados de TensorFlow Lite, puedes utilizar el AA de Firebase para implementarlos. Si implementas modelos con Firebase, puedes reducir el tamaño de la descarga inicial de tu app y actualizar sus modelos de AA sin lanzar una nueva versión. Además, con Remote Config y A/B Testing, puedes entregar de manera dinámica diferentes modelos a conjuntos distintos de usuarios.

Requisitos previos

  • La biblioteca MLModelDownloader solo está disponible para Swift.
  • TensorFlow Lite se ejecuta solo en dispositivos con iOS 9 y versiones posteriores.

Modelos de TensorFlow Lite

Los modelos de TensorFlow Lite son modelos de AA optimizados para ejecutarse en dispositivos móviles. Deberás realizar lo siguiente para obtener un modelo de TensorFlow Lite:

Antes de comenzar

Para usar TensorFlowLite con Firebase, debes usar CocoaPods, ya que, por el momento, TensorFlow Lite no admite la instalación con Swift Package Manager. Consulta la guía de instalación de CocoaPods para obtener instrucciones sobre cómo instalar MLModelDownloader.

Una vez instalado, importa Firebase y TensorFlow Lite para poder usarlos.

Swift

import FirebaseMLModelDownloader
import TensorFlowLite

1. Implementa tu modelo

Implementa tus modelos personalizados de TensorFlow con Firebase console o los SDK de Firebase Admin para Python y Node.js. Consulta la sección sobre cómo implementar y administrar modelos personalizados.

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. En cualquier momento, puedes implementar un nuevo modelo de TensorFlow Lite y descargarlo en los dispositivos de los usuarios llamando a getModel() (consulta a continuación).

2. Descarga el modelo en el dispositivo y, luego, inicializa un intérprete de TensorFlow Lite

Para usar tu modelo de TensorFlow Lite en tu app, primero usa el SDK de AA de Firebase a fin de descargar la última versión del modelo en el dispositivo.

Para iniciar la descarga del modelo, llama al método getModel() del usuario que descargó el modelo, especifica el nombre que le asignaste al modelo cuando lo subiste, si quieres descargar siempre el último modelo y las condiciones en las que deseas permitir la descarga.

Puedes elegir entre tres comportamientos de descarga:

Tipo de descarga Descripción
localModel Obtén el modelo local del dispositivo. Si no hay un modelo local disponible, el método se comporta como latestModel. Usa este tipo de descarga si no quieres buscar las actualizaciones del modelo. Por ejemplo, si quieres usar Remote Config para recuperar nombres de modelos y siempre subes modelos con nombres nuevos (recomendado).
localModelUpdateInBackground Obtén el modelo local del dispositivo y comienza a actualizarlo en segundo plano. Si no hay un modelo local disponible, el método se comporta como latestModel.
latestModel Obtén el modelo más reciente. Si el modelo local es la versión más reciente, se muestra ese modelo. De lo contrario, descarga el modelo más reciente. Este comportamiento se bloqueará hasta que se descargue la versión más reciente (no se recomienda). Usa este comportamiento solo si necesitas de forma explícita la versión más reciente.

Debes inhabilitar la funcionalidad relacionada con el modelo, por ejemplo, ocultar o inhabilitar parte de la IU, hasta que confirmes que se descargó el modelo.

Swift

let conditions = ModelDownloadConditions(allowsCellularAccess: false)
ModelDownloader.modelDownloader()
    .getModel(name: "your_model",
              downloadType: .localModelUpdateInBackground,
              conditions: conditions) { result in
        switch (result) {
        case .success(let customModel):
            do {
                // Download complete. Depending on your app, you could enable the ML
                // feature, or switch from the local model to the remote model, etc.

                // The CustomModel object contains the local path of the model file,
                // which you can use to instantiate a TensorFlow Lite interpreter.
                let interpreter = try Interpreter(modelPath: customModel.path)
            } catch {
                // Error. Bad model file?
            }
        case .failure(let error):
            // Download was unsuccessful. Don't enable ML features.
            print(error)
        }
}

Muchas apps comienzan la tarea de descarga en su código de inicialización, pero puedes hacerlo en cualquier momento antes de usar el modelo.

3. Realiza inferencias sobre los datos de entrada

Obtén las formas de entrada y salida de tu modelo

El intérprete del modelo de TensorFlow Lite toma como entrada y produce como salida uno o más arrays multidimensionales. Estos arrays contienen valores byte, int, long o float. Antes de pasar datos a un modelo o utilizar su resultado, debes conocer el número y las dimensiones (“forma”) de los arrays que usa tu modelo.

Si creaste el modelo tú mismo o si el formato de entrada y salida del modelo está documentado, es posible que ya tengas esta información. Si no conoces la forma y el tipo de datos de la entrada y la salida del modelo, puedes usar el intérprete de TensorFlow Lite para inspeccionarlo. Por ejemplo:

Python

import tensorflow as tf

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

# Print input shape and type
inputs = interpreter.get_input_details()
print('{} input(s):'.format(len(inputs)))
for i in range(0, len(inputs)):
    print('{} {}'.format(inputs[i]['shape'], inputs[i]['dtype']))

# Print output shape and type
outputs = interpreter.get_output_details()
print('\n{} output(s):'.format(len(outputs)))
for i in range(0, len(outputs)):
    print('{} {}'.format(outputs[i]['shape'], outputs[i]['dtype']))

Salida de ejemplo:

1 input(s):
[  1 224 224   3] <class 'numpy.float32'>

1 output(s):
[1 1000] <class 'numpy.float32'>

Ejecuta el intérprete

Después de determinar el formato de entrada y salida del modelo, obtén los datos de entrada y realiza las transformaciones necesarias a fin de obtener una entrada con la forma correcta para tu modelo.

Por ejemplo, si el modelo procesa imágenes y tiene dimensiones de entrada con valores de punto flotante [1, 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 }

var inputData = Data()
for row in 0 ..&lt; 224 {
  for col in 0 ..&lt; 224 {
    let offset = 4 * (row * context.width + col)
    // (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(&amp;bytes, &amp;normalizedRed, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&amp;bytes, &amp;normalizedGreen, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&ammp;bytes, &amp;normalizedBlue, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
  }
}

A continuación, copia tu NSData de entrada en el intérprete y ejecútalo:

Swift

try interpreter.allocateTensors()
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()

Puedes obtener los datos de salida del modelo llamando al método output(at:) del intérprete. 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:

Swift

let output = try interpreter.output(at: 0)
let probabilities =
        UnsafeMutableBufferPointer<Float32>.allocate(capacity: 1000)
output.data.copyBytes(to: probabilities)

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 labels.indices {
    print("\(labels[i]): \(probabilities[i])")
}

Apéndice: Seguridad del modelo

Independientemente de cómo hagas que tus modelos de TensorFlow Lite estén disponibles para AA de Firebase, AA de Firebase los almacena en forma local en el formato estándar de protobuf serializado.

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 aplicación y ofuscados por las optimizaciones que el riesgo es comparable a que alguien de la competencia desensamble y vuelva a usar tu código. No obstante, debes estar al tanto de ese riesgo antes de usar un modelo personalizado en tu app.