Многофункциональные архитектуры Полное руководство

Многофункциональные архитектуры' - 'Multifunctional Architectures' 'Полное руководство' - 'Complete Guide

Легкие модели для многозадачного вывода в реальном времени

Фото от Julien Duduoglu на Unsplash

Введение

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

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

  • Мотивация — Почему бы нам это сделать?
  • Подход — Как мы собираемся это делать?
  • Архитектура модели
  • Подход к обучению
  • Вывод — Проверка производительности и изучение интересной ошибки
  • Заключение

Мотивация

Зачем нам использовать легкую модель? Разве это не ухудшит производительность? Если мы не разворачиваем на краю, не следует ли использовать максимально большую модель?

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

Легкие модели могут помочь снизить затраты и уменьшить выбросы углерода за счет меньшего энергопотребления

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

Подход

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

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

Набор данных

Мы будем использовать набор данных City Scapes, чтобы предоставить изображения входных данных (левая камера), маски сегментации и карты глубины. Для карт сегментации мы выбрали использовать стандартные обучающие метки с 19 классами + 1 категорией без меток.

Подготовка карты глубины — стандартная разница

Карты разности, созданные с помощью SteroSGBM, доступны на сайте CityScapes. Разность описывает разницу в пикселях между объектами, видимыми с точки зрения каждой стереокамеры, и обратно пропорциональна глубине, которую можно вычислить с помощью следующей формулы:

Вычисление глубины City Scapes, единицы указаны в скобках. Источник: Автор.

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

  1. Обрезать нижние 20%, а также части левого и верхнего краев
  2. Изменить размер до исходного масштаба
  3. Применить сглаживающий фильтр
  4. Выполнить заполнение дыр

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

Рисунок 1. Глубина данных из городского пейзажа. Источник: автор.

Подробные сведения об этом подходе выходят за рамки данного поста, но если вас интересует, вот видеообъяснение на YouTube.

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

Подготовка карты глубины — диспаратность CreStereo

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

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

Рисунок 2. Пример обучающей выборки. Источник: автор.

Теперь, когда у нас есть данные, давайте посмотрим на архитектуру.

Архитектура модели

Следуя [1], архитектура будет состоять из основы/кодера MobileNet, декодера LightWeight RefineNet и головок для каждой отдельной задачи. Общая архитектура показана на рисунке 3.

Рисунок 3. Архитектура модели. Источник.

Для кодера/основы мы будем использовать MobileNetV3 и передавать пропускные соединения с разрешениями 1/4, 1/8, 1/16 и 1/32 в LightWeight Refine Net. Наконец, вывод передается каждой головке, которая отвечает за отдельную задачу. Обратите внимание, что мы даже можем добавить больше задач в эту архитектуру, если захотим.

Рисунок 4. (Слева) Подробная архитектура многозадачного кодировщика-декодировщика. (Справа) детали блоков LightWeight RefineNet. Изменено по источнику.

Для реализации кодера мы используем предварительно обученный кодер MobileNetV3, где мы передадим кодер MobileNetV3 в пользовательский модуль PyTorch. Результатом его функции forward является ParameterDict пропускных соединений для входа в LightWeight Refine Net. Фрагмент кода ниже показывает, как это сделать.

class MobileNetV3Backbone(nn.Module):    def __init__(self, backbone):        super().__init__()        self.backbone = backbone        def forward(self, x):        """ Передает входные данные через слои извлечения признаков кодера MobileNetV3            слои для добавления соединений                - 1:  1/4 разрешение                - 3:  1/8 разрешение                - 7, 8:  1/16 разрешение                - 10, 11: 1/32 разрешение           """        skips = nn.ParameterDict()        for i in range(len(self.backbone) - 1):            x = self.backbone[i](x)            # добавить выходы пропускных соединений            if i in [1, 3, 7, 8, 10, 11]:                skips.update({f"l{i}_out" : x})        return skips

Декодер LightWeight RefineNet очень похож на тот, который реализован в [1], за исключением нескольких модификаций, чтобы он был совместим с MobileNetV3, а не с MobileNetV2. Также отметим, что часть декодера состоит из головок сегментации и глубины. Полный код модели доступен на GitHub. Мы можем собрать модель следующим образом:

from torchvision.models import mobilenet_v3_small    mobilenet = mobilenet_v3_small(weights='IMAGENET1K_V1')encoder = MobileNetV3Backbone(mobilenet.features)decoder = LightWeightRefineNet(num_seg_classes)model = MultiTaskNetwork(encoder, freeze_encoder=False).to(device)

Подход к обучению

Мы разделяем обучение на три фазы: первая на разрешении 1/4, вторая на разрешении 1/2 и последняя на полном разрешении. Все веса обновлялись, так как замораживание весов энкодера не давало хороших результатов.

Преобразования

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

Преобразование глубины

В целом, карты глубины содержат в основном меньшие значения, так как большая часть информации, содержащейся в карте глубины, состоит из объектов и поверхностей, близких к камере. Поскольку карта глубины имеет большую часть своей глубины, сосредоточенной вокруг меньших значений (см. левую часть рисунка 4 ниже), ее необходимо преобразовать для эффективного обучения нейронной сети. Карта глубины ограничивается значениями от 0 до 250, потому что стереодиспаратные/глубинные данные на больших расстояниях обычно ненадежны, и в данном случае мы хотим от них избавиться. Затем мы берем естественный логарифм и делим его на 5, чтобы сжать диапазон чисел. Подробнее см. в этом блокноте.

Рисунок 4. Слева - ограниченное распределение глубины. Справа - преобразованное распределение глубины. Глубина была выбрана из 64 случайных полноразмерных обучающих масок глубины. Источник: автор.

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

Функции потерь

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

Скорость обучения

Мы используем одноцикловую косинусную возрастающую скорость обучения с максимумом 5e-4 и тренируемся в течение 150 эпох на разрешении 1/4. Блокнот, использовавшийся для обучения, находится здесь.

Рисунок 5. Одноцикловая косинусная возрастающая скорость обучения. Источник: автор.

Мы затем тонко настраиваем на разрешении 1/2 в течение 25 эпох и снова на полном разрешении в течение еще 25 эпох, оба со скоростью обучения 5e-6. Обратите внимание, что нам пришлось уменьшить размер пакета каждый раз, когда мы тонко настраивали его при увеличении разрешения.

Инференция

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

Рисунок 6. Результаты инференции (верхние 2 - из тестового набора, нижние 2 - из проверочного набора). Источник: автор.

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

Интересный сбой

В нижней части рисунка 6 показан интересный случай сбоя при полной сегментации фонарного столба в левой части изображения. Сегментация покрывает только нижнюю половину фонарного столба, тогда как глубина показывает, что нижняя половина фонарного столба находится гораздо ближе, чем верхняя половина. Сбой глубины может быть вызван смещением в сторону нижних пикселей, как правило, соответствующих ближней глубине; обратите внимание на горизонтальную линию вокруг пикселя 500, здесь явно видно разделение между близкими и дальними пикселями. Кажется, что такое смещение могло просочиться в задачу сегментации модели. Такой тип просачивания задачи следует учитывать при тренировке мультитаск-моделей.

В мультитаск-обучении данные обучения от одной задачи могут повлиять на производительность другой задачи

Распределение глубины

Давайте проверим, как распределена предсказанная глубина по сравнению с истинной. Для простоты мы будем использовать только 94 истинных/предсказанных карт глубины полного разрешения.

Рисунок 8. Распределение истинной (слева) и предсказанной (справа) карты глубины, каждая с 1000 интервалами. Источник автор.

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

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

Бонус: Скорость вывода

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

# найти оптимальную платформу для выполнения сверток
torch.backends.cudnn.benchmark = True
# масштабирование до половины размера
rescaled_sample = Rescale(400, 1024)(sample)
rescaled_left = rescaled_sample['left'].to(DEVICE)
# ИНИЦИАЛИЗАЦИЯ ЛОГГЕРОВ
starter, ender = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
repetitions = 300
timings=np.zeros((repetitions,1))
# ПРОГРЕВ GPU
for _ in range(10):
    _, _ = model(rescaled_left.unsqueeze(0))
# ИЗМЕРЕНИЕ ПРОИЗВОДИТЕЛЬНОСТИ
with torch.no_grad():
    for rep in range(repetitions):
        starter.record()
        _, _ = model(rescaled_left.unsqueeze(0))
        ender.record()
        # ОЖИДАНИЕ СИНХРОНИЗАЦИИ GPU
        torch.cuda.synchronize()
        curr_time = starter.elapsed_time(ender)
        timings[rep] = curr_time
mean_syn = np.sum(timings) / repetitions
std_syn = np.std(timings)
print(mean_syn, std_syn)

Тест вывода показывает, что эта модель может работать со скоростью 18.69+/-0.44 мс или около 55 Гц. Важно отметить, что это всего лишь прототип на языке Python, запущенный на ноутбуке с графическим процессором NVIDIA RTX 3060, и другое оборудование изменит скорость вывода. Следует также отметить, что SDK, такой как Torch-TensorRt, может значительно увеличить скорость вывода при развертывании на графическом процессоре NVIDIA.

Вывод

В этом посте мы узнали, как мультитаскинг может снизить затраты и уменьшить выбросы углерода. Мы узнали, как построить легковесную мультитаск-архитектуру, способную одновременно выполнять классификацию и регрессию на наборе данных CityScapes. Мы также использовали CreStereo и Knowledge Distillation, чтобы помочь нашей модели научиться предсказывать более точные карты глубины.

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

Ссылки

[1] Некрасов, Владимир и др. «Совместная семантическая сегментация и оценка глубины в реальном времени с использованием асимметричных аннотаций». CoRR, том abs/1809.04766, 2018, http://arxiv.org/abs/1809.04766

[2] Стэндли, Тревор и др. «Какие задачи следует изучать вместе при мультизадачном обучении?» CoRR, том abs/1905.07553, 2019, http://arxiv.org/abs/1905.07553

[3] Cordts, M., Omran, M., Ramos, S., Rehfeld, T., Enzweiler, M., Benenson, R., Franke, U., Roth, S., & Schiele, B. (2016). Набор данных cityscapes для семантического понимания городской среды. 2016 IEEE Конференция по компьютерному зрению и распознаванию образов (CVPR). https://doi.org/10.1109/cvpr.2016.350