Предварительная обработка миллионов изображений за несколько минут (без нарушения законов физики)

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

Постановка задачи

Предположим, вы экспериментируете с подходом поиск сходства для поиска связанных математических задач среди всех задач, для которых известно и хранится пошаговое решение, щедро предоставленное нам экспертами по математике Photomath.

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

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

Клиентские библиотеки VM + GCP

Поскольку мы находимся в облаке, а точнее в Google Cloud Platform, первым и наиболее очевидным решением было использование клиентских библиотек, предоставляемых Google. Я реализовал логику загрузки, предварительной обработки и хранения образов в докеризированном стиле и развернул ее на одной из наших виртуальных машин Compute Engine.

Положительными сторонами такого подхода является гарантия того, что наше решение, вероятно, будет легко поддерживать (большинство инженеров владеют GCP, и документация по большей части кажется довольно понятной), это зависит от стабильности и поддерживаются GCP PythonClient Libraries и инструменты обработки изображений (Pillow). Это означает, что если какая-то проблема присутствует, инженерам будет относительно легко ее исправить, и мы, скорее всего, будем не единственной командой, которая столкнется с проблемой, поэтому исправление, скорее всего, будет применено быстро без особых рисков или потерь.

Минусы заключаются в том, что клиентские библиотеки GCP принудительно используют некоторый тип шаблона, их список зависимостей, скорее всего, не оптимален, и, что наиболее важно, время, затрачиваемое на обработку всего миллиона изображений даже с многопоточностью, неумолимо, более 12 часов на миллион изображений. Я отказался от этого подхода очень рано, поэтому я не измерял время, необходимое для передачи этих изображений в выделенное хранилище наборов данных, то есть в другое сегмент, но можно с уверенностью сказать, что это около 3–4 часов. ; это необходимо, чтобы получить набор данных для всех наших учебных машин. Это вызывает небольшие неудобства; повторение обучения на различных наборах данных происходит не так быстро, в результате чего общая скорость доставки и проведения экспериментов далека от оптимальной. Можем ли мы сделать лучше?

VM + GCSFuse (и, в конечном итоге, rclone)

Почему бы не абстрагироваться от предыдущего подхода еще немного? По сути, мы читаем изображение из некоторого места, обрабатываем его и сохраняем в другом месте. Хотя этот источник находится где-то в сети, он все еще находится на высоком уровне, как чтение из вашей локальной папки где-то в файловой системе, так почему бы не попробовать, разумно ли монтировать центральное хранилище как файловую систему к нашей ВМ? Оказывается, это более чем возможно с помощью либо GCSFuse (официально поддерживается Google), либо rclone (программа командной строки с открытым исходным кодом, независимая от поставщика, для управления ресурсами в облачном хранилище). Я экспериментировал с обоими решениями, но ради аргументации (и его преимуществ перед GCSFuse) я выберу rclone.

Улучшения по сравнению с первым подходом заключаются в том, что наши зависимости Python теперь являются только библиотеками обработки изображений; доступ к изображению так же прост, как если бы оно хранилось локально, поэтому больше никаких клиентских библиотек, больше никаких шаблонов только чистый Python. rclone хорошо работает в докере, хотя при его настройке были некоторые незначительные проблемы, в основном потому, что это был новый инструмент, который может быть небольшим недостатком в зависимости от вашего варианта использования. Однако я считаю, что широкий спектр задач, которые вы можете выполнять с помощью rclone, определенно стоит потратить время на изучение, особенно из-за его независимой от производителя философии, если однажды Photomath захочет чтобы переключиться на AWS или Azure, инженерам не нужно изучать какой-либо инструмент другого поставщика, но они могут продолжать использовать rclone. С другой стороны, улучшение пропускной способности по сравнению с первым подходом незначительно и по-прежнему составляет 12 часов для одного миллиона изображений. Опять же, я не замерял время для переноса набора данных в его ведро, поэтому еще раз добавляю 3–4 часа для полного процесса.

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

PubSub + облачные функции

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

Загрузка изображений из специального хранилища наборов данных на тренировочную машину занимает около 3 часов. Это означает, что данное решение примерно в 5раз быстрее исходного.

PubSub — это, по сути, очередь, в которую вы отправляете пакет изображений (точнее, их расположение в центральном репозитории), а на другой стороне этой очереди находится Cloud Function, который загружает каждое изображение в пакете, обрабатывает и сохраняет его обратно в выделенное хранилище наборов данных. Кроме того, обработка выполняется в многопоточном режиме, что дает нам дополнительное ускорение. Поскольку мы вызываем эту функцию только при загрузке нового набора данных, цена решения более чем приемлема. Это решение также совместимо с упомянутыми вариантами монтажа хранилища, что позволяет использовать все, что было изучено в ходе экспериментов с одним экземпляром процессора.

Решение быстрое, элегантное и хорошо масштабируется.

Рисование линии

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

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

Для тех из вас, кто все еще сомневается в моем последнем подходе или нашел лучший, возможно, именно вы. Вакансия Data Engineer по-прежнему открыта, поэтому свяжитесь с нами, поднимите нашу разработку данных на новый уровень, если осмелитесь, и не удивляйтесь, если мы ожидаем, что вы напишете блог, чтобы поделиться своими знаниями с остальными из нас.

Нравится то, что вы прочитали? Узнайте больше о #LifeAtPhotomath и ознакомьтесь с нашими объявлениями о вакансиях: https://careers.photomath.com/