Puoi utilizzare ML Kit per eseguire l'inferenza on-device con una Modello TensorFlow Lite.
ML Kit può utilizzare i modelli TensorFlow Lite solo su dispositivi con iOS 9 e più recenti.
Prima di iniziare
- Se non hai già aggiunto Firebase alla tua app, puoi farlo seguendo le istruzioni riportate in passaggi nella Guida introduttiva.
- Includi le librerie del kit ML nel tuo podfile:
Dopo aver installato o aggiornato i pod del progetto, assicurati di aprire Xcode utilizzando il suopod 'Firebase/MLModelInterpreter', '6.25.0'
.xcworkspace
. - Nell'app, importa Firebase:
Swift
import Firebase
Objective-C
@import Firebase;
- Converti il modello TensorFlow che vuoi utilizzare nel formato TensorFlow Lite. Consulta TOCO: TensorFlow Lite Optimizing Converter.
Ospita o raggruppa il modello
Prima di poter utilizzare un modello TensorFlow Lite per l'inferenza nella tua app, il modello deve essere disponibile per ML Kit. ML Kit può utilizzare TensorFlow Lite modelli ospitati in remoto utilizzando Firebase, in bundle con il programma binario dell'app o entrambi.
Se ospiti un modello su Firebase, puoi aggiornarlo senza rilasciare un nuova versione dell'app e puoi usare Remote Config e A/B Testing per di pubblicare dinamicamente modelli diversi per insiemi di utenti diversi.
Se scegli di fornire il modello solo ospitandolo con Firebase e non puoi ridurne le dimensioni di download iniziali. Tieni presente, tuttavia, che se il modello non è integrato nella tua app, le funzionalità correlate al modello non saranno disponibili finché l'app non scarica l'app per la prima volta.
Associando il modello alla tua app, puoi garantire che le caratteristiche ML dell'app continueranno a funzionare quando il modello ospitato da Firebase non è disponibile.
Ospita modelli su Firebase
Per ospitare il tuo modello TensorFlow Lite su Firebase:
- Nella sezione ML Kit della console Firebase, fai clic su la scheda Personalizzata.
- Fai clic su Aggiungi modello personalizzato (o Aggiungi un altro modello).
- Specifica un nome che verrà utilizzato per identificare il modello in Firebase
progetto, quindi carica il file del modello TensorFlow Lite (che di solito termina con
.tflite
o.lite
).
Dopo aver aggiunto un modello personalizzato al tuo progetto Firebase, puoi fare riferimento alla tuo modello nelle tue app utilizzando il nome specificato. Puoi caricare un nuovo modello TensorFlow Lite in qualsiasi momento e la tua app lo scaricherà e inizierà a utilizzarlo al successivo riavvio. Puoi definire il dispositivo le condizioni necessarie affinché l'app tenti di aggiornare il modello (vedi di seguito).
Raggruppa i modelli con un'app
Per raggruppare il modello TensorFlow Lite con l'app, aggiungi il file del modello (in genere termina con .tflite
o .lite
) al progetto Xcode, assicurandoti di selezionare Copia risorse del bundle. Il file del modello verrà incluso nel bundle dell'app e sarà disponibile per ML Kit.
Carica il modello
Per utilizzare il modello TensorFlow Lite nella tua app, devi prima configurare ML Kit con le località in cui è disponibile il modello: da remoto tramite Firebase, archiviazione locale o entrambe. Se specifichi sia un modello locale che uno remoto, puoi usare il modello remoto, se disponibile, e tornare archiviato localmente se il modello remoto non è disponibile.
Configura un modello ospitato da Firebase
Se hai ospitato il tuo modello con Firebase, crea un oggetto CustomRemoteModel
,
specificando il nome assegnato al modello al momento della pubblicazione:
Swift
let remoteModel = CustomRemoteModel(
name: "your_remote_model" // The name you assigned in the Firebase console.
)
Objective-C
// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
[[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];
Poi, avvia l'attività di download del modello, specificando le condizioni in cui vuoi consentire il download. Se il modello non è presente sul dispositivo o se una versione più recente del modello, l'attività scaricherà in modo asincrono modello di Firebase:
Swift
let downloadConditions = ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
let downloadProgress = ModelManager.modelManager().download(
remoteModel,
conditions: downloadConditions
)
Objective-C
FIRModelDownloadConditions *downloadConditions =
[[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
allowsBackgroundDownloading:YES];
NSProgress *downloadProgress =
[[FIRModelManager modelManager] downloadRemoteModel:remoteModel
conditions:downloadConditions];
Molte app avviano l'attività di download nel codice di inizializzazione, ma puoi farlo quindi in qualsiasi momento prima di dover usare il modello.
configura un modello locale
Se hai in bundle il modello con la tua app, crea un oggetto CustomLocalModel
,
che specifica il nome file del modello TensorFlow Lite:
Swift
guard let modelPath = Bundle.main.path(
forResource: "your_model",
ofType: "tflite",
inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)
Objective-C
NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
ofType:@"tflite"
inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
[[FIRCustomLocalModel alloc] initWithModelPath:modelPath];
Crea un interprete dal tuo modello
Dopo aver configurato le origini del modello, crea un oggetto
ModelInterpreter
da una di queste.
Se hai solo un modello in bundle locale, è sufficiente trasmettere CustomLocalModel
oggetto in modelInterpreter(localModel:)
:
Swift
let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
Objective-C
FIRModelInterpreter *interpreter =
[FIRModelInterpreter modelInterpreterForLocalModel:localModel];
Se il tuo modello è ospitato in remoto, dovrai verificare che sia stato
scaricato prima di eseguirlo. Puoi controllare lo stato del download del modello
utilizzando il metodo isModelDownloaded(remoteModel:)
del gestore del modello.
Anche se devi solo confermare prima di avviare l'interprete, se
sia un modello ospitato in remoto sia uno in bundle locale,
di eseguire questo controllo durante la creazione di un'istanza per ModelInterpreter
:
dall'interprete del modello remoto, se è stato scaricato, e dal servizio
modello di machine learning.
Swift
var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}
Objective-C
FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}
Se hai solo un modello ospitato in remoto, devi disattivare le funzionalità correlate al modello, ad esempio disattivare o nascondere parte dell'interfaccia utente, finché non confermi che il modello è stato scaricato.
Puoi ottenere lo stato di download del modello collegando gli osservatori all'impostazione predefinita
Centro notifiche. Assicurati di utilizzare un riferimento debole a self
nel blocco osservatore, poiché i download possono richiedere del tempo e l'oggetto di origine può essere liberato al termine del download. Ad esempio:
Swift
NotificationCenter.default.addObserver( forName: .firebaseMLModelDownloadDidSucceed, object: nil, queue: nil ) { [weak self] notification in guard let strongSelf = self, let userInfo = notification.userInfo, let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue] as? RemoteModel, model.name == "your_remote_model" else { return } // The model was downloaded and is available on the device } NotificationCenter.default.addObserver( forName: .firebaseMLModelDownloadDidFail, object: nil, queue: nil ) { [weak self] notification in guard let strongSelf = self, let userInfo = notification.userInfo, let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue] as? RemoteModel else { return } let error = userInfo[ModelDownloadUserInfoKey.error.rawValue] // ... }
Objective-C
__weak typeof(self) weakSelf = self; [NSNotificationCenter.defaultCenter addObserverForName:FIRModelDownloadDidSucceedNotification object:nil queue:nil usingBlock:^(NSNotification *_Nonnull note) { if (weakSelf == nil | note.userInfo == nil) { return; } __strong typeof(self) strongSelf = weakSelf; FIRRemoteModel *model = note.userInfo[FIRModelDownloadUserInfoKeyRemoteModel]; if ([model.name isEqualToString:@"your_remote_model"]) { // The model was downloaded and is available on the device } }]; [NSNotificationCenter.defaultCenter addObserverForName:FIRModelDownloadDidFailNotification object:nil queue:nil usingBlock:^(NSNotification *_Nonnull note) { if (weakSelf == nil | note.userInfo == nil) { return; } __strong typeof(self) strongSelf = weakSelf; NSError *error = note.userInfo[FIRModelDownloadUserInfoKeyError]; }];
Specifica l'input e l'output del modello
A questo punto, configura i formati di input e di output dell'interprete del modello.
Un modello TensorFlow Lite riceve come input e produce come output uno o più array multidimensionali. Questi array contengono byte
,
Valori int
, long
o float
. Devi
configurare ML Kit con il numero e le dimensioni ("forma") degli array che
utilizzati dal modello.
Se non conosci la forma e il tipo di dati dell'input e output del modello, puoi utilizzare l'interprete Python di TensorFlow Lite per ispezionare il tuo modello. Ad esempio:
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'>
Dopo aver determinato il formato di input e output del modello, configura
l'interprete del modello di app creando un'immagine
ModelInputOutputOptions
.
Ad esempio, un modello di classificazione delle immagini a virgola mobile potrebbe prendere come input un array di valori Float
di Nx224x224x3, che rappresenta un batch di N immagini a tre canali (RGB) di 224 x 224 e produrre come output un elenco di 1000 valori Float
, ciascuno dei quali rappresenta la probabilità che l'immagine appartenga a una delle 1000 categorie previste dal modello.
Per un modello di questo tipo, devi configurare l'input e l'output dell'interprete del modello come mostrato di seguito:
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; }
Esegui l'inferenza sui dati di input
Infine, per eseguire l'inferenza utilizzando il modello, ottenere i dati di input, eseguire
le trasformazioni necessarie per il tuo modello e crea un
Data
oggetto che contiene i dati.
Ad esempio, se il modello elabora immagini e ha dimensioni di input
di [BATCH_SIZE, 224, 224, 3]
valori in virgola mobile, potresti dover scalare
i valori dei colori dell'immagine in un intervallo con rappresentazione in virgola mobile, come nell'esempio seguente:
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; }
Dopo aver preparato l'input del modello (e dopo aver confermato che il modello è
disponibili), passa le opzioni di input e input/output
run(inputs:options:completion:)
dell'interprete di modelli
.
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 // ... }];
Puoi ottenere l'output chiamando il metodo output(index:)
dell'oggetto che
viene restituito. Ad esempio:
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];
Il modo in cui utilizzi l'output dipende dal modello utilizzato.
Ad esempio, se esegui la classificazione, come passaggio successivo, mappare gli indici del risultato alle etichette che rappresentano. Supponiamo che tu abbia un file di testo con stringhe di etichetta per ciascuna delle categorie del modello; potresti mappare le stringhe di etichetta alle probabilità di output eseguendo un'operazione simile seguenti:
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); }
Appendice: Sicurezza del modello
Indipendentemente da come rendi disponibili i tuoi modelli TensorFlow Lite ML Kit, ML Kit li archivia nel formato protobuf serializzato standard in formato archiviazione locale.
In teoria, questo significa che chiunque può copiare il modello. Tuttavia, Nella pratica, la maggior parte dei modelli è così specifica per l'applicazione e offuscata secondo cui il rischio è simile a quello dello smontaggio della concorrenza a riutilizzare il codice. Tuttavia, è necessario essere consapevoli di questo rischio prima di utilizzare un modello personalizzato nella tua app.