Sie können ML Kit verwenden, um Inferenzen auf dem Gerät mit einem TensorFlow Lite -Modell durchzuführen.
ML Kit kann TensorFlow Lite-Modelle nur auf Geräten mit iOS 9 und höher verwenden.
Bevor Sie beginnen
- Wenn Sie Ihrer App Firebase noch nicht hinzugefügt haben, befolgen Sie dazu die Schritte im Leitfaden „Erste Schritte“ .
- Schließen Sie die ML Kit-Bibliotheken in Ihre Poddatei ein:
pod 'Firebase/MLModelInterpreter', '6.25.0'
Nachdem Sie die Pods Ihres Projekts installiert oder aktualisiert haben, müssen Sie Ihr Xcode-Projekt mit seiner.xcworkspace
. - Importieren Sie in Ihrer App Firebase:
Schnell
import Firebase
Ziel c
@import Firebase;
- Konvertieren Sie das TensorFlow-Modell, das Sie verwenden möchten, in das TensorFlow Lite-Format. Siehe TOCO: TensorFlow Lite-Optimierungskonverter .
Hosten oder bündeln Sie Ihr Modell
Bevor Sie ein TensorFlow Lite-Modell für Inferenz in Ihrer App verwenden können, müssen Sie das Modell für ML Kit verfügbar machen. ML Kit kann TensorFlow Lite-Modelle verwenden, die remote mit Firebase gehostet werden, gebündelt mit der App-Binärdatei, oder beides.
Indem Sie ein Modell auf Firebase hosten, können Sie das Modell aktualisieren, ohne eine neue App-Version zu veröffentlichen, und Sie können Remote Config und A/B-Tests verwenden, um verschiedene Modelle dynamisch für verschiedene Gruppen von Benutzern bereitzustellen.
Wenn Sie das Modell nur bereitstellen, indem Sie es bei Firebase hosten, und es nicht mit Ihrer App bündeln, können Sie die anfängliche Downloadgröße Ihrer App reduzieren. Denken Sie jedoch daran, dass, wenn das Modell nicht mit Ihrer App gebündelt ist, alle modellbezogenen Funktionen erst verfügbar sind, wenn Ihre App das Modell zum ersten Mal herunterlädt.
Indem Sie Ihr Modell mit Ihrer App bündeln, können Sie sicherstellen, dass die ML-Funktionen Ihrer App auch dann funktionieren, wenn das von Firebase gehostete Modell nicht verfügbar ist.
Hosten Sie Modelle auf Firebase
So hosten Sie Ihr TensorFlow Lite-Modell auf Firebase:
- Klicken Sie im Abschnitt ML Kit der Firebase-Konsole auf die Registerkarte Benutzerdefiniert .
- Klicken Sie auf Benutzerdefiniertes Modell hinzufügen (oder Weiteres Modell hinzufügen ).
- Geben Sie einen Namen an, der zur Identifizierung Ihres Modells in Ihrem Firebase-Projekt verwendet wird, und laden Sie dann die TensorFlow Lite-Modelldatei hoch (die normalerweise auf
.tflite
oder.lite
).
Nachdem Sie Ihrem Firebase-Projekt ein benutzerdefiniertes Modell hinzugefügt haben, können Sie in Ihren Apps mit dem von Ihnen angegebenen Namen auf das Modell verweisen. Sie können jederzeit ein neues TensorFlow Lite-Modell hochladen, und Ihre App lädt das neue Modell herunter und verwendet es beim nächsten Neustart der App. Sie können die Gerätebedingungen definieren, die für Ihre App erforderlich sind, um zu versuchen, das Modell zu aktualisieren (siehe unten).
Bündeln Sie Modelle mit einer App
Um Ihr TensorFlow Lite-Modell mit Ihrer App zu bündeln, fügen Sie die Modelldatei (normalerweise mit der Endung .tflite
oder .lite
) zu Ihrem Xcode-Projekt hinzu und achten Sie darauf, dass Sie dabei Bundle-Ressourcen kopieren auswählen. Die Modelldatei wird in das App-Bundle aufgenommen und steht ML Kit zur Verfügung.
Laden Sie das Modell
Um Ihr TensorFlow Lite-Modell in Ihrer App zu verwenden, konfigurieren Sie zunächst ML Kit mit den Orten, an denen Ihr Modell verfügbar ist: remote mit Firebase, im lokalen Speicher oder beidem. Wenn Sie sowohl ein lokales als auch ein Remote-Modell angeben, können Sie das Remote-Modell verwenden, falls es verfügbar ist, und auf das lokal gespeicherte Modell zurückgreifen, wenn das Remote-Modell nicht verfügbar ist.
Konfigurieren Sie ein von Firebase gehostetes Modell
Wenn Sie Ihr Modell mit Firebase gehostet haben, erstellen Sie ein CustomRemoteModel
Objekt und geben Sie dabei den Namen an, den Sie dem Modell bei der Veröffentlichung zugewiesen haben:
Schnell
let remoteModel = CustomRemoteModel(
name: "your_remote_model" // The name you assigned in the Firebase console.
)
Ziel c
// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
[[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];
Starten Sie dann die Aufgabe zum Herunterladen des Modells und geben Sie die Bedingungen an, unter denen Sie das Herunterladen zulassen möchten. Wenn sich das Modell nicht auf dem Gerät befindet oder eine neuere Version des Modells verfügbar ist, lädt die Aufgabe das Modell asynchron von Firebase herunter:
Schnell
let downloadConditions = ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
let downloadProgress = ModelManager.modelManager().download(
remoteModel,
conditions: downloadConditions
)
Ziel c
FIRModelDownloadConditions *downloadConditions =
[[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
allowsBackgroundDownloading:YES];
NSProgress *downloadProgress =
[[FIRModelManager modelManager] downloadRemoteModel:remoteModel
conditions:downloadConditions];
Viele Apps starten die Download-Aufgabe in ihrem Initialisierungscode, aber Sie können dies jederzeit tun, bevor Sie das Modell verwenden müssen.
Konfigurieren Sie ein lokales Modell
Wenn Sie das Modell mit Ihrer App gebündelt haben, erstellen Sie ein CustomLocalModel
Objekt und geben Sie den Dateinamen des TensorFlow Lite-Modells an:
Schnell
guard let modelPath = Bundle.main.path(
forResource: "your_model",
ofType: "tflite",
inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)
Ziel c
NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
ofType:@"tflite"
inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
[[FIRCustomLocalModel alloc] initWithModelPath:modelPath];
Erstellen Sie einen Interpreter aus Ihrem Modell
Nachdem Sie Ihre Modellquellen konfiguriert haben, erstellen Sie ein ModelInterpreter
Objekt aus einer davon.
Wenn Sie nur ein lokal gebündeltes Modell haben, übergeben Sie einfach das CustomLocalModel
-Objekt an modelInterpreter(localModel:)
:
Schnell
let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
Ziel c
FIRModelInterpreter *interpreter =
[FIRModelInterpreter modelInterpreterForLocalModel:localModel];
Wenn Sie ein remote gehostetes Modell haben, müssen Sie überprüfen, ob es heruntergeladen wurde, bevor Sie es ausführen. Sie können den Status der Modell-Download-Aufgabe mit der isModelDownloaded(remoteModel:)
Methode des Modellmanagers überprüfen.
Obwohl Sie dies nur bestätigen müssen, bevor Sie den Interpreter ausführen, kann es sinnvoll sein, diese Überprüfung durchzuführen, wenn Sie ModelInterpreter
instanziieren, wenn Sie sowohl ein remote gehostetes als auch ein lokal gebündeltes Modell haben: erstellen Sie einen Interpreter aus dem Remote-Modell, falls dies der Fall ist heruntergeladen wurden, und ansonsten vom lokalen Modell.
Schnell
var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}
Ziel c
FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}
Wenn Sie nur über ein remote gehostetes Modell verfügen, sollten Sie modellbezogene Funktionen deaktivieren, z. B. Teile Ihrer Benutzeroberfläche ausgrauen oder ausblenden, bis Sie bestätigen, dass das Modell heruntergeladen wurde.
Sie können den Downloadstatus des Modells abrufen, indem Sie Beobachter an das standardmäßige Benachrichtigungscenter anhängen. Stellen Sie sicher, dass Sie im Beobachterblock einen schwachen Verweis auf sich self
verwenden, da Downloads einige Zeit dauern können und das Ursprungsobjekt bis zum Abschluss des Downloads freigegeben werden kann. Zum Beispiel:
Schnell
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] // ... }
Ziel 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]; }];
Geben Sie die Ein- und Ausgabe des Modells an
Konfigurieren Sie als Nächstes die Eingabe- und Ausgabeformate des Modellinterpreters.
Ein TensorFlow Lite-Modell verwendet als Eingabe und erzeugt als Ausgabe ein oder mehrere mehrdimensionale Arrays. Diese Arrays enthalten entweder byte
-, int
-, long
- oder float
-Werte. Sie müssen ML Kit mit der Anzahl und den Abmessungen ("Form") der Arrays konfigurieren, die Ihr Modell verwendet.
Wenn Sie die Form und den Datentyp der Eingabe und Ausgabe Ihres Modells nicht kennen, können Sie den TensorFlow Lite-Python-Interpreter verwenden, um Ihr Modell zu untersuchen. Zum Beispiel:
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'>
Nachdem Sie das Format der Ein- und Ausgabe Ihres Modells bestimmt haben, konfigurieren Sie den Modellinterpreter Ihrer App, indem Sie ein ModelInputOutputOptions
Objekt erstellen.
Beispielsweise könnte ein Gleitkomma-Bildklassifizierungsmodell als Eingabe ein N x 224 x 224 x 3-Array von Float
-Werten verwenden, das einen Stapel von N 224 x 224-Dreikanalbildern (RGB) darstellt, und als Ausgabe eine Liste von 1000 Float
-Werten erzeugen, die jeweils das darstellen Wahrscheinlichkeit, dass das Bild zu einer der 1000 Kategorien gehört, die das Modell vorhersagt.
Für ein solches Modell würden Sie die Ein- und Ausgabe des Modellinterpreters wie unten gezeigt konfigurieren:
Schnell
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)") }
Ziel 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; }
Führen Sie Rückschlüsse auf Eingabedaten durch
Um schließlich eine Inferenz mit dem Modell durchzuführen, rufen Sie Ihre Eingabedaten ab, führen Sie alle Transformationen an den Daten durch, die für Ihr Modell erforderlich sein könnten, und erstellen Sie ein Datenobjekt, das die Data
enthält.
Wenn Ihr Modell beispielsweise Bilder verarbeitet und Ihr Modell Eingabedimensionen von [BATCH_SIZE, 224, 224, 3]
Gleitkommawerten hat, müssen Sie die Farbwerte des Bildes möglicherweise wie im folgenden Beispiel auf einen Gleitkommabereich skalieren :
Schnell
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)") }
Ziel 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; }
Nachdem Sie Ihre Modelleingabe vorbereitet haben (und nachdem Sie bestätigt haben, dass das Modell verfügbar ist), übergeben Sie die Eingabe- und Eingabe-/Ausgabeoptionen an die Methode run(inputs:options:completion:)
Ihres Modellinterpreters .
Schnell
interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in guard error == nil, let outputs = outputs else { return } // Process outputs // ... }
Ziel c
[interpreter runWithInputs:inputs options:ioOptions completion:^(FIRModelOutputs * _Nullable outputs, NSError * _Nullable error) { if (error != nil || outputs == nil) { return; } // Process outputs // ... }];
Sie können die Ausgabe abrufen, indem Sie die Methode output(index:)
des zurückgegebenen Objekts aufrufen. Zum Beispiel:
Schnell
// 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]
Ziel c
// Get first and only output of inference with a batch size of 1 NSError *outputError; NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];
Wie Sie die Ausgabe verwenden, hängt vom verwendeten Modell ab.
Wenn Sie beispielsweise eine Klassifizierung durchführen, können Sie im nächsten Schritt die Indizes des Ergebnisses den Labels zuordnen, die sie darstellen. Angenommen, Sie hätten eine Textdatei mit Beschriftungszeichenfolgen für jede Kategorie Ihres Modells; Sie könnten die Label-Strings den Ausgabewahrscheinlichkeiten zuordnen, indem Sie etwa wie folgt vorgehen:
Schnell
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)") } }
Ziel 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); }
Anhang: Modellsicherheit
Unabhängig davon, wie Sie Ihre TensorFlow Lite-Modelle für ML Kit verfügbar machen, speichert ML Kit sie im standardmäßigen serialisierten Protobuf-Format im lokalen Speicher.
Theoretisch bedeutet dies, dass jeder Ihr Modell kopieren kann. In der Praxis sind die meisten Modelle jedoch so anwendungsspezifisch und durch Optimierungen verschleiert, dass das Risiko ähnlich ist wie bei Wettbewerbern, die Ihren Code zerlegen und wiederverwenden. Dennoch sollten Sie sich dieses Risikos bewusst sein, bevor Sie ein benutzerdefiniertes Modell in Ihrer App verwenden.