Сегодня мы обсудим, как обрабатывать большие объемы данных с помощью многопроцессорной обработки Python. Я расскажу некоторую общую информацию, которую можно найти в руководствах, и поделюсь некоторыми небольшими приемами, которые я обнаружил, например, использование tqdm
с многопроцессорной обработкой imap
и параллельная работа с архивами.
Так почему мы должны прибегать к параллельным вычислениям? При работе с данными иногда возникают проблемы, связанные с большими данными. Каждый раз, когда у нас есть данные, которые не помещаются в ОЗУ, нам необходимо обрабатывать их по частям. К счастью, современные языки программирования позволяют нам создавать несколько процессов (или даже потоков), которые отлично работают на многоядерных процессорах (NB: это не означает, что одноядерные процессоры не могут обрабатывать многопроцессорность, вот поток переполнения стека на этом тема)
Сегодня мы попробуем свои силы в часто выполняемой задаче трехмерного компьютерного зрения вычисления расстояний между сеткой и облаком точек. Вы можете столкнуться с этой проблемой, например, когда вам нужно найти сетку среди всех доступных сеток, которая определяет тот же трехмерный объект, что и данное облако точек.
Наши данные состоят из .obj
файлов, хранящихся в .7z
архиве, что очень удобно с точки зрения эффективности хранения. Но когда нам нужно получить доступ к точной его части, мы должны приложить усилия. Здесь я определяю класс, который охватывает 7-zip-архив и предоставляет интерфейс для базовых данных.
Этот класс вряд ли полагается на пакет py7zlib
, который позволяет нам распаковывать данные каждый раз, когда мы вызываем метод get
, и сообщать нам количество файлов внутри архива. Мы также определяем __iter__
, который поможет нам начать map
многопроцессорную обработку на этом объекте как на итерируемом.
Это определение дает нам возможность перебирать архив, но позволяет ли оно нам выполнять произвольный доступ к содержимому параллельно? Это интересный вопрос, на который я не нашел ответа в Интернете, но мы можем ответить на него, если погрузимся в исходный код py7zlib
.
Здесь я привожу сокращенные фрагменты кода от пылзма.
Я считаю, что из вышеизложенного ясно, что нет причин для блокировки архива, когда он читается несколько раз одновременно.
Затем давайте быстро познакомимся с сетками и облаками точек. Во-первых, сетки, это наборы вершин, ребер и граней. Вершины определяются как (x, y, z) координаты в пространстве и присваиваются уникальными номерами. Ребра и грани - это группы пар и троек точек соответственно, определенные с помощью упомянутых уникальных идентификаторов точек. Обычно, когда мы говорим о «сетке», мы имеем в виду «треугольную сетку», то есть поверхность, состоящую из треугольников. Работать с сетками в Python намного проще с библиотекой trimesh
, например, она предоставляет интерфейс для загрузки .obj
файлов в память. Для отображения и взаимодействия с 3D-объектами в jupyter notebook
можно использовать k3d
библиотеку.
Итак, с помощью следующего фрагмента кода я отвечаю на вопрос: «как построить atrimesh
object в 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 и его обширный набор библиотек помогают нам, специалистам по обработке данных, решать такие проблемы.