Verwenden Sie ein TensorFlow Lite-Modell für die Inferenz mit ML Kit unter iOS

Sie können das 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 neuer verwenden.

Bevor Sie beginnen

  1. Wenn Sie Firebase noch nicht zu Ihrer App hinzugefügt haben, befolgen Sie dazu die Schritte im Leitfaden „Erste Schritte“ .
  2. Fügen Sie die ML-Kit-Bibliotheken in Ihre Pod-Datei ein:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    Nachdem Sie die Pods Ihres Projekts installiert oder aktualisiert haben, stellen Sie sicher, dass Sie Ihr Xcode-Projekt mit seinem .xcworkspace öffnen.
  3. Importieren Sie Firebase in Ihre App:

    Schnell

    import Firebase

    Ziel c

    @import Firebase;
  4. Konvertieren Sie das TensorFlow-Modell, das Sie verwenden möchten, in das TensorFlow Lite-Format. Siehe TOCO: TensorFlow Lite Optimizing Converter .

Hosten oder bündeln Sie Ihr Modell

Bevor Sie ein TensorFlow Lite-Modell zur 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 über Firebase gehostet werden, gebündelt mit der App-Binärdatei oder beides.

Durch das Hosten eines Modells auf Firebase 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 Benutzergruppen bereitzustellen.

Wenn Sie sich dafür entscheiden, das Modell nur durch Hosten mit Firebase bereitzustellen und es nicht mit Ihrer App zu bündeln, können Sie die anfängliche Downloadgröße Ihrer App reduzieren. Beachten Sie jedoch, 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.

Durch die Bündelung Ihres Modells mit Ihrer App 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:

  1. Klicken Sie im Abschnitt „ML Kit“ der Firebase-Konsole auf die Registerkarte „Benutzerdefiniert“ .
  2. Klicken Sie auf „Benutzerdefiniertes Modell hinzufügen“ (oder „Anderes Modell hinzufügen “).
  3. 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 (normalerweise mit der Endung .tflite oder .lite ).

Nachdem Sie Ihrem Firebase-Projekt ein benutzerdefiniertes Modell hinzugefügt haben, können Sie in Ihren Apps unter 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 beginnt mit der Verwendung, wenn die App das nächste Mal neu gestartet wird. Sie können die Gerätebedingungen definieren, die erforderlich sind, damit Ihre App versucht, 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 dabei darauf, Bundle-Ressourcen kopieren auszuwählen. Die Modelldatei wird im App-Bundle enthalten 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 Standorten, an denen Ihr Modell verfügbar ist: remote über Firebase, im lokalen Speicher oder beides. Wenn Sie sowohl ein lokales als auch ein Remote-Modell angeben, können Sie das Remote-Modell verwenden, sofern 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 beim Veröffentlichen 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 den Download 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, Sie können dies jedoch 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 aus einer davon ein ModelInterpreter Objekt.

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 über ein remote gehostetes Modell verfügen, müssen Sie überprüfen, ob es heruntergeladen wurde, bevor Sie es ausführen. Sie können den Status der Modell-Download-Aufgabe mithilfe der Methode isModelDownloaded(remoteModel:) des Modellmanagers überprüfen.

Obwohl Sie dies nur bestätigen müssen, bevor Sie den Interpreter ausführen, kann es sinnvoll sein, diese Prüfung bei der Instanziierung des ModelInterpreter durchzuführen, wenn Sie sowohl ein remote gehostetes Modell als auch ein lokal gebündeltes Modell haben: Erstellen Sie ggf. einen Interpreter aus dem Remote-Modell heruntergeladen wurden, andernfalls 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. einen Teil Ihrer Benutzeroberfläche ausgrauen oder ausblenden), bis Sie bestätigen, dass das Modell heruntergeladen wurde.

Sie können den Modell-Download-Status abrufen, indem Sie Beobachter an das Standard-Benachrichtigungscenter anhängen. Stellen Sie sicher, dass Sie im Beobachterblock einen schwachen Verweis auf self verwenden, da Downloads einige Zeit dauern können und das ursprüngliche Objekt 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 Eingabe und Ausgabe des Modells an

Als nächstes konfigurieren Sie die Eingabe- und Ausgabeformate des Modellinterpreters.

Ein TensorFlow Lite-Modell verwendet als Eingabe ein oder mehrere mehrdimensionale Arrays und erzeugt als Ausgabe. 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 Ihr Modell mit dem Python-Interpreter TensorFlow Lite überprüfen. 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 Eingabe 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 x224x224x3-Array mit Float verwenden, das einen Stapel von N 224 x 224 Dreikanalbildern (RGB) darstellt, und als Ausgabe eine Liste mit 1000 Float erzeugen, die jeweils die 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 Eingabe 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 mithilfe des Modells durchzuführen, rufen Sie Ihre Eingabedaten ab, führen Sie alle für Ihr Modell erforderlichen Transformationen an den Daten durch und erstellen Sie ein Data , das die Daten enthält.

Wenn Ihr Modell beispielsweise Bilder verarbeitet und Eingabeabmessungen von [BATCH_SIZE, 224, 224, 3] Gleitkommawerten hat, müssen Sie möglicherweise die Farbwerte des Bildes 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 run(inputs:options:completion:) Methode 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 erhalten, 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 Beschriftungen zuordnen, die sie darstellen. Angenommen, Sie hätten eine Textdatei mit Beschriftungszeichenfolgen für jede Kategorie Ihres Modells. Sie könnten die Beschriftungszeichenfolgen den Ausgabewahrscheinlichkeiten zuordnen, indem Sie etwa Folgendes tun:

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 das, dass jeder Ihr Modell kopieren kann. In der Praxis sind die meisten Modelle jedoch so anwendungsspezifisch und durch Optimierungen verschleiert, dass das Risiko mit dem der Konkurrenz vergleichbar ist, die Ihren Code zerlegt und wiederverwendet. Dennoch sollten Sie sich dieses Risikos bewusst sein, bevor Sie ein benutzerdefiniertes Modell in Ihrer App verwenden.