Недавно Google переименовал TensorflowLite в LiteRT. И да, это был действительно гениальный ход. Потому что теперь впервые в жизни я действительно хочу попробовать TFLite… да, я имею в виду LiteRT.
В реальном мире вы в идеале должны думать как обычный ML-разработчик и начинать с поиска набора данных, который затем используется для обучения модели. А затем, в качестве следующего шага, вы бы придумали проблему, которую можно решить с помощью обученной модели.
Но в этом эксперименте мы не будем усложнять ситуацию и построим «Hello World» во вселенной машинного обучения с упражнением «Собаки против кошек».
Настройка
По случайному совпадению я нашел обученную модель из Flutter-проекта, которая может определить, кто изображен на данной фотографии — собака или кошка.
Отлично! Итак, следуя инструкциям в официальной документации по началу работы, нам нужно добавить tensorflowlite в качестве зависимости в наш проект.
use_frameworks! # pod 'TensorFlowLiteSwift' pod 'TensorFlowLiteSwift', '~> 0.0.1-nightly', :subspecs => ['CoreML', 'Metal']
Мы используем «ночную» сборку, а не стабильный релиз, потому что на сегодняшний день последний стабильный релиз TensorFlowLiteC не работает на симуляторе iOS. Но, судя по последнему комментарию к этому вопросу, похоже, что TensorFlowLiteC теперь также поставляется как xcframework, но только в ночных релизах.
А пока мы ждем окончания установки библиотеки, мы можем беззастенчиво поискать образцы изображений различных собак и кошек из курса «Введение в TensorFlow Lite» на Udacity в качестве тестового набора.
И все готово!
Создание приложения
Для пользовательского интерфейса нам нужно простое представление изображения, текстовая метка и кнопка. Кнопка, очевидно, будет показывать ответ, а затем случайным образом загружать следующее изображение.
Теперь о самом интересном. Сначала нам нужен PetClassifier
, который принимает изображение и возвращает текст.
class PetClassifier { init?(named: String, labels: [String]) { // ... } func labelForImage(_ image: UIImage) -> String? { // ... } }
А затем в слое пользовательского интерфейса мы можем использовать наш PetClassifier
для обновления метки при нажатии на кнопку «Evaluate».
class ViewController: UIViewController { var classifier: PetClassifier? @IBOutlet var answerLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() classifier = PetClassifier(named: "dogvscat", labels: ["Cat", "Dog"]) } @IBAction func handleTap() { answerLabel.text = classifier?.labelForImage(selectedImage) ?? "Potato" } }
Наконец, загрузка модели довольно проста. Нам просто нужно инстанцировать Interpreter
, указав путь к нашей tflite-модели.
class PetClassifier { let interpreter: Interpreter let labels: [String] init?(named: String, labels: [String]) { guard let modelPath = Bundle.main.path(forResource: named, ofType: "tflite") else { return nil } do { var options = Interpreter.Options() options.threadCount = Self.threadCount interpreter = try Interpreter(modelPath: modelPath, options: options) self.labels = labels } catch { print(error) return nil } } // ... }
Вызов модели
Получение ответа от модели состоит из 4 шагов:
- Подготовить входные данные
- Отправить их
- Прочитать вывод
- Разобрать вывод
/* * 1. Prepare input */ // user provided image let image: UIImage // image size used for training model let inputWidth = 224 let inputHeight = 224 // convert image to pixel buffer for further manipulation let pixelBuffer = ImageUtils.pixelBufferCreate(image: image) // crop image to size used for training model let scaledPixelBuffer = ImageUtils.pixelBufferCreateWith( pixelBuffer: pixelBuffer, resizedTo: CGSize(width: Self.inputWidth, height: Self.inputHeight) ) // Remove the alpha component from the image buffer to get the RGB data. let rgbData = ImageUtils.pixelBufferCreateRGBData( pixelBuffer: scaledPixelBuffer, byteCount: Self.inputWidth * Self.inputHeight * 3 ) /* * 2. Send input data */ interpreter.allocateTensors() interpreter.copy(rgbData, toInputAt: 0) interpreter.invoke() /* * 3. Read output data */ let outputTensor = try interpreter.output(at: 0) let results: [Float] = outputTensor.data.withUnsafeBytes { Array($0.bindMemory(to: Float.self)) } /* * 4. Parse output */ // Create a zipped array of tuples [(labelIndex: Int, confidence: Float)]. // Sort the zipped results by confidence value let inferences = zip(labels.indices, results) .sorted { $0.1 > $1.1 } .map { (label: labels[$0.0], confidence: $0.1) } let bestInference = inferences.first
Вот и все.
Вот как можно переложить такую ментальную задачу на компьютер.
ImageUtils
из этого эксперимента можно найти здесь. Но, вероятно, существуют и лучшие библиотеки для этих операций. Например, CoreMLHelpers или сам Core ML.