Эта статья — вторая в серии, в которой я подробно объясняю, как работает модель YOLOX (You Only Look Once X). Если вас заинтересовал код, вы можете найти ссылку на него ниже:



Эта серия состоит из 4 частей, посвященных алгоритму YOLOX:

Даркнет-53 — Магистраль YOLOX

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

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

Основное изменение YOLOv3 заключается в том, что он использует большую магистраль под названием Darknet-53. (Магистраль — это просто очень неспециализированная структура, через которую изначально передаются данные). Архитектура магистрали использует свертки 1 × 1, остаточные соединения и свертки 3 × 3, чтобы сделать очень мощный экстрактор признаков. Эта магистраль также используется YOLOX и имеет следующую архитектуру.

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

Голова модели YOLOv3 в основном такая же, как и модель YOLOv1. В нашем случае разница между ними не имеет значения, так как YOLOX полностью меняет голову модели. Помните, что окончательные прогнозы модели YOLOv1 были в основном массивным трехмерным тензором, где длина и ширина были разными прогнозами, а глубина была разными характеристиками прогноза.

Один вопрос, который у меня возник при чтении документа YOLOv3, заключался в том, как мне получить функции из магистрали Darknet, поскольку выходные данные проходят через слой softmax? Что ж, в модели используется нечто, называемое Feature Pyramid Network (FPN). Сеть функциональных пирамид извлекает информацию из изображения с разными аспектами (разной ширины и высоты). Чтобы сделать это с Даркнетом, мы берем переходные состояния из модели и используем их как несколько выходов вместо одного выхода, поступающего с конца сети. Ниже представлена ​​схема того, как это работает.

По сути, магистраль Darknet-53 (которую я теперь могу называть FPN) выводит три разных прогноза в разных масштабах:

  1. 256 каналов (выход первого перехода)
  2. 512 каналов (второй переходный выход)
  3. 1024 канала (третий переходный выход)

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

Голова YOLOv3 против головы YOLOX

Хотя магистраль YOLOv3 и магистраль YOLOX одинаковы, модели начинают отличаться от их головок. Ниже приведено изображение, показывающее разницу между двумя головками.

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

На этой диаграмме показано, что вход как в головку YOLOv3, так и в головку YOLOX — это 3 выхода из магистрали FPN (даркнет) в трех разных масштабах — 1024, 512, 256 каналов.

Выход двух головок практически одинаков с размерами (В × Ш × характеристики), что точно такое же, как у оригинального YOLO. Разница между двумя головками заключается в том, что YOLOv3 использует связанную головку, а YOLOX использует развязанную головку. Таким образом, выход YOLOX на самом деле представляет собой 3 тензора, каждый из которых содержит разную информацию, вместо 1 массивного тензора со всей информацией.

Выходные данные трех тензоров YOLOX содержат ту же информацию, что и выходные данные массивного тензора YOLOv3:

  1. Cls: класс каждой ограничивающей рамки
  2. Reg: 4 части ограничивающей рамки (x, y, w, h)
  3. IoU (Obj): по какой-то причине авторы используют IoU вместо Obj, но этот вывод показывает, насколько сеть уверена в том, что в ограничивающей рамке есть объект (объектность).

Как и в случае с исходным выводом, каждый «пиксель» по высоте и ширине вывода представляет собой другое предсказание ограничительной рамки. Итак, существует H*W разных прогнозов.

Выходы, перечисленные выше, предназначены только для одного выхода в FPN. Помните, что есть три выхода из FPN, которые подаются на головки YOLOv3 и YOLOX. Это означает, что на самом деле есть три разных выхода от каждой из головок вместо 1. Таким образом, выход YOLOv3 на самом деле (3 × В × Ш × функции), а выход YOLOX — фактически 3 каждого из Cls, Reg, и выходы IoU (obj), что дает 9 итоговых выходов.

Переход на модель без привязки

Одно из наиболее важных изменений, внесенных YOLOX, заключалось в том, что якоря не использовались, тогда как YOLOv3 в значительной степени полагается на якоря.

Что такое якорь?

Якорь — это, по сути, предопределенная форма ограничивающей рамки, которая помогает сети. Вместо предсказания прямой ограничивающей рамки предыдущие алгоритмы YOLO предсказывали смещение от предопределенной рамки привязки. Представьте, что якорная рамка имеет длину и ширину 100 и 50, в то время как модель предсказала длину и ширину 10 и 15. Окончательный прогноз ограничивающей рамки будет смещением от якорной рамки с длиной и шириной 110 и 65. Подробнее информацию о якорных ящиках можно найти в этой беседе.

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

Проблема с блоками привязки

Блоки привязки в основном являются дополнительными параметрами. Сколько якорей должна использовать модель? Каковы должны быть размеры анкеров? Эти вопросы ведут к большей настройке гиперпараметров и меньшему разнообразию модели.

Как YOLOX решает проблему с блоком привязки?

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

Шагать

YOLOX был основан не только на YOLOv3, он также был основан на FCOS, которая является еще одной моделью ограничивающей рамки, но она не является частью серии YOLO, что делает ее не очень крутой.

FCOS использует шаг, чтобы помочь модели. Представьте, что модель должна научиться предсказывать ограничивающую рамку в любом месте от левого верхнего угла изображения в 0,0 до правого нижнего края изображения в 1024,1024. В дискретном пространстве у модели есть 1048576 возможных местоположений для прогнозирования, и она, вероятно, не сможет ничего узнать из-за такого широкого диапазона прогнозов.

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

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

Приведенные выше сетки можно создать, определив определенный шаг, который является расстоянием между каждой из точек пересечения на сетке. В алгоритме YOLOX для каждого уровня FPN используются шаги 32, 16 и 8 соответственно. Если на изображении размером 256×256 используется шаг 32, то всего будет 256/32 = 8 точек пересечения в каждом измерении, всего 64 точки пересечения.

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

На следующем изображении есть наложение сетки для изображения медведя:

Каждая точка пересечения на изображении называется точкой привязки. Не путайте это с якорем, описанным ранее, поскольку этот тип якоря немного отличается. Точка привязки — это смещение для перемещения местоположения прогноза по осям x, y, в то время как привязка, описанная ранее (от которой YOLOX избавляется), представляет собой предопределенное поле, которое используется в качестве смещения для частей w, h прогноза. Блоки привязки плохи, поскольку они являются дополнительными гиперпараметрами для настройки, в то время как точки привязки хороши, поскольку они не включают дополнительные параметры, которые нам нужно настраивать.

Примечание. Отныне, когда я говорю «якорь», я имею в виду местоположение в сетке, которое использует YOLOX, а не предопределенную ограничивающую рамку, которую использует YOLOv3.

Расположение якоря на изображении можно получить по следующим формулам:

x = s/2 + s*i
y = s/2 + s*j

Где s — шаг, ii-я точка пересечения на оси x и j — это j-я точка пересечения на оси y.

Для YOLOX мы используем точки сетки как верхнее левое смещение ограничительной рамки. Следующие формулы используются для сопоставления прогнозируемой ограничивающей рамки (p_x, p_y, p_w, p_h) с фактическим местоположением на изображении (l_x, l_y, l_w, l_h), если (x, y) является точкой пересечения на сетке, которая прогноз принадлежит и s — это шаг на текущем уровне FPN:

l_x = p_x + x
l_y = p_y + y
l_w = s*e^(p_w)
l_h = s*e^(p_h)

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

Например, давайте вернемся к изображению медведя с шагом 32. Если точкой привязки для этого предсказания было (i, j) = (2, 1), что означает пересечение точка 2 на оси x и 1 на оси y, я бы смотрел на следующую точку на изображении:

Примечание. Точка находится в (2, 1) на сетке, но по пикселям она находится в:

x = 32/2 + 32*2 = 16 + 64 = 80
y = 32/2 + 32*1 = 16 + 32 = 48

Если модель дала мне прогноз (20, 15, 0,2, 0,3), то мы можем рассчитать поле как:

l_x = 20 + 80 = 100
l_y = 15 + 48 = 63
l_w = 32*e^(0.2) = 39
l_h = 32*e^(0.3) = 43

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

Назначение метки

Не все прогнозы одинаковы. Некоторые из них явно мусор, и мы даже не хотим, чтобы наша модель их оптимизировала. Чтобы различать хорошие и плохие прогнозы, YOLOX использует что-то под названием SimOTA, которое используется для динамического присвоения меток.

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

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

С другой стороны, положительные ярлыки действительно важны. Как узнать, какую ограничивающую рамку правды мы хотим оптимизировать для каждого якоря? (Примечание: наземная правда означает ограничивающую рамку, которую мы хотим, чтобы модель предсказывала). SimOTA не просто назначает положительные/отрицательные метки, но также назначает ограничивающие рамки наземной истины каждому положительно помеченному якорю на изображении. Эта ограничивающая рамка истинности очень важна для оптимизации модели.

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

Модель YOLOX имеет три выхода, и каждый выход имеет свою собственную функцию потерь, поскольку их необходимо оптимизировать по-разному.

Оптимизация класса

Согласно модели YOLOX, выходные данные класса имеют следующую форму: В×Ш×C. Таким образом, для каждого прогноза модель прогнозирует вектор из элементов C.

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

Чтобы оптимизировать это, мы можем использовать вектор с горячим кодированием, который кодирует класс ограничивающей рамки наземной истины для каждого якоря/прогноза. Горячий вектор содержит C элементов для каждого прогноза. 1 в горячем векторе идет в местоположении класса, который мы хотим, чтобы модель предсказывала, в то время как 0 размещается во всех других местах. Например, если бы у меня было четыре класса и я хотел, чтобы модель предсказывала второй, векторы могли бы выглядеть следующим образом

pred1: [0.45, 0.25, 0.05, 0.25] # The model is most confident in the 1st class
pred2: [0.25, 0.25, 0.25, 0.25] # The model is not confident in any class
pred3: [0.1, 0.7, 0.1, 0.1] # The model is very confident in the second class
labels: [0, 1, 0, 0] # 1 in the second location which is what we want the model to predict

Чтобы оптимизировать эти предсказания, мы можем поместить как предсказания (формы H×W×C), так и метки истинности (также формы H×W×C) через Binary Cross Entropy (BCE) > функция потерь. В частности, мы проводим все положительные прогнозы через BCE с логитами, что является причудливым способом сказать, что прогнозы проходят через сигмоид, а затем проходят через функцию BCE.

Примечание. Прогнозы с отрицательными метками не используются в этом проигрыше.

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

Регрессионная оптимизация

Оптимизация выходных данных регрессии (предсказание ограничивающей рамки) немного сложнее, чем выходных данных класса. Помните, что форма выходных данных регрессии — H×W×4, где каждый прогноз равен (x, y, w, h).

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

Пересечение через объединение (IOU)

Чтобы решить эту проблему, YOLOX использует оценочную метрику, называемую IoU.

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

Давайте подробнее рассмотрим IoU. Во-первых, пересечение никогда не может быть больше объединения, а наименьшее пересечение равно 0, что дает нам следующее ограничение:

0 ≤ I ≤ U

Когда нет пересечения, объединение будет площадью обоих прямоугольников вместе взятых (A₁+A₂), а когда прямоугольники пересекаются на 100%, объединение будет площадью одного из прямоугольников (A₁), что дает нам следующее ограничение :

A₁ ≤ U ≤ A₁+A₂

Таким образом, по мере роста пересечения IoU приближается к 1, потому что пересечение и объединение сходятся к одному и тому же значению. По мере уменьшения пересечения IoU приближается к 0, потому что объединение растет, делая числитель меньше, а знаменатель больше. Итак, IoU имеет следующие значения:

0 ≤ IoU ≤ 1

На самом деле мы хотим максимизировать значение IoU, поскольку мы хотим, чтобы пересечение содержало оба блока полностью. Проблема заключается в том, что градиентный спуск минимизирует потери, поэтому потеря IoU берется за 1-IoU, что дает нам те же значения, но перевернутые. Таким образом, потери имеют более высокое значение, когда пересечение ближе к 0%, и более низкое значение, когда пересечение ближе к 100%.

Оценка результатов регрессии

Для оценки результатов регрессии мы фактически используем Generic Intersection Over Union (GIoU). GIoU похож на IoU, за исключением того, что он имеет значения от -1 до 1. Проблема с IoU (значение, а не потеря) заключается в том, что ящики с IoU, равным 0, не содержат никакой дополнительной информации. Существует широкий спектр IoU со значением 0, поэтому GIoU исправляет это. Идея та же, за исключением того, что она кодирует больше информации и позволяет использовать более плавную функцию, которая имеет ненулевое значение, когда IoU будет равно 0.

Чтобы оптимизировать модель, мы минимизируем потери GIoU напрямую, беря сумму по всем положительным прогнозам и минимизируя эту сумму.

Примечание. Прогнозы с отрицательными метками не используются в этом проигрыше.

Задолженность/объектность Потери

Мы хотим, чтобы показатель объектности модели был близок к 1, если она думает, что в коробке есть объект, около 0, если она не думает, что в коробке что-то есть, и где-то посередине (надеюсь, около 0,5), если она не уверена. .

Итак, нам нужна функция в диапазоне от 0 до 1, которая ближе к 1, когда ограничивающая рамка полностью закрывает объект, и к 0, когда объект вообще не закрывает объект. Идеальной функцией для этого является IoU. В частности, мы хотим использовать IoU, а не GIoU, поскольку диапазон GIoU равен 2, а диапазон IoU — 1.

Подобно функции потери класса, мы будем использовать BCE для оптимизации прогнозов объектности. Для оптимизации одного прогноза есть две возможности:

  1. Легкие прогнозы для рассмотрения — это те, которые помечены как положительные.
  2. Второй набор прогнозов, которые мы должны рассмотреть, помечен как негативные.

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

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

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

Лучший способ получить истинное значение для отрицательных прогнозов — просмотреть все ограничивающие прямоугольники истинного положения на изображении. Мы можем вычислить IoU между предсказанной ограничивающей рамкой с отрицательной маркировкой и всеми истинными фактами. Затем мы берем наибольшее значение IoU (это означает, что предсказанная ограничивающая рамка покрывает эту основную истину больше, чем все другие основные истины) и присваиваем это значение IoU предсказанной ограничивающей рамке. Затем мы можем взять BCE с логитами между предсказанной объектностью и присвоенным значением IoU, чтобы получить потери для этой отрицательной метки.

Примечание. В этом проигрыше используются все прогнозы, включая прогнозы с отрицательной меткой.

Функция окончательной потери

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

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

reg_weight — это уравновешивающий термин, используемый для взвешивания регрессионных потерь по сравнению с другими потерями, так как это наиболее важно для оптимизации. Авторы используют вес 5,0.

Делать выводы

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

Прежде чем что-либо делать с выходными данными, помните, что мы используем BCE с логитами, а не BCE для оптимизации прогнозов класса и объектности. BCE с логитами оптимизирует сигмоид выходов, а не вывод по умолчанию. Итак, первое, что нужно сделать, чтобы получить данные в правильном виде, — это взять сигмоид как значений класса, так и значений объектности. Кроме того, помните, что модель предсказывает распределение для каждого предсказания класса, а не значение класса. Итак, мы хотим взять argmax каждого прогноза класса, чтобы получить окончательный прогноз для класса:

final_cls = argmax(sigmoid(cls), axis=-1)
final_obj = sigmoid(obj)

Что касается целей регрессии, мы должны переместить их в правильное положение, как это было сделано с шагом, определенным ранее в этой статье.

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

  1. Удалите все выходные данные с оценкой достоверности (объективности) ниже определенного порога. Когда я кодировал модель YOLOX, я удалил все прогнозы с показателем достоверности ниже 0,5.
  2. Используйте Soft Nonmax Suppression, чтобы еще больше сократить прогнозы.

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

Немаксимальное подавление

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

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

Ниже приведен псевдокод, который я использовал для реализации soft-NMS (подавление Nonmax):

Definitions:
B - The predicted bounding boxes with shape (x, y, w, h)
S - The confidence score for each bounding box (objectness)
C - The class for each boudning box
score_thresh - The score threshold to remove boxes
IoU_thresh - The IoU threshold to update scores
softNMS(B, S, C, score_thresh, IoU_thresh):
  D = []   <- Boudning boxes we want to keep for all images
  for img in imgs:
    b = B[img]
    s = S[img]
    d = []
    while b not empty:
      Get the bounding box with the highest score and save it
      m = argmax(s)
      M = b[m]
      d.append(M)
    
      Remove the bounding box with the highest score from the lists
    
      Get the mean of all confidence scores
      mean_scores = mean(s)
      Get the IoU between M and all b
      IoU = IoU_funct(M, b)
    
      Update all scores, s, where the IoU > IoU_thresh
      idx = argwhere(IoU > IoU_thresh)
      s[idx] = s[idx]*e^(-(IoU[idx]**2)/mean_scores)
      Remove the bounding boxes from b where s < score_thresh
      b = b[s >= score_thresh]
    Save the bounding boxes for this image
    D.append(d)
return D

Ниже приведены шаги в другом формате:

  1. Получите ограничивающую рамку с наивысшей оценкой объектности
  2. Удалить выбранный прогноз из списков
  3. Получите среднее значение всех оставшихся оценок объектности
  4. Получите IoU между M, выбранной ограничивающей рамкой, и b, всеми остальными ограничивающими рамками
  5. Обновите оценки ограничительных рамок с высоким IoU
  6. Удалите все ограничивающие рамки из b, где оценка меньше порогового значения.
  7. Повторяйте шаги 1–7, пока b не станет пустым.

Формулу, используемую для обновления оценки, можно найти в исходной статье о мягком немаксимальном подавлении (на стр. 4). Это одна из предложенных в статье формул, которую можно использовать для создания мягкого немаксимального подавления наряду с несколькими другими.

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

Это в основном все, что есть в YOLOX. В следующей статье мы рассмотрим как SimOTA работает для динамического назначения меток.

Источники

YOLOX: https://arxiv.org/abs/2107.08430

YOLOv3: https://arxiv.org/abs/1804.02767

ФКОС: https://arxiv.org/abs/1904.01355

Софт-НМС: https://arxiv.org/abs/1704.04503