Utilizzare un modello TensorFlow Lite personalizzato sulle piattaforme Apple

Se la tua app utilizza modelli TensorFlow Lite personalizzati, puoi utilizzare Firebase ML per eseguire il deployment dei modelli. Se esegui il deployment dei modelli con Firebase, puoi ridurre le dimensioni del download iniziale della tua app e aggiornare i modelli di ML della tua app senza rilasciare una nuova versione. Inoltre, con Remote Config e A/B Testing, puoi pubblicare dinamicamente modelli diversi per diversi gruppi di utenti.

Prerequisiti

  • La libreria MLModelDownloader è disponibile solo per Swift.
  • TensorFlow Lite funziona solo sui dispositivi con iOS 9 e versioni successive.

Modelli TensorFlow Lite

I modelli TensorFlow Lite sono modelli ML ottimizzati per l'esecuzione su dispositivi mobili. Per ottenere un modello TensorFlow Lite:

Prima di iniziare

Per utilizzare TensorFlow Lite con Firebase, devi utilizzare CocoaPods, in quanto TensorFlow Lite al momento non supporta l'installazione con Swift Package Manager. Consulta la guida all'installazione di CocoaPods per istruzioni su come installare MLModelDownloader.

Una volta installati, importa Firebase e TensorFlowLite per utilizzarli.

Swift

import FirebaseMLModelDownloader
import TensorFlowLite

1. Esegui il deployment del modello

Esegui il deployment dei tuoi modelli TensorFlow personalizzati utilizzando la console Firebase o gli SDK Firebase Admin Python e Node.js. Consulta Eseguire il deployment e gestire modelli personalizzati.

Dopo aver aggiunto un modello personalizzato al progetto Firebase, puoi fare riferimento al modello nelle tue app usando il nome specificato. In qualsiasi momento, puoi eseguire il deployment di un nuovo modello TensorFlow Lite e scaricarlo sui dispositivi degli utenti chiamando getModel() (vedi sotto).

2. Scarica il modello sul dispositivo e inizializza un interprete TensorFlow Lite

Per utilizzare il modello TensorFlow Lite nella tua app, utilizza prima l'SDK Firebase ML per scaricare l'ultima versione del modello sul dispositivo.

Per avviare il download del modello, chiama il metodo getModel() del downloader del modello, specificando il nome che hai assegnato al modello quando lo hai caricato, se vuoi scaricare sempre l'ultimo modello e le condizioni in cui vuoi consentire il download.

Puoi scegliere tra tre comportamenti di download:

Tipo di download Descrizione
localModel Recupera il modello locale dal dispositivo. Se non è disponibile alcun modello locale, questo si comporta come latestModel. Utilizza questo tipo di download se non ti interessa verificare la presenza di aggiornamenti del modello. Ad esempio, utilizzi Remote Config per recuperare i nomi dei modelli e carichi sempre i modelli con nuovi nomi (consigliato).
localModelUpdateInBackground Recupera il modello locale dal dispositivo e inizia ad aggiornarlo in background. Se non è disponibile alcun modello locale, questo si comporta come latestModel.
latestModel Acquista l'ultimo modello. Se il modello locale è l'ultima versione, restituisce il modello locale. In caso contrario, scarica l'ultimo modello. Questo comportamento verrà bloccato finché non verrà scaricata l'ultima versione (non consigliato). Utilizza questo comportamento solo nei casi in cui hai esplicitamente bisogno dell'ultima versione.

Devi disattivare le funzionalità correlate al modello, ad esempio oscurare o nascondere parte della UI, finché non confermi che il modello è stato scaricato.

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

Molte app avviano l'attività di download nel codice di inizializzazione, ma puoi farlo in qualsiasi momento prima di dover utilizzare il modello.

3. Esegue l'inferenza sui dati di input

Ottenere le forme di input e output del modello

L'interprete del modello TensorFlow Lite prende come input e produce come output uno o più array multidimensionali. Questi array contengono valori byte, int, long o float. Prima di poter trasmettere dati a un modello o utilizzarne il risultato, devi conoscere il numero e le dimensioni ("forma") degli array utilizzati dal modello.

Se hai creato il modello autonomamente o se il formato di input e output del modello è documentato, potresti già disporre di queste informazioni. Se non conosci la forma e il tipo di dati dell'input e dell'output del modello, puoi utilizzare l'interprete TensorFlow Lite per esaminare il modello. Ad esempio:

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']))

Output di esempio:

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

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

Esegui l'interprete

Dopo aver determinato il formato dell'input e dell'output del modello, recupera i dati di input ed esegui le trasformazioni necessarie per ottenere un input della forma corretta per il modello.

Ad esempio, se il modello elabora immagini e ha dimensioni di input di [1, 224, 224, 3] valori in virgola mobile, potresti dover scalare i valori di colore dell'immagine a un intervallo in virgola mobile come nel seguente esempio:

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

Quindi, copia l'input NSData nell'interprete ed eseguilo:

Swift

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

Puoi ottenere l'output del modello chiamando il metodo output(at:) dell'interprete. Il modo in cui utilizzi l'output dipende dal modello che utilizzi.

Ad esempio, se esegui la classificazione, come passaggio successivo potresti mappare gli indici del risultato alle etichette che rappresentano:

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

Appendice: Sicurezza del modello

Indipendentemente da come rendi disponibili i tuoi modelli TensorFlow Lite per Firebase ML, Firebase ML li memorizza nel formato protobuf serializzato standard nello spazio di archiviazione locale.

In teoria, questo significa che chiunque può copiare il tuo modello. Tuttavia, in pratica, la maggior parte dei modelli è così specifica per l'applicazione e offuscata dalle ottimizzazioni che il rischio è simile a quello dei concorrenti che disassemblano e riutilizzano il tuo codice. Tuttavia, devi essere consapevole di questo rischio prima di utilizzare un modello personalizzato nella tua app.