Используйте модель TensorFlow Lite для вывода с помощью ML Kit на iOS.

,

Вы можете использовать ML Kit для выполнения вывода на устройстве с помощью модели TensorFlow Lite .

ML Kit может использовать модели TensorFlow Lite только на устройствах под управлением iOS 9 и новее.

Прежде чем вы начнете

  1. Если вы еще не добавили Firebase в свое приложение, сделайте это, выполнив действия, описанные в руководстве по началу работы .
  2. Включите библиотеки ML Kit в свой подфайл:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    После установки или обновления модулей вашего проекта обязательно откройте проект Xcode, используя его .xcworkspace .
  3. Импортируйте Firebase в свое приложение:

    Быстрый

    import Firebase

    Цель-C

    @import Firebase;
  4. Преобразуйте модель TensorFlow, которую вы хотите использовать, в формат TensorFlow Lite. См. TOCO: Оптимизирующий конвертер TensorFlow Lite .

Разместите или свяжите свою модель

Прежде чем вы сможете использовать модель TensorFlow Lite для вывода в своем приложении, вы должны сделать модель доступной для ML Kit. ML Kit может использовать модели TensorFlow Lite, размещенные удаленно с помощью Firebase, в комплекте с двоичным файлом приложения или и то, и другое.

Разместив модель в Firebase, вы можете обновить ее, не выпуская новую версию приложения, а также использовать удаленную настройку и A/B-тестирование для динамического предоставления разных моделей разным группам пользователей.

Если вы решите предоставить модель только путем размещения ее в Firebase, а не связывать ее со своим приложением, вы можете уменьшить первоначальный размер загрузки вашего приложения. Однако имейте в виду, что если модель не связана с вашим приложением, любые связанные с моделью функции не будут доступны до тех пор, пока ваше приложение не загрузит модель в первый раз.

Объединив свою модель со своим приложением, вы можете быть уверены, что функции машинного обучения вашего приложения будут работать, даже если модель, размещенная на Firebase, недоступна.

Размещайте модели в Firebase

Чтобы разместить модель TensorFlow Lite на Firebase:

  1. В разделе «ML Kit» консоли Firebase перейдите на вкладку «Пользовательский» .
  2. Нажмите «Добавить пользовательскую модель» (или «Добавить другую модель »).
  3. Укажите имя, которое будет использоваться для идентификации вашей модели в проекте Firebase, затем загрузите файл модели TensorFlow Lite (обычно заканчивающийся на .tflite или .lite ).

После добавления пользовательской модели в проект Firebase вы можете ссылаться на нее в своих приложениях, используя указанное вами имя. В любой момент вы можете загрузить новую модель TensorFlow Lite, и ваше приложение загрузит новую модель и начнет ее использовать при следующем перезапуске приложения. Вы можете определить условия устройства, необходимые для того, чтобы ваше приложение попыталось обновить модель (см. ниже).

Объединение моделей с приложением

Чтобы связать модель TensorFlow Lite с вашим приложением, добавьте файл модели (обычно заканчивающийся на .tflite или .lite ) в свой проект Xcode, не забывая при этом выбирать «Копировать ресурсы пакета ». Файл модели будет включен в пакет приложения и доступен в ML Kit.

Загрузите модель

Чтобы использовать модель TensorFlow Lite в своем приложении, сначала настройте ML Kit, указав места, где ваша модель доступна: удаленно с помощью Firebase, в локальном хранилище или и то, и другое. Если вы укажете как локальную, так и удаленную модель, вы можете использовать удаленную модель, если она доступна, и вернуться к локально сохраненной модели, если удаленная модель недоступна.

Настройка модели, размещенной в Firebase

Если вы разместили свою модель в Firebase, создайте объект CustomRemoteModel , указав имя, которое вы присвоили модели при ее публикации:

Быстрый

let remoteModel = CustomRemoteModel(
  name: "your_remote_model"  // The name you assigned in the Firebase console.
)

Цель-C

// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
    [[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];

Затем запустите задачу загрузки модели, указав условия, при которых вы хотите разрешить загрузку. Если модели нет на устройстве или доступна более новая версия модели, задача асинхронно загрузит модель из Firebase:

Быстрый

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Цель-C

FIRModelDownloadConditions *downloadConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[FIRModelManager modelManager] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

Многие приложения запускают задачу загрузки в своем коде инициализации, но вы можете сделать это в любой момент, прежде чем вам понадобится использовать модель.

Настройка локальной модели

Если вы связали модель со своим приложением, создайте объект CustomLocalModel , указав имя файла модели TensorFlow Lite:

Быстрый

guard let modelPath = Bundle.main.path(
  forResource: "your_model",
  ofType: "tflite",
  inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)

Цель-C

NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
                                                    ofType:@"tflite"
                                               inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
    [[FIRCustomLocalModel alloc] initWithModelPath:modelPath];

Создайте интерпретатор из вашей модели

После настройки источников модели создайте объект ModelInterpreter на основе одного из них.

Если у вас есть только локально связанная модель, просто передайте объект CustomLocalModel в modelInterpreter(localModel:) :

Быстрый

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Цель-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

Если у вас есть удаленно размещенная модель, вам придется убедиться, что она загружена, прежде чем запускать ее. Вы можете проверить состояние задачи загрузки модели с помощью метода isModelDownloaded(remoteModel:) менеджера моделей.

Хотя вам нужно подтвердить это только перед запуском интерпретатора, если у вас есть как удаленно размещенная модель, так и локально связанная модель, возможно, имеет смысл выполнить эту проверку при создании экземпляра ModelInterpreter : создайте интерпретатор из удаленной модели, если он скачано, а из локальной модели иначе.

Быстрый

var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
  interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
  interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}

Цель-C

FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
  interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
  interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}

Если у вас есть только удаленно размещенная модель, вам следует отключить функции, связанные с моделью (например, сделать их серыми или скрыть часть пользовательского интерфейса), пока вы не подтвердите, что модель загружена.

Вы можете получить статус загрузки модели, присоединив наблюдателей к Центру уведомлений по умолчанию. Обязательно используйте слабую ссылку на self в блоке наблюдателя, поскольку загрузка может занять некоторое время, а исходный объект может быть освобожден к моменту завершения загрузки. Например:

Быстрый

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

Цель-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];
            }];

Укажите входные и выходные данные модели

Затем настройте форматы ввода и вывода интерпретатора модели.

Модель TensorFlow Lite принимает на входе и выдает на выходе один или несколько многомерных массивов. Эти массивы содержат значения byte , int , long или float . Вам необходимо настроить ML Kit, указав количество и размеры («форму») массивов, которые использует ваша модель.

Если вы не знаете форму и тип данных входных и выходных данных вашей модели, вы можете использовать интерпретатор Python TensorFlow Lite для проверки вашей модели. Например:

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

Определив формат входных и выходных данных вашей модели, настройте интерпретатор модели вашего приложения, создав объект ModelInputOutputOptions .

Например, модель классификации изображений с плавающей запятой может принимать в качестве входных данных массив N x224x224x3 значений с Float , представляющий пакет трехканальных (RGB) изображений размером N 224x224, и создавать на выходе список из 1000 значений Float , каждое из которых представляет вероятность того, что изображение принадлежит к одной из 1000 категорий, прогнозируемых моделью.

Для такой модели вы должны настроить вход и выход интерпретатора модели, как показано ниже:

Быстрый

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

Цель-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; }

Выполните вывод по входным данным

Наконец, чтобы выполнить вывод с использованием модели, получите входные данные, выполните любые преобразования данных, которые могут потребоваться для вашей модели, и создайте объект Data , содержащий данные.

Например, если ваша модель обрабатывает изображения и ваша модель имеет входные размеры со значениями с плавающей запятой [BATCH_SIZE, 224, 224, 3] , вам может потребоваться масштабировать значения цвета изображения до диапазона с плавающей запятой, как в следующем примере. :

Быстрый

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

Цель-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; }

После того как вы подготовите входные данные модели (и после того, как вы подтвердите, что модель доступна), передайте параметры ввода и ввода-вывода в run(inputs:options:completion:) интерпретатора вашей модели .

Быстрый

interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in
    guard error == nil, let outputs = outputs else { return }
    // Process outputs
    // ...
}

Цель-C

[interpreter runWithInputs:inputs
                   options:ioOptions
                completion:^(FIRModelOutputs * _Nullable outputs,
                             NSError * _Nullable error) {
  if (error != nil || outputs == nil) {
    return;
  }
  // Process outputs
  // ...
}];

Вы можете получить выходные данные, вызвав метод output(index:) возвращаемого объекта. Например:

Быстрый

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

Цель-C

// Get first and only output of inference with a batch size of 1
NSError *outputError;
NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];

Как вы используете выходные данные, зависит от используемой модели.

Например, если вы выполняете классификацию, в качестве следующего шага вы можете сопоставить индексы результата с метками, которые они представляют. Предположим, у вас есть текстовый файл со строками меток для каждой категории вашей модели; вы можете сопоставить строки меток с выходными вероятностями, выполнив что-то вроде следующего:

Быстрый

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

Цель-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);
}

Приложение: Безопасность модели

Независимо от того, как вы делаете свои модели TensorFlow Lite доступными для ML Kit, ML Kit сохраняет их в стандартном сериализованном формате protobuf в локальном хранилище.

Теоретически это означает, что кто угодно может скопировать вашу модель. Однако на практике большинство моделей настолько специфичны для приложения и запутаны оптимизациями, что риск аналогичен риску дизассемблирования и повторного использования вашего кода конкурентами. Тем не менее, вы должны знать об этом риске, прежде чем использовать пользовательскую модель в своем приложении.