Машинное обучение наконец-то становится доступным

Несколько месяцев назад мне пришла в голову идея дать моей машине возможность обнаруживать и распознавать объекты. Мне в основном понравилась эта идея, потому что я видел, на что способны Tesla, и хотя я не хотел сразу покупать Tesla (Модель 3 с каждым днем ​​выглядит все сочнее, я должен сказать) , Я подумал, что попробую пойти навстречу своей мечте.

Итак, я сделал это.

Ниже я задокументировал каждый этап проекта. Если вы просто хотите увидеть видео детектора в действии / ссылку на GitHub, пропустите его.

Шаг 1. Определение объема проекта

Я начал с размышлений о том, на что должна быть способна такая система. Если что-то, чему я научился в течение своей жизни, всегда лучше начинать с малого: маленькие шажки. Итак, помимо очевидной задачи удержания полосы движения (которую все уже выполнили), я подумал о том, чтобы просто идентифицировать номерные знаки, когда я вел машину. Этот процесс идентификации включает 2 этапа:

  1. Обнаружение автомобильных номеров.
  2. Распознавание текста в рамке каждого номерного знака.

Я подумал, что если я смогу это сделать, то переход к другим задачам должен быть довольно легким (например, определение рисков столкновения, расстояния и т. Д.). Может быть, даже создать представление окружающей среды в векторном пространстве - это было бы круто.

Прежде чем беспокоиться о деталях, я знаю, что мне нужно:

  • Модель машинного обучения, которая принимает в качестве входных данных немаркированные изображения и определяет номерные знаки.
  • Какое-то железо. Грубо говоря, мне нужна компьютерная система, связанная с одной или несколькими видеокамерами, которые запрашивают мою модель.

Для начала я занялся построением правильной модели для обнаружения объектов.

Шаг 2. Выбор подходящей модели

После тщательного исследования я решил использовать следующие модели машинного обучения:

  1. YOLOv3. Это самая быстрая модель на сегодняшний день, которая mAP сопоставима с другими современными моделями. Эта модель используется для обнаружения объектов.
  2. CRAFT Детектор текста - для обнаружения текста в изображениях.
  3. CRNN. По сути, это повторяющаяся модель CNN (сверточная нейронная сеть). Он должен быть повторяющимся, потому что он должен иметь возможность размещать обнаруженные символы в правильном порядке для образования слов.

Как это трио моделей будет работать вместе? Итак, вот последовательность операций:

  1. Во-первых, модель YOLOv3 определяет ограничивающие рамки каждого номерного знака в каждом кадре, полученном с камеры. Предсказанные ограничивающие рамки не рекомендуется быть очень точными - охватывать больше, чем занимает обнаруженный объект, является хорошей идеей. Если он будет слишком тесным, то выполнение последующих процессов может быть затруднено. И это идет рука об руку со следующей моделью.
  2. Детектор текста CRAFT получает обрезанные номерные знаки от YOLOv3. Теперь, если кадрированные кадры будут слишком тесными, то будет очень высока вероятность того, что часть текста номерного знака будет пропущена, и тогда прогноз будет с треском провал. Но когда ограничивающие рамки больше, мы можем просто позволить модели CRAFT определять, где находятся буквы. Это дает нам очень точное расположение каждой буквы.
  3. Наконец, мы можем передать ограничивающие прямоугольники каждого слова из CRAFT в нашу модель CRNN, чтобы предсказать фактические слова.

Набросав архитектуру базовой модели, я мог перейти к аппаратному обеспечению.

Шаг 3. Проектирование оборудования

Зная, что мне нужно что-то маломощное, я подумал о своей давней любви: Raspberry Pi. У него достаточно вычислительной мощности для предварительной обработки кадров с приличной частотой кадров, и у него есть камера Pi. Камера Pi - это де-факто система камер для Raspberry Pi. У него действительно отличная библиотека, и она очень зрелая.

Что касается доступа в Интернет, я мог бы просто добавить EC25-E для доступа 4G, в который также встроен модуль GPS из одного из моих предыдущих проектов. Вот статья об этом щите.

Решил начать с корпуса. Повесить его на зеркало заднего вида должно хорошо, поэтому в итоге я разработал двухкомпонентную опорную конструкцию:

  1. На одной стороне зеркала заднего вида останется модуль Raspberry Pi + GPS + модуль 4G. Ознакомьтесь с моей статьей о модуле EC25-E, чтобы увидеть мой выбор антенн GPS и 4G.
  2. С другой стороны, у меня была бы камера Pi, поддерживаемая через руку с шаровым шарниром для ориентации.

Эти опоры / корпуса будут напечатаны на моем надежном 3D-принтере Prusa i3 MK3S.

Рис. 1 и Рис. 2 показывают, как выглядят структуры при визуализации. Обратите внимание, что держатели C-Style являются съемными, поэтому корпус для Raspberry Pi и опора для камеры Pi не поставляются с уже напечатанными держателями. У них есть розетка, в которую вставляются держатели. Это очень полезно, если один из моих читателей решит повторить проект. Им нужно только приспособить держатели для работы с зеркалом заднего вида своей машины. В настоящее время держатели хорошо работают на моей машине: это Land Rover Freelander.

Очевидно, для их моделирования потребовалось некоторое время - мне потребовалось несколько итераций, чтобы конструкция стала прочной. Я использовал PETG материал с высотой слоя 200 микрон. PETG хорошо работает в 80-90-х годах (градусы Цельсия) и довольно устойчив к УФ-излучению - хотя и не так хорошо, как ASA, но сильно.

Это было разработано в SolidWorks, поэтому все мои _5 _ / _ 6_ файлы вместе со всеми STLs и gcodes можно найти здесь. Используйте их также для печати своей версии.

Шаг 4. Обучение моделей

Когда у меня появилось оборудование, я перешел к обучению моделей.

Как и ожидалось, лучше не изобретать велосипед и как можно чаще использовать чужие работы. В этом вся суть трансферного обучения - использование информации из других очень больших наборов данных. Один очень уместный пример трансферного обучения - это тот, о котором я прочитал в этой статье пару дней назад. Где-то посередине рассказывается о команде, связанной с Гарвардской медицинской школой, которая смогла точно настроить модель для прогнозирования долгосрочной смертности, включая нераковую смерть, по рентгенограммам грудной клетки. У них был небольшой набор данных, состоящий всего из 50 000 помеченных изображений, но предварительно обученная модель (Inception-v4), которую они использовали, была обучена примерно на 14 миллионах изображений. Им потребовалось меньше части того, что требовалось для обучения первоначальной модели (как по времени, так и по деньгам), и тем не менее точность, которой они достигли, была довольно высокой.

Это то, что я намеревался сделать.

YOLOv3

Я поискал в Интернете предварительно обученные модели номерных знаков, и их оказалось не так много, как я ожидал изначально, но я нашел одну, обученную примерно на 3600 изображениях с номерными знаками. Это немного, но это также больше, чем ничего, и в дополнение к этому, он также был обучен на предварительно обученной модели Darknet. Я мог бы это использовать. Вот модель этого парня.

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

Я использовал VOTT, чтобы аннотировать собранные кадры (конечно, с номерами). В итоге я создал небольшой набор данных из 534 изображений с помеченными ограничивающими рамками для номерных знаков. Вот набор данных.

Затем я нашел эту реализацию Keras сети YOLOv3. Я использовал его для обучения своего набора данных, а затем прикрепил свою модель к этому репо, чтобы другие могли использовать его. Карта, которую я получил на тестовом наборе, составляет 90%, что действительно хорошо, учитывая, насколько мал мой набор данных.

КРАФТ И CRNN

После бесчисленных попыток найти хорошую сеть для распознавания текста я наткнулся на keras-ocr, который представляет собой упакованную и гибкую версию CRAFT и CRNN. И это также идет с их предварительно обученными моделями. Это потрясающе. Я решил не настраивать модели и оставить их как есть.

И, прежде всего, предугадывать текст с помощью keras-ocr очень просто. По сути, это всего лишь несколько строк кода. Посетите их домашнюю страницу, чтобы узнать, как это делается.

Шаг 5. Развертывание моих моделей детекторов номерных знаков

Я могу использовать два основных подхода к развертыванию модели:

  1. Выполнение всех логических выводов локально.
  2. Сделайте логический вывод в облаке.

Оба подхода имеют свои проблемы. Первый предполагает наличие большой компьютерной системы с «мозгом», а это сложно и дорого. Второй представляет проблемы, связанные с задержкой и инфраструктурой, в частности, с использованием графических процессоров для вывода.

В своем исследовании я наткнулся на проект с открытым исходным кодом под названием cortex. Это новинка для игры, но, безусловно, имеет смысл стать следующим шагом в эволюции инструментов разработки для ИИ.

По сути, cortex - это платформа для развертывания моделей машинного обучения в качестве производственных веб-сервисов одним щелчком мыши. Это означает, что я могу сосредоточиться на своем приложении и оставить остальное cortex на управление. В этом случае он выполняет всю подготовку на AWS, и единственное, что мне нужно сделать, это использовать модели шаблонов для написания моих предикторов. Еще более удивительно то, что мне нужно написать всего несколько десятков строк для каждой модели.

Вот cortex в действии в терминале, взятый из их репозитория GitHub. Если это не красиво и просто, тогда я не знаю, как это назвать:

Поскольку эта система компьютерного зрения не используется автопилотом, задержка для меня не так важна, и я могу использовать cortex для этого. Если бы он был частью системы автопилота, то использование услуг, предоставляемых облачным провайдером, не было бы такой хорошей идеей, по крайней мере, сегодня.

Развертывание моделей машинного обучения с cortex - это всего лишь вопрос:

  1. Определение файла cortex.yaml, который является файлом конфигурации для наших API. Каждый API будет обрабатывать определенный тип задачи. API yolov3, который я назначаю, предназначен для обнаружения ограничивающих рамок номерных знаков в данном кадре, а crnn API предназначен для прогнозирования номера автомобильного знака с помощью детектора текста CRAFT и CRNN .
  2. Определение предикторов каждого API. По сути, определите predictmethod определенного класса в cortex для приема полезной нагрузки (вся часть servy уже обрабатывается платформой), спрогнозируйте результат, используя полезную нагрузку, а затем верните прогноз. Просто как тот!

Не вдаваясь в подробности того, как я это сделал (и чтобы статья была приличной длины), вот пример предиктора для классического набора данных радужной оболочки глаза. Ссылку на реализацию этих двух API в Cortex можно найти в их репозитории здесь - все другие ресурсы для этого проекта представлены в конце этой статьи.

# predictor.pyimport boto3
import picklelabels = ["setosa", "versicolor", "virginica"]
class PythonPredictor:
    def __init__(self, config):
        s3 = boto3.client("s3")
        s3.download_file(config["bucket"], config["key"], "model.pkl")
        self.model = pickle.load(open("model.pkl", "rb"))    def predict(self, payload):
        measurements = [
            payload["sepal_length"],
            payload["sepal_width"],
            payload["petal_length"],
            payload["petal_width"],
        ]        label_id = self.model.predict([measurements])[0]
        return labels[label_id]

А затем, чтобы сделать прогноз, вы просто используете curl вот так

curl http://***.amazonaws.com/iris-classifier \
    -X POST -H "Content-Type: application/json" \
    -d '{"sepal_length": 5.2, "sepal_width": 3.6, "petal_length": 1.4, "petal_width": 0.3}'

И ответ с предсказанием будет выглядеть так "setosa". Очень простой!

Шаг 6. Развитие клиента

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

Я подумал о следующей архитектуре:

  1. Собирайте кадры со скоростью 30 кадров в секунду с камеры Pi с приемлемым разрешением (800x450 или 480x270) и помещайте каждый кадр в общую очередь.
  2. В отдельном процессе я извлекал кадры из очереди и распределял их среди нескольких рабочих в разных потоках.
  3. Каждый рабочий поток (или поток вывода, как я их называю) будет делать запросы API к моим cortex API. Сначала запрос к моему yolov3API, а затем, если обнаружен какой-либо номерной знак, другой запрос с партией обрезанных номерных знаков к моему crnn API. Ответ будет содержать предсказанные номера в текстовом формате.
  4. Перемещайте каждый обнаруженный номерной знак (с распознанным текстом или без него) в другую очередь, чтобы в конечном итоге транслировать его на страницу браузера. Одновременно переместите прогнозы номеров в другую очередь, чтобы сохранить их позже на диск (в формате csv).
  5. Очередь широковещательной передачи получит группу неупорядоченных кадров. Задача его потребителя - переупорядочить их, помещая их в очень маленький буфер (размером в несколько кадров) каждый раз, когда клиенту транслируется новый кадр. Этот потребитель запускает еще один процесс отдельно. Этот потребитель также должен попытаться сохранить размер очереди на указанном значении, чтобы кадры могли отображаться с постоянной частотой кадров, а именно 30 FPS. Очевидно, что если размер очереди уменьшается, то уменьшение частоты кадров пропорционально, и наоборот, оно пропорционально увеличивается с увеличением размера. Изначально я хотел реализовать функцию гистерезиса, но понял, что это создаст ощущение резкости в потоке.
  6. В то же время в основном процессе будет выполняться другой поток, который будет извлекать прогнозы из другой очереди, а также данные GPS. Когда клиент получает сигнал уничтожения, прогнозы, данные GPS и время также выгружаются в файл csv.

Вот блок-схема клиента по отношению к облачным API на AWS.

В нашем случае клиентом является Raspberry Pi, а облачные API, на которые отправляются запросы логического вывода, предоставляются cortex на AWS (Amazon Web Services).

Исходный код клиента также можно посмотреть в его репозитории GitHub.

Одна конкретная проблема, которую мне пришлось преодолеть, заключалась в пропускной способности 4G. Лучше всего уменьшить требуемую полосу пропускания для этого приложения, чтобы уменьшить возможные зависания или чрезмерное использование доступных данных. Я решил использовать камеру Pi с очень низким разрешением: 480x270 (мы можем использовать небольшое разрешение, потому что поле зрения камеры Pi очень узкое, поэтому мы все еще можем легко идентифицировать номерные знаки). Тем не менее, даже при этом разрешении размер кадра в формате JPEG составляет около 100 КБ при 10 МБит. Умножив это на 30 кадров в секунду, мы получим 3000 КБ, что составляет около 24 Мбит / с, и это без накладных расходов HTTP - это много.

Вместо этого я проделал следующие уловки:

  • Ширина уменьшена до 416 пикселей, что является именно тем, к чему модель YOLOv3 изменяет размеры изображений. Шкала явно сохранена.
  • Преобразовал изображение в оттенки серого.
  • Убрана верхняя 45% часть изображения. Считается, что номерные знаки не будут отображаться в верхней части кадра, поскольку машины не летают, верно? Судя по тому, что я видел, вырезание 45% изображения не влияет на работу предсказателя.
  • Снова конвертируйте изображение в JPEG, но с более низким качеством.

Результирующий фрейм имеет размер примерно 7–10 КБ, что исключительно хорошо. Это соответствует 2,8 Мбит / с. Но со всеми накладными расходами это около 3,5 Мбит / с (включая ответ).

Для crnn API обрезанные номерные знаки не занимают много времени, даже без применения трюка со сжатием. Они сидят примерно на 2-3 КБ за штуку.

В целом, чтобы запустить его со скоростью 30 кадров в секунду, необходимая пропускная способность для API-интерфейсов вывода составляет около 6 Мбит / с. Это число, с которым я могу жить.

Полученные результаты

Оно работает!

Выше приведен пример выполнения вывода через cortex в реальном времени. Мне потребовалось около 20 экземпляров с графическим процессором, чтобы обеспечить бесперебойную работу. В зависимости от задержки с кластером вам может потребоваться больше или меньше экземпляров. Средняя задержка между моментом захвата кадра и его трансляцией в окно браузера составляет около 0,9 секунды, что настолько фантастично, учитывая, что вывод происходит где-то далеко - это все еще меня удивляет.

Часть распознавания текста, возможно, не самая лучшая, но, по крайней мере, это подтверждает точку зрения - она ​​могла бы быть намного более точной, если увеличить разрешение видео, уменьшить поле зрения камеры или настроить его.

Что касается большого количества графических процессоров, его можно уменьшить с помощью оптимизации. Например, преобразование моделей для использования смешанной / полной половинной точности (FP16 / BFP16). Использование в моделях смешанной точности, вообще говоря, минимально повлияет на точность, поэтому мы не сильно жертвуем.

Графические процессоры T4 и V100 имеют специальные тензорные ядра, которые предназначены для сверхбыстрого матричного умножения на типах половинной точности. Ускорение с половинной точностью по сравнению с операциями с одинарной точностью на T4 составляет примерно 8x, а на V100 - в 10x. Это разница на порядок. Это означает, что для модели, которая была преобразована для использования одинарной / смешанной точности, может потребоваться до 8 раз меньше времени для выполнения логического вывода и, соответственно, десятая часть времени на V100.

Я не преобразовывал модели для использования одинарной / смешанной точности, потому что это выходит за рамки этого проекта. Насколько я понимаю, это всего лишь проблема оптимизации. Скорее всего, я сделаю это в момент выпуска версии 0.14 из cortex (с настоящей поддержкой нескольких процессов и автоматическим масштабированием на основе очередей), чтобы я мог также воспользоваться преимуществами веб-сервера с несколькими обработками.

В общем, если все оптимизации будут реализованы, уменьшение размера кластера с 20 экземпляров, оснащенных графическим процессором, до одного фактически осуществимо. При правильной оптимизации невозможно даже максимально использовать один экземпляр с графическим процессором.

Чтобы сделать затраты еще более приемлемыми, использование Elastic Inference на AWS может снизить их до 75%, что очень много! Образно говоря, у вас может быть конвейер для обработки потока в реальном времени за копейки. К сожалению, на данный момент Elastic Inference не поддерживается на cortex, но я вижу, что он будет поддерживаться в ближайшем будущем, поскольку он уже появился на их радарах. См. Билет cortexlabs / cortex / issues / 618.

Примечание: модели YOLOv3 и CRNN можно значительно улучшить, настроив их на гораздо больших наборах данных (около 50–100 тыс. выборок). В этот момент даже размер кадров может быть дополнительно уменьшен, чтобы уменьшить использование данных без потери точности: «компенсировать где-то, чтобы иметь возможность брать из другого места». Это в сочетании с преобразованием всех этих моделей для использования типов с половинной точностью (и, возможно, также с эластичным выводом) может создать очень эффективную / экономичную машину вывода.

Обновление I

В версии 0.14 из cortex, которая поддерживает многопроцессорные рабочие процессы для веб-сервера, я смог уменьшить количество экземпляров графического процессора для API yolov3 с 8 до 2 и для API crnn (который выполняет вывод на CRNN и CRAFT) с 12 до 10. Это фактически означает, что общее количество экземпляров было уменьшено на 40%, что является очень хорошим преимуществом. Все эти экземпляры оснащены одним графическим процессором T4 и 4 виртуальными ЦП каждый.

Я обнаружил, что наиболее ресурсоемкой моделью является модель CRAFT, которая построена на основе модели VGG-16, имеющей вес около 138 миллионов. Имейте в виду, что для каждого кадра обычно требуется несколько выводов, поскольку в одном кадре может быть несколько обнаруженных номерных знаков. Это резко увеличивает требования к вычислительным ресурсам. Теоретически модель CRAFT следует исключить и вместо этого улучшить (настроить) модель CRNN, чтобы лучше распознавать автомобильные номера. Таким образом, crnn API можно было бы значительно уменьшить - до одного или двух экземпляров.

Обновление II

Еще пара вещей, которые могут значительно снизить вычислительные затраты:

  1. Используйте TinyYOLOv3 вместо YOLOv3. На выполнение вывода требуется примерно в 12 раз меньше времени. Утраченную карту из-за запуска менее сложной модели можно восстановить, обучив модель на более полном наборе данных.
  2. Используйте отслеживание объектов, чтобы не обнаруживать номерные знаки на каждом кадре. Это существенно сокращает общее количество выводов, необходимых для обоих API. Отслеживание объекта должно производиться локально, так как это не требует больших вычислительных затрат.
  3. Кроме того, нам не обязательно нужны все типы якорных ящиков для YOLOv3. Нам нужны только те, которые соответствуют общей форме номерных знаков. Уменьшение числа может ускорить вывод.
  4. Используйте гораздо более высокую частоту кадров для источника ввода и выбирайте только n-й кадр при обработке. Например, если камера работает со скоростью 120 кадров в секунду, просто выбирайте каждый 4-й кадр, чтобы по-прежнему получать 30 кадров в секунду. Это позволяет получить гораздо более четкий / ясный кадр, что приводит к более точному прогнозированию. Быстро движущиеся автомобили являются виновниками текстовой идентификации.
  5. Используйте OpenALPR вместо CRAFT + CRNN. Я не совсем уверен, что это хорошая идея, но, судя по тому, что я слышал от других, это довольно быстро.
  6. Зная, что каждый пакет кадров, отправленный в API распознавания текста, является номерным знаком, я мог видеть, как можно использовать AutoEncoders для сжатия изображения с большим запасом при передаче данных. А если не то, то, по крайней мере, их можно было бы использовать для удаления шума из этих кадров. А поскольку AutoEncoders, как правило, очень дешевы в эксплуатации, я мог видеть, как их можно использовать на Raspberry Pi для предварительной обработки кадров. Затем они будут декодированы на уровне API.

Эти улучшения обсуждались здесь, здесь, здесь или здесь.

Что касается понесенных затрат на кластер AWS, вот мои мысли:

  • С текущей конфигурацией это стоит около 4 долларов США в час. Возможно только при использовании спотовых экземпляров.
  • Предполагая, что все улучшения будут внесены, затраты могут снизиться до ~ 0,2 цента / час плюс-минус. Фактически, для их работы требуется всего около 1% мощности графического процессора T4. Это опять же очень приблизительная цифра. Это, конечно, стало возможным благодаря запуску спотовых экземпляров.

Выводы (и как 5G вписывается во все это)

Я вижу, что устройства начинают все больше и больше зависеть от облачных вычислений, особенно для периферийных устройств с ограниченной вычислительной мощностью. И поскольку в настоящее время развертывается 5G, теоретически он должен приблизить облако к этим устройствам с ограниченными вычислительными ресурсами. Следовательно, влияние облака должно расти вместе с ним. Чем надежнее и шире представлены сети 5G, тем больше будет уверенности в переносе вычислений в облако для так называемых критически важных задач - подумайте об автономных автомобилях.

Еще одна вещь, которую я узнал в этом проекте, - это то, насколько легко стало это делать с появлением оптимизированных платформ для развертывания моделей машинного обучения в облаке. 5 лет назад это было бы довольно сложно, но сейчас одинокий мужчина может сделать так много за относительно короткий период времени.

Ресурсы

  • Все _41 _ / _ 42 _ / _ 43 _ / _ 44_ для 3D-печатных держателей находятся здесь.
  • Клиентская реализация этого проекта находится здесь.
  • Реализация этого проекта cortex находится здесь.
  • Библиотека для модели YOLOv3 в Keras находится здесь.
  • Библиотека для детектора текста CRAFT + распознаватель текста CRNN находится здесь.
  • Набор данных для европейских номерных знаков (состоящий из 534 образцов, снятых моей камерой Pi) находится здесь.
  • Модель YOLOv3 в Keras (license_plate.h5) и SavedModel (yolov3 папка / zip) находится здесь.