Ищем Арнольда Шварценеггера среди мужчин, женщин и детей с помощью нейросети на С++ |
||
МЕНЮ Главная страница Поиск Регистрация на сайте Помощь проекту Архив новостей ТЕМЫ Новости ИИ Голосовой помощник Разработка ИИГородские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Искусственный интеллект Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Психология ИИ Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Творчество ИИ Техническое зрение Чат-боты Авторизация |
2024-03-22 16:37 Привет, Хабр! Меня зовут Кирилл Колодяжный, я ведущий инженер-программист в YADRO. Помимо основных рабочих задач, включающих исследование проблем производительности СХД, я увлекаюсь машинным обучением. Участвовал в коммерческих проектах, связанных с техническим зрением, 3D-сканерами и обработкой фотографий. В задачах часто использовал С++, хотя машинное обучение традиционно ассоциируется с Python. Этот язык программирования буквально захватил сферу, его используют повсюду — от обучающих курсов до серьезных ML-проектов. Однако Python — не единственный язык, на котором можно решать задачи машинного обучения. Так, альтернативой может стать С++. Если последний вам ближе, вам будет интересен и полезен этот текст. Под катом разберемся:
Где в машинном обучении применяется С++ Реализация базовых вычислительных алгоритмов C++ часто используется в машинном обучении для реализации сложных алгоритмов. Он широко применяется в бэкенде популярных фреймворков, таких как PyTorch, TensorFlow и scikit-learn. Они, в свою очередь, основаны на математических библиотеках, реализующих стандарт BLAS — например, OpenBLAS или cuBLAS. Это позволяет строить высокоуровневые математические библиотеки, к которым относятся Eigen, Armadillo и ATen. NumPy, популярная библиотека Python для работы с математическими операциями, также использует C++ для реализации внутренних функций, обеспечивая быструю и эффективную работу с тензорами и матрицами. Кастомные функции для фреймворков PyTorch и TensorFlow предоставляют специальный API для расширения функционала. Допустим, вам нужно реализовать тензорную операцию, которая не представлена в фреймворке и у вас есть достаточно удобный механизм для ее реализации на C++. Почему на C++? Потому что, скорее всего, вам потребуется использовать CUDA или OpenCL для эффективной реализации, а они тоже используются в связке с С++. Машинное обучение на конечных устройствах Наиболее массово C++ для машинного обучения применяется, когда готовый продукт деплоится, например, на устройство видеоаналитики. Чаще всего там нет смысла разворачивать Python-среду и ставить интерпретатор — достаточно выполнить алгоритм в режиме вывода (inference). Поэтому существует достаточно большой спектр фреймворков, заточенных на адаптацию под микроконтроллеры и embedded-платформы. Допустим, есть проект OpenVINO. Он специализируется на деплое машинного обучения на Intel-платформы. TensorRT — это платформа для конвертации нейронных сетей от NVIDIA, достаточно массово используются на платформах типа Jetson, с архитектурами Xavier или Orin. PyTorch и TensorFlow — популярные фреймворки — тоже предоставляют свои механизмы для запуска этих алгоритмов в рамках C++ на устройствах. Это TorchScript, torch.jit, и TensorFlow Lite, tflite micro.
Решаем задачу поиска лиц с помощью С++ и его библиотек Мы поговорим о том, как использовать C++ на самом высоком уровне и решить задачи, где обычно используют Python или технологии Julia и R. Задачу по поиску лиц можно разделить на два шага:
Этап 1. Ищем регионы с лицами людей В поиске лица на фото с использованием метода скользящего окна есть нюанс: лица на фотографии могут быть разного масштаба, а у окна — фиксированный размер. Чтобы найти нужное лицо, мы построим пирамиду масштабируемых изображений: изменим размеры исходного фото, чтобы маленькие лица стали больше и попадали в фиксированное скользящее окно. Чтобы определить, является регион лицом или нет, опишем его математическим представлением (вектором). Для этого используем гистограмму направленных градиентов. Далее эти векторы можно будет разделить на два класса: лицо и не лицо. Рассмотрим на примере, что такое пирамида масштабированных изображений. На следующей фотографии есть несколько людей. Лицо на переднем плане достаточно большое, оно попадет в скользящее окно. На заднем плане лица маленькие — мы не сможем корректно описать и классифицировать их. Для решения проблемы построим несколько масштабов входного изображения. Чтобы описать регион на фотографии для классификации, сделаем фото черно-белым — теперь каждый пиксель содержит только интенсивность цвета. Скользящее окно нужно разбить на прямоугольные подобласти и вычислить в них направления изменений от светлого к темному. Таких направлений может быть много. Мы ограничимся определенными: вверх, вниз, вправо, влево и по диагонали. Из полученных направлений сформируем гистограмму по частоте встречаемости. Эта гистограмма представляет собой одномерный вектор чисел, который мы сможем использовать для тренировки классификатора и оценивать, находится в этом регионе изображения лицо или нет. Библиотека Dlib Загрузка изображений Для решения задачи нужна библиотека с ограниченным набором функций, простой установкой и без лишних зависимостей. Будем использовать Dlib — известную библиотеку, которая предоставляет алгоритмы для компьютерного зрения и машинного обучения. Она легковесная, собирается и импортируется проще, чем ее аналоги — например, OpenCV. Для использования детекции лиц на фотографии достаточно использовать два заголовочных файла. Это Изображения могут быть представлены двумя типами данных: двумерным массивом или матрицей. Для работы с цветными изображениями или с изображениями в оттенках серого нам достаточно специализировать шаблон Также нам потребуется матрица. Это математический примитив, класс которого может специализироваться нужным типом данных. Мы используем float. Тип Для загрузки изображений нам достаточно использовать функцию Пирамида масштабированных изображений Следующий этап — построение пирамиды масштабированных изображений с помощью функции Существует и функция Детектор лиц Для детекции лиц мы создаем объект типа У формата есть перегруженный оператор функции, который принимает на вход изображение. Результатом является контейнер, который содержит прямоугольники — регионы с лицами. Использование Постобработка регионов с лицами С помощью функции Если мы передадим двумерный массив, получим матрицу. А с помощью функции Отложенные вычисления в C++ Зачастую в математических библиотеках для C++ и фреймворках машинного обучения некоторые операции, например, обрезка матрицы или умножение матрицы, не выполняются сразу. Вместо этого возвращается объект, описывающий данную операцию. Этот подход называется отложенным вычислением (lazy calculation). Результат операции вычисляется только тогда, когда он действительно нужен. Это позволяет библиотеке оптимизировать вычисления или вообще их не проводить, если результат не используется. Если требуется получить результат операции сразу, необходимо указать тип объекта результата, например, После того как мы вырезали прямоугольники с лицами, нужно привести их к стандартному размеру и формату. Для этого используем функцию Далее нам нужно преобразовать целочисленные значения пикселей изображения в вещественные. Сначала используем функцию Эти действия важны для следующего этапа — обучения нейронной сети. Объединение всех частей кода для детекции лиц Этих функций достаточно для реализации нашего первого этапа, который включает загрузку изображения, нахождение в нем лиц, получение изображений лиц, приведение их к нужному масштабу и формату. Код получится компактным и выразительным, похожим на аналоги на Python. Этап 2. Сравниваем регион с целевым лицом Сиамские сети Один из самых распространенных способов обучить нейронную сеть поиску похожих объектов — сиамские сети, тренированные с разными целевыми функциями, например, с функциями потерь Contrastive Loss или Triplet Loss. Такой подход также называют «изучением метрик» (metric learning), потому что сравнивать можно не только изображения, но и объекты из разных предметных областей. На входы подаются изображения разных людей. Наша задача — обучить нейронную сеть так, чтобы она выдавала векторы, схожие для одного человека и разные для нескольких людей. Подход к тренировке будет отличаться в зависимости от выбранной функции потерь. Contrastive Loss Функция потерь Contrastive Loss основана на том, что мы максимизируем расстояние между разными классами векторов и минимизируем между одинаковыми. В формуле ниже d — это расстояние, а y принимает значения 0 или 1 в зависимости от того, одинаковые изображения или нет. Здесь d вычисляется как квадрат расстояния, что соответствует минимизации евклидового расстояния, описывающего два объекта. Triplet Loss Подход с другой целевой функцией Triplet Loss чем-то похож: мы снова будем вычислять расстояние. У нас есть три сети с одной архитектурой и весами, через них проходит три изображения: якорное и два примера — позитивный и негативный. Для примеров мы получаем три характеристических вектора (embbedings vectors), которые поступают на вход функции Triplet Loss. Здесь вычисляется два евклидовых расстояния между якорным изображением, позитивным и негативным примерами. Функция должна обучить сеть так, чтобы расстояние между якорным и позитивным примером было минимальное, а между якорным и негативным — максимальное. Датасет Для обучения нейронных сетей нужны тренировочные данные — датасет. Мы используем доступный датасет Facial Recognition Dataset collected from Pinterest. Это набор из 105 фотографий знаменитостей с приблизительно одинаковым масштабом, но разным ракурсом и освещением. Обратим внимание, что наш модуль для определения лиц реализован только для лиц, снятых в фас. Значит, на некоторых фото найти лица не получится. Это приведет к несбалансированности датасета, и в последствии может стать одной из причин низкого качества сравнения лиц. В реальных задачах уделяйте достаточно внимания подготовке тренировочных и проверочных данных. Применение библиотеки PyTorch для работы с тренировочными данными Для реализации нашей задумки воспользуемся библиотекой PyTorch, которая представляет собой набор инструментов для машинного обучения. Важно отметить, что для C++ также существует LibTorch — библиотека, которая предоставляет С++ интерфейс для ядра PyTorch. LibTorch включает практически всю функциональность Python-версии и имеет схожий синтаксис. Если вы уже работали с PyTorch на Python, освоить LibTorch на C++ будет достаточно просто. Для начала работы с LibTorch нам достаточно подключить один заголовочный файл, вся функциональность помещена в пространство имен torch. Начнем с построения конвейера обучения (training pipeline) нейронной сети. Первый шаг — работа с данными:
Для этого достаточно унаследоваться от класса Dataset и реализовать два метода:
Интерфейс для работы с тренировочными данными Мы уже знаем, как загрузить изображение с помощью библиотеки Dlib и функции В приведенном примере используется функция Чтобы выделить память под наш тензор и скопировать данные, мы вызываем метод Затем нужно реализовать проход по директории с фотографиями, их загрузку и преобразование в тензоры. А потом — доступ по индексу к каждому тензору, который представляет лицо в методе Чтобы использовать Triplet Loss в нашей функции обучения, необходимо объединить три изображения лиц в один тензор. Обычно объект Здесь обратим внимание, как мы используем библиотеку PyTorch для реализации эффективного обучения нейронных сетей. Мы будем работать не с одним экземпляром обучаемых данных, а с набором (batch) — например, из 8, 32, 16 экземпляров. Это необходимо для эффективной утилизации вычислительных ресурсов и эффективной передачи данных из «оперативки» в память видеокарты. Поэтому PyTorch предоставляет для объектов типа В данном случае в методе map используется объект типа Stack — он обозначает, что батч наших данных будет объединен в один объект типа Объекты типа Dataset используются совместно с объектами типа Каким образом создается объект Дальше функция принимает на вход объект датасета и дополнительные опции для работы с ним. Этими опциями выступают После того как мы создали датасет и завернули его в
Если вспомним, когда мы реализовывали функцию Архитектура нейронной сети После того, как мы научили программу работать с данными, загружать их, предоставлять в формате, необходимом для тренировки, мы можем описать архитектуру нейронной сети для решения задачи. Для примера реализуем достаточно простой подход: три сверточных слоя, за которыми следует три линейных полносвязных слоя. Чтобы реализовать какой-то элемент нейронной сети или другого алгоритма машинного обучения в фреймворке библиотеки PyTorch, необходимо унаследоваться от класса Здесь важно использовать макрос Сверточные и полносвязные слои Мы разделим нейронную сеть на сверточные и полносвязные (линейные) слои и объединим их в контейнер Sequential. Sequential реализует набор методов, похожий на вектор C++, у него есть векторы Сверточные слои Мы добавляем в контейнер объект типа Регистрация Sequential-контейнера с использованием метода Полносвязные слои Аналогично для линейных полносвязных слоев, мы также помещаем их в Sequential-контейнер с помощью методов Хотя все это можно было сделать в одном контейнере Sequential, мы их разделили для наглядности. Создав слои, из которых состоит нейронная сеть, мы можем реализовать метод В этом методе реализован прямой проход сети: мы программируем то, как входные данные проходят через слои в нужном нам порядке. Переход от сверточных слоев к полносвязным требует развертывания 3D-тензора в одномерный. Это достигается операцией И этот метод возвращает уже преобразованные данные, в нашем случае — вектор, который характеризует входное изображение. Создание конвейера для обучения нейронной сети Описав структуру нейронной сети, мы можем создать конвейер для ее обучения. Для этого сначала надо создать объект модели: Так как класс модели унаследован от класса Теперь описываем нашу целевую функцию (функцию потерь) Тренировка состоит из двух циклов: первый проходит по эпохам, второй, «вложенный», — по батчам. Прохождение одной эпохи означает прохождение по всему датасету. Что включает в себя цикл:
После такой тренировки с помощью функции Чтобы пропустить эти изображения через нейронную сеть, мы вызываем метод После этого достаточно вызвать метод Пример использования нейронной сети Представим, что у нас есть метод Как использовать эту функцию:
Если значение расстояния меньше порогового значения, мы считаем, что лица похожи. При использовании моделей важно учитывать их состояния. Например, модель может находиться в состоянии тренировки (train), когда мы обучаем ее, или в состоянии вывода (inference или evaluation), которое мы получаем с помощью вызова метода eval. Когда мы переводим модель в состояние eval, необходимо также указать PyTorch, что мы не хотим использовать автоградиент. Для этого создается объект типа Функция Дальше мы преобразуем объект матрицы в формате PyTorch с помощью метода Результаты обучения нейронной сети Мы сравнивали лицо Арнольда Шварценеггера с лицами людей на фото. На картинке видно, что значение расстояния на мужских лицах находятся в одном диапазоне, а на женских и детских — в другом. Это значит, что нейросеть не научилась определять целевое лицо, но смогла отличить мужчин от женщин и детей. Большую задачу, которую мы ставили в начале статьи — научить нейросеть искать лицо Арнольда на фото, мы не решили. Это связано с тем, что мы использовали маленький датасет, у нас была небольшая и простая по архитектуре нейросеть, мы не использовали специальные твики для обучения. Мы рассмотрели общий подход к использованию библиотеки PyTorch, в частности С++ API, реализованный в LibTorch. Мы создали нейронную сеть и настроили конвейер обучения. Этот подход можно применять не только для простых моделей, но и для более сложных и масштабных решений. В следующем блоке расскажу, как решить такую же задачу и получить более выразительный результат — нам понадобятся предтренированные сети. Та же задача, но другое решение: используем предтренированную нейронную сеть Описание сети Чтобы решить задачу поиска лиц, мы можем использовать нейросети, ранее обученные другими компаниями или инженерами. Мы воспользуемся сетью Inception Resnet (V1) — это большая сеть, натренированная на большем объеме данных, который называется VGGFace2. У нее другая архитектура, она намного сложнее ранее использованной сети. Убедитесь сами: Как использовать предтренированную сеть Для использования этой нейронной сети сначала нам необходимо обратиться к Python. Мы экспортируем сеть в формат, доступный для использования в C++. Для этого можем создать объект типа InceptionResnetV1 в Python, передавая в конструктор дополнительные параметры, такие как имя датасета, на котором сеть была обучена. Библиотека предоставляет различные варианты инициализации сети в зависимости от использованного датасета. Мы также вызываем метод eval, чтобы переключить сеть в режим вывода, а затем создаем произвольный тензор, который будет использоваться как пример входных данных, как мы видели ранее в C++. Тут важно только сохранить размерность. В коде мы видим 1, 3, 100, 100 — это значит, что создаются три канала размером 100х100 пикселей. Для экспорта воспользуемся трейсингом для нейронных сетей. Это механизм, который реализован в PyTorch. Передав на вход сети этот пример входных данных, механизм трейсинга последовательно сохранит все вызванные операции, которые происходили в нейронной сети: свертки, функции активации и так далее. Результат трейса можно сохранить в файл, который можно будет загрузить как в C++, так и в Python. Загрузка в C++ делается достаточно просто. Функция load, которую предоставляет нам заголовочный файл Как его дальше использовать? Загрузив этот модуль, нам необходимо подать ему на вход наше изображение, чтобы получить характеристический вектор для сравнения. Рассмотрим реализацию функции Функцию Загруженная сеть обучалась на цветных изображениях. При анализе примера для трассировки мы создавали случайный тензор с тремя каналами — формат С помощью функции После получения RGB-тензора Результаты использования предтренированной сети Применив уже тренированную глубокую нейронную сеть, мы увидим другие результаты. На этой фотографии мы видим, что сравнив Арнольда с Арнольдом, мы получаем значение порядка 0,6, для других двух лиц это значение уже больше единицы. На следующей фотографии мы видим, что при сравнении характеристических векторов для людей, не похожих на Арнольда, мы получаем значение порядка единицы или выше. Вот этот подход уже можно использовать для сравнения лиц. Задав порог в 0,7, сеть может отличить Арнольда от лиц других людей. Писать или не писать нейронную сеть на С++ Мое мнение — конечно, писать. В первую очередь я думаю о командах, которые хотят решать задачи, связанные с машинным обучением, но не имеют в штате Python-разработчика. Для них есть альтернатива. Еще мой способ может быть полезным для тех, кто уже написал код работы с данными на С++ и хочет использовать его в реализации или обучении нейросети — таким инженерам не придется переписывать код на Python или писать обертки. Неважно, на каком языке решается задача — она в любом случае довольно сложная. Инженеры до сих пор проводят исследования и ищут лучшее решение для тренировки нейронных сетей и решения задач по поиску лиц. Кто знает — возможно, это будет С++.
Источник: habr.com Комментарии: |
|