Я - большой фанат задачи детекции, она мне нравится по всем критериям. Она самая интересная концептуально - одновременно нужно и искать объекты, и определять их тип. Классификация целых изображений скучновата и не так часто применима на практике (по крайней мере в медицине), а сегментация мне кажется нудноватой - ну их, эти конкретные пиксели. Ещё статьи про детекцию - самые интересные для меня в техническом плане. Мне нравится разбираться в разных видах архитектур - anchor-based и anchor-free, one-stage и multi-stage, а ещё я очень люблю разные крутые идеи, которые улучшают тот или иной компонент детекционного пайплайна - например, PISA для умного взвешивания разных сэмплов в лоссе, Precise RoIPooling и Deformable RoIPooling для более точного и хитрого пулинга фичей, D2Det для декаплинга задач локализации и классификации, SoftNMS для замены традиционного NMS.
В 2020 году вышла крутая статья про новую архитектуру для детекции - DETR. Она меня очень вдохновила, и я тут же бросился впиливать её в проект Маммография (ММГ), тем более что код был с виду очень простой. После недели мучений я не смог выжать ничего адекватного - обучалось ужасно, долго и предиктило в основном фигню. Возможно, я где-то набаговал, но возиться дольше не хотелось.
Тем не менее, все три года идея всё-таки впилить DETR преследовала меня по пятам, тем более что за это время вышло несколько десятков статей, тем или иным образом улучшающим оригинальную архитектуру. И вот, в один прекрасный день я зачем-то решил прочитать вообще все статьи про DETRы, а заодно попробовать несколько вариаций в ММГ. Задача оказалась слегка сложнее, чем я ожидал…
Но я решил сдержать пацанское слово - и прочитал все статьи по DETR, которые смог найти, и большую часть из них даже смог понять.
![Когда дописал статью и нашёл ещё один DETR Когда дописал статью и нашёл ещё один DETR](https://habrastorage.org/getpro/habr/upload_files/0d7/261/51b/0d726151bad24617fe0d6d6a9e06b035.jpg)
Важное примечание - в этот гайд включены только статьи, которые решают задачу классической 2D-детекции. Никаких semi-supervised, 3D и так далее. Если захотите сделаю по ним отдельный пост… А во второй части (выйдет позже, если к этой статье будет хоть какой-то интерес) расскажу про свои эксперименты, покажу иллюстрации и порассуждаю про разные компоненты архитектуры.
Я постарался сделать описания в гайде более интуитивными и менее опирающимися на формулы, а также оставить побольше ссылок на конкретные места в коде разных архитектур. Если вам захочется копнуть в какую-то модификацию глубже, верхнеуровневое интуитивное понимание идеи поможет сделать это быстрее и легче.
DETR Original (2020)
5682 цитирования, 10.8к звёздочек
![](https://habrastorage.org/getpro/habr/upload_files/369/8ee/661/3698ee661db1740ea6edeb0b5bf64dce.png)
Давайте для начала разберёмся, какие преимущества имеет DETR перед классическими детекторами, а главное откуда они берутся? Начнём с краткого ревью самой архитектуры. Для совсем детального погружения можно также посмотреть вот такой видос от авторов статьи или пробежаться по Annotated DETR.
Как и в обычных детекторах, всё начинается с бэкбоуна, он может быть абсолютно любой - например, старый добрый ResNet-50. FPN и его аналоги в Детре не используются (хотя конечно же, есть и такие модификации).
CNN-фичи затем дополнительно обогащаются с помощью трансфомер-энкодера, который добавляет глобальный self-attention фичей друг на друга. Интуиция каждого слоя энкодера довольно простая - репрезентацию каждого "пикселя" (в дальнейшем "пиксель" в кавычках будет обозначать именно элемент фича-мапы, а не пиксель исходного изображения) мы обновляем с помощью информации с других релевантных "пикселей" изображения. Например, он может получить информацию от других частей того же самого объекта или от близлежащих областей, и эта информация впоследствии помогает более точной детекции.
Вообще говоря, это опциональная часть, ведь у нас уже есть CNN-фичи, но отсутствие энкодера согласно статье прилично ухудшает метрики. По логике авторов, self-attention позволяет лучше разделять объекты, особенно если они находятся близко друг к другу. Но есть варианты и без энкодера - например, в AdaMixer его убрали без потери качества.
Трансформеры инвариативны к пермутациям входных 1D-токенов, а мы не хотим терять “географическую” информацию о фичах. Значит, нужно добавить к фичам энкодинг локации - чаще всего используется синусоидальный. На картинке в левой части изображён пример одного из каналов энкодинга прямоугольного ММГ-изображения по x, а в правой - по y. Таких каналов присоединяется много - с различными температурами. Во второй части статьи детальнее покажу, как они выглядят.
![](https://habrastorage.org/getpro/habr/upload_files/4d2/9d1/210/4d29d1210b6af10e46c1f39f28994810.png)
Ключевые отличия от обычных детекторов начинаются в декодере. DETR не использует классические геометрические энкоры, вместо них используются некие “object queries”, которые состоят из двух частей:
Выучиваемые positional queries, которые отвечают за поиск объектов на разных частях изображения. В некотором смысле это гибкие выучиваемые энкоры, но прямой геометрической интерпретации DETR им не присваивает. На картинке (взята из доклада авторов DETR) каждая точка соответствует одному query. Видно, что средние предсказания каждого query довольно равномерно распределяются по изображению, так что они действительно по своей сути похожи на классические энкоры.
![](https://habrastorage.org/getpro/habr/upload_files/a1d/97a/e75/a1d97ae759967373c16c2235453d1009.png)
Content queries, которые постепенно формируются в ходе декодинга и отвечают за сбор визуальной информации о текущем объекте интереса. Перед первым слоем инициализируются как нулевые векторы.
Две эти части суммируются для того, чтобы получить полную текущую репрезентацию object query - по сути текущего состояния разных объектов. Обычно их используют от 100 до 900 в зависимости от конкретной модификации архитектуры.
Давайте сразу определимся с терминологией, которая будет работать на протяжении всего этого поста. В разных статьях под одними и теми же терминами часто понимаются разные вещи, так что это важно.
Когда я буду говорить об object query, речь будет идти о полной репрезентации каждого объекта - то есть, о сумме или конкатенации positional и content частей.
Positional query или spatial query или box query - часть object query, которая отвечает за энкодинг пространственной информации. В ванильном DETR и в некоторых других статьях она выучивается отдельно для каждого query и подаётся в декодер один раз в неизменном виде. Во многих модификациях эта часть обретает прямую геометрическую интерпретацию и уточняется по ходу работы декодера - например, с помощью предсказания оффсета (об этом подробнее в следующем разделе).
Если я буду упоминать content query, то это часть object query, которая обогащается визуальной информацией по ходу работы декодера. В оригинальном DETR инициализируется как нулевой вектор для каждого query перед первым слоём декодера. Именно по этим векторам предсказываются вероятности и координаты объектов, так что в коде их промежуточные и финальные состояния ещё называют decoder output или просто output.
Когда мне нужно будет говорить о queries, keys и values в модулях этеншна, они будут по традиции называться Q, K и V.
Итак, вернёмся к архитектуре. Внутри каждого слоя декодера есть два вида внимания:
Self-attention, который служит для обмена информации между этими самыми object queries. Раз это self-attention, то Q и K здесь одинаковые - это сумма content queries и positional queries. Для V positional-часть не добавляется - поскольку мы хотим обогатить инфой от других объектов только content-часть. Интересно, что self-attention на самом первом слое декодера бесполезен, ведь обмениваться ещё нечем (content-части нулевые), но его не убрали - то ли из лени, то ли чтоб не портить код.
Cross-attention. В этой части object queries смотрят на результат работы бэкбоуна и трансформер-энкодера и поглощают визуальную информацию. В качестве Q в данном случае также выступает сумма positional queries и content queries, а вот K и V другие - аутпут энкодера с positional embedding и без него соответственно. Таким образом, каждый object query производит некий софт-пулинг релевантных визуальных фичей из тех или иных частей изображения. Можно сказать, что этот модуль заменяет традиционный RoIPooling, только объекты могут считывать информацию со всего изображения, а не только из ограниченной области.
Основным аутпутом декодера являются преобразованные object queries, которые поступают в простенькую маленькую feed-forward network, которая генерит лоджиты классов и предсказанные координаты. Обычно такие предикты генерят после каждого слоя декодера и накладывают на них дополнительный лосс - это помогает стабилизировать и ускорить обучение. На графике ниже жёлтый, зелёный, синий и красный - это классификационный трейн-лосс по предсказаниям первого, второго, третьего и четвёртого слоёв декодера. Получается, что каждый следующий слой предсказывает объекты чуть точнее предыдущего, при этом выгода от каждого следующего слоя всё меньше и меньше.
![](https://habrastorage.org/getpro/habr/upload_files/e2a/391/b1d/e2a391b1d22ac0f332f842f974e5416e.png)
Последняя магия кроется в расчёте лосса, а точнее в матчинге GT-объектов и предсказаний. Вместо использования эвристик для матчинга (например, на основе IoU или IoR) мы с помощью комбинаторной оптимизации находим лучший one-to-one матчинг, который даёт минимальный возможный суммарный лосс. Этот лосс, как и в обычных детекторах, складывается из суммы лоссов классификации и локализации. Конкретно используются кросс-энтропия, L1 и Generalized IoU. Количество предсказаний (равное количеству object queries) почти всегда будет больше, чем количество реальных GT-объектов на картинке, поэтому “лишние” предсказания отправляются в класс “no object” или по старинке “background”. Лучший матчинг ищется с помощью функции linear_sum_assignment из scipy, которой на вход подаются лоссы всех возможных сматченных пар. Внутри этой функции и реализован тот самый венгерский алгоритм, который упоминается во всех статьях про DETR. Хотя, если быть совсем точным, то в последних версиях scipy используется не сам венгерский алгоритм, а его более быстрая модификация.
![](https://habrastorage.org/getpro/habr/upload_files/c49/6a9/238/c496a92383483985e925a43b3bb34b5c.png)
Итак, какие же преимущества даёт эта архитектура?
Взаимодействие object queries через self-attention декодера вкупе с использованием matching loss теоретически приводят к отсутствию дубликатов предсказаний, а значит отпадает нужда в затратной и не всегда хорошо работающей процедуре Non-Maximum Suppression. Забегая вперёд - на нашей задаче дубликаты предсказаний у меня встречались, поэтому попробовать накинуть какой-нибудь NMS всё-таки стоит.
Хотя self-attention изначально был задуман именно для дедубликации предиктов, он в теории также может моделировать отношения между объектами - например, если объекты двух классов почти всегда встречаются вместе, или присутствие одного объекта влияет на класс другого. К примеру, на маммограмах встречается признак "втяжение соска", который сигнализирует о возможным наличии злокачественного процесса.
Энкодер и cross-attention позволяют собирать визуальную информацию со всей картинки и прилично улучшают качество детекции больших объектов.
Cross-attention использует слой MultiHeadAttention, что помогает разделять задачи локализации и классификации. Для предсказания координат нужны границы объекта, а для классификации - фокусировка на семантически важных частях. В классических детекторах decoupling этих задач часто докидывает (например, в упомянутой в начале архитектуре D2Det).
Архитектура очень гибкая - например, кроме self-attention и cross-attention, мы можем добавить attention между разными картинками. Это может быть важно, если картинки связаны, что очень часто бывает в медицине. Например, часто мы имеем дело с разными проекциями одного и того же органа, снятыми под разными углами.
Раз поговорили о преимуществах, давайте сразу затронем и недостатки, ведь в дальнейшем речь пойдёт в основном о модификациях, которые их пытаются исправить.
Плохое качество на маленьких объектах. DETR использует только один скейл из бэкбоуна, который может имеет слишком маленькое разрешение для точной детекции маленьких объектов. Почему бы не прикрутить FPN и использовать разрешение повыше или вообще всю пирамиду фичей? Ответ простой и грустный - операции self-attention в энкодере и cross-attention в декодере очень чувствительны к размерности фичей. Например, в энкодере каждый “пиксель” фиче-мапы может этендится к каждому другому пикселю, что создаёт квадратичную зависимость от размера фичей. В маммографии мы подаём в сетку исходное изображение размером примерно 2000 на 1000 пикселей, а некоторые важные объекты при этом имеют размер не больше 20-30 пикселей, так что по фича-мапам 80x40 маленькие объекты точно локализовать сложно.
Очень долгие трейн-раны. Для достижения адекватных метрик DETRу нужно на порядок больше эпох, чем аналогичным классическим детекторам. В Цельсе мы и так обучаем сетки по несколько дней и даже недель, так что это критический недостаток.
Back to anchors
Одним из важных отличий Детра стал отказ от прямого использования геометрических priors - модель выучивала spatial queries прямо из данных. Увы, отсутствие прямой ассоциации между ними и конкретными локациями на изображении в итоге стало одной из причин медленного обучения сетки. Визуализация этеншн-мап разных object queries из cross-attention декодера (на картинке слева) показывает, что каждый query "смотрит" на обширную область, которая легко может содержать несколько объектов.
![](https://habrastorage.org/getpro/habr/upload_files/e30/a78/9a6/e30a789a624079d7b5a049c7d7a27389.png)
Целый ряд статей исследует разные формулировки positional-части object queries, которые позволяют им фокусироваться на конкретных частях изображения и ускорять обучение.
Deformable DETR (2020)
1932 цитирования, 2.2к звёздочек.
Очень важная статья в мире DETRов, основная идея которой используется во многих последующих модификациях. Эта идея направлена на решение сразу обеих упомянутых выше проблем с помощью Deformable Attention.
![](https://habrastorage.org/getpro/habr/upload_files/d6e/cba/133/d6ecba133eeaa4d8c487ef20da9151cb.png)
Ключевой вопрос - зачем нам собирать с помощью этеншна информацию со всех точек картинки? Скорее всего, каждому "пикселю" достаточно информации из своей окрестности и каких-то особенно важных точек из других мест картинки. Окей, давайте тогда разрешим этеншн только на фиксированное число точек - например, на четыре. Это сразу решает проблему квадратичной зависимости числа операций от размера картинки, превращая её в линейную. Как в энкодере и декодере выбирать эти точки?
В энкодере по репрезентации каждого “пикселя” будем предсказывать sampling offsets - то есть, из каких локаций сэмплить фичи, на которые он будет обращать внимание. Причём для каждой attention-головы локации сделаем свои. Одна голова может обращать внимание на ближайшие локации, а другая - на области вокруг объекта. Эта идея может быть вам знакома по Deformable Convoluton. Более того, если для каждой attention-головы сэмплить ровно одну точку, то Deformable Attention по сути и сводится к Deformable Convolution.
В декодере по каждому positional query (в коде разных DETRов эта часть в разных местах называется то query_embed, то query_pos) предсказывается reference point (по сути - центр энкора, соответствующего данному query), а по текущему состоянию object query - sampling offsets относительно этой точки референса. Из этих локаций (референс + оффсет) мы и будем сэмплить аутпут энкодера, релевантный для данного object query. Оффсет, естественно, предсказывается нецелочисленный, так что при сэмплинге используется функция grid_sample с билинейной интерполяцией.
Такая формулировка позволяет значительно снизить количество точек, для которых надо считать attention-веса. Это значит, что мы теперь можем собирать информацию сразу с нескольких уровней фиче-мап и не умирать от количества операций и требований к видеопамяти. Делается это несложно: мы просто суммируем информацию, полученную с разных уровней фиче-мап (и не забываем добавлять эмбеддинг уровня, ведь все фичи со всех уровней конкатятся в единый ряд токенов перед подачей в трансформер).
![](https://habrastorage.org/getpro/habr/upload_files/12c/c7f/333/12cc7f333dc40b88a15b59160344b712.png)
В статье предлагается ещё два важных механизма, которые тоже впоследствии будут использованы во многих модификациях:
Iterative Bounding Box Refinement. В ванильном Deformable DETR мы один раз перед декодером предсказываем референс-пойнты (две координаты) для каждого positional query. Есть идея получше - давайте перед первым слоем декодера предсказывать полноценный энкор (две координаты, ширина и высота коробки), а после каждого слоя его обновлять с помощью предсказанного оффсета. Таким образом, каждый следующий слой будет получать на вход всё более точный энкор. Предсказанные ширину и высоту также можно использовать, чтобы в cross-attention сэмплить точки, расположенные внутри бокса. Важная деталь - градиенту запрещается протекать от слоя i+1 к предыдущему через предсказанные коробки. Вместо этого градиент течёт только через промежуточные предсказания на каждом слое. Если непонятно - можно посмотреть на эту картинку, хотя, если честно, скорее всего, придётся копаться в коде, если хочется реально понять суть.
![](https://habrastorage.org/getpro/habr/upload_files/9f6/086/dcb/9f6086dcb64bb4b930153a3e49e7a538.png)
Two-Stage. Ещё одна идея, отсылающая к классическим детекторам. Обучаемые positional queries никак не зависят от текущего изображения. Вместо них мы можем использовать фичи энкодера для предсказания хороших локаций, где, скорее всего, находятся объекты. В общем, эдакий Region Proposal Network, предсказывающий proposals. Каждый “пиксель” фичи-мап пытается предсказать какую-то коробку и класс - если ещё точнее, то оффсеты относительно базового энкора вот с фиксированным для уровня размером стороны. Чем больше по размеру фича-мапа, тем меньше размер базового энкора. На получившиеся предикты для дополнительной супервизии накладывается такой же матчинг-лосс, как и для всей сетки. А top-k предсказанных энкодером коробок берётся в качестве исходных референсов для декодера.
Важно отметить, что уже в этой статье мы по факту возвращаемся к геометрической интерпретации object queries как энкоров. При использовании Iterative Refinement мы на каждом слое декодера предсказываем оффсет нового предсказания относительно предыдущего (энкора). Очень похоже на какой-нибудь Cascade-RCNN, но без фиксированных энкоров.
Есть ещё пара технических изменений, которые наследуются всеми последователями. Первое - отказ от явного "background"-класса. Вместо этого background-таргеты теперь кодируются как вектор нулей в one-hot энкодинге, а на лоджиты накладывается сигмоида и бинарная кросс-энтропия. Второе - изначальные content queries теперь не инициализируются нулями, а тоже выучиваются.
Статья очень важная - замена классического DETR на Deformable отлично сочетается вместе с другими модификациями и стабильно поднимает метрики.
Dynamic DETR (2021)
![](https://habrastorage.org/getpro/habr/upload_files/0e9/60e/a1a/0e960ea1ab7f221d7ebb387e7c624f8a.png)
95 цитирований.
Не самая популярная и интересная модификация, но раз уж взялся рассказать обо всех детрах…
Перед нами ещё одна попытка решить те же проблемы - плохую детекцию маленьких объектов и долгое неэффективное обучение. Как и в Deformable DETR, авторы задаются вопросом - как нам агрегировать фичи с разных скейлов и при этом не офигеть от вычислительных расходов? Предлагается две основных идеи. Первая - замена дорогого self-attention на ряд других операций, которые аппроксимируют этеншн на уровне пространства, скейлов и репрезентаций. Вторая - использование Box Encoding и RoI Pooling для ускорения обучения.
Начнём с трансформер-энкодера. Первая идея - давайте аппроксимировать дорогостояющую операцию attention с помощью конволюции. Для агрегации фичей с нескольких уровней апсэмплим результаты конволюции с предыдущего (меньшего по размеру) уровня и даунсэмплим результаты конволюции со следующего (большего) уровня:
![](https://habrastorage.org/getpro/habr/upload_files/777/3d5/e31/7773d5e31d89d1a0ceb76ad89591b452.png)
К сожалению, замена полноценного на self-attention на локальную конволюцию с маленьким kernel size в общем-то убивает всю суть трансформер-энкодера. Не проще ли при таких раскладах навесить на фичи какой-нибудь FPN? Но ребята не сдаются - и предлагают заменить обычную конволюцию на deformable, что позволит собирать информацию из разных регионов всех уровней фиче-мап. Важное замечание: оффсеты для deformable convolution предсказываются только один раз, для текущего центрального уровня, и переиспользуются для соседних. Это сделано, чтобы избежать ситуации, когда мы суммируем фичи разных уровней с абсолютно разных и несвязанных друг с другом локаций.
![](https://habrastorage.org/getpro/habr/upload_files/f85/0e1/f7f/f850e1f7fefeac3e7672de98dc4337f8.png)
Окей, deformable convolution позволил нам получить некий пространственный этеншн, который ещё и собирает инфу с соседних уровней пирамиды фичей. Следующий этап - получить этеншн-веса для разных уровней новой пирамиды. Для этого используем нечто вроде Squeeze-and-Excitation. К сожалению, к этой статье нет ни приложений, ни кода, что затрудняет подробный анализ деталей имплементации. Наконец, последний "этеншн" выполняется с помощью Dynamic ReLU, интересной функции активации, которая может принимать различные формы в зависимости от входных данных:
![](https://habrastorage.org/getpro/habr/upload_files/c13/20b/30d/c1320b30d606a7c8f998f7267a1a9f81.png)
Причём параметры этих зависимостей могут шариться между каналами и локациями, а могут выучиваться независимо:
![](https://habrastorage.org/getpro/habr/upload_files/e3a/e1b/e12/e3ae1be1218eb6d6f150062c4f364095.png)
В декодере тоже творится много интересного. Self-attention остался без изменений, а вот cross-attention полностью изменён:
Инициализируем так называемый Box Encoding для каждого positional query - в начале обучения делаем его равным всей картинке: [0, 0, 1, 1]. По сути, это обучаемый анкор для каждого query, который будет изменяться по ходу обучения.
Делаем классический RoiPooling из фичей энкодера из региона Box Encoding.
Генерируем динамические конволюционные фильтры с помощью простого линейного слоя, наложенного на object queries.
Генерируем cross-attention между спуленными фичами и object queries с помощью 1x1-конволюции.
Полученные обновлённые content queries можно по традиции пропустить через feed-forward networks, чтобы получить обновлённый box encoding. Таким образом, от слоя к слою декодера мы будем получать всё более точный box encoding для объектов на изображении.
В принципе это всё. Кода я не нашёл, особого ажиотажа в плане развития предложенных идей статья не вызвала, но из неё я, например, узнал про Dynamic ReLU. А включил статью я в эту секцию из-за RoI-attention, который по сути использует прямые энкоры.
Anchor DETR (2021)
290 звёздочек.
![](https://habrastorage.org/getpro/habr/upload_files/a94/cc8/081/a94cc8081019c204e6bb765f46b7c7a4.png)
Ещё одна работа, которая напрямую возвращает DETR-модели к идеологии энкоров. Каждый positional query здесь отвечает за определённую точку на изображении, и предсказывает объекты вблизи неё. Поскольку примерно в одном месте может находиться несколько объектов, авторы вносят такое изменение - один query теперь имеет несколько так называемых паттернов, а каждый паттерн может предсказать один объект. Для снижения расходов по памяти предлагается особая схема этеншна Row-Column Decouple Attention (RCDA).
Координаты энкоров (они могут быть фиксированными заранее или выучиваемыми) и координаты точек на фиче-мапах прогоняются через один и тот же Position Encoder - в данном случае это маленький MLP. Дополнительно мы инициализируем эмбеддинги трёх "паттернов", которые шарятся между всеми object queries. Итого positional-часть каждого object query формируется как сумма positional-энкодинга энкора и эмбеддинга паттерна. Если у нас есть 100 энкор-точек и 3 паттерна, то итого мы получим 300 уникальных object queries.
Теперь поговорим о RCDA. Вспомним, что в качестве ключей в self-attention энкодера и cross-attention декодера у нас выступают фичи картинки из энкодера. Основная идея RCDA, как можно понять из названия - делать отдельный этеншн по строкам и по столбцам этих фиче-мап. Для желающих - тут можно поразбираться в деталях имплементации. Если кратко - мы делаем усреднение по двум измерениям, чтоб получить ключи строчек и ключи столбцов, а в качестве queries берём энкодинг соответствующей координаты энкора.
Дальнейшего развития идея не получила, в комбинации с другими трюками встречается редко.
Conditional DETR (2021) и Conditional DETRv2 (2022)
201 цитирование, 295 звёздочек.
![](https://habrastorage.org/getpro/habr/upload_files/fa5/cc7/43c/fa5cc743cb5e3bbc298b1298940d3fa3.png)
Вы не поверите - мы снова пытаемся пофиксить долгое обучение классического DETRа. В оригинальной статье про DETR были проведены разные ablation studies - в том числе, про positional queries. Оказывается, что если прибавлять их к Q и K только на входе декодера, а не в каждом слое, то AP падает всего на 1.4 единицы. А positional-веса этеншна, если обучать DETR 50 эпох, не очень хорошо соответствуют границам объекта.
![](https://habrastorage.org/getpro/habr/upload_files/e2e/cdf/b42/e2ecdfb424aa1f874831d76c80463591.png)
Короче говоря, positional queries халявят и оставляют большую часть работу на content queries, которые учатся довольно долго. Для решения этих проблем предлагается сделать positional query адаптивными к изображению, то есть зависящими (тот самый conditional из названия) от текущего состояния content queries. Давайте разбираться, как это устроено.
Как и в предыдущих работах, здесь используются референсные точки - центры объектов, которые предсказываются по выучиваемому вектору каждого positional query. Эти точки затем пропускаются через синусоидальный энкодинг для получения spatial-части query. После этого и начинается та самая conditional часть - мы скейлим этот энкодинг, умножая его на вектор, предсказанный из текущего состояния content query (кроме первого слоя, потому что там ещё нет никакой информации о картинке). То есть, позиционный энкодинг на каждом этапе начинает зависеть и от content-части query. Зачем это нужно?
![](https://habrastorage.org/getpro/habr/upload_files/613/595/00f/61359500f9cd82ac68d82e2d87ccb0e3.png)
Если мы посмотрим на формулу (сверху) финального предсказания координат боксов, то увидим, что помимо референс-точки (s) в ней используется аутпут декодера (f), а это значит, что он явно содержит в себе информацию о расположении экстремальных точек объекта. Получается, что для получения корректной positional-репрезентации query (в v2 их называют box query) нам нужно сделать её зависимой от content-части.
После этого мы не суммируем, а конкатенируем content и positional-части, и делаем то же самое для фичей из энкодера. Итого - мы явным образом разделили cross-attention на две составляющие, при этом добавили зависимость positional от content. На картинке ниже в первом ряду показаны карты весов этешн positional-части, на второй - content-части, а на третьей - суммарные.
![](https://habrastorage.org/getpro/habr/upload_files/e48/341/b42/e48341b42086818b463ff7c1307ebe32.png)
Во второй версии архитектуры авторы решают вовсе отказаться от выучиваемых positional queries, по которым предсказывались референс-точки. Вместо этого они предлагают что-то очень похожее на two-stage из Deformable DETR, хотя и отмечают, что делают это с другой целью. Берётся аутпут энкодера, и по каждому "пикселю" предсказывается вероятность, что там находится объект. Топ-k точек забираем как референсы и перегоняем их энкодер-репрезентации в box queries.
Второе нововведение - инициализация content queries не нулевыми векторами или выучиваемыми фиксированными эмбеддингами, а с использованием того же аутпута энкодера.
Наконец, они придумывают свой row-column этеншн, который выглядит очень похоже на своего собрата из Anchor DETR.
DAB DETR (2022)
131 цитирование, 374 звёздочки.
![](https://habrastorage.org/getpro/habr/upload_files/f63/f41/35c/f63f4135ca2a761f458a86ef9a52c4dc.png)
Почти все описанные статьи работают с 2D-точками в качестве референсов. Исключение - Deformable DETR с iterative box refinement. DAB DETR предлагает вернуться к классическим старым добрам энкорам, которые кодируются четырьмя числами - координатами верхнего левого угла, шириной и высотой коробки. Ширина и высота энкора необходимы для более корректного софт-пулинга фичей энкодера. Подобно механизму iterative box refinement, от слоя к слою декодера мы можем всё точнее предсказывать координаты и размеры коробки и пулить фичи всё более точно.
В предыдущих работах мы либо предсказывали референс-точки по positional queries как в Deformable DETR или Conditional DETR, либо фиксировали их и переводили в эмбеддинги как в Anchor DETR. Авторы DAB DETR утверждают, что никакой другой полезной информации кроме координат бокса в positional query и не содержится. А зачем тогда вообще использовать какие-то positional queries, если их можно заменить на конкретные обучаемые энкоры?
Сказано - сделано, каждый энкор теперь - это выучиваемый вектор, состоящий из четырёх компонент - координаты центра, высота и ширина. Теперь каким-то нужно информацию о них прокинуть в self- и cross-attention декодера. Для self-attention всё просто - энкодим каждый энкор синусоидально и пропускаем через MLP, а затем складывем с content-частью. Cross-attention заимствует идею из Conditional DETR - всё тот же синусоидальный энкодинг, который затем домножается на так называемый рескейл-вектор, предсказанный по content-части, и конкантенируем с content-частью.
Раз positional queries теперь имеют вполне конкретную интерпретацию (координаты центра и размеры коробки), то вполне логично их делать более точными от слоя к слою, предсказывая оффсеты и накладывая стандартный matching loss.
Последнее важное изменение DAB DETR - использование ширины и высоты энкора для модулирования cross-attention, которая позволяет учитывать его размер для получения более сфокусированных этенш-мап.
![](https://habrastorage.org/getpro/habr/upload_files/df4/58b/013/df458b013417ddbe67ee79a19aafc7dc.png)
Для этого синусоидальный эмбеддинг энкора мы домножаем на отношение предсказанного размера объекта (например, ширины) и текущего размера энкора. Если объект сильно отличается от энкора, то prior на веса будет соответствующим образом скорректирован. Правда, авторы, кажется, сами не до конца понимают, почему это нормально работает, но какая разница? =)
![](https://habrastorage.org/getpro/habr/upload_files/03b/7f0/f19/03b7f0f19d70acc50b257dad8ee2ddd6.png)
Подводя итог, главная идея архитектуры - простая, интерпретируемая и хорошо сочетается с другими модификациями - например, с тем же Deformable DETR.
DETR SMCA (2021)
146 цитирований, 145 звёздочек.
![](https://habrastorage.org/getpro/habr/upload_files/4df/ca7/5b0/4dfca75b0f8e6052dd0c289eb4a391ec.png)
Идея Spatially Modulated Co-Attention, как понятно и из названия, как раз заключается в модуляции этеншн-весов. Логично предположить, что вероятность того, что близлежащие локации окажутся важнее дальних, в большинстве случаев выше. Так что можно домножать этеншн-мапы на spatial prior, чтобы ближайшие локации автоматически получали больше веса.
Работает это всё не очень сложно:
Перед первым слоем декодера по каждому object query предсказывается референс-точка (центр объекта).
Перед каждым слоем декодера по каждому object query предсказывается оффсет относительно референс-точки.
Чем дальше каждая точка фиче-мапы от предсказанной точки (рефренс + оффсет), тем меньше исходный вес ей присваивается. Если точнее, итоговая карта весов из этенешна складывается с этими исходными значениями, и уже на эту сумму накладывается софтмакс. В формуле ниже c - это координаты предсказанной точки, s - предсказанные размеры объекта, β - гиперпараметр, i и j - координаты “пикселя” на фиче-мапе.
![](https://habrastorage.org/getpro/habr/upload_files/694/c44/404/694c44404be993f33f491ccbeaa10ba7.png)
Ещё в статье упоминается адаптация SMCA под multi-scale фичи. Я хотел в ней разобраться и скачал ZIP-архив с имплементацией, который лежал в репе, и внутри увидел это:
![](https://habrastorage.org/getpro/habr/upload_files/278/7e2/ac6/2787e2ac61df7ea44e76e9de0d3a350f.png)
Пожалуй, оставлю разбор в качестве домашнего задания. В сухом остатке SMCA докидывает и даже используется в качестве дополнительного улучшайзера в некоторых других статьях.
Efficient DETR (2021)
52 цитирования.
![](https://habrastorage.org/getpro/habr/upload_files/03e/638/d99/03e638d9943a9488821cb3e389312f11.png)
Во всех модификациях DETRа по умолчанию используется 6 слоёв энкодера и 6 слоёв декодера - авторы оригинальной статьи в своём ablation study показали, что такое количество даёт оптимальные результаты. А почему так? Почему не хватает пары слоёв? Авторы Efficient DETR винят рандомные инициализации positional queries (DETR) или референс-точек (Deformable DETR).
![](https://habrastorage.org/getpro/habr/upload_files/cbe/21e/aa6/cbe21eaa6f7f91cad1373ab19a7ad670.png)
Как видно из картинки, вне зависимости от способа инициализации исходных референс-пойнтов, на 6 слое декодера они приходят к практически одинаковым позициям. Это наводит на мысль, что если сразу похитрее инициализировать изначальные точки, то несколько слоёв декодера можно безболезненно срезать. А как получить хорошие начальные точки? Да просто навесив RPN на фичи энкодера.
Так-так, а в чём ключевые отличия архитектуры от two-stage Deformable DETR?
Content queries инициализируются как фичи энкодера из соответствующей позиции. То есть, каждому query перед первым слоём декодера у нас соответствует предсказанный энкор и визуальные фичи из энкодера. Аналогичная идея впоследствии была применена в Conditional DETR v2
![](https://habrastorage.org/getpro/habr/upload_files/d7d/c97/07c/d7dc9707c9f77294a80cff9afd39dbc9.png)
Постепенно снижают число queries с 300 до 100 по ходу обучения.
Показывают, что такая инициализация позволяет уменьшить количество слоёв декодера аж до 1 без особой потери качества.
Официальной имплементации я не нашёл, но нужные куски можно найти в репозитории Sparse DETR - вот инициализация queries с помощью фичей энкодера.
Team DETR (2023)
13 звёздочек.
![](https://habrastorage.org/getpro/habr/upload_files/89f/d9d/4c2/89fd9d4c2194f52b025ff68504f2f43a.png)
В этой достаточно свежей статье тоже работают с привязкой object queries к геометрии. Авторы берут за основу DAB-DETR и показывают, что каждый энкор (object query) в этой архитектуре предсказывает достаточно разнообразные коробки. Да, они сосредоточены вокруг энкора, но разброс по размеру весьма значительный (верхний ряд на картинке).
![](https://habrastorage.org/getpro/habr/upload_files/e36/06c/ea2/e3606cea20f2d7418a84a732eb9f18fe.png)
Предлагается простая идея - queries делятся на группы, и каждая группа отвечает за предсказание объектов своего скейла. Например, при разделении на 4 группы будут такие диапазоны скейлов (относительно размера изображения) - (0, 0.25], (0.25, 0.5], (0.5, 0.75], (0.75, 1.0). Детали имплементации:
Маска для self-attention создаётся таким образом, что группы не могут обмениваться информацией.
Размер изначальных энкоров инициализируются не случайно, а с учётом скейла группы.
Венгерский матчинг при обучении тоже происходит внутри групп.
К лоссу добавляется компонент, который наказывает queries за предсказания, которые находятся слишком далеко от соответствующего энкора.
Самый интересный нюанс - на валидации для каждого query вычисляется средний предикт, и этот предикт становится новым энкором.
Вообще статья интересная, и имплементация несложная. Меня немножко смутил одушевляющий лексикон - “query’s personality”, “capabilities of each team member”. Вообще я когда-то писал, что такие метафоры могут помочь пониманию, но в данном конкретном случае выглядит странновато.
AdaMixer (2022)
Код (предупреждаю - паршивая структура mmdetection)
28 цитирований, 204 звёздочки.
![](https://habrastorage.org/getpro/habr/upload_files/871/0e1/d30/8710e1d302df2d6e1fa8e1380fdb4ff1.png)
AdaMixer показывает очень неплохие результаты и является сильным бейзлайном в нескольких других статьях. Основная идея заключается в том, что процесс декодирования должен быть более адаптивным. Давайте попробуем раскрыть, что это значит.
Positional query в AdaMixer состоит из четырёх частей - x, y (координаты центра), z (логарифм размера), r (соотношение сторон). Из них при необходимости легко можно получить стандартное представление коробок - координаты центра, ширина, высота.
Первая задача архитектуры - сделать так, чтоб декодер сэмплил фичи энкодера с учётом обеих частей object query - positional (включая размер объекта) и content. В классических детекторах энкоры маппятся на конкретный уровень фича-пирамиды в зависимости от их размера. Здесь решили поступить хитрее и элегантнее. Сначала фича-мапы располагаются в пространстве по третьей координате z. Затем мы берём content query и с помощью линейного слоя предсказываем три набора оффсетов - по x, y и z, и добавляем их к точкам из positional queries. Наконец, из полученных точек можем засэмплить фичи. Сначала на каждом уровне пирамиды делается стандартный сэмплинг с помощью grid_sample по x и y, а затем фичи с каждого уровня сэмплируются с учётом гауссовских весов, рассчитанных по предсказанной координате по z. Сэмплирование производится по группам, чтобы разнообразить набор точек, из которых производится сэмплирование. Итоговые фичи зависят от обеих частей object query, а ещё и учитывают размер объекта благодаря трюку с сэмплированием по z.
![](https://habrastorage.org/getpro/habr/upload_files/0ea/035/630/0ea03563087ea8eca75af406c5733afb.png)
Сэмплировать фичи - это только первый шаг, их ещё нужно декодировать в объекты. В DETR и Deformable DETR фичи энкодера в этеншн-механизме просто проецируются линейным слоем, и эта проекция не зависит от текущего состояния object queries. AdaMixer вместо стандартного cross-attention использует механизм Adaptive Mixing.
![](https://habrastorage.org/getpro/habr/upload_files/053/55b/625/05355b625f41aed41a8b04d48af82dc7.png)
Adaptive Mixing состоит из двух частей. Сначала производится channel mixing. Content queries линейно проецируются в матрицу параметров размером CxC, где C - количество каналов. Фичи энкодера умножаются на эту матрицу, тем самым происходит смешивание фичей между каналами. Матрица рассчитывается независимо для каждой группы фичей, но шарится для всех пространственных точек.
Следующий шаг - spatial mixing. Аналогично, по content queries рассчитывается адаптивная матрица весов, с помощью которой производится миксинг фичей по пространственному измерению. Полученные фичи проецируются в размерность content queries и складываются с ними же, чтобы получить финальный результат работы слоя. Новые фичи пропускаются через FFN, а затем по ним предсказываются оффсеты для positional queries (x, y, z, r) и вероятности классов.
Последнее, что стоит отметить - это то, каким образом в self-attention прокидывается positional-информация. Помимо стандартного синусоидального энкодинга всех четырёх частей используется информация о том, насколько пересекаются коробки, соответствующие каждой паре object queries. Чем меньше пересекаются две коробки, тем более жёсткий отрицательный базовый вес присваивается этой паре. В случае, если коробка i содержится внутри коробки j, то штрафа нет. Таким образом, облегчается дедупликация предсказаний. Чтобы сеть могла всё-таки моделировать и отношения между непересекающимися объектами, штраф домножается на скаляр, который выучивается отдельно для каждой головы.
![](https://habrastorage.org/getpro/habr/upload_files/6b0/446/116/6b0446116060dc74ad5344312477844e.png)
Вот в общем-то и всё. Итоговая сетка бьёт DETR, Deformable DETR и Sparse-RCNN.
Конец первой части. Вторая часть скоро выйдет на Хабре. Не забудьте подписаться на Варим ML в ТГ!