Сегодня мы обсудим, как обрабатывать большие объемы данных с помощью многопроцессорной обработки Python. Я расскажу некоторую общую информацию, которую можно найти в руководствах, и поделюсь некоторыми небольшими приемами, которые я обнаружил, например, использование tqdm с многопроцессорной обработкой imap и параллельная работа с архивами.

Так почему мы должны прибегать к параллельным вычислениям? При работе с данными иногда возникают проблемы, связанные с большими данными. Каждый раз, когда у нас есть данные, которые не помещаются в ОЗУ, нам необходимо обрабатывать их по частям. К счастью, современные языки программирования позволяют нам создавать несколько процессов (или даже потоков), которые отлично работают на многоядерных процессорах (NB: это не означает, что одноядерные процессоры не могут обрабатывать многопроцессорность, вот поток переполнения стека на этом тема)

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

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

Этот класс вряд ли полагается на пакет py7zlib, который позволяет нам распаковывать данные каждый раз, когда мы вызываем метод get, и сообщать нам количество файлов внутри архива. Мы также определяем __iter__, который поможет нам начать map многопроцессорную обработку на этом объекте как на итерируемом.

Это определение дает нам возможность перебирать архив, но позволяет ли оно нам выполнять произвольный доступ к содержимому параллельно? Это интересный вопрос, на который я не нашел ответа в Интернете, но мы можем ответить на него, если погрузимся в исходный код py7zlib.

Здесь я привожу сокращенные фрагменты кода от пылзма.

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

Затем давайте быстро познакомимся с сетками и облаками точек. Во-первых, сетки, это наборы вершин, ребер и граней. Вершины определяются как (x, y, z) координаты в пространстве и присваиваются уникальными номерами. Ребра и грани - это группы пар и троек точек соответственно, определенные с помощью упомянутых уникальных идентификаторов точек. Обычно, когда мы говорим о «сетке», мы имеем в виду «треугольную сетку», то есть поверхность, состоящую из треугольников. Работать с сетками в Python намного проще с библиотекой trimesh, например, она предоставляет интерфейс для загрузки .obj файлов в память. Для отображения и взаимодействия с 3D-объектами в jupyter notebook можно использовать k3d библиотеку.

Итак, с помощью следующего фрагмента кода я отвечаю на вопрос: «как построить atrimeshobject в jupyter с k3d

Во-вторых, облака точек, они представляют собой массивы трехмерных точек, которые представляют объекты в космосе. Многие 3D-сканеры создают облака точек как представление отсканированного объекта. В демонстрационных целях мы можем прочитать ту же самую сетку и отобразить ее вершины в виде облака точек.

Как уже упоминалось выше, 3D-сканер предоставляет нам облако точек. Предположим, у нас есть база данных сеток, и мы хотим найти сетку в нашей базе данных, которая выровнена с отсканированным объектом, также называемым облаком точек. Для решения этой проблемы мы можем предложить наивный подход. Мы будем искать наибольшее расстояние между точками данного облака точек и каждой сеткой из нашего архива. И если такое расстояние будет меньше 1e-4 для некоторой сетки, мы будем считать эту сетку выровненной с облаком точек.

И, наконец, мы подошли к разделу многопроцессорности. Помня, что в нашем архиве много файлов, которые могут не поместиться в памяти, мы предпочитаем обрабатывать их параллельно. Для этого мы будем использовать Pool многопроцессорную обработку, которая обрабатывает множественные вызовы пользовательской функции с помощью методов map или imap/imap_unordered. Разница между map и imap, которая влияет на нас, заключается в том, что map преобразует итерацию в список перед отправкой в ​​рабочие процессы. Если архив слишком велик для записи в ОЗУ, его не следует распаковывать в список Python. В другом случае скорость их выполнения одинакова.

[Loading meshes: pool.map w/o manager] Pool of 4 processes elapsed time: 37.213207403818764 sec
[Loading meshes: pool.imap_unordered w/o manager] Pool of 4 processes elapsed time: 37.219303369522095 sec

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

Двигаемся дальше с imap. Давайте обсудим, как достичь нашей цели - найти сетку рядом с облаком точек. Вот данные, там 5 разных сеток из Стэнфордских моделей. Мы моделируем 3D-сканирование, добавляя шум к вершинам сетки Стэнфордского кролика.

Конечно, в дальнейшем мы нормализуем облако точек и вершины сетки, чтобы масштабировать их в трехмерном кубе.

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

Здесь read_meshes_get_distances_pool_imap - центральная функция, в которой выполняется следующее:

  • MeshesArchive и multiprocessing.Pool инициализированы
  • tqdm применяется для наблюдения за прогрессом пула, а профилирование всего пула выполняется вручную
  • вывод выполненных результатов

Обратите внимание, как мы передаем аргументы в imap, создавая новый итерабельный объект из archive и point_cloud, используя zip(archive, itertools.repeat(point_cloud)). Это позволяет нам прикрепить массив облака точек к каждой записи архива, избегая преобразования archive в список.

Результат выполнения выглядит так

100%|####################################################################| 5/5 [00:00<00:00,  5.14it/s]
100%|####################################################################| 5/5 [00:00<00:00,  5.08it/s]
100%|####################################################################| 5/5 [00:00<00:00,  5.18it/s]
[Process meshes: pool.imap w/o manager] Pool of 4 processes elapsed time: 1.0080536206563313 sec
armadillo.obj 0.16176825266293382
beast.obj 0.28608649819198073
cow.obj 0.41653845909820164
spot.obj 0.22739556571296735
stanford-bunny.obj 2.3699851136074263e-05

Мы можем заметить, что Стэнфордский кролик - ближайшая сетка к данному облаку точек. Также видно, что мы не используем большой объем данных, но мы показали, что это решение будет работать, даже если у нас есть большое количество сеток внутри архива.

Многопроцессорность позволяет специалистам по обработке данных достичь высокой производительности не только в 3D-компьютерном зрении, но и в других областях машинного обучения. Очень важно понимать, что параллельное выполнение намного быстрее, чем выполнение внутри цикла. Разница становится значительной, особенно когда алгоритм написан правильно. Большой объем данных свидетельствует о проблемах, которые невозможно решить без творческих подходов к использованию ограниченных ресурсов. И, к счастью, язык Python и его обширный набор библиотек помогают нам, специалистам по обработке данных, решать такие проблемы.