YOLO

Двоетапний детектор питає двічі: спершу «де тут може бути предмет», потім «що це». YOLO питає інакше — і відповідає на все одразу, одним проходом мережі. Це не прискорена стара схема, а інша постановка задачі. За неї доводиться платити, і в цій темі ми порахуємо, скільки саме.

Двоетапний детектор із попередньої теми влаштований як конвеєр: спершу окремий механізм пропонує місця, де щось може бути, потім кожне запропоноване місце окремо класифікується. Схема працює, дає високу якість — і має вбудовану ваду. Вона робить дві різні роботи двома різними частинами, і жодна з них не бачить картинку цілком. У 2016 році вийшла робота з назвою, що звучала як гасло: You Only Look Once, «дивишся лише раз». Ідея всередині була не про швидкість. Вона була про те, що детекцію можна не збирати з двох задач, а поставити як одну — і розвʼязувати одним проходом мережі.

ПостановкаІнша постановка, а не швидший конвеєр

Спершу згадаймо, як виглядає двоетапна схема. Є механізм кандидат-областей: він дивиться на зображення й видає кілька сотень або тисяч прямокутників, у яких може щось бути. Далі кожен такий прямокутник вирізається з карти ознак, приводиться до однакового розміру й подається в голову, яка каже, що там за клас і як підправити рамку. Дві частини, два навчання, два набори гіперпараметрів.

Тепер поставимо питання інакше. Уяви, що ти не шукаєш кандидатів, а просто заповнюєш анкету фіксованого розміру. Анкета розбита на клітинки — скажімо, сім на сім. У кожній клітинці кілька рядків: де центр предмета, який його розмір, наскільки я в цьому впевнений і що це за клас. Мережа дивиться на зображення один раз і заповнює всю анкету одразу. Ніяких кандидат-областей, ніякого вирізання, ніякого другого проходу.

Це і є одноетапна детекція. Формально: детекція як регресія. Вхід — зображення, вихід — один тензор фіксованої форми, і між ними одна мережа. Не «знайти, потім класифікувати», а «одразу передбачити все».

Чому це принципово, а не косметично. У двоетапній схемі перший етап вирішує, що взагалі буде розглянуто. Якщо кандидат-область не зʼявилась — жодна класифікація вже не допоможе. В одноетапній схемі такого фільтра немає: усі місця розглядаються завжди. Натомість зʼявляється інше обмеження, і воно вбудоване в саму сітку. Про нього — розділ 08, і це найчесніша частина теми.

Побічний наслідок постановки — швидкість, і саме він зробив YOLO знаменитою. Але швидкість тут наслідок, а не мета. Мета — прибрати проміжну сутність під назвою «кандидат-область» й навчати все наскрізно, однією втратою.

СіткаСітка ділить відповідальність

Щойно ми сказали «анкета з клітинок», зʼявилось питання: а хто заповнює яку клітинку? Якщо на зображенні три предмети, а клітинок сорок девʼять, треба якесь правило, інакше всі клітинки почнуть описувати найпомітніший предмет.

Правило YOLO коротке до непристойності. Розділимо зображення на сітку S × S. Клітинка відповідає за предмет, чий центр у неї потрапив. Не «перетинається з рамкою», не «лежить усередині рамки» — саме центр. Предмет може бути втричі більший за клітинку й накривати половину кадру; відповідає за нього одна-єдина клітинка, у якій опинилась його середина.

крок 1 · знайти центр

Рамка задана кутами x1, y1, x2, y2. Центр — середина по кожній осі.

cx = (x1 + x2) ÷ 2    cy = (y1 + y2) ÷ 2
крок 2 · знайти клітинку

Сторона клітинки — це розмір зображення, поділений на S. Номер клітинки — це ціла частина від ділення координати на сторону клітинки. Знак ⌊ ⌋ означає «відкинути дробову частину».

cell = SIZE ÷ S    col = ⌊cx ÷ cell⌋    row = ⌊cy ÷ cell⌋
крок 3 · порахувати на числах

Наше полотно — 64 пікселі, сітка 8 на 8, тобто клітинка 8 пікселів. Нехай центр предмета в точці (46.5, 19.5). Тоді col = ⌊46.5 ÷ 8⌋ = 5 і row = ⌊19.5 ÷ 8⌋ = 2. Відповідає клітинка в рядку 2, стовпці 5. Зверни увагу: обидва ділення цілочисельні, тож дробова частина координати (0.5 пікселя) на вибір клітинки не впливає — вона знадобиться пізніше, у розділі 04, коли ми записуватимемо, де саме всередині клітинки центр.

Уся простота правила окупається двічі. По-перше, воно не потребує жодного навчання: центр або в клітинці, або ні. По-друге, воно робить ціль однозначною — для кожного предмета є рівно один правильний адресат, тож мережа не отримує суперечливих сигналів від сусідніх клітинок. Порівняй із детектором теми 23: там позитивною була кожна клітинка, чий центр опинився всередині рамки, і на один предмет припадало по чотири-пʼять клітинок. Це теж працює, але й дублікатів породжує вчетверо більше.

Сітка й відповідальність

Сцена 64×64 з предметами. Повзунок міняє густину сітки: видно, як разом із нею змінюється те, яка саме клітинка відповідає за який предмет. Лінія веде від центра предмета до його клітинки.

клітинок—
сторона—
предметів—
у цілі—
Сітка — це не розрізання зображення. Мережа однаково бачить увесь кадр: сітка живе тільки у вихідному тензорі й каже, хто за що звітує. Тому предмет, більший за клітинку, чудово описується — рамка не обмежена межами клітинки, обмежений лише центр.

ВихідЩо передбачає клітинка

Тепер запишемо анкету точно. Кожна клітинка передбачає B рамок і C чисел класів. Кожна рамка — це пʼять чисел: x, y, w, h і впевненість. Класи — одні на всю клітинку, не на кожну рамку окремо; це особливість першої YOLO, і в розділі 11 ми побачимо, як від неї відмовились.

розмір виходу = S × S × (B · 5 + C)

Прочитаймо формулу словами. S × S — скільки клітинок. Множник 5 — це чотири числа рамки плюс одне число впевненості. B · 5 — усе, що клітинка каже про свої рамки. Плюс C — по одному числу на клас. Глибина не залежить від того, скільки предметів у кадрі: вихід має фіксований розмір завжди, і в цьому вся суть постановки.

Підставимо числа першої YOLO: сітка 7 на 7, дві рамки на клітинку, двадцять класів набору Pascal VOC.

7 × 7 × (2 · 5 + 20) = 7 × 7 × 30 = 1470

Тисяча чотириста сімдесят чисел — уся відповідь мережі на зображення. Для порівняння, наша навчальна мережа працює на сітці 8 на 8 із трьома класами: 8 × 8 × 13 = 832 числа: 2 · 5 + 3 = 13 на клітинку, шістдесят чотири клітинки.

Структура виходу

Три повзунки задають сітку, кількість рамок на клітинку й кількість класів. Угорі — вміст однієї клітинки по числах, унизу — весь тензор і його розмір.

глибина—
рамок усього—
чисел на зображення—
Густіша сітка коштує дешевше, ніж здається. Додати клітинок — це збільшити два перші виміри; глибина лишається та сама, бо голова застосовується до кожної клітинки однаково. Додати рамку на клітинку — це +5 до глибини, тобто +5 на кожну клітинку одразу.

КоординатиКоординати відносно клітинки

Тут ховається деталь, повз яку легко пройти, а вона визначає, чи мережа взагалі навчиться. Числа x і y — це не координати в пікселях. Це положення центра всередині своєї клітинки: нуль — лівий верхній кут клітинки, одиниця — правий нижній. А w і h — навпаки, частки від усього зображення.

числовідносно чогодіапазончому саме так
x, yсвоєї клітинки0…1голова однакова для всіх клітинок і не знає своїх координат
w, hусього зображення0…1предмет буває більший за клітинку, тож у межах клітинки розмір не запишеш

Друге пояснення варте окремого абзацу. Голова YOLO — це згортка 1×1: одні й ті самі ваги проходять по всіх клітинках. Така голова не має способу дізнатись, у якій клітинці вона зараз працює. Якби ми вимагали від неї абсолютну координату в пікселях, їй довелося б вивчити сорок девʼять різних відповідей на однаковий вхід — задача без розвʼязку. Вимагаючи зсув усередині клітинки, ми робимо задачу однаковою скрізь: «наскільки центр зміщений від лівого краю моєї клітинки». Номер клітинки додається вже після мережі, під час декодування.

кодування · із пікселів у числа мережі
col = ⌊cx ÷ cell⌋ row = ⌊cy ÷ cell⌋ x = cx ÷ cell − col y = cy ÷ cell − row w = ширина ÷ SIZE h = висота ÷ SIZE
декодування · назад у пікселі
cx = (col + x) · cell cy = (row + y) · cell ширина = w · SIZE висота = h · SIZE

Дві формули обертають одна одну, і це варто перевіряти не на слово, а числом: закодувати всі рамки, одразу декодувати назад і порівняти з тим, що було. На наших 247 істинних рамках перевірного набору таке кільце дало найбільше розходження 9.5 · 10⁻⁷ пікселя — чиста похибка округлення float32, тобто формули точні, а не «приблизно зворотні».

приклад руками

Клітинка в рядку 3, стовпці 5, сторона клітинки 8 пікселів. Мережа видала x = 0.25, y = 0.75. Тоді центр по горизонталі — (5 + 0.25) · 8 = 42.0, по вертикалі — (3 + 0.75) · 8 = 30.0. Прогони це в зворотний бік і перевір себе: 42.0 ÷ 8 = 5.25, ціла частина 5 — той самий стовпець, дробова 0.25 — той самий x.

Координати: що саме передбачає мережа

Рухай рамку по сцені 64×64 й міняй її розмір. Праворуч — чотири числа, які мережа мусить видати для цієї рамки, і клітинка, від якої вони рахуються.

клітинка—
x, y у клітинці—
w, h від кадру—
корені—
Рамка вільно виходить за межі своєї клітинки. Клітинка обмежує лише те, де може бути центр: як тільки центр переїжджає межу, відповідальність миттєво переходить сусідці, і чотири числа перераховуються від нової точки відліку.

КоріньКорінь із ширини й висоти

У попередній таблиці w і h були просто частками від розміру зображення. Насправді рання YOLO регресує не їх, а корені: √w замість w. Виглядає як довільна витівка. Насправді це виправлення цілком конкретної несправедливості.

Уяви дві квадратні рамки: одна стороною 20 пікселів, друга — 200. Мережа обидві промахнула на 5 пікселів по кожній стороні — центр вгадала, а розмір завищила. Квадратична помилка каже, що це однакова біда: 5² = 25 в обох випадках. А тепер подивись на IoU — метрику, заради якої все й робиться.

звідки береться 0.64

IoU — це площа перетину, поділена на площу обʼєднання (тема 23). Наша передбачена рамка накриває істинну цілком, тож перетин — це вся істинна рамка, а обʼєднання — вся передбачена. Для рамки 20 на 20, завищеної до 25 на 25:

перетин = 20 · 20 = 400 обʼєднання = 25 · 25 = 625 IoU = 400 ÷ 625 = 0.6400

Для рамки 200 на 200, завищеної до 205 на 205, той самий рахунок дає 40 000 ÷ 42 025 = 0.9518. Похибка в пікселях однакова, а втрачена частка перекриття — 36 % проти 4.8 %, тобто в сім із половиною разів більша на малій рамці.

сторона рамки(Δw)²(Δ√w)²IoU при похибці 5 px по кожній стороні
20 px25.00.278640.6400
50 px25.00.119120.8264
100 px25.00.060980.9070
200 px25.00.030870.9518

Читаємо середню колонку проти правої. Без кореня штраф на рамці 20 пікселів і на рамці 200 пікселів однаковий — відношення рівно 1 : 1. А шкода різна: у першому випадку IoU падає до 0.64, у другому лишається 0.95, тобто помилки практично не видно. З коренем відношення штрафів стає 9.03 : 1 — маленька рамка кричить у девʼять разів голосніше за велику, приблизно так само, як кричить IoU.

Чому саме корінь, а не щось точніше. Корінь не виводиться з IoU — його ніхто не оптимізував під метрику. Це найдешевша монотонна функція, яка стискає великі значення сильніше за малі, і в статті вона так і названа частковим розвʼязком. Пізніші версії пішли далі: замість підганяти штраф під IoU вони почали оптимізувати сам IoU — про це в розділі 11.

Що робить корінь зі штрафом

Перемикач обирає, що саме регресує мережа: сиру ширину чи корінь із неї. Повзунок задає похибку в пікселях. Стовпчики показують штраф для рамок різного розміру, зведений до найменшої.

що регресуємо
штраф, 20 px—
штраф, 200 px—
відношення—
IoU, 20 / 200—
Стовпчики зведено до найменшої рамки. У режимі сирої ширини всі чотири однакової висоти — метриці байдуже, велику рамку зіпсували чи малу. У режимі кореня стовпчики спадають, і спадають приблизно так само, як росте IoU на табло праворуч.

ВпевненістьВпевненість — це не ймовірність класу

Пʼяте число рамки зветься впевненістю (confidence), і його зміст у YOLO незвичний. Це не «ймовірність, що тут кіт». Це добуток двох речей:

впевненість = P(предмет є) × IoU(моя рамка, істинна рамка)

Перший множник — «чи тут узагалі щось є». Другий — «наскільки добре я поставив рамку». Перемножені, вони дають одне число, яке відповідає на питання «наскільки корисне це передбачення». Якщо предмета немає, впевненість має бути нулем. Якщо предмет є, але рамка поставлена криво, впевненість має бути невеликою — навіть попри те, що предмет справді там.

Найдивніше тут — що ціллю для навчання є сам IoU. Не одиниця, а фактичне значення перекриття, яке ця рамка щойно дала. Мережу вчать передбачати власну якість: «ось моя рамка, і ось наскільки, на мою думку, вона влучна».

Навіщо це, якщо є ймовірності класів. Ймовірність класу відповідає на питання «що це, якщо тут щось є» — вона умовна. Впевненість відповідає на питання «чи є». Перемножені, вони дають остаточну оцінку рамки: оцінка = впевненість × P(клас | предмет). Саме ця оцінка йде в NMS і в криву точність-повнота. Розділення не косметичне: клітинка, яка впевнена, що предмет є, але не знає, кіт це чи собака, поводиться інакше, ніж клітинка, яка нічого не бачить.

Впевненість як добуток двох речей

Ліворуч — істинна рамка (бірюзова) і рамка, яку видала мережа (малинова); повзунок зсуває передбачену рамку, і IoU перераховується по площах. Другий повзунок задає, наскільки мережа впевнена, що предмет тут узагалі є. Праворуч від рамок — прямокутник, чия ширина дорівнює P(предмет), висота — IoU, а площа і є впевненість.

IoU—
P(предмет)—
впевненість = P × IoU—
Ціль навчання для впевненості — не одиниця, а сам IoU. Зсунь рамку: щойно перекриття падає, падає й число, якого мережа мусить досягти. Тому висока впевненість на кривій рамці — не «сміливість», а помилка, за яку втрата штрафує: мережа пообіцяла якість, якої не дала. І навпаки, нуль у лівому множнику обнуляє все — хоч би як точно була поставлена рамка.

ВтратаФункція втрати та її ваги

Тепер зберемо все в одну втрату. Вона складається з чотирьох доданків, і головне в ній — де саме кожен доданок рахується. Не по всій сітці, а кожен по своїй частині.

доданокде рахуєтьсявага
x, yтільки у відповідальної рамкиλ_coord
√w, √hтільки у відповідальної рамкиλ_coord
впевненість, коли предмет єтільки у відповідальної рамки1
впевненість, коли предмета немаєу всіх інших рамокλ_noobj
класиу клітинок із предметом1

Усі доданки — квадрати різниць, підсумовані. Це найпростіша можлива втрата, і автори прямо визнають, що обрали її за легкість оптимізації, а не за правильність: вона однаково важить помилку локалізації й помилку класифікації, хоча оптимізуємо ми зовсім не це.

Звідки взялися дві дивні ваги

Порахуймо, скільки в сітці рамок і скільки з них про щось звітують. У нашій мережі 8 × 8 клітинок по 2 рамки — це 128 рамок на сцену. Предметів у сцені один-три, тобто відповідальних рамок у середньому 2.05. Решта 126 — фон, який має сказати «мене немає».

Якщо всі доданки важать однаково, сума по 126 фонових рамках просто задавить суму по двох відповідальних. Це не здогад: розкладку втрати можна порахувати до будь-якого навчання — узяти свіжу, щойно ініціалізовану мережу, прогнати крізь неї партію з 32 сцен і скласти кожен доданок окремо. Ось що виходить:

вагикоординатипредмет єфонкласиразом
1 і 10.4850.11025.9151.55128.062
частки1.7 %0.4 %92.3 %5.5 %100 %
5 і 0.52.4260.11012.9581.55117.045
частки14.2 %0.6 %76.0 %9.1 %100 %

З рівними вагами 92.3 % усієї втрати — це крик фону. Градієнт майже цілком складається з вимоги «мовчи», а на координати припадає 1.7 %. Мережі буквально вигідніше не побачити нічого, ніж навчитися ставити рамку. Канонічні ваги першої YOLO — λ_coord = 5 і λ_noobj = 0.5 — піднімають координати вдесятеро відносно фону: частка координат росте до 14.2 %, частка фону падає до 76.0 %.

Чесний результат заміру, який не підтвердив очікування. Ми навчили обидві настройки по три зерна. Канонічні ваги дали середній mAP@0.5 = 0.9761, рівні — 0.9714. Різниця 0.0047, а розкид усередині однієї настройки сягає 0.0141 — тобто різниця тоне в шумі, і на двох зернах із трьох канонічні ваги виграли, а на третьому програли. На нашому датасеті ваги не вирішують нічого. Наші сцени занадто легкі: три великі предмети на порожньому полотні, мережа виїжджає й із поганим балансом. Не переноси цей результат на справжні дані — переноси метод: перш ніж вірити в гіперпараметр, поміряй його на трьох зернах і порівняй виграш із розкидом.

Ваги втрати

Два повзунки міняють ваги. Стовпчики — фактичні складники втрати свіжої мережі на нашій партії; числа під ними — те саме у відсотках. Дві точки, які ми справді навчили, підписані заміряним mAP.

частка фону—
частка координат—
сума втрати—
Ваги не міняють, що мережа рахує — вони міняють, на що вона дивиться. Сирі суми складників тут фіксовані, це заміряні числа свіжої мережі; повзунки лише перерозподіляють увагу між ними. Крайні положення ламають навчання з обох боків: при нульовій вазі фону мережа не має жодної причини мовчати там, де порожньо.

МежаОбмеження сітки: чого YOLO не бачить у принципі

Настав час чесної частини. Правило «клітинка відповідає за предмет, чий центр у ній» має зворотний бік: клітинка відповідає щонайбільше за один предмет. Якщо центри двох предметів упали в одну клітинку, другий предмет просто не потрапляє в ціль. Мережа ніколи не отримає за нього градієнта й ніколи не навчиться його бачити. Це не поганий поріг, не брак даних і не помилка навчання — це властивість постановки.

Порахуємо, скільки це коштує. Замір іде по тисячі сцен двох видів — рідкі (один-три великі предмети) і щільні (чотири-шість дрібніших); для кожної сцени беремо істинні рамки, розкладаємо їх по клітинках і дивимось, скільки предметів не дісталось власної клітинки.

сітка Sклітинкарідкі: втраченощільні: втраченощільні: зіпсовано сцен
4 × 416.0 px2.45 %8.95 %39.2 %
7 × 79.1 px0.45 %1.71 %8.3 %
8 × 88.0 px0.30 %1.30 %6.4 %
13 × 134.9 px0.10 %0.12 %0.6 %

Головне число тут — 8.95 %. На сітці 4 × 4 щільна сцена втрачає кожен одинадцятий предмет, а зіпсовано виявляється 39.2 % усіх сцен: у чотирьох із десяти кадрів мережа фізично не може знайти все. Дрібніша сітка лікує це швидко: на 13 × 13 втрата падає до 0.12 %.

Наші сцени рідкі, і на них ефект малий — це теж треба сказати прямо. Але справжня фотографія так себе не поводить. Чого чекати на щільній сцені, можна порахувати не генеруючи її. Задача та сама, що у відомому «парадоксі днів народження», і механізм у неї простий до банальності: клітинки закінчуються швидше, ніж здається.

Розкладаймо предмети по клітинках по одному. Нехай клітинок n = S², а предметів k.

крок 1 · перший предмет

Він падає куди завгодно — вільні всі n клітинок із n. Імовірність, що конфлікту ще немає: n ÷ n = 1.

крок 2 · другий предмет

Одна клітинка вже зайнята, вільних n − 1. Імовірність, що він не влучив у зайняту: (n − 1) ÷ n.

крок 3 · i-й предмет

Перед ним зайнято i − 1 клітинок, вільних n − i + 1. Його частка — (n − i + 1) ÷ n, і вона менша за попередню: кожен покладений предмет забирає клітинку в наступного.

крок 4 · збираємо докупи

Щоб конфлікту не було жодного разу, мусять справдитись усі k умов підряд. Кожну наступну ми вже рахували в припущенні, що попередні справдились (саме тому вільних клітинок стає менше), а ймовірності такого ланцюжка перемножуються. Отже ймовірність чистої сцени — це добуток спадних часток, а ймовірність зіткнення — те, що лишилось до одиниці.

P(зіткнення) = 1 − (n ÷ n) · ((n−1) ÷ n) · … · ((n−k+1) ÷ n)

Порахуймо руками найменший випадок — три предмети на сітці 4 × 4, тобто n = 16, k = 3:

чиста сцена = (16 ÷ 16) · (15 ÷ 16) · (14 ÷ 16) = 1 · 0.9375 · 0.8750 = 0.8203 P(зіткнення) = 1 − 0.8203 = 0.1797 ≈ 18.0 %

Це перше число таблиці нижче. І тут видно, чому далі все так швидко псується: множники не просто менші за одиницю — вони спадають. При n = 49 і двадцяти предметах останній множник дорівнює 30 ÷ 49 = 0.61, тобто двадцятий предмет має 39 % шансів зіткнутись сам по собі, ще до того, як ми врахували перші девʼятнадцять. Двадцять таких множників перемножуються — і від чистої сцени лишається один випадок зі ста.

предметівS = 4 (16 кл.)S = 7 (49)S = 8 (64)S = 13 (169)
318.0 %6.0 %4.6 %1.8 %
550.0 %19.0 %14.8 %5.8 %
1097.4 %62.6 %52.3 %23.8 %
20100.0 %98.9 %96.4 %69.0 %

Ось відповідь на питання, чому першу YOLO лаяли за дрібні предмети. На фотографії з двома десятками обʼєктів сітка 7 × 7 майже напевно щось загубить — 98.9 % імовірності зіткнення. У статті це визнано прямо: модель погано працює з дрібними предметами, що зʼявляються групами, і як приклад названо зграю птахів.

Чия це сцена — і чому фігура нижче дасть менше число. Таблиця вище описує рівномірний жереб: центри кидаються в клітинки незалежно один від одного, як кульки в лунки. Справжні предмети так не лягають — вони мають розмір і не накладаються один на одного, тож їхні центри самі собою розштовхані. Наш генератор сцен теж не дає фігурам злипатись, і на тій самій конфігурації S = 7, 20 предметів він дає 70.3 % зіпсованих сцен замість 98.9 % — постав повзунки фігури нижче саме так і перевір. Обидва числа правильні, просто вони про різні сцени: 98.9 % — це верхня межа (найгірший, зовсім невпорядкований випадок), а 70.3 % — те, що виходить, коли предмети займають місце. Напрямок обидві оцінки показують однаковий, і саме він тут головний: чим більше предметів і чим грубша сітка, тим більше їх зникає ще до навчання.

Конфлікт у клітинці

Головний замір теми. Ліворуч — одна сцена: предмети, чиї центри зіткнулись, перекреслені. Праворуч — стовпчики втрати по всіх сітках, порахованої тут же на 300 сценах. Другий повзунок міняє щільність сцени.

предметів усього—
втрачено—
частка втрати—
зіпсовано сцен—
Перекреслені предмети не «погано знайдені» — їх немає в цілі взагалі. Скільки не навчай, скільки не знижуй поріг, ці рамки не зʼявляться: мережа жодного разу не бачила за них градієнта. Тому головний важіль тут — не навчання, а густина сітки.

Дві рамкиДві рамки на клітинку — і навіщо

Якщо клітинка однаково відповідає за один предмет, навіщо їй дві рамки? Не для двох предметів — для спеціалізації за формою.

Під час навчання відповідальною призначається та з двох рамок, чий IoU з істинною рамкою більший саме зараз. Друга в цьому кроці не отримує градієнта по координатах узагалі. Через сотні кроків виходить розподіл праці: одна рамка поступово стає кращою на високих і вузьких предметах, друга — на широких і низьких. У статті це названо своїм іменем: кожен передбачувач стає кращим на певних розмірах, співвідношеннях сторін і класах, і від цього росте повнота.

Тонкість, яку легко проґавити. Спеціалізація тут ніде не задана. Ніхто не сказав рамці 0 бути вертикальною. Вона стає такою сама, бо кожен раз, коли їй трапляється вертикальний предмет, вона трохи ближча до нього й тому отримує градієнт. Це самопідсилення: невелика випадкова перевага на старті перетворюється на стійку роль. Рівно та сама ідея, доведена до логічного кінця, дасть у другій версії якорі — тільки там форми не виникають самі, а задаються наперед.

Ціна теж є, і вона видна в арифметиці розділу 03: кожна додаткова рамка додає пʼять чисел до глибини виходу для кожної клітинки. Дві рамки на сітці 7 × 7 дають 98 рамок на зображення — саме стільки передбачала перша YOLO. Для порівняння, двоетапна схема розглядала кілька тисяч місць.

ШвидкістьШвидкість: наслідок постановки

Тепер про те, за що YOLO запамʼятали. Одноетапна схема коштує один прохід мережі. Класична R-CNN проганяла мережу по кожній кандидат-області окремо. Порівняймо це чесно, у двох варіантах.

Варіант перший — однакова ємність. Беремо наше тіло мережі й міряємо його двічі: один прохід по цілій сцені 64 × 64 проти ста проходів по вирізаних кандидат-областях 32 × 32. Той самий код, ті самі ваги, різна лише постановка.

схемапроходів по вмістучасвідношення
одноетапна11.43 мс1×
по 100 кандидат-областях10026.4 мс18×
Ці мілісекунди — не константа світу. Вони заміряні на чотирьох ядрах без відеокарти й залежать від машини та від того, чим вона зайнята: у сусідніх прогонах того самого коду виходило від 1.41 до 1.43 мс і від 26.4 до 27.8 мс. Абсолютні числа плавають, стійке тут відношення — близько вісімнадцяти-двадцяти разів. Повторюючи цей замір у себе, звіряй саме відношення, а не мілісекунди.

Варіант другий — проти справжнього Faster R-CNN. Будуємо fasterrcnn_resnet50_fpn без ваг і міряємо його прохід на тому самому вході: 177 мс проти наших 1.43 мс, тобто 124 рази. Але тут треба сказати вголос: у цій моделі 41 362 531 параметр проти наших 61 421, тобто в 673 рази більше. Більша частина розриву — це розмір моделі, а не постановка. Внесок самої постановки — це перша таблиця, вісімнадцять разів.

Швидша, але не краща

Ось те, чого не буває в рекламних графіках. Перша YOLO працювала на 45 кадрах за секунду й давала 63.4 mAP на Pascal VOC 2007. Faster R-CNN із тілом VGG-16 на тих самих даних давав 73.2 mAP — але на 7 кадрах за секунду. Полегшена Fast YOLO розганялась до 155 кадрів і падала до 52.7 mAP. Це числа з оригінальної статті, наведені як цитата, а не наш замір.

модель (з літератури)mAP, VOC 2007кадрів за секунду
Fast YOLO52.7155
YOLO63.445
Fast R-CNN70.00.5
Faster R-CNN VGG-1673.27

Розрив у майже десять пунктів mAP — це не дрібниця. І він не рівномірний: розбір помилок у тій самій статті показав, що помилок локалізації в YOLO більше, ніж усіх інших її помилок разом, тоді як Fast R-CNN промахується рамкою значно рідше. Дзеркально: у Fast R-CNN 13.6 % найкращих передбачень — це фон, у якому взагалі немає предмета, і за цим показником він приблизно втричі гірший за YOLO.

Дві моделі помиляються по-різному, і це не риторична фігура: у статті їх поєднали — YOLO прибирала фонові спрацювання Fast R-CNN — і 71.8 mAP перетворилось на 75.0. Одна модель відповідала за точність рамки, друга — за відсутність привидів.

Один прохід проти багатьох

Повзунок задає, скільки кандидат-областей розглядає двоетапна схема. Час порахований із наших замірів: 1.43 мс на прохід по цілій сцені й 0.264 мс на одну кандидат-область тим самим тілом.

одноетапна—
двоетапна—
у скільки разів—
кадрів за секунду—
Час одноетапної схеми — горизонтальна лінія. Він не залежить від кількості предметів у кадрі й від того, скільки місць варто було б перевірити: прохід один завжди. Час двоетапної росте разом із кількістю кандидат-областей, і саме цей нахил вирішує все.

РозвитокЯк YOLO розвивалась: що саме змінили

Далі — короткий огляд без наших замірів. Важливо не «стало краще», а що конкретно замінили.

версіящо саме змінилось
v1сітка 7 × 7, дві рамки на клітинку, класи одні на клітинку, координати з повнозвʼязних шарів, втрата — сума квадратів
v2прибрано повнозвʼязні шари, введено якорі; вхід 416 замість 448, щоб сітка вийшла 13 × 13 із непарним центром; форми якорів підібрані k-середніми по даних, а не руками; клас і обʼєктність передбачаються для кожного якоря окремо
v3багатомасштабність: передбачення на трьох рівнях різної роздільності, девʼять якорів розподілено по три на рівень; softmax по класах замінено на незалежні логістичні класифікатори, тобто предмет може мати кілька міток одночасно
v4, v5інженерія без зміни постановки: інше тіло, інша шия, важка аугментація (зокрема мозаїка з чотирьох зображень), нові прийоми навчання
v6-v8anchor-free: якорі прибрано зовсім, клітинка знову передбачає рамку прямо; голова розділена на дві гілки — окрему для рамки й окрему для класів; замість фіксованого правила «центр у клітинці» — навчене призначення відповідальності

Два числа, які варто зіставити

Перехід до якорів у другій версії — рідкісний випадок, коли автори чесно показали, що метрика впала. Без якорів модель давала 69.5 mAP при повноті 81 %; із якорями — 69.2 mAP при повноті 88 %. Точність трохи гірша, зате знайдено на сім пунктів більше предметів, і саме цей запас дозволив рости далі. Числа цитовані зі статті про YOLOv2.

А ось замір, який можна зробити самому й офлайн. Бібліотека ultralytics дозволяє зібрати архітектуру YOLOv8 без жодних ваг. Подивимось на її голову й порівняймо з першою версією:

YOLOv1YOLOv8n (зібрано офлайн)
масштабів13
позицій сітки498400
чисел на позицію30144
чисел разом14701 209 600

Але цікавіше не розмір, а з чого складаються ці 144 числа: 80 класів плюс 4 × 16. Ані сліду впевненості. Окремого числа «тут є предмет» у YOLOv8 немає взагалі — його роль перебрала найбільша ймовірність класу. А четвірка помножена на 16 означає, що кожна сторона рамки передбачається не одним числом, а розподілом по шістнадцяти позиціях, з якого потім береться середнє. Голова складається з двох окремих гілок: одна рахує рамку, друга — класи. Усі ці числа видно з самої конфігурації моделі — навчені ваги для такого підрахунку не потрібні, досить зібрати архітектуру й подивитись на форму останніх згорток.

СпадокЩо з YOLO лишилось у сучасних детекторах

Від першої версії не лишилось майже нічого з деталей: ані сітки 7 × 7, ані кореня з ширини, ані двох ваг, ані спільних класів на клітинку. Лишилось три речі, і вони визначають вигляд усіх швидких детекторів сьогодні.

Це і є справжня спадщина YOLO — не швидкість, а те, що детекцію перестали збирати з двох задач.

ПідсумокПідсумок і місток далі

ідеяу чому суть
Регресія за один прохідвихід фіксованої форми S × S × (B·5+C); у нас 832 числа, у YOLOv1 — 1470
Сітка як розподіл ролейклітинка відповідає за предмет, чий центр у ній
Координати відносно клітинкиінакше згортка 1×1 мусила б знати, де вона
Корінь із розміруштраф на рамці 20 px стає в 9.03 раза більший, ніж на 200 px
ВпевненістьP(предмет) × IoU — мережа передбачає власну якість
Ваги втратибез них фон важить 92.3 % суми; на нашому датасеті це, утім, не змінило mAP
Ціна сіткина щільних сценах сітка 4 × 4 губить 8.95 % предметів назавжди

Головне число, яке варто винести: 8.95 %. Не «YOLO гірша на дрібних предметах», а «стільки предметів не існує для моделі ще до початку навчання». І головна пара чисел: 1.43 мс проти 26.4 мс при однаковій ємності — приблизно вісімнадцять-двадцять разів лише за те, що тіло проганяється один раз замість ста.

Одноетапна ідея дала швидкість, але принесла проблему, якої у двоетапних детекторів не було. Двоетапна схема мала фільтр: перший етап відбирав кілька сотень місць, і серед них співвідношення «предмет проти фону» було ще стерпним. Одноетапна голова дивиться всюди, і фон її топить. У темі 23 ми вже дістали це число на наших сценах: 85.4 % клітинок — чистий фон. У нашій YOLO-подібній мережі з двома рамками на клітинку співвідношення ще різкіше: відповідальних рамок 2.05 зі 128, тобто 1.6 %.

Витягувати сигнал із такої суміші вагами λ_noobj — спосіб грубий: ми бачили, що на легких даних він нічого не дає, а підібрати його на важких доводиться наосліп. Наступна тема береться саме за це: як переписати саму втрату так, щоб легкі фонові приклади затихали автоматично, і як позбутися якорів, які YOLO встигла завести й почати від них відмовлятись. Focal loss і anchor-free — це продовження історії, яку ми щойно почали.