Użyj modelu TensorFlow Lite do wnioskowania za pomocą ML Kit na iOS

Możesz użyć ML Kit do wnioskowania na urządzeniu za pomocą Model TensorFlow Lite.

ML Kit może używać modeli TensorFlow Lite tylko na urządzeniach z iOS 9 nowszą.

Zanim zaczniesz

  1. Jeśli nie masz jeszcze w aplikacji dodanej Firebase, wykonaj czynności podane w przewodniku dla początkujących.
  2. Umieść biblioteki ML Kit w pliku Podfile:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    Po zainstalowaniu lub zaktualizowaniu podów projektu otwórz Xcode projektu za pomocą jego .xcworkspace.
  3. W aplikacji zaimportuj Firebase:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. Przekonwertuj model TensorFlow, którego chcesz używać, na format TensorFlow Lite. Zobacz TOCO: TensorFlow Lite Optimizing Converter

Hostowanie lub pakietowanie modelu

Zanim użyjesz modelu TensorFlow Lite do wnioskowania w aplikacji, musi udostępnić model ML Kit. ML Kit może używać TensorFlow Lite modele hostowane zdalnie przez Firebase, w pakiecie z plikiem binarnym aplikacji lub oba.

Hostując model w Firebase, możesz go aktualizować bez konieczności publikowania nowej wersji aplikacji. Możesz używać Remote Config i A/B Testing do dynamicznie udostępniać różne modele różnym grupom użytkowników.

Jeśli zdecydujesz się udostępniać model tylko poprzez hosting w Firebase, a nie pakietu z aplikacją, możesz zmniejszyć początkowy rozmiar pobieranej aplikacji. Pamiętaj jednak, że jeśli do aplikacji nie dołączony jest model, funkcje związane z modelem będą dostępne dopiero po pobraniu przez aplikację z użyciem modelu po raz pierwszy.

Jeśli połączysz model z aplikacją, będziesz mieć pewność, że funkcje ML w aplikacji będą działać. działają też wtedy, gdy model hostowany przez Firebase jest niedostępny.

Hostowanie modeli w Firebase

Aby hostować model TensorFlow Lite w Firebase:

  1. W sekcji ML Kit w konsoli Firebase kliknij kartę Niestandardowe.
  2. Kliknij Dodaj model niestandardowy (lub Dodaj kolejny model).
  3. Podaj nazwę, która będzie używana do identyfikowania Twojego modelu w Firebase projektu, a następnie prześlij plik modelu TensorFlow Lite (zwykle kończący się .tflite lub .lite).

Po dodaniu do projektu Firebase modelu niestandardowego możesz się odwoływać do w swoich aplikacjach o podanej przez Ciebie nazwie. W każdej chwili możesz przesłać nowego modelu TensorFlow Lite, a aplikacja go pobierze będzie można go używać po ponownym uruchomieniu aplikacji. Możesz określić, warunki wymagane do aktualizacji modelu przez aplikację (patrz poniżej).

Połącz modele z aplikacją

Aby połączyć model TensorFlow Lite z aplikacją, dodaj plik modelu (zwykle o numerze kończącym się cyframi .tflite lub .lite) do Twojego projektu Xcode, wybierając Skopiuj zasoby pakietu. Plik modelu zostanie dołączony do pliku pakietu aplikacji i dostępne dla ML Kit.

Wczytaj model

Aby użyć modelu TensorFlow Lite w aplikacji, najpierw skonfiguruj ML Kit za pomocą w lokalizacjach, w których Twój model jest dostępny: zdalnie za pomocą Firebase, pamięci lokalnej lub obu tych metod. Jeśli określisz model lokalny i zdalny, możesz użyj modelu zdalnego, jeśli jest dostępny, i użyj funkcji model przechowywany lokalnie, jeśli model zdalny jest niedostępny.

Konfigurowanie modelu hostowanego w Firebase

Jeśli model był hostowany w Firebase, utwórz obiekt CustomRemoteModel, określając nazwę przypisaną do modelu podczas jego publikowania:

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

Następnie rozpocznij zadanie pobierania modelu, określając warunki, które którzy chcą zezwolić na pobieranie. Jeśli nie ma modelu na urządzeniu lub jest on nowszy gdy dostępna będzie wersja modelu, zadanie asynchronicznie pobierze model z Firebase:

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

Wiele aplikacji rozpoczyna zadanie pobierania w kodzie inicjowania, ale możesz to zrobić. więc w dowolnym momencie przed użyciem modelu.

Konfigurowanie modelu lokalnego

Jeśli Twoja aplikacja jest powiązana z modelem, utwórz obiekt CustomLocalModel, podaj nazwę pliku modelu TensorFlow Lite:

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

Tworzenie interpretera na podstawie modelu

Po skonfigurowaniu źródeł modelu utwórz ModelInterpreter z jednego z nich.

Jeśli masz tylko model zbiorczy, wystarczy, że prześlesz CustomLocalModel obiekt w modelInterpreter(localModel:):

Swift

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Objective-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

Jeśli masz model hostowany zdalnie, musisz sprawdzić, czy został pobrane przed uruchomieniem. Stan pobierania modelu możesz sprawdzić za pomocą metody isModelDownloaded(remoteModel:) menedżera modeli.

Mimo że przed uruchomieniem tłumaczenia rozmowy trzeba to potwierdzić, korzystają zarówno z modelu hostowanego zdalnie, jak i z pakietu lokalnego, może to sprawić, warto przeprowadzić tę kontrolę przy tworzeniu wystąpienia ModelInterpreter: utwórz z tłumacza zdalnego z modelu zdalnego, jeśli został on pobrany, oraz z lokalnego w inny sposób.

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

Jeśli masz tylko model hostowany zdalnie, wyłącz powiązany z nim model funkcji – na przykład wyszarzenia lub ukrycia części interfejsu – do potwierdzasz, że model został pobrany.

Stan pobierania modelu możesz sprawdzić, dołączając obserwatorów do wartości domyślnej. Centrum powiadomień. Pamiętaj, aby w obserwatorium używać słabego odniesienia do self bo pobieranie może trochę potrwać, a źródłowy obiekt zwolniony do momentu zakończenia pobierania. Przykład:

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

Określ dane wejściowe i wyjściowe modelu

Następnie skonfiguruj formaty wejściowe i wyjściowe interpretera modelu.

Model TensorFlow Lite przyjmuje jako dane wejściowe i generuje jako dane wyjściowe co najmniej wielowymiarowych tablic. Te tablice zawierają: byte, Wartości int, long lub float. Musisz skonfiguruj w ML Kit liczbę i wymiary („kształt”) tablic przez model.

Jeśli nie znasz kształtu i typu danych wejściowych i wyjściowych modelu, możesz użyć interpretera TensorFlow Lite Python do sprawdzenia modelu. Przykład:

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'>

Po określeniu formatu danych wejściowych i wyjściowych modelu skonfiguruj tłumacza modelu aplikacji przez utworzenie ModelInputOutputOptions.

Na przykład model klasyfikacji obrazów zmiennoprzecinkowych może przyjąć jako dane wejściowe Tablica Nx224 x 224 x 3 z wartościami Float, reprezentująca grupę N Obrazy 3-kanałowe (RGB), 224 x 224, jako wynik wyjściowy 1000 wartości Float, każda reprezentująca prawdopodobieństwo, do którego należy obraz jedną z 1000 kategorii prognozowanych przez model.

W przypadku takiego modelu trzeba skonfigurować dane wejściowe i wyjściowe interpretera jak poniżej:

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

Przeprowadź wnioskowanie na danych wejściowych

Aby przeprowadzić wnioskowanie przy użyciu modelu, pobierz dane wejściowe, wykonaj dowolne przekształcenia danych, które mogą być niezbędne dla Twojego modelu, Data obiekt zawierający dane.

Na przykład jeśli model przetwarza obrazy, a model ma wymiary wejściowe z [BATCH_SIZE, 224, 224, 3] wartości zmiennoprzecinkowych, konieczne może być skalowanie kolor obrazu do zakresu zmiennoprzecinkowego, jak w tym przykładzie:

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

Gdy przygotujesz dane wejściowe modelu (i potwierdzisz, że model jest dostępne), należy przekazać opcje wejścia i wyjścia oraz run(inputs:options:completion:) Twojego tłumacza modelu .

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

Aby uzyskać dane wyjściowe, wywołaj metodę output(index:) obiektu, który . Przykład:

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

Sposób wykorzystania danych wyjściowych zależy od używanego modelu.

Jeśli na przykład przeprowadzasz klasyfikację, kolejnym krokiem może być zmapować indeksy wyników na etykiety, które reprezentują. Załóżmy, że masz plik tekstowy z ciągami etykiet dla każdej kategorii modelu; możesz umieścić na mapie ciągi etykiet do prawdopodobieństwa wynikowego, wykonując w ten sposób :

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

Dodatek: zabezpieczenia modelu

Niezależnie od tego, jak udostępnisz swoje modele TensorFlow Lite ML Kit przechowuje je w standardowym zserializowanym formacie protokołu w formacie pamięci lokalnej.

Teoretycznie oznacza to, że każdy może skopiować Twój model. Pamiętaj jednak: W praktyce większość modeli jest specyficzna dla danej aplikacji i pod kątem podobnych optymalizacji, jakie stwarzają konkurencji, demontaż ponownego wykorzystania kodu. Musisz jednak wiedzieć o tym ryzyku, zanim zaczniesz niestandardowy model w swojej aplikacji.