Puoi utilizzare ML Kit per eseguire l'inferenza sul dispositivo con un modello TensorFlow Lite .
ML Kit può utilizzare i modelli TensorFlow Lite solo su dispositivi con iOS 9 e versioni successive.
Prima di iniziare
- Se non hai già aggiunto Firebase alla tua app, fallo seguendo i passaggi nella guida introduttiva .
- Includi le librerie ML Kit nel tuo Podfile:
pod 'Firebase/MLModelInterpreter', '6.25.0'
Dopo aver installato o aggiornato i Pod del tuo progetto, assicurati di aprire il tuo progetto Xcode utilizzando il relativo.xcworkspace
. - Nella tua app, importa Firebase:
Veloce
import Firebase
Obiettivo-C
@import Firebase;
- Converti il modello TensorFlow che desideri utilizzare nel formato TensorFlow Lite. Vedere TOCO: Convertitore di ottimizzazione TensorFlow Lite .
Ospita o raggruppa il tuo modello
Prima di poter utilizzare un modello TensorFlow Lite per l'inferenza nella tua app, devi rendere il modello disponibile per ML Kit. ML Kit può utilizzare modelli TensorFlow Lite ospitati in remoto utilizzando Firebase, in bundle con il file binario dell'app o entrambi.
Ospitando un modello su Firebase, puoi aggiornare il modello senza rilasciare una nuova versione dell'app e puoi utilizzare Remote Config e A/B Testing per servire dinamicamente modelli diversi a diversi gruppi di utenti.
Se scegli di fornire il modello solo ospitandolo con Firebase e non raggruppandolo con la tua app, puoi ridurre le dimensioni di download iniziali della tua app. Tieni presente, tuttavia, che se il modello non è incluso nella tua app, qualsiasi funzionalità relativa al modello non sarà disponibile finché l'app non scaricherà il modello per la prima volta.
Raggruppando il tuo modello con la tua app, puoi assicurarti che le funzionalità ML della tua app continuino 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 sulla scheda Personalizzato .
- Fai clic su Aggiungi modello personalizzato (o Aggiungi un altro modello ).
- Specifica un nome che verrà utilizzato per identificare il tuo modello nel tuo progetto Firebase, quindi carica il file del modello TensorFlow Lite (di solito termina con
.tflite
o.lite
).
Dopo aver aggiunto un modello personalizzato al tuo progetto Firebase, puoi fare riferimento al modello nelle tue app utilizzando il nome specificato. In qualsiasi momento, puoi caricare un nuovo modello TensorFlow Lite e la tua app scaricherà il nuovo modello e inizierà a utilizzarlo al successivo riavvio dell'app. Puoi definire le condizioni del dispositivo richieste affinché la tua app tenti di aggiornare il modello (vedi sotto).
Raggruppa i modelli con un'app
Per raggruppare il tuo modello TensorFlow Lite con la tua app, aggiungi il file del modello (di solito che termina con .tflite
o .lite
) al tuo progetto Xcode, avendo cura di selezionare Copia risorse del pacchetto quando lo fai. Il file del modello verrà incluso nel pacchetto dell'app e sarà disponibile per ML Kit.
Carica il modello
Per utilizzare il tuo modello TensorFlow Lite nella tua app, configura innanzitutto ML Kit con le posizioni in cui il tuo modello è disponibile: in remoto utilizzando Firebase, nello spazio di archiviazione locale o entrambi. Se specifichi sia un modello locale che uno remoto, puoi utilizzare il modello remoto se è disponibile e ricorrere al modello 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 che hai assegnato al modello quando lo hai pubblicato:
Veloce
let remoteModel = CustomRemoteModel(
name: "your_remote_model" // The name you assigned in the Firebase console.
)
Obiettivo-C
// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
[[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];
Avvia quindi l'attività di download del modello, specificando le condizioni alle quali desideri consentire il download. Se il modello non è presente sul dispositivo o se è disponibile una versione più recente del modello, l'attività scaricherà in modo asincrono il modello da Firebase:
Veloce
let downloadConditions = ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
let downloadProgress = ModelManager.modelManager().download(
remoteModel,
conditions: downloadConditions
)
Obiettivo-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 in qualsiasi momento prima di dover utilizzare il modello.
Configurare un modello locale
Se hai raggruppato il modello con la tua app, crea un oggetto CustomLocalModel
, specificando il nome file del modello TensorFlow Lite:
Veloce
guard let modelPath = Bundle.main.path(
forResource: "your_model",
ofType: "tflite",
inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)
Obiettivo-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 uno di essi.
Se disponi solo di un modello raggruppato localmente, passa semplicemente l'oggetto CustomLocalModel
a modelInterpreter(localModel:)
:
Veloce
let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
Obiettivo-C
FIRModelInterpreter *interpreter =
[FIRModelInterpreter modelInterpreterForLocalModel:localModel];
Se disponi di un modello ospitato in remoto, dovrai verificare che sia stato scaricato prima di eseguirlo. È possibile verificare lo stato dell'attività di download del modello utilizzando il metodo isModelDownloaded(remoteModel:)
del gestore modelli.
Sebbene sia necessario confermarlo solo prima di eseguire l'interprete, se si dispone sia di un modello ospitato in remoto che di un modello raggruppato localmente, potrebbe avere senso eseguire questo controllo quando si istanzia ModelInterpreter
: creare un interprete dal modello remoto se è stato scaricato e altrimenti dal modello locale.
Veloce
var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}
Obiettivo-C
FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}
Se disponi solo di un modello ospitato in remoto, dovresti disabilitare le funzionalità relative al modello, ad esempio disattivare o nascondere parte della tua interfaccia utente, finché non confermi che il modello è stato scaricato.
È possibile ottenere lo stato di download del modello collegando gli osservatori al Centro notifiche predefinito. Assicurati di utilizzare un riferimento debole a self
nel blocco dell'osservatore, poiché i download possono richiedere del tempo e l'oggetto di origine può essere liberato al termine del download. Per esempio:
Veloce
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] // ... }
Obiettivo-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]; }];
Specificare l'input e l'output del modello
Successivamente, configura i formati di input e output dell'interprete del modello.
Un modello TensorFlow Lite prende come input e produce come output uno o più array multidimensionali. Questi array contengono valori byte
, int
, long
o float
. È necessario configurare ML Kit con il numero e le dimensioni ("forma") degli array utilizzati dal modello.
Se non conosci la forma e il tipo di dati dell'input e dell'output del tuo modello, puoi utilizzare l'interprete Python TensorFlow Lite per ispezionare il tuo modello. Per 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 dell'input e dell'output del modello, configura l'interprete del modello dell'app creando un oggetto ModelInputOutputOptions
.
Ad esempio, un modello di classificazione delle immagini a virgola mobile potrebbe prendere come input un array N x224x224x3 di valori Float
, che rappresenta un batch di immagini N 224x224 a tre canali (RGB), e produrre come output un elenco di 1000 valori Float
, ciascuno rappresentante il probabilità che l'immagine appartenga a una delle 1000 categorie previste dal modello.
Per un modello di questo tipo, configureresti l'input e l'output dell'interprete del modello come mostrato di seguito:
Veloce
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)") }
Obiettivo-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; }
Eseguire l'inferenza sui dati di input
Infine, per eseguire l'inferenza utilizzando il modello, ottieni i dati di input, esegui eventuali trasformazioni sui dati che potrebbero essere necessarie per il tuo modello e crea un oggetto Data
che contenga i dati.
Ad esempio, se il tuo modello elabora immagini e ha dimensioni di input di [BATCH_SIZE, 224, 224, 3]
valori a virgola mobile, potresti dover ridimensionare i valori di colore dell'immagine su un intervallo a virgola mobile come nell'esempio seguente :
Veloce
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)") }
Obiettivo-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 è disponibile), passa le opzioni di input e input/output al metodo run(inputs:options:completion:)
dell'interprete del modello .
Veloce
interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in guard error == nil, let outputs = outputs else { return } // Process outputs // ... }
Obiettivo-C
[interpreter runWithInputs:inputs options:ioOptions completion:^(FIRModelOutputs * _Nullable outputs, NSError * _Nullable error) { if (error != nil || outputs == nil) { return; } // Process outputs // ... }];
È possibile ottenere l'output chiamando il metodo output(index:)
dell'oggetto restituito. Per esempio:
Veloce
// 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]
Obiettivo-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 che stai utilizzando.
Ad esempio, se stai eseguendo la classificazione, come passaggio successivo potresti associare gli indici del risultato alle etichette che rappresentano. Supponiamo di avere un file di testo con stringhe di etichette per ciascuna delle categorie del modello; potresti mappare le stringhe di etichetta alle probabilità di output facendo qualcosa di simile al seguente:
Veloce
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)") } }
Obiettivo-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 i tuoi modelli TensorFlow Lite disponibili per ML Kit, ML Kit li archivia nel formato protobuf serializzato standard nell'archivio locale.
In teoria, ciò significa che chiunque può copiare il tuo modello. Tuttavia, in pratica, la maggior parte dei modelli sono così specifici per l'applicazione e offuscati dalle ottimizzazioni che il rischio è simile a quello dei concorrenti che disassemblano e riutilizzano il codice. Tuttavia, dovresti essere consapevole di questo rischio prima di utilizzare un modello personalizzato nella tua app.