Метод тестирования Core ML моделей

На сайте https://developer.apple.com/machine-learning/build-run-models/ опубликован перечень Core ML моделей, готовых к применению в приложениях пользователей. Но какая модель лучше подходит для конкретной задачи? Неплохо бы предварительно протестировать различные модели, например, на задаче классификации неподвижных изображений. Выбирая модель и проверяя ее на тестовых множествах, можно получить оптимальный результат. Да, но тестовые множества могут содержать сотни и тысячи фотографий. Похоже, что нужна программа, которая позволит выбрать модель, альбом с фотографиями, а затем автоматически выполнит классификацию и группировку по принадлежности каждой фотографии в альбоме, сформирует результат в виде таблицы. Ниже предлагается вариант такой программы.
Программа разработана на аппаратных и программных средствах компании Apple. Язык программирования – Swift 4. Операционная система на устройстве тестирования - IOS 11.2. При разработке программы использовались модифицированные фрагменты текста из примеров "SamplePhotosApp" и "Vision + ML Example" компании Apple. Эти примеры позволили значительно сократить время разработки. Спасибо авторам.
Идея программы и реализация принадлежит автору статьи.
1. Работа с программой.
Снимки для статьи подготовлены при работе программы на симуляторе.
1.1. Пользователь может выбрать любую из предустановленных в программе моделей: Inceptionv3, MobileNet, SequeezeNet, Resnet50, GoogLeNetPlaces. Выглядит это так:

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

1.2. Затем необходимо выбрать альбом с фотографиями, в котором находится тестовое множество.

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

1.3. Пользователь может открыть содержимое любого альбома и просмотреть фотографии.

Кнопка Flow предназначена для запуска потоковой классификации фотографий в альбоме. Нажатие на Image запустит процесс только для выбранной ячейки.

1.4. При нажатии на кнопку Flow программа в автоматическом режиме последовательно классифицирует каждую фотографии из указанного альбома и отправит ее Asset в группу по принадлежности. Вот так выглядит экран после окончания классификации последней фотографии

Кнопка List Result предназначена для вывода на экран результатов классификации и группировки.

1.5. При нажатии на кнопку List Result пользователь получит на экране устройства таблицу с именами групп распознанных объектов и количеством фотографий в каждой группе.


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

2. Результат классификации.
Результат работы программы представлен в виде таблицы. При анализе каждой группы выявляются ошибки классификации. Соотнося общее количество элементов в тестовом множестве и число ошибок, можно получить численные характеристики эффективности той или иной модели.
3. Организация цикла просмотра.
Определим транзакцию так: загрузка фотографии + классификация + группировка. Транзакция требует значительной вычислительной работы. Например, на устройстве IPhone SE с моделью MobileNet для альбома с 200 фотографиями потребуется около 15 секунд, чтобы выполнить задачу. С моделью Inceptionv3 потребуется уже почти в два раза больше времени. Естественно, что возникает вопрос, как лучше организовать такую работу. Простое решение организовать цикл перебора в DispatchQueue.main.async приведет к тому, что устройство перестанет отвечать на действия пользователя до завершения задачи. Если перенести цикл перебора в DispatchQueue.global(qos: .userInitiated).async , то на альбомах, содержащих небольшое количество фотографий задача успешно выполняется, а интерфейс программы откликается.
Однако, если запустить задачу на большом количестве фотографий, она потерпит крах. В чем причина? Анализ ситуации указывает на бесконтрольное нарастание потоков вплоть до исчерпания ресурсов системы, транзакции, запущенные на выполнение не успевают выполняться до запуска новых. С учетом указанных причин в программе применен метод семафоров – новая транзакция не запускается до завершения предыдущей. Каждая транзакция использует как фоновый так и главный потоки.
Другой вариант организации перебора фотографий в альбоме можно реализовать с помощью таймера. Этот вариант проверен - он нормально работает. Таймер работает по прерыванию и снимает часть нагрузки c процессора.
4. Группировка классифицированных изображений.
Переменная типа:
var assetDictonary = [String : [PHAsseet!]]
используется в качестве основной структуры для накопления и группировки распознанных объектов. В качестве ключа в этом словаре используется первое имя классифицированного объекта. Процент вероятности и второе имя в этой версии программы не учитываются. В качестве значения для ключа принимается массив типа [PHAsset], в котором хранятся Asset фотографий.
Принцип работы со словарем таков: каждой классифицированной фотографии ставится в соответствие имя (key) изображенного объекта, затем проверяется, есть ли такое имя в словаре. Если имени нет – добавляется новая строка:
self.assetDictonary[key] = [self.asset]
Если такое имя уже есть, то к существующему ключу в качестве значения в массив добавляется Asset фотографии:
self.assetDictonary[key]!.append(self.asset)
Таким образом, по окончании цикла перебора выбранного пользователем альбома, мы получим заполненный словарь, который, затем выводится на экран пользователя в классе ResultClassificationTable.
5. Свойство lazy var request: VNCoreMLRequest.
На первый взгляд непонятно, почему данное свойство в примере Apple объявлено как lazy. Я переписал это свойство в виде функции func request() -> VNCoreMLRequest { …}. Запустил и протестировал программу в режиме Flow. Посмотрел и вернул все обратно. Что оказалось не так? Дело в том, что время работы программы увеличилось почти в два раза - вот вам и lazy!

Евгений Вересов.
27.03.2018 года.