Вы можете использовать ML Kit для выполнения логических выводов на устройстве с помощью модели TensorFlow Lite .
ML Kit может использовать модели TensorFlow Lite только на устройствах под управлением iOS 9 и новее.
Прежде чем вы начнете
- Если вы еще не добавили Firebase в свое приложение, сделайте это, следуя инструкциям в руководстве по началу работы.
- Включите библиотеки ML Kit в свой подфайл:
pod 'Firebase/MLModelInterpreter', '6.25.0'
После установки или обновления подов вашего проекта обязательно откройте проект Xcode, используя его.xcworkspace
. - В вашем приложении импортируйте Firebase:
Быстрый
import Firebase
Цель-C
@import Firebase;
- Преобразуйте модель TensorFlow, которую вы хотите использовать, в формат TensorFlow Lite. См. TOCO: оптимизирующий преобразователь TensorFlow Lite .
Разместите или свяжите свою модель
Прежде чем вы сможете использовать модель TensorFlow Lite для вывода в своем приложении, вы должны сделать модель доступной для ML Kit. ML Kit может использовать модели TensorFlow Lite, размещенные удаленно с помощью Firebase, в комплекте с двоичным файлом приложения или и то, и другое.
Разместив модель в Firebase, вы можете обновить модель, не выпуская новую версию приложения, и вы можете использовать удаленную настройку и A/B-тестирование для динамического предоставления различных моделей различным группам пользователей.
Если вы решите предоставить модель только путем размещения ее в Firebase, а не в комплекте с приложением, вы можете уменьшить первоначальный размер загружаемого приложения. Однако имейте в виду, что если модель не связана с вашим приложением, любые функции, связанные с моделью, будут недоступны до тех пор, пока ваше приложение не загрузит модель в первый раз.
Связав свою модель с приложением, вы можете гарантировать, что функции машинного обучения вашего приложения по-прежнему работают, когда модель, размещенная в Firebase, недоступна.
Хост-модели в Firebase
Чтобы разместить модель TensorFlow Lite в Firebase:
- В разделе ML Kit консоли Firebase перейдите на вкладку « Пользовательский ».
- Нажмите « Добавить пользовательскую модель» (или «Добавить другую модель» ).
- Укажите имя, которое будет использоваться для идентификации вашей модели в проекте 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 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 в локальном хранилище.
Теоретически это означает, что любой может скопировать вашу модель. Однако на практике большинство моделей настолько зависят от приложения и запутаны оптимизацией, что риск аналогичен риску дизассемблирования и повторного использования вашего кода конкурентами. Тем не менее, вы должны знать об этом риске, прежде чем использовать пользовательскую модель в своем приложении.