Пройдя недавно курс по машинному обучению, я отправился на поиски готовых библиотек — в первую очередь — для платформы .NET, так как я именно ее использую на своей работе. Запрос в гугл «Machine Learning + .NET» немедленно дал ссылку на фреймворк ML.NET, который сейчас имеет статус «preview». Здесь вкратце опишу, как я попробовал его использовать для классификации изображений.
Создание модели
ML.NET, насколько я успел понять, одной из своих целей имеет обеспечение очень высокого уровня абстракции для неискушенных в машинном обучении пользователей. Т. е. программист может не вникать в практическую реализацию и иметь лишь шапочное знакомство с теорией. В видеоуроках для начинающих излагается типовой сценарий применения ML.NET. Вы устанавливаете расширение для Visual Studio под названием ML .NET Model Builder (Preview). Это расширение добавляет в контекстное меню значка проекта в окне [Обозреватель Решений] пункт [Добавить] > [Machine Learning]. Вы создаете проект приложения (.NET Framework или .NET Core) — например, свой проект я назвал ImageBinaryClassification. Далее вы щелкаете правой кнопкой мыши на значке проекта в Обозревателе Решений и выбираете [Добавить] > [Machine Learning]. Открывается wizard, в котором вы выбираете одну из типовых задач машинного обучения:
- бинарная классификация или мультиклассификация данных (данные должны быть в формате CSV или TSV)
- линейная регрессия (данные так же — в CSV или TSV)
- бинарная классификация или мультиклассификация изображений (jpeg, png и что-то еще)
- recommender system
Других встроенных сценариев пока нет. Заметим, что большинство сценариев это — supervised learning, а unsupervised только один — «recommender system».
Что касается классификации изображений, то вы должны указать папку, в которой будут находиться тренировочные изображения. Изображения должны быть рассортированы по подпапкам, каждая из которых соответствует отдельному классу нашей классификации. Впрочем, все это написано в самом wizard’е. К примеру, у меня структура папок была такая:
├── True
└── False
В папку True я поместил изображения, на которых есть искомый объект, а в папку False, соответственно, изображения, на которых искомого объекта нет. Далее Model Builder сам тестирует различные алгоритмы/модели машинного обучения, подходящие к случаю, и выбирает тот, который дает наибольшую точность. Точность различных алгоритмов/моделей измеряется на наборе cross validation set, который изыскивается из тех данных (в моем случае — изображений), которые выше я назвал «тренировочными».
Далее Model Builder сгенерировал в моем решении два проекта:
- ImageBinaryClassificationML.ConsoleApp — это приложение, которое призвано делать то же, что Model Builder — обучать модель, используя ваши тренировочные данные, и сохранять ее в виде ZIP-архива MLModel.zip.
- ImageBinaryClassificationML.Model — это простенькая библиотека, которая содержит код для использования модели (модель, напоминаю, находится в ZIP-архиве, созданном Model Builder’ом). В этой библиотеке имеется три очень простых класса: ConsumeModel, ModelInput и ModelOutput.
Помимо двух указанных проектов Model Builder сгенерировал ZIP-архив MLModel.zip, в котором, очевидно, сохраняются параметры нашей уже обученной модели. Этот файл затем понадобится непосредственно при использовании модели, в моем случае — для классификации изображений. Model Builder поместил этот ZIP-архив в проект ImageBinaryClassificationML.Model.
Стоит обратить внимание на класс ModelInput. Он содержит то, что называется data sample:
{
[ColumnName("Label"), LoadColumn(0)]
public bool Label { get; set; }
[ColumnName("ImageSource"), LoadColumn(1)]
public string ImageSource { get; set; }
}
Свойства класса помечены атрибутами, которые между прочим, имеют непосредственное отношение к TSV-файлу, содержащему data set (см. ниже)
Использование модели в своем приложении.
Итак, коль скоро есть обученная модель (параметры которой находятся в ModelML.zip) и сгенерирован API для ее использования (класс ConsumeModel в проекте ImageBinaryClassificationML.Model) я решил создать консольное приложение и попробовать классифицировать какое-нибудь изображение. Выше я уже создал проект ImageBinaryClassification. Теперь я добавил в него ссылку на проект ImageBinaryClassificationML.Model и написал простейший код вроде следующего:
ModelOutput prediction = ConsumeModel.Predict(input);
Console.WriteLine(prediction.Prediction);
Подводный камень 1. Зависимости. Пакеты Nuget
Проект не компилировался и требовал сослаться на сборку Microsoft.ML.Data. Я пошел в меню [Ссылки] > [Управление пакетами Nuget] и стал искать там одноименный пакет. Нашел в итоге пакет Microsoft.ML, который и установил, и стал пытаться строить проект дальше.
Подводный камень 2. Допустима только конфигурация сборки x64
Однако при очередной попытке построения проекта, я получил ошибку:
Please ensure your application is targeting 'x64' or 'x86'
Пришлось сделать [Свойства Проекта] > [Сборка] > [Целевая платформа]: x86. Программы для x86 я предпочитаю при прочих равных потому, что они работают на большем числе компьютеров и операционных систем. Проект успешно скомпилировался и я его запустил.
Однако при запуске вылезло исключение FileNotFoundException: Не удалось загрузить файл или сборку "Microsoft.ML.ImageAnalytics"
Установил Nuget package Microsoft.ML.ImageAnalytics.
Далее при запуске возникла ошибка FileNotFoundException: Не удалось загрузить файл или сборку "Microsoft.ML.Vision"
Установил пакет Microsoft.ML.Vision.
Теперь при запуске возникла ошибка другого сорта: DllNotFoundException: Не удается загрузить DLL "tensorflow": Не найден указанный модуль
. Это уже было похоже на то, что не удается найти нативную dll-библиотеку, а не .NET’овскую сборку. Эту библиотеку я нашел в Nuget-пакете SciSharp.TensorFlow.Redist, но копировать её в папку с программой пришлось вручную. А кроме того, оказалось что библиотека tensorflow.dll существует только в 64-разрядной версии (я нашел ее в папке packages\SciSharp.TensorFlow.Redist.2.1.0\runtimes\win-x64\native
). Пришлось поменять целевую платформу проекта на x64 (иначе ошибка DllNotFoundException, понятное дело, не исчезала бы).
И наконец программа заработала! И успешно классифицировала мое тестовое изображение.
Подводный камень 3. Model Builder генерирует и использует временные файлы
Далее передо мной встала задача научиться пользоваться программой ImageBinaryClassificationML.ConsoleApp (которую сгенерировал Model Builder). В исходном коде этой программы был по сути только один класс под названием ModelBuilder. Класс был большой (порядка двухсот строк кода) — я даже не стал вникать в его код. Но обратил внимание на то, что в нем было два статических поля:
{
private static string TRAIN_DATA_FILEPATH
= @"C:\Users\Дмитрий\AppData\Local\Temp\dcf6abce-ed1c-4bda-a236-4fc9d770099b.tsv";
private static string MODEL_FILEPATH
= @"C:\Users\Дмитрий\AppData\Local\Temp\MLVSTools\ImageBinaryClassificationML\ImageBinaryClassificationML.Model\MLModel.zip";
public static void CreateModel() { ... }
. . .
}
Поля содержали пути к временным файлам, сгенерированным в папке Temp на моем компьютере. Один из них, TSV — содержал данные следующего вида:
False \TrainingSet\False\image1.jpg
False \TrainingSet\False\image2.jpg
False \TrainingSet\False\image3.jpg
True \TrainingSet\True\image4.jpg
True \TrainingSet\True\image5.jpg
True \TrainingSet\True\image6.jpg
. . .
Второй файл — MLModel.zip — это параметры нашей модели, т. е. точно такой же архив, что и тот, который, как я уже говорил находился непосредственно в проекте ImageBinaryClassificationML.Model.
Итак, поскольку в полях были прописаны пути к временным файлам, то если бы я стал запускать данную программу на другом компьютере, то она бы просто не нашла нужных файлов. Понял я, что придется в этот сгенерированный код влезть и кое-что там подправить. Что я и сделал, и поместил весь код в открытый репозиторий. Я также написал утилиту ImagePreprocessing, которая подготавливает изображения для последующего обучения: конвертирует bmp в jpg (ML.NET не переваривает bmp-файлы), понижает разрешение изображений, делает их монохромными а также создает TSV-файл. Обо всем этом я надеюсь написать следующую заметку, так что продолжение следует.