Учебное пособие по использованию многопроцессорной обработки в Python для ускорения работы обнаружения объектов TensorFlow и YOLO.

Это руководство представляет собой краткое введение в многопроцессорность в Python. В конце этого руководства я покажу, как ускорить обнаружение объектов TensorFlow и YOLO.

Что такое многопроцессорность?

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

Почему многопроцессорность?

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

Однако интерпретатор Python по умолчанию был разработан с учетом простоты и имел поточно-ориентированный механизм, так называемый «GIL» (Global Interpreter Lock). Чтобы предотвратить конфликты между потоками, он выполняет только один оператор за раз (так называемая последовательная обработка или однопоточная обработка). Так работает наш обычный скрипт Python, и мы выполняем задачи линейно.

Из этого туториала Вы узнаете, как создать несколько подпроцессов, чтобы избежать некоторых недостатков GIL.

Модуль multiprocessing в стандартной библиотеке Python имеет множество мощных функций. Если вы хотите прочитать обо всех советах, приемах и деталях, я рекомендую использовать официальную документацию в качестве отправной точки. В этом руководстве я покажу только конкретные примеры, связанные с моими задачами обнаружения объектов YOLO.

Итак, будет две части:

  • Многопроцессорное взаимодействие между процессами;
  • Использование многопроцессорной обработки с функцией обнаружения объектов YOLO при предварительной и постобработке.

Многопроцессорное взаимодействие между процессами

Эффективное использование нескольких процессов обычно требует связи между ними, чтобы можно было разделить работу между процессами и агрегировать результаты. Многопроцессорность поддерживает два типа каналов связи между процессами: очередь и конвейер.

Очередь

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

Модули Python Multiprocessing предоставляют Queue класс, который представляет собой структуру данных в порядке очереди. Они могут хранить любой объект Python (хотя лучше всего подходят простые) и чрезвычайно полезны для обмена данными между процессами.

Очереди особенно полезны при передаче в качестве параметра целевой функции процесса, чтобы позволить процессу потреблять данные. Используя функцию put(), мы можем вставлять данные в очередь, а используя get(), мы можем получать элементы из очередей. См. Следующий код для быстрого примера связи:

С помощью приведенного выше кода мы запускаем два процесса: один помещает данные в очередь, а другой выбирает. Моя цель - проверить, сколько времени нужно, чтобы расставить числа от 0 до 1000000 в Очередь, а затем прочитать их.

Результаты были следующие:

A queue is now empty! 10.352750539779663

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

Результаты были следующие:

A queue is now empty! 1.0990705490112305

Труба

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

Модуль многопроцессорности предоставляет функцию Pipe(), которая возвращает пару объектов соединения, соединенных конвейером. Два объекта соединения, возвращаемые Pipe(), представляют два конца трубы. Каждый объект подключения имеет методы send() и recv() (среди прочих).

Как и раньше, мы изменим код Queue для использования Pipe. См. Следующий код для быстрого примера связи:

Результаты были следующие:

A pipe is now empty! 9.079113721847534

Как и раньше, поскольку мы будем работать с изображениями, давайте протестируем отправку 100 случайных изображений через конвейер со следующим кодом:

Результаты были следующие:

A pipe is now empty! 1.6539678573608398

Итак, теперь у нас есть некоторые результаты, давайте объединим их в одну таблицу, и тогда мы сможем их сравнить:

Итак, простые данные - это числа, перечисленные в диапазоне 1–1000000, данные Numpy - это 100 случайных изображений 416x416x3. Результаты были впечатляющими, и трудно сказать, почему они были такими. Хотя мы отправляли простые данные в Pipe, это было примерно на 14% быстрее, чем при использовании Queue. Но когда мы отправляли случайные данные изображения таким же образом, очередь была быстрее примерно на 50%. Я не могу ответить, почему такая разница между результатами, но в любом случае меня это не волнует; Я выбираю метод в зависимости от типа данных.

Использование многопроцессорной обработки с функцией обнаружения объектов YOLO при предварительной и постобработке

Обычно мы хотим использовать многопроцессорность, чтобы задачи выполнялись быстрее. Моя Реализация обнаружения объектов YOLO актуальна для нескольких методов: обнаружение видео, обнаружение в реальном времени и отслеживание объектов. Он не подходит для обнаружения изображений, потому что обычно мы делаем это один раз, что не занимает много времени. Но для других методов, о которых я упоминал, многопроцессорность может сэкономить нам значительное количество времени. Используя многопроцессорность в задачах реального времени, мы можем получить больше кадров в секунду и гораздо более плавные результаты.

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

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

Проблема. Обычно мы используем многопроцессорность для математических задач, где мы можем разделить вычисления на процессы. Но теперь это более сложно, потому что мы можем сделать следующий шаг только в том случае, если наш предыдущий шаг выполнил свою работу. Хотя обнаружение YOLO занимает большую часть времени (около 80%), мы ничего не можем с этим поделать, за исключением того, что у нас будет несколько графических процессоров, но, как правило, ни у кого нет. Итак, моя идея заключалась в том, чтобы разделить каждую из этих функций на каждый процесс. Это означает, что обнаружение YOLO не будет ждать, пока другие задачи закончат свою работу. Итак, это сложно объяснить, поэтому я снова нарисовал то, что имею в виду:

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

Так что я не буду подробно останавливаться на деталях, это просто обзор, стоит ли переводить код на параллельную обработку. Итак, чтобы все сравнить, я проверю, сколько времени занимает обработка видео без многопроцессорности и с ней. Кроме того, я знаю, что большую часть времени занимает обнаружение YOLO, поэтому мы постараемся учесть это при оценке.

Я решил провести тесты с моделью YOLOv4 с размером входа 416x416. У меня процессор i7–7700k, а у меня графический процессор 1080TI. TensorRT преобразован в прецизионную модель FP32, чтобы сохранить ту же точность, что и исходная модель. Чтобы получить результаты и сделать их сопоставимыми, мне пришлось изменить мои исходные функции utils.py, которые можно найти по главной ссылке на GitHub.

Я измерял 2-х кратные параметры: время обнаружения YOLO и общее время обработки всего видео. Чтобы сделать обнаружения сопоставимыми, я начинаю отсчитывать время начала после обнаружения первого изображения, потому что мы не хотим измерять время загрузки модели. Хотя загрузка модели TensorRT занимает в 3-4 раза больше времени, чем модель TensorFlow, мы должны учитывать время загрузки модели.

В таблице ниже представлены мои результаты тестирования видео test.mp4 с многопроцессорной обработкой и без нее. Все видео имеет длину всего 14 секунд.

Во-первых, давайте обсудим результаты TensorFlow YOLOv4. Как видите, без использования многопроцессорной обработки время обнаружения составило 23 секунды, а время постобработки - около 7,73 секунды. При многопроцессорной обработке время обнаружения было медленнее на 4,42 секунды, но постобработка была быстрее на 5,35 секунды, что на 325% лучше. Но сравнивая окончательные результаты, это всего на 3% быстрее. Даже если бы я использовал графический процессор, TensorFlow по-прежнему требует много ресурсов процессора. Пока мы используем ЦП для постобработки, обнаружение происходит медленнее из-за меньшего количества свободных ресурсов ЦП.

В моем предыдущем уроке мы сравнивали результаты TensorRT и TensorFlow, поэтому я решил, что будет интересно увидеть его эффекты в многопроцессорной обработке. Таким образом, сравнивая время обнаружения, скорость обнаружения почти не снижалась. Он упал всего на 0,86 секунды, но постобработка не улучшилась так сильно, как модель TensorFlow, всего на 208%. Но глядя на окончательные результаты, мы видим, что улучшение составило 24%! Это намного лучшее улучшение, чем в модели TensorFlow.

Вывод:

Таким образом, результаты с TensorRT были намного лучше. Похоже, что скомпилированная замороженная модель требует меньше ресурсов процессора для обнаружения, чем модель TensorFlow; кроме того, это более чем в два раза быстрее. Итак, если у вас есть графический процессор, я рекомендую использовать только модели TensorRT для вашего окончательного проекта. Для разработки вы можете использовать TensorFlow, потому что он быстрее отлаживает и вносит изменения. Целью этого руководства было измерить, будет ли улучшение во времени при использовании многопроцессорной обработки. Окончательные результаты говорят нам: да, стоит реализовать многопроцессорность. Код становится беспорядочным, и его сложно отлаживать, но если вам нужно хоть какое-то улучшение, это того стоит!

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

Первоначально опубликовано на https://pylessons.com/YOLOv4-TF2-multiprocessing

Больше контента на plainenglish.io