TensorFlow Lite-Modell für Inferenz mit ML Kit unter iOS verwenden

Sie können ML Kit verwenden, um auf dem Gerät Inferenzen mit einer TensorFlow Lite-Modell verfügbar.

ML Kit kann TensorFlow Lite-Modelle nur auf Geräten mit iOS 9 und höher verwenden.

Hinweis

  1. Wenn Sie Firebase Ihrer App noch nicht hinzugefügt haben, folgen Sie der Anleitung im Einstiegsleitfaden.
  2. Fügen Sie die ML Kit-Bibliotheken in Ihre Podfile ein:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    Nachdem Sie die Pods Ihres Projekts installiert oder aktualisiert haben, öffnen Sie unbedingt Ihren Xcode mithilfe der .xcworkspace zu erstellen.
  3. Importieren Sie Firebase in Ihre App:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. Konvertieren Sie das gewünschte TensorFlow-Modell in das TensorFlow Lite-Format. Weitere Informationen finden Sie unter TOCO: TensorFlow Lite Optimization Converter.

Modell hosten oder bündeln

Bevor Sie ein TensorFlow Lite-Modell für Inferenzen in Ihrer App verwenden können, müssen Sie muss das Modell für ML Kit verfügbar gemacht werden. ML Kit kann TensorFlow Lite verwenden Modelle, die remote mit Firebase gehostet werden und mit dem App-Binärprogramm gebündelt sind, oder beides.

Wenn Sie ein Modell auf Firebase hosten, können Sie es aktualisieren, ohne ein neue App-Version und du kannst Remote Config und A/B Testing für Folgendes verwenden: verschiedenen Gruppen von Nutzern dynamisch verschiedene Modelle bereitstellen.

Wenn Sie das Modell nur durch das Hosting mit Firebase und nicht mit Ihrer App bündeln, können Sie die anfängliche Downloadgröße Ihrer App reduzieren. Wenn das Modell nicht in Ihrer App enthalten ist, modellbezogene Funktionen sind erst verfügbar, wenn Ihre App die um ein neues Modell zu erstellen.

Wenn Sie Ihr Modell mit Ihrer App bündeln, können Sie dafür sorgen, dass die ML-Features Ihrer App funktionieren auch, wenn das von Firebase gehostete Modell nicht verfügbar ist.

Modelle in Firebase hosten

So hosten Sie Ihr TensorFlow Lite-Modell in Firebase:

  1. Klicken Sie in der Firebase-Konsole im Bereich ML Kit auf den Tab Benutzerdefiniert.
  2. Klicken Sie auf Benutzerdefiniertes Modell hinzufügen oder Weiteres Modell hinzufügen.
  3. Geben Sie einen Namen an, mit dem das Modell in Firebase identifiziert wird und laden Sie dann die TensorFlow Lite-Modelldatei hoch, die in der Regel auf .tflite oder .lite).

Nachdem Sie Ihrem Firebase-Projekt ein benutzerdefiniertes Modell hinzugefügt haben, können Sie auf das Modell in Ihren Apps unter Verwendung des von Ihnen angegebenen Namens. Sie können jederzeit ein neues TensorFlow Lite-Modell an. Ihre App lädt das neue Modell verwenden, wenn die App das nächste Mal neu gestartet wird. Sie können festlegen, Bedingungen erfüllt, damit Ihre App versucht, das Modell zu aktualisieren (siehe unten).

Modelle mit einer App bündeln

Wenn Sie Ihr TensorFlow Lite-Modell mit Ihrer App bündeln möchten, fügen Sie die Modelldatei (normalerweise endet sie auf .tflite oder .lite) Ihrem Xcode-Projekt hinzu. Achten Sie dabei darauf, Bundle-Ressourcen kopieren auszuwählen. Die Modelldatei wird in das App-Bundle aufgenommen und für ML Kit verfügbar gemacht.

Modell laden

Um Ihr TensorFlow Lite-Modell in Ihrer App zu verwenden, konfigurieren Sie zuerst ML Kit mit Standorte, an denen Ihr Modell verfügbar ist: remote mit Firebase, in lokalen Speicher oder beides. Wenn Sie sowohl ein lokales als auch ein Remote-Modell angeben, können Sie das Remote-Modell verwenden, wenn es verfügbar ist. Andernfalls wird das lokal gespeicherte Modell verwendet.

Von Firebase gehostetes Modell konfigurieren

Wenn Sie Ihr Modell bei Firebase gehostet haben, erstellen Sie ein CustomRemoteModel-Objekt. Geben Sie den Namen an, den Sie dem Modell bei der Veröffentlichung zugewiesen haben:

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"];

Starten Sie dann den Modelldownload und geben Sie die Bedingungen an, unter denen Sie den Download erlauben möchten. Wenn das Modell nicht auf dem Gerät installiert ist oder ein neueres Modell Version des Modells verfügbar ist, lädt die Aufgabe asynchron das aus Firebase verwenden:

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];

Viele Apps starten die Downloadaufgabe in ihrem Initialisierungscode, Sie können dies aber auch jederzeit tun, bevor Sie das Modell verwenden müssen.

Lokales Modell konfigurieren

Wenn Sie das Modell mit Ihrer App gebündelt haben, erstellen Sie ein CustomLocalModel-Objekt. und geben den Dateinamen des TensorFlow Lite-Modells an:

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];

Interpreter aus dem Modell erstellen

Nachdem Sie die Modellquellen konfiguriert haben, erstellen Sie ModelInterpreter-Objekt aus einem von ihnen.

Wenn Sie nur ein lokal gebündeltes Modell haben, müssen Sie nur die CustomLocalModel Objekt für modelInterpreter(localModel:):

Swift

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Objective-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

Wenn Sie ein extern gehostetes Modell haben, müssen Sie prüfen, die Sie vor der Ausführung heruntergeladen haben. Sie können den Status des Modelldownloads mit der Methode isModelDownloaded(remoteModel:) des Modellmanagers.

Sie müssen dies nur vor der Ausführung des Dolmetschers bestätigen. Wenn Sie ein remote gehostetes und ein lokal gebündeltes Modell haben, sinnvoll, diese Prüfung bei der Instanziierung von ModelInterpreter durchzuführen: Interpreter aus dem Remote-Modell, falls es heruntergeladen wurde, und aus dem modellieren.

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];
}

Wenn Sie nur ein remote gehostetes Modell haben, sollten Sie die modellbezogenen wie z. B. das Ausgrauen oder Ausblenden eines Teils der Benutzeroberfläche, bis bestätigen Sie, dass das Modell heruntergeladen wurde.

Sie können den Downloadstatus des Modells abrufen, indem Sie Beobachter an das Standard-Benachrichtigungscenter anhängen. Achten Sie darauf, im Beobachter einen schwachen Verweis auf self zu verwenden -Block, da Downloads einige Zeit in Anspruch nehmen können und das ursprüngliche Objekt wird erst wieder freigegeben, wenn der Download abgeschlossen ist. Beispiel:

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];
            }];

Eingabe und Ausgabe des Modells angeben

Konfigurieren Sie als Nächstes die Ein- und Ausgabeformate des Modellinterpreters.

Ein TensorFlow Lite-Modell übernimmt als Eingabe und erzeugt als Ausgabe eine oder mehrere multidimensionale Arrays. Diese Arrays enthalten entweder byte-, int-, long- oder float-Werte. Sie müssen ML Kit mit der Anzahl und den Dimensionen („Form“) der Arrays konfigurieren, die in Ihrem Modell verwendet werden.

Wenn Sie die Form und den Datentyp der Eingabe und Ausgabe Ihres Modells nicht kennen, können Sie Ihr Modell mit dem Python-Interpreter von TensorFlow Lite überprüfen. 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 festgelegt haben, konfigurieren Sie den Modellinterpreter Ihrer App, indem Sie ein ModelInputOutputOptions-Objekt erstellen.

Ein Gleitkomma-Bildklassifizierungsmodell kann beispielsweise Nx224x224x3-Array mit Float-Werten, die eine Gruppe von N 224 x 224 Drei-Kanal-Bilder (RGB) und produzieren als Ausgabe eine Liste mit 1.000 Float-Werte, die jeweils die Wahrscheinlichkeit darstellen, zu der das Bild gehört einer der 1.000 Kategorien, die das Modell vorhersagt.

Für ein solches Modell konfigurieren Sie die Eingabe und Ausgabe des Modellinterpreters wie unten gezeigt:

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

Inferenzen auf Eingabedaten durchführen

Um schließlich eine Inferenz mit dem Modell durchzuführen, rufen Sie Ihre Eingabedaten ab, führen Sie Transformationen der Daten, die für Ihr Modell erforderlich sein könnten, und erstellen ein Data-Objekt, das die Daten enthält.

Beispiel: Ihr Modell verarbeitet Bilder und hat Eingabeabmessungen von [BATCH_SIZE, 224, 224, 3] Gleitkommawerten ist, müssen Sie möglicherweise skalieren die Farbwerte des Bildes in einen Gleitkommabereich, wie im folgenden Beispiel gezeigt:

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

Nachdem Sie Ihre Modelleingabe vorbereitet und bestätigt haben, dass das Modell verfügbar), übergeben Sie die Eingabe- und Eingabe-/Ausgabeoptionen an run(inputs:options:completion:) Ihres Modellinterpreters .

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
  // ...
}];

Sie können die Ausgabe erhalten, indem Sie die Methode output(index:) des Objekts aufrufen, zurückgegeben. Beispiel:

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];

Wie Sie die Ausgabe verwenden, hängt vom verwendeten Modell ab.

Bei der Klassifizierung könnten Sie als nächsten Schritt ordnen Sie die Indexe des Ergebnisses den von ihnen dargestellten Labels zu. Angenommen, Sie haben eine Textdatei mit Beschriftungszeichenfolgen für jede Kategorie Ihres Modells könnten Sie kartografieren, den Ausgabewahrscheinlichkeiten hinzu, indem Sie eine Methode wie Folgendes:

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

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 kann also jeder Ihr Modell kopieren. Sie können jedoch in der Praxis sind die meisten Modelle so anwendungsspezifisch Optimierungen vorzunehmen, bei denen das Risiko dem der Konkurrenz beim Auseinanderbauen und Ihren Code wiederverwenden. Sie sollten sich jedoch über dieses Risiko im Klaren sein, bevor Sie ein benutzerdefiniertes Modell in Ihrer App erstellen.