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

Sie können ML Kit verwenden On-Device - Inferenz mit einem auszuführen TensorFlow Lite - Modell.

ML Kit kann TensorFlow Lite-Modelle nur auf Geräten verwenden, auf denen iOS 9 und höher ausgeführt wird.

Bevor Sie beginnen

  1. Wenn Sie nicht bereits Firebase zu Ihrer App, tun Sie dies , indem Sie die Schritte im Leitfaden zur Inbetriebnahme .
  2. Fügen Sie die ML - Kit - Bibliotheken in Ihrem Podfile:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    Nachdem Sie Ihr Projekt Pods installieren oder aktualisieren, sollten Sie Ihre Xcode - Projekt zu öffnen , seine Verwendung .xcworkspace .
  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 TOKO: TensorFlow Lite Optimizing Converter .

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, mit der App-Binärdatei gebündelt werden 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 das Modell nur bereitstellen, indem Sie es mit Firebase hosten, und es nicht mit Ihrer App bündeln, können Sie die anfängliche Downloadgröße Ihrer App reduzieren. Beachten Sie jedoch, dass keine modellbezogenen Funktionen verfügbar sind, wenn das Modell nicht mit Ihrer App gebündelt ist, bis 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.

Modelle auf Firebase hosten

So hosten Sie Ihr TensorFlow Lite-Modell auf Firebase:

  1. Im ML Kit Abschnitt der Firebase - Konsole , klicken Sie auf die Registerkarte Benutzerdefinierte.
  2. Klicken Sie auf Hinzufügen benutzerdefiniertes Modell (oder ein anderes Modell hinzufügen).
  3. Geben Sie einen Namen, Ihr Modell in Ihrem Projekt Firebase verwendet wird zu identifizieren, dann laden Sie die TensorFlow Lite Modell - Datei ( in der Regel in endend .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 Ihre App benötigt, um zu versuchen, das Modell zu aktualisieren (siehe unten).

Modelle mit einer App bündeln

Zu bündeln Ihr TensorFlow Lite - Modell mit Ihrer Anwendung, fügen Sie die Modell - Datei ( in der Regel in endend .tflite oder .lite zu Ihrem Xcode - Projekt), wobei darauf kopierten Bündel Ressourcen zu wählen , wenn Sie dies tun. 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 zuerst 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 Remotemodell angeben, können Sie das Remotemodell verwenden, sofern verfügbar, und auf das lokal gespeicherte Modell zurückgreifen, wenn das Remotemodell nicht verfügbar ist.

Konfigurieren eines von Firebase gehosteten Modells

Wenn Sie Ihr Modell mit Firebase gehostet, erstellen Sie ein CustomRemoteModel Objekt unter Angabe des Namens Sie das Modell zugewiesen , wenn Sie es veröffentlicht:

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 Modell-Download-Aufgabe 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, erstellen Sie ein CustomLocalModel Objekt, den Dateinamen des TensorFlow Lite - Modell festgelegt wird :

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 Ihr Modell Quellen konfigurieren, erstellen Sie eine ModelInterpreter von ihnen Objekt von einem.

Wenn Sie nur eine lokal gebündelt Modell haben, geben Sie einfach die CustomLocalModel Objekt 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 des Task - Modell Download Prüfen Sie die Modellmanager mit isModelDownloaded(remoteModel:) Methode.

Obwohl Sie nur dies zu bestätigen, bevor Sie den Interpreter ausgeführt wird , wenn Sie sowohl ein entfernt gehostete Modell und ein lokal gebündelt Modell haben, kann es sinnvoll sein , diese Prüfung durchzuführen , wenn das Instanziieren ModelInterpreter : einen Dolmetscher aus dem Remote - Modell erstellen , wenn es heruntergeladen wurde, 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. einen Teil Ihrer Benutzeroberfläche ausblenden oder ausblenden, bis Sie bestätigen, dass das Modell heruntergeladen wurde.

Sie können den Download-Status des Modells abrufen, indem Sie Beobachter an die Standard-Benachrichtigungszentrale anhängen. Achten Sie darauf , einen schwachen Hinweis auf die Verwendung self in dem Beobachter Block, da Downloads kann einige Zeit dauern, und das Ursprungsobjekt kann durch die Zeit , die Download - Oberflächen befreit werden. 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

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 Ein- und Ausgabe Ihres Modells nicht kennen, können Sie Ihr Modell mit dem TensorFlow Lite-Python-Interpreter ü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 Ihres Modells Eingabe und Ausgabe bestimmen, konfigurieren Modell Interpreter Ihrer App durch die Schaffung eines ModelInputOutputOptions Objekt.

Zum Beispiel kann ein Gleitkommawert Bildklassifikationsmodell eine als Eingabe könnte nehmen N x224x224x3 Array von Float - Werten, die eine Charge von N 224x224 Dreikanal (RGB) Bilder, und erzeugt als Ausgabe eine Liste von 1000 Float - Werten, die jeweils die 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; }

Inferenz auf Eingabedaten durchführen

Schließlich auszuführen Inferenz unter Verwendung des Modells, Eingabedaten erhalten, führen Sie alle Transformationen auf den Daten , die für Ihr Modell erforderlich sein könnten, und baut ein Data , das die Daten enthält.

Zum Beispiel, wenn Ihr Modell Prozesse Bilder und Ihre Modelleingangs Dimensionen hat [BATCH_SIZE, 224, 224, 3] Gleitkommawerte, können Sie das Bild der Farbwerte auf einen Floating-Point - Bereich wie im folgenden Beispiel skalieren müssen :

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 vorzubereiten (und nachdem Sie das Modell verfügbar bestätigen), übergeben Sie die Eingabe und Eingabe / Ausgabe - Optionen zu Ihrem Modell Interpreter ‚s run(inputs:options:completion:) Methode.

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 durch den anrufenden output(index:) Methode des Objekts, der zurückgegeben wird. 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 haben eine Textdatei mit Beschriftungszeichenfolgen für jede Kategorie Ihres Modells; Sie könnten die Label-Strings 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 dem ML Kit zur Verfügung stellen, 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 Konkurrenten, die Ihren Code zerlegen und wiederverwenden. Sie sollten sich dieses Risikos jedoch bewusst sein, bevor Sie ein benutzerdefiniertes Modell in Ihrer App verwenden.