
Введение.
Когда пытаешься разобраться в работе YOLO по статьям в интернете, постоянно натыкаешься на примерно такое объяснение: «Алгоритм делит изображение сеткой SxS, где каждому элементу этой сетки соответствует N ббоксов с координатами, предсказаниями классов и тд...». Но лично мне становилось только непонятнее от такого высокоуровнего описания.. Ведь в исследованиях часто всё происходит примерно так: перебирают гипотезы, пока не получат приемлемый результат, а потом уже придумывают красивое описание. Поэтому для ясности хочется рассказать, как вообще приходили к идеям, которые ложились в основу YOLOv1 и последующих версий.
Немного про особенности Feature Map
Сначала поговорим про Feature Map, чтобы потом стало понятно откуда взялась эта непонятная сетка SxS)) Итак, давайте представим, что у нас есть маленькое трехканальное (RGB) изображение 10×10 пикселей. Мы прогоняем его через две свёртки: с ядром 5×5 и ядром 3×3. Соответствующий код на Torch выглядит так:
import torch
import torch.nn as nn
image = torch.rand(3,10,10) # Типа изображение
conv1 = nn.Conv2d(3,1,5) # Свертка с ядром 5
conv2 = nn.Conv2d(1,1,3) # Свертка с ядром 3
feature_map1 = conv1(image) # Результат после первой свёрки
print(f'FM1 shape: {feature_map1.shape}') # FM1 shape: torch.Size([1, 6, 6])
feature_map2 = conv2(feature_map1) # Результат после второй всёртки
print(f'FM2 shape: {feature_map2.shape}') # FM2 shape: torch.Size([1, 4, 4])Визуализация этого кода примерно такая:

На изображении видно, что левая верхняя ячейка Feature map 2 является результатом свертки выделенной области из Feature map 1. В то же время, каждая ячейка из выделенной области на Feature map 1 является результатом сверки соответствующей области исходного изображения. Получается, что выделенный элемент на Feature map 2 отображает признаки выделенной области на исходном изображении Input image. Поскольку Feature map 2 имеет размер 4×4 — можно сказать, что он как бы делит исходное изображение сеткой 4×4, так как каждый его элемент смотрит на 1 из 16 частей изображения.
Вдобавок становится понятно, что чем глубже находится Feature Map, тем более высокоуровневые признаки на изображении он описывает. Вот довольно популярная картинка, которая показывает это свойство:

Low‑Level Feature описывает всякие непонятные линии и закарючки, так как ядро сворачивает очень маленькую часть изображения. Mid‑Level Feature описывает уже что‑то более осмысленное, так как отображает часть побольше. И наконец High‑Level Feature уже содержит информацию, которую можно как‑то интерпртировать (наличие колеса, фар, решетки радиатора и тп)
YOLOv1
В 2015 году Joseph Redmon опубликовал статью You Only Look Once: Unified, Real‑Time Object Detection. Гипотеза, которую ему удалось проверить и получить хороший результат заключалась в следующем: «А что если взять какую‑то предобученную модель для классификации картинок и просто заменить последнии слои так, чтобы она предсказывала не вероятность классов, а какой‑то тензор, в котором будет содержаться информация о ббоксах и классах?».
Архитектура
В статье сначала строится архитектура для классификации изображений, вдохновленная GoogLeNet:

Эта архитектура училась классифицировать изображения на ImageNet (1000 classes) и на валидации вошла в top-5 Accuracy с 88%. После того, как мы обучили классификатор, замораживаем первые 20 слоёв. Т.е. их веса больше не будут меняться в ходе дальнейшего обучения (ничего волшебного - это обычный Fine-Tuning)
Теперь у нас есть предобученные 20 слоёв, которые извлекают информацию из изображения. Добавим к ним 4 необученных сверточных слоя + полносвязный слой. В самом конце возвращается тензор, в котором будет информация о ббоксах и соответствующих классах.
Вот финальная архитектура (Изменились только последние слои
