Введение

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

Когда я впервые услышал о докладе Pix2seq: A Language Modeling Framework for Object Detection на ICLR 2022, я очень обрадовался и был уверен, что мой следующий пост в блоге будет об этом; Итак, я пишу этот пост и надеюсь, что он вам понравится и вы найдете модель pix2seq простой для понимания и реализации.

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

Я сделал весь свой код доступным как Блокнот Google Colab и Блокнот Kaggle. Я также разместил весь проект и коды на моем GitHub.

Чем интересна эта бумага

Идея довольно проста: переформулируйте проблему обнаружения объектов как задачу генерации текста (токена)! Мы хотим, чтобы модель сообщала нам, какие объекты существуют на изображении, а также координаты (x, y) их ограничивающих рамок (bboxes), все в определенном формате в сгенерированной последовательности, просто как генерация текста!

Как видите, задача обнаружение объекта трансформируется в задачу типа составления подписи к изображению: опишите изображение в тексте (последовательность), но на этот раз сообщите нам, где именно объекты есть.

Pix2Seq: простая реализация

Необходимые модули

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

Моя простая реализация Pix2Seq

Вы можете увидеть конвейер высокого уровня этого проекта на картинке выше. Как видите, нам нужен набор данных изображений и их bbox, для которых мы будем использовать Pascal VOC 2012 dataset. Далее мы с нуля напишем собственный токенизатор, чтобы преобразовать классы и координаты bbox в последовательность токенов. Затем мы будем использовать DeiT (из этой бумаги) в качестве нашего кодировщика изображений и передавать вложения изображений в ванильный декодер Transformer (из Эта бумага"). Задача декодера — предсказать следующий токен по предыдущим. Выходы декодера передаются функции потерь моделирования языка.

Коды этого руководства доступны по следующим ссылкам:
- Блокнот Google Colab
- Блокнот Kaggle
- Мой репозиторий GitHub

Набор данных

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

Нам нужен класс набора данных PyTorch, который дает нам изображение и его координаты bbox и классы в виде последовательности.

Как видите, большая часть кода здесь — это то, что вы ожидаете от простого набора данных для классификации, но есть и небольшие отличия. Нам нужен Tokenizer, чтобы преобразовать наши метки и координаты bbox (x и y) в последовательность, чтобы мы могли обучить нашу модель задаче языкового моделирования (предсказание следующих токенов на основе ранее просмотренных токенов). ).

Токенизатор

Как мы собираемся преобразовать эту информацию в последовательность? Ну, это не так сложно. Для представления объекта на изображении нам потребуется 5 чисел: 4 координатных числа и 1 для обозначения того, к какому классу он принадлежит.

На самом деле вам нужно знать координаты 2 точек ограничивающей рамки, чтобы иметь возможность рисовать ее на изображении; в формате паскаля мы используем верхнюю левую точку и нижнюю правую точку bbox в качестве этих двух критических точек, и каждая точка представлена по значениям x и y → поэтому нам понадобится 4 числа, чтобы нарисовать ограничивающую рамку. Вы можете увидеть альтернативные форматы для представления ограничивающей рамки ниже. Также посмотрите, где находится начало осей x и y (точка 0, 0).

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

  1. отметить начало и конец последовательности специальными токенами (токенами BOS и EOS).
  2. квантовать непрерывное значение координат (у нас может быть x=34,7 в качестве координаты точки, но нам нужны дискретные значения, такие как 34, в качестве наших токенов, потому что мы, наконец, делаем классификацию на конечном наборе токенов)
  3. закодировать метку объектов в соответствующие токены
  4. рандомизировать порядок объектов в финальной последовательности (подробнее об этом ниже)

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

Что касается номера 4 в этом списке, это то, что делает статья, и есть обширное исследование абляции на предмет того, является ли это хорошей идеей. Это говорит о том, что каждый раз, когда мы показываем одно и то же изображение модели (в разные эпохи), мы рандомизируем порядок, в котором объекты появляются в соответствующей последовательности, которую мы передаем модели (со смещением одного маркера), и наши функция потерь. Например, если на изображении есть «человек», «автомобиль» и «кот», токенизатор и набор данных разместят эти объекты в случайном порядке в последовательности:

  • BOS, car_xmin, car_ymin, car_xmax, car_ymax, car_label, person_xmin, person_ymin, person_xmax, person_ymax, person_label, cat_xmin, cat_ymin, cat_xmax, cat_ymax, cat_label, EOS
  • BOS, person_xmin, person_ymin, person_xmax, person_ymax, person_label, car_xmin, car_ymin, car_xmax, car_ymax, car_label, cat_xmin, cat_ymin, cat_xmax, cat_ymax, cat_label, EOS

Еще одно замечание о том, как квантовать непрерывные значения координат: представьте, что размер изображения равен 224. У вас может быть бокс с этими 4 координатами (12.2, 35.8, 68.1, 120.5).

Вам потребуется как минимум 224 токена (num_bins), чтобы иметь возможность токенизировать (квантовать) эти 4 числа с точностью до 1 пикселя (вы потеряете информацию ниже 1 пикселя). Как вы видите в коде токенизатора, чтобы преобразовать эти координаты bbox в их токенизированную версию, нам нужно сделать следующее:

  1. нормализовать координаты (сделать их между 0 и 1, разделив их на максимальное значение = 224)
  2. сделайте это: int(x * (num_bins-1))

поэтому преобразованная версия будет: (12, 35, 67, 119). Помните, что функция int() в Python не округляет число до ближайшего целого, а сохраняет только целую часть числа. Как видите, мы потеряли некоторую информацию о точном положении bbox, но это все еще очень хорошее приближение. Мы можем использовать большее количество токенов (количество бинов, как указано в документе), и у нас будет более точное местоположение. Наш токенизатор также имеет функцию decode(), которую мы будем использовать для преобразования последовательностей в координаты и метки bbox.

Функция подбора

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

Кодер

Наконец-то я добрался до самой крутой части для каждого любителя глубокого обучения: Модель 😍

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

Я буду использовать библиотеку timm для реализации предварительно обученной модели DeiT.

Уровень узкого места заключается в том, чтобы уменьшить количество функций этих вложений до количества функций декодера. В документе использовался размер декодера 256, и именно поэтому я уменьшаю его здесь, используя среднее объединение. Кроме того, первый токен в этой модели относится к токену CLS, и я пропускаю его в прямом методе (features[:, 1:]).

Декодер

Наш декодер берет вложения патчей входного изображения и учится предсказывать последовательность, содержащую bbox. Здесь я использую модуль PyTorch nn.TransformerDecoder для реализации 6-уровневого декодера с размерностью признаков 256. Нам также нужно добавить позиционные вложения к вложениям, чтобы модель знала о положении каждого токена в последовательность (я добавляю позиционное встраивание как для токенов кодировщика, так и для токенов декодера. Хотя мы должны сделать это для декодера, нам может не понадобиться добавлять их к токенам кодировщика, поскольку модель DeiT знает о порядке самих патчей). Я делаю это с помощью тех модулей nn.Parameter, которые изучают 1 параметр для каждой позиции токена. Наконец, мы будем использовать слой nn.Linear для предсказания следующего токена из нашего словаря.

Функция create_mask дает нам две маски, необходимые для обучения декодера: одну, чтобы указать модели игнорировать токены PAD и не включать их в свои модули внимания, и другую, чтобы маскировать будущие токены, чтобы сделать декодер предсказывает токены, только просматривая текущий и предыдущие токены.

Соединяем их вместе

Это простой класс, инкапсулирующий кодировщик и декодер. Он также имеет функцию predict, которая вызывает функцию прогнозирования декодера (не показана выше, мы увидим ее позже) для обнаружения объектов на изображении.

Обучение

Теперь давайте посмотрим, как мы можем обучить эту модель. Большая часть следующего кода представляет собой стандартный обучающий шаблон PyTorch, но в нем есть простой, но важный момент. Как упоминалось ранее, мы обучаем модель как языковую модель (например, GPT), и она работает следующим образом: модель должна предсказывать следующий токен, только видя предыдущие (токены слева). В начале он видит только предложение BOS, и ему нужно предсказать следующий токен, и так далее и тому подобное. И достигается это просто этой частью:

  • у_вход = у[:, :-1]
  • y_ожидаемое = y[:, 1:]
  • preds = модель (x, y_input)

Я обучил эту модель на Kaggle с использованием одного графического процессора и всего на 25 эпох за 6 часов. Это немного, но этого было достаточно, чтобы получить достойные показатели по оценочным показателям.

Наиболее распространенной метрикой для обнаружения объектов является Средняя точность (AP), о которой вы можете прочитать подробнее здесь. Документ получает AP 43 с магистралью ResNet50 после обучения на большом количестве данных в течение многих часов обучения. Я мог получить AP 26,4 на моем проверочном наборе с этой небольшой моделью и коротким временем обучения, что было здорово, поскольку это руководство о том, как легко реализовать эту статью, и я не стремился превзойти SOTA с этим!

Вывод

Теперь давайте посмотрим, как мы можем сгенерировать последовательность обнаружения с помощью этой модели для тестового изображения. Это метод предсказания класса Decoder. Он берет ранее сгенерированные токены, дополняет их до max_length и предсказывает следующий токен для каждой последовательности в пакете и возвращает эти новые токены.

Следующая функция generate() показывает упрощенную версию всего конвейера генерации последовательности → Сначала мы создадим пакет с формой (batch_size, 1), содержащий только токен BOS для каждого изображения в пакете. Модель берет изображения и эти токены BOS, а затем предсказывает следующий токен для каждого изображения. Мы берем предсказания модели, выполняем над ней softmax и argmax, чтобы получить предсказанный токен, и объединяем этот вновь предсказанный токен с предыдущим тензором batch_preds, который имел токены BOS. Затем мы повторяем этот цикл для max_len количество раз.

Наконец, мы декодируем предсказанные токены с помощью метода decode() нашего токенизатора. Вы можете посмотреть точную реализацию на моем GitHub, в блокноте Colab или блокноте Kaggle.

Полученные результаты

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

Наша модель лучше всего работает, когда на изображении есть 1-2 bbox, и ее производительность ухудшается, когда на изображении много объектов. На следующем изображении показаны некоторые случаи отказа нашей модели:

Заключительные слова

Надеюсь, вам понравился этот урок, и вы узнали что-то новое. Как всегда, я буду рад услышать ваши комментарии по этому уроку или ответить на любые ваши вопросы относительно бумаги и модели.

Хорошего дня!

Я сделал весь свой код доступным как Блокнот Google Colab и Блокнот Kaggle. Я также разместил весь проект и коды на моем GitHub.

Обо мне

Я Моейн Шариатния, студент-медик, разработчик машинного обучения и исследователь. Меня интересует обобщение модели глубокого обучения и перенос эффективности обучения для моего исследования. Я также люблю внедрять современные глубокие модели и понимаю, как они работают.

Моя Академия Google: https://scholar.google.com/citations?user=YLHsTOUAAAAJ&hl=en
Мой GitHub: https://github.com/moein-shariatnia