У попередній темі ми розібрали саму операцію уваги: як із набору векторів робляться запити, ключі й значення, чому в знаменнику стоїть корінь із розмірності, навіщо кілька голів і чому увага не бачить порядку, поки їй не підкажеш. Операція є. Тепер питання інженерне: як із неї зібрати мережу, якій можна показати фотографію. Відповідь, яку дали автори Vision Transformer, приблизно така: «а давайте нічого не збиратимемо — візьмемо готову мережу для тексту й наріжемо картинку на шматочки, щоб вона була схожа на речення».
ІдеяСмілива ідея
Щоб оцінити зухвалість цього кроку, треба згадати, як виглядав компʼютерний зір до 2020 року. Вісімнадцять тем цього курсу — і майже кожна стоїть на згортці. Згортка не просто «якось працює»: вона вносить у мережу три припущення про світ, і всі три для зображень правдиві.
Сусідні пікселі повʼязані сильніше, ніж далекі. Ядро 3×3 дивиться на девʼять пікселів поруч і не бачить решти — і це не обмеження, а підказка: у зображенні справді важливо, що поруч, а не що за двісті пікселів звідси.
Те саме ядро проїжджає по всьому зображенню. Детектор краю, вивчений у лівому верхньому куті, працює й у правому нижньому. Мережа вчить одну штуку замість тисячі однакових.
Зсунув предмет — карта ознак зсунулась разом із ним, а після пулінга відповідь майже не змінилась. У темі 06 це поміряно прямо: на зсунутих предметах повнозвʼязна мережа падає до 0.360 (рівень вгадування — 0.333), а згорткова тримає 0.607, маючи при цьому в тринадцять разів менше ваг.
Vision Transformer прибирає всі три. Він ріже зображення на квадратні патчі, робить із кожного вектор — і далі поводиться з ними як зі словами в реченні. Кожен патч із першого ж шару бачить усі інші. Ніякої локальності, ніякої пірамідки, ніякого пулінга. Позиція патча в кадрі перестає бути частиною будови мережі й стає просто ще одним числом, яке треба вивчити.
Далі в темі ми не переказуватимемо саму увагу — вона розібрана в темі 19. Нас цікавить те, що навколо неї: як зображення перетворюється на послідовність, що робить проєкція патча, навіщо зайвий токен, як улаштований блок і скільки все це коштує в параметрах і в часі.
ВхідЗображення як послідовність
Трансформер для тексту їсть послідовність векторів: одне слово — один вектор. Зображення — це не послідовність, це двовимірна сітка чисел. Треба якось перетворити одне на інше.
Найпростіше, що спадає на думку: нехай кожен піксель буде «словом». Для нашої картинки 28×28 це 784 токени. Увага рахує вагу для кожної пари токенів, тобто 784 × 784 = 614 656 пар — вже на першому шарі. Для звичайної фотографії 224×224 пікселів вийде 50 176 токенів і понад два з половиною мільярди пар. Це не «повільно», це неможливо.
Розвʼязок, який дає ViT, зумисне грубий: розріжемо зображення на квадратні патчі (patch) розміром P×P і оголосимо патч одним токеном. Для 28×28 і P = 4 виходить сітка 7×7, тобто 49 патчів — і 49 × 49 = 2 401 пара замість 614 656. У 256 разів дешевше.
Тепер про те, чим за це заплачено, бо задарма така економія не буває. Заплачено роздільністю, і це має два боки.
Перший: патч стає найдрібнішим, що мережа взагалі вміє назвати. Далі все відбувається між патчами — увага роздає ваги патчам, а не пікселям, і сказати «ось тут, у лівому верхньому пікселі цього квадрата» вона не може, бо такої одиниці в неї більше немає. Сітка з 784 позицій стала сіткою з 49: там, де раніше можна було вказати на піксель, тепер указують на квадрат 4×4.
Другий бік — просте стиснення, і воно тим сильніше, чим більший патч. Числа патча треба вкласти у вектор токена фіксованої довжини. У нас патч 4×4 дає 16 чисел, а токен має 48 — місця з запасом. Але візьми патч 14×14: це вже 196 чисел на ті самі 48, тобто вчетверо менше, ніж було, і частина дрібних відмінностей туди просто не вміщається. Те саме в дорослій моделі: ViT-B/32 тисне 32 × 32 × 3 = 3 072 числа патча у 768 — теж учетверо.
Ось чому розмір патча — компроміс, а не безкоштовне прискорення. Ця друга половина рахунку стоїть окремим стовпчиком «роздільність» у таблиці розділу 07 і в інтерактиві 5: при патчі 14×14 від кадру 28×28 лишається сітка 2×2 — чотири квадрати, усередині яких модель нічого не розрізняє.
Зображення в патчі
Покрути розмір патча й подивись, як одночасно міняються чотири речі: скільки виходить токенів, якої довжини вектор кожного, скільки ваг у матриці, що робить із нього токен, і наскільки грубою стає сітка.
Тепер порахуємо руками, що саме відбувається з одним патчем. Беремо картинку 28×28 і патч 4×4.
- Ріжемо. 28 ділиться на 4 без остачі, тому сітка виходить 7×7 = 49 патчів. Патчі не перекриваються й не мають проміжків.
- Розгортаємо. Патч 4×4 з одного каналу — це 16 чисел. Виписуємо їх у рядок: спершу перший рядок патча зліва направо, потім другий, і так усі чотири. Вийшов вектор довжини 16.
- Множимо. Множимо цей вектор на матрицю розміром 16 × 48 і додаємо зсув із 48 чисел. Вийшов вектор довжини 48 — це й є токен.
Тут W — матриця, яку мережа вчить; вона одна на всі 49 патчів. b — вектор зсуву, теж один на всіх. Число 48 називають розмірністю токена: це ширина «шини», якою всі 49 векторів поїдуть далі крізь мережу. Ваг тут 16 × 48 + 48 = 816 — і цією ж матрицею обробляється кожен із 49 патчів.
Патч → вектор → токен
Обери патч на зображенні. Побачиш його 16 чисел крупно, той самий патч розгорнутий у рядок і результат множення на матрицю — токен із 48 чисел. Зверни увагу: перемикаючи патч, ти міняєш числа ліворуч, але не матрицю. Вона одна на всіх.
Чесна детальПроєкція патча — це згортка
Прочитай ще раз, що ми щойно зробили: взяли квадратне вікно, розгорнули його в вектор, помножили на спільну матрицю, зсунули вікно на його ширину й повторили. Це дослівний опис згортки з ядром P×P і кроком (stride) P.
Це не аналогія й не «майже те саме». Це перевірено прямим
порівнянням: ми власноруч нарізали зображення циклом, склали патчі в матрицю
49 × 16, помножили на ту саму матрицю ваг — і звірили з тим, що видає
Conv2d. Результат: np.allclose дає True,
найбільша різниця 1.8e-07 — це чисельний шум float32, а не розбіжність.
Наша нарізка збігається з torch.nn.functional.unfold уже
побітово: різниця рівно 0.
torchvision
цей шар так і зветься — conv_proj. Тобто гасло «ViT довів, що
згортки не потрібні» неточне навіть щодо самого ViT.Але це особлива згортка, і різниця принципова. Ядро дорівнює кроку, тому вікна не перекриваються: жоден піксель не потрапляє у два патчі. Локальність працює усередині патча й закінчується на його межі. Усе, що відбувається далі — 49 токенів, які дивляться один на одного без жодного уявлення про те, хто кому сусід. Згортка тут не будує ієрархію, вона просто пакує пікселі в токени.
Звідси й перша практична порада, до якої дійшли пізніші роботи: якщо замість однієї згортки з кроком P поставити на вході маленький згортковий стем із кількох шарів 3×3, трансформер навчається помітно стабільніше. Локальність, повернута хоча б на вході, коштує дешево й окупається.
CLSCLS-токен
Після енкодера ми маємо ті самі 49 токенів — кожен по 48 чисел. Класифікатору потрібен один вектор, з якого він зробить шість чисел (по одному на клас). Як із 49 зробити 1?
Очевидний спосіб — усереднити: скласти всі 49 векторів і поділити на 49. Це рівно те саме глобальне усереднення, яке ми робили в темі 08 над картою ознак, і воно нічого не коштує в параметрах.
ViT робить інакше. Він додає пʼятдесятий токен, який не відповідає жодному патчу зображення. Це просто вектор із 48 чисел, який мережа вчить разом з усім іншим, і він однаковий для будь-якої картинки на вході. Токен називають CLS (від classification) — прийом запозичено з мовної моделі BERT. Після енкодера беруть вихід саме цього токена й подають у класифікатор.
Логіка така. Токен патча мусить одночасно нести інформацію про свій патч і служити підсумком усього зображення — це дві різні роботи в одному векторі. CLS ніякого патча не представляє, тож уся його місткість вільна під підсумок. А механізм уваги дає йому право взяти з кожного патча рівно стільки, скільки треба: усереднення жорстко ставить усім вагу 1/49, CLS вибирає ваги сам.
Куди дивиться CLS-токен
Ваги уваги від CLS до кожного з 49 патчів у нашій навченій моделі. Перемикай фігуру, шар і голову. Рівномірна увага дала б скрізь 0.0204 = 1/49 — саме з цим числом і треба порівнювати те, що бачиш.
self_attn з need_weights=True.Картина повчальна й не така, як на красивих ілюстраціях у статтях. У першому шарі окремі голови ставлять майже всю вагу на один-єдиний патч: на колі найбільша вага доходить до 0.9505, на кільці — до 0.9950 при рівномірних 0.0204. Це в 46-49 разів більше за рівномірну, тобто голова просто вибрала одну клітинку й дивиться тільки туди.
У другому шарі на колі увага розмазується — максимум падає з 0.9505 до 0.2813. Але це не універсальне правило: на хресті картина інша, там максимум у першому шарі 0.5644, а в другому 0.5692, тобто гостроти не додалось і не зникло. Красиві силуети предмета на теплових картах, які показують у статтях, зʼявляються на великих навчених моделях, а не на двошаровій іграшці, яку вчили на 600 прикладах.
А чи потрібен CLS насправді?
Це перевіряється заміром. Ми навчили ту саму модель двічі: з CLS-токеном і з усередненням по всіх 49 токенах, по три зерна на кожну.
| як зводимо 49 токенів в 1 | три прогони | середнє | розкид | зайвих ваг |
|---|---|---|---|---|
| CLS-токен | 0.332 · 0.435 · 0.378 | 0.382 | 0.103 | 48 |
| середнє по токенах | 0.372 · 0.437 · 0.398 | 0.402 | 0.065 | 0 |
Різниця середніх — 0.021. А розкид від самої лише зміни зерна — 0.103, тобто впʼятеро більший. За правилом, яке ми вивели в темі 18, різниця, менша за розкид, різницею не є: на нашій задачі CLS не дає нічого. Він коштує 48 ваг і не окупає їх.
БлокБлок енкодера
Тепер серце мережі. Енкодер ViT — це один і той самий блок, повторений кілька разів. У нашій моделі їх два, у ViT-B — дванадцять. Блок складається з чотирьох речей, і три з них ти вже знаєш.
Нормалізація по шару: для кожного токена окремо віднімаємо середнє його 48 чисел і ділимо на їхнє стандартне відхилення, потім множимо на вивчений масштаб і додаємо вивчений зсув. На відміну від батчнорму з теми 11, тут нічого не усереднюється по батчу: кожен токен нормалізується сам по собі. Ваг — рівно 2 × 48 = 96 на один LayerNorm.
Багатоголова увага з теми 19. Кожен токен формує запит, ключ і значення, зважує всі інші токени й забирає з них зважену суму. Ваг при розмірності 48: три матриці 48×48 зі зсувами (запити, ключі, значення) — 3 × (48 × 48) + 3 × 48 = 6 912 + 144, і вихідна матриця 48×48 + 48 = 2 352. Разом 9 408.
Дві повнозвʼязні операції з нелінійністю посередині, застосовані до кожного токена окремо: розширити 48 → 96, застосувати нелінійність, стиснути 96 → 48. Тут токени між собою не спілкуються взагалі — це чиста обробка кожного вектора нарізно. Ваг: (48 × 96 + 96) + (96 × 48 + 48) = 9 360.
І увага, і MLP додаються до входу, а не заміняють його — той самий скіп, що в темі 14. Ми не переказуватимемо, навіщо він; але саме через нього те, що проходить крізь весь стос блоків, прийнято називати залишковим потоком (residual stream).
Разом один блок при розмірності 48 важить 9 408 + 9 360 + 2 × 96 = 18 960 ваг. Наша модель має два такі блоки — 37 920 ваг, або 91 % усієї мережі. Усе інше — проєкція патча, позиційне кодування, CLS і класифікатор — це решта 9 %.
Схема · Блок енкодера цілком
Ті самі чотири кроки, але видно, як вони складені: магістраль іде наскрізь, а обидві операції лише додаються до неї збоку. Розмір 50 × 48 не міняється ніде — ані всередині блоку, ані між блоками.
Норма спереду чи ззаду
Лишається питання, де саме стоїть LayerNorm, і воно зовсім не косметичне. Є два варіанти, і історично першим був другий.
Різниця в одному: у першому варіанті нормалізація стоїть на гілці, а магістраль від входу до виходу лишається чистою сумою. У другому LayerNorm сидить на самій магістралі і перемасштабовує її після кожного блоку. Тобто в post-norm наскрізного тотожного шляху, заради якого скіп і придумали, насправді немає.
Це не теорія, це видно на числах без жодного навчання. Ми пропустили той самий випадковий вхід крізь стос із дванадцяти ненавчених блоків і після кожного міряли косинус між тим, що тече зараз, і початковим входом — тобто наскільки початковий сигнал іще вцілів.
| після скількох блоків | 1 | 4 | 8 | 12 |
|---|---|---|---|---|
| норма спереду | 0.969 | 0.668 | 0.358 | 0.223 |
| норма ззаду | 0.953 | 0.613 | 0.164 | 0.029 |
Після дванадцяти блоків від початкового сигналу в pre-norm лишається 0.223,
у post-norm — 0.029, у 7.7 раза менше. Практично це означає, що глибокий
трансформер із нормою ззаду не навчається без обережного розігріву швидкості
навчання на початку, а з нормою спереду — навчається просто так. Тому сучасні
реалізації, включно з тією, що в torch.nn.TransformerEncoderLayer
за прапорцем norm_first=True, беруть норму спереду. Ми теж.
nn.TransformerEncoderLayer на тих самих вагах: різниця виходить
рівно 0.0, не «майже нуль». Блок трансформера справді складається тільки
з того, що перелічено вище.РозкладРозклад справжнього ViT
Досі ми говорили про іграшку на 41 574 ваги. Подивимось на дорослу модель.
torchvision уміє зібрати ViT-B — базовий Vision Transformer — двох
різновидів: з патчем 16×16 і з патчем 32×32. Обидва працюють із зображенням
224×224 і мають розмірність токена 768 та дванадцять блоків. Різниця між ними
рівно одна — розмір патча. Ми зібрали обидва (нічого не завантажуючи з мережі)
і порахували, скільки ваг у кожній частині.
| частина | ViT-B/16 | ViT-B/32 |
|---|---|---|
| токенів (із CLS) | 197 | 50 |
| проєкція патча | 590 592 | 2 360 064 |
| позиційне кодування | 151 296 | 38 400 |
| CLS-токен | 768 | 768 |
| шари енкодера | 85 054 464 | 85 054 464 |
| фінальний LayerNorm | 1 536 | 1 536 |
| класифікатор | 769 000 | 769 000 |
| усього | 86 567 656 | 88 224 232 |
Головне число теми стоїть у передостанньому рядку: 85 054 464 в обох. Не «приблизно однаково» — однаково до біта. Дванадцять блоків енкодера не знають і не хочуть знати, звідки взялися токени: їм байдуже, чи це 196 патчів по 16×16, чи 49 патчів по 32×32. Розмір патча міняє тільки вхідний каскад.
Розклад ViT-B по частинах
Перемикай патч 16 і 32 й дивись на стовпчики. Один із них не рухається зовсім — це і є відкриття розділу. Нижні стовпчики намальовані в логарифмічній шкалі, інакше все, крім енкодера, було б невидиме.
weights=None — це чиста
арифметика будови, нічого не завантажується. Класифікатор на 1000 класів
ImageNet: 768 × 1000 + 1000 = 769 000.Перевірмо проєкцію рахунком, щоб число не було чарівним. Патч 32×32 із трьох кольорових каналів — це 32 × 32 × 3 = 3072 числа. Матриця переводить їх у 768: 3072 × 768 = 2 359 296, плюс 768 зсувів = 2 360 064. Збіглося. Для патча 16 те саме: 16 × 16 × 3 × 768 + 768 = 590 592.
А тепер найцікавіше. Патч 32 дає вчетверо менше токенів (50 замість 197), тобто увага стає дешевшою приблизно в шістнадцять разів — вона квадратична. Але матриця проєкції росте вчетверо. Підсумок: ViT-B/32 має більше параметрів, ніж ViT-B/16 — і рахується помітно швидше. Це вже третій випадок у курсі, коли кількість параметрів і кількість обчислень розходяться в різні боки; перші два були в темі 15.
Ще одна деталь, яку варто помітити: позиційне кодування ViT-B/16 — це 197 × 768 = 151 296 ваг, тобто 0.2 % моделі. Це вся геометрія, яку має Vision Transformer. Уся інформація про те, що патч номер 5 стоїть праворуч від патча номер 4, а не десь інде, сидить у цих 151 296 числах — і мережа мусить вивчити її з даних. Згортка знала це задарма.
КомпромісСкільки коштує розмір патча
Розмір патча — головна ручка, яку крутить проєктувальник трансформера зору. Вона тягне за собою одразу чотири величини, і всі чотири — в різні боки.
| патч | токенів | пар уваги | ваг у проєкції | роздільність |
|---|---|---|---|---|
| 2×2 | 196 | 38 416 | 240 | 14×14 |
| 4×4 | 49 | 2 401 | 816 | 7×7 |
| 7×7 | 16 | 256 | 2 400 | 4×4 |
| 14×14 | 4 | 16 | 9 456 | 2×2 |
Читати таблицю треба так. Токенів — довжина послідовності, з якою працює енкодер. Пар уваги — квадрат цього числа: стільки ваг рахує один шар уваги, і саме цей стовпчик визначає час. Ваг у проєкції — розмір вхідної матриці, P × P × 48 + 48. Роздільність — наскільки дрібні деталі модель узагалі здатна розрізнити: усередині патча вона нічого не бачить, там усе злилося в один вектор.
Ціна розміру патча
Чотири числа, які тягнуть у різні боки. Немає положення повзунка, де всі чотири були б добрі — саме тому це компроміс, а не налаштування, яке можна «поставити правильно».
Ліворуч таблиці — патч 2×2: майже піксельна роздільність, дешева матриця проєкції, але 38 416 пар уваги на кожному шарі. Праворуч — патч 14×14: шістнадцять пар уваги, тобто увага коштує нічого, зате від зображення лишилось чотири квадрати, і всередині кожного модель сліпа.
Практичний вибір майже завжди лежить посередині й визначається розміром зображення: для 224×224 беруть 16, рідше 32; для нашої 28×28 — 4. Правило просте: цільова кількість токенів десь від кількох десятків до кількох сотень. Менше — модель не бачить деталей, більше — увага зʼїдає всю памʼять.
ЗамірViT проти CNN на нашій задачі
Тепер чесна перевірка. У нас є той самий наскрізний приклад, що й в усьому блоці: шість класів фігур 28×28, згенерованих формулами, шум 0.45, центр гуляє на ±5 пікселів. Порівнюємо три моделі на трьох обсягах даних, по два зерна на кожну точку. Перевірка — 600 фігур, вгадування навмання дає 0.167.
| прикладів | CNN, 4 926 ваг | ViT, 41 574 ваги | ViT без позиційного |
|---|---|---|---|
| 150 | 0.599 | 0.260 | 0.237 |
| 600 | 0.812 | 0.383 | 0.411 |
| 2400 | 0.925 | 0.607 | 0.642 |
Скажемо це прямо, без помʼякшень. Згорткова мережа виграє на кожному обсязі даних, маючи у вісім із половиною разів менше ваг. На 2400 прикладах вона дає 0.925 проти 0.607 — це не «трохи краще», це різні ліги.
І друге, важливіше: ViT не наздоганяє. Від 150 до 2400 прикладів, тобто при зростанні даних у шістнадцять разів, розрив скоротився з 0.339 до 0.318 — на два пункти. Такими темпами він закриється приблизно ніколи. Канонічне «ViT наздоганяє на великих даних» тут просто не настає: 2400 прикладів для трансформера — це нічого.
ViT проти CNN зі зростанням даних
Три криві на одній шкалі. Повзунок обирає обсяг даних; праворуч — точність кожної моделі й розрив між найкращою та ViT. Вусики показують два зерна, щоб було видно, коли різниця більша за розкид.
Чому так — уже сказано в розділі 1. Згорткова мережа знає про зображення три речі безкоштовно, ViT мусить купити їх за дані. При 2400 прикладах грошей на покупку немає.
ГоловнеПозиційне кодування допомагає не завжди
Придивись до останнього стовпчика таблиці. ViT без позиційного кодування ніде не програє, а на двох більших обсягах іде попереду: 0.411 проти 0.383 на 600 прикладах і 0.642 проти 0.607 на 2400. Ми викинули 2 400 ваг, які мали розповідати моделі про геометрію, — і стало не гірше.
Навіть із цим застереженням висновок лишається дивним: 2 400 ваг, які мали розповідати моделі про геометрію, не окупились ніде. Це схоже на спростування всього, що ми казали про потребу в позиційному кодуванні. Насправді це не спростування, а підказка про задачу. Гіпотеза така: наші фігури класифікуються за вмістом, а не за положенням. Коло лишається колом, де б воно не стояло; центр і так гуляє на ±5 пікселів. Задача майже позиційно інваріантна за побудовою — і тоді 2 400 ваг позиційного кодування не несуть корисної інформації, але їх усе одно треба вивчити з мізерних даних.
Гіпотезу треба перевіряти, а не переказувати. Тому ми зробили другу задачу, де положення вирішує все.
| модель | «яка фігура», 6 класів | «де фігура», 4 класи |
|---|---|---|
| CNN | 0.812 | 1.000 |
| ViT із позиційним | 0.383 | 0.995 |
| ViT без позиційного | 0.411 | 0.248 |
| вгадування навмання | 0.167 | 0.250 |
Гіпотеза підтвердилась, і навіть різкіше, ніж очікувалось. На задачі «де фігура» ViT без позиційного кодування дає 0.248 — це рівно вгадування навмання, три зерна поспіль: 0.252, 0.243, 0.250. Модель не просто гірша, вона не працює взагалі. Той самий ViT із позиційним кодуванням розвʼязує ту саму задачу практично ідеально — 0.995.
Коли позиційне кодування потрібне
Перемикай задачу. На «яка фігура» кодування не окупається: різниця з ним і без нього тоне в розкиді від зерна. На «де фігура» без нього моделі просто немає. Пунктир — рівень вгадування навмання для відповідної задачі.
Чому саме 0.25, а не «трохи гірше»
Провал до рівня вгадування — не випадковість і не невдале навчання. Він доводиться алгеброю, без жодного прогону.
Без позиційного кодування увага еквіваріантна щодо перестановки (тема 19): переставиш токени на вході — вони переставляться на виході, і більше нічого не зміниться.
А CLS-токен (як і усереднення) не залежить від порядку взагалі. Тобто вся модель стає інваріантною щодо перестановки патчів: вона бачить не сітку, а невпорядкований мішок із 49 векторів.
Ми навмисне зробили зсув між чвертями рівним 12 пікселям = трьом патчам. Отже, той самий предмет у різних чвертях дає той самий набір патчів, просто розкладений по інших місцях сітки. Для моделі, яка бачить мішок, ці чотири картинки не відрізняються нічим.
Отже, модель фізично не може дати різні відповіді на чотири класи. Її стеля — вгадування, тобто 0.25. Саме це замір і показує.
Кроки 1 і 2 — це вже доведене: тема 19 показала на числах, що два зображення, друге з яких є перестановкою патчів першого, дають без позиційного кодування різницю логітів на рівні чисельного шуму. Нам лишається крок 3 — і його теж можна перевірити за секунду, не навчаючи мережу. Беремо ненавчену модель, подаємо їй ту саму фігуру в чотирьох чвертях і дивимось, наскільки відрізняються виходи:
| модель (ненавчена) | найбільша різниця виходів між чвертями |
|---|---|
| ViT без позиційного | 0.00000024 |
| ViT із позиційним | 0.0130 |
| CNN, доповнення нулями | 0.0027 |
| CNN, доповнення циклічне | 0.0000000000 |
Перший рядок — це нуль із точністю до float32. ViT без позиційного кодування видає на чотирьох різних картинках однаковий вихід. Ніяке навчання цього не виправить: інформації про чверть у моделі немає взагалі.
Несподіванка в останніх двох рядках
А ось чого ми не очікували. Наша CNN закінчується глобальним усередненням, яке за побудовою викидає просторову інформацію (тема 08). Здавалося б, вона теж мала б провалитись на «де фігура». Але вона дає 1.000 — ідеально.
Останні два рядки таблиці пояснюють, звідки вона бере позицію. Різниця виходів у звичайної CNN — 0.0027, тобто вона розрізняє чверті. А та сама мережа з циклічним доповненням країв (коли замість нулів по краях підставляється протилежний край) дає рівно 0.0000000000 — не «майже нуль», а нуль до останнього знака float32. Отже, джерело позиції — саме доповнення нулями. Згортка біля межі бачить смугу штучних нулів, і по тому, як близько ця смуга, мережа відновлює абсолютні координати.
ЧесноЧому справжній ViT усе-таки працює
Питання, яке мусить виникнути після розділу 8: якщо ViT так програє згортці, чому він узагалі став популярним? Відповідь чесна й трохи прикра: тому що в авторів було стільки даних, скільки в нас немає.
В оригінальній роботі про Vision Transformer (Dosovitskiy та співавтори, 2021) картина була така. Навчений тільки на ImageNet-1k — приблизно 1.3 мільйона зображень — ViT програвав порівнянній залишковій мережі. Після передтренування на ImageNet-21k (близько 14 мільйонів зображень) він зрівнявся. А після передтренування на закритому наборі JFT-300M (близько 300 мільйонів зображень) найбільший варіант, ViT-H/14, вийшов на 88.55 % точності на ImageNet — тобто попереду всіх згорткових мереж свого часу.
Тобто перелом стався не в архітектурі, а в кількості даних. Три припущення, які згортка дає задарма, трансформер вивчає сам — але для цього йому потрібні приблизно два порядки більше прикладів.
Через рік стало зрозуміло, що річ не лише в даних. Робота DeiT (Touvron та співавтори, 2021) показала, що ViT-B можна навчити на самому ImageNet-1k до 81.8 % — якщо застосувати важку аугментацію, сильну регуляризацію й довге навчання; дистиляція знань від згорткової мережі-вчителя піднімає результат ще вище. Іншими словами, бракувало не тільки даних, а й рецепта — рівно те, про що попереджала тема 18.
Наші 2400 прикладів на цьому тлі — це навіть не крапля. Тому правильний висновок із розділу 8 не «ViT гірший за CNN», а «на 2400 прикладах ViT не має шансів, і жодна таблиця з ImageNet цього не змінює».
СпадокЩо ViT дав галузі
Якби вся заслуга ViT зводилась до «переміг згортку на дуже великих даних», тема не варта була б окремої лекції. Значення в іншому.
Після ViT текст і зображення обробляються однаковим блоком. Речення — послідовність токенів, картинка — послідовність токенів; далі та сама увага, той самий MLP, той самий залишковий потік. Це відкрило дорогу моделям, які працюють з обома видами даних одночасно, і саме про них буде тема 22.
Трансформери ростуть добре: збільшив модель і дані — точність зросла приблизно так, як обіцяє крива. Зі згортковими мережами цей звʼязок був менш надійним і вимагав ручного добору глибини, ширини й роздільності (тема 15).
Один блок, повторений N разів. Ані пірамідки, ані вручну підібраних розмірів ядер, ані окремих гілок — порівняй із Inception. Менше рішень для людини означає більше свободи для пошуку архітектур і легше перенесення коду між задачами.
Найпарадоксальніший наслідок. Розібравшись, що саме в ViT
працює, дослідники перенесли ці рішення назад у згортки — великі ядра,
норма на гілці, широкий MLP-блок, менше нелінійностей. Так зʼявився
ConvNeXt, який на тих самих даних тримається нарівні з трансформерами.
У нашому середовищі обидва є: convnext_tiny має 28 589 128 ваг,
swin_t — 28 288 354.
Практична порада на 2026 рік лишається прозаїчною. Якщо в тебе тисячі зображень, а не мільйони, — бери згорткову мережу або передтреновану модель, байдуже якої архітектури. Якщо мільйони або якщо тобі потрібна одна модель для тексту й картинок разом — тоді трансформер.
ПідсумокПідсумок і місток далі
Що ми зібрали в цій темі:
- Зображення стає послідовністю через нарізання на патчі. Патч 4×4 для 28×28 дає 49 токенів замість 784 пікселів — 2 401 пара уваги замість 614 656, у 256 разів дешевше. Заплачено роздільністю: найдрібніше, що модель тепер уміє назвати, — це патч.
- Проєкція патча — це згортка з ядром P і кроком P. Перевірено прямим порівнянням: різниця 1.8e-07, тобто нуль у float32.
- CLS-токен — зручна конвенція, а не джерело сили: 0.382 проти 0.402 в усереднення при розкиді від зерна 0.103.
- Блок енкодера — LayerNorm, увага, MLP, залишкові звʼязки. Норму беруть спереду, бо тоді магістраль від входу до виходу лишається чистою: після 12 блоків від входу вціліло 0.223 проти 0.029.
- Розмір патча міняє лише вхід. Шари енкодера ViT-B/16 і ViT-B/32 однакові до біта — 85 054 464 ваги в обох.
- На малих даних згортка виграє — 0.925 проти 0.607 на 2400 прикладах, маючи у 8.4 раза менше ваг.
- Позиційне кодування допомагає тоді, коли положення визначає клас. На задачі про вміст — 2 400 зайвих ваг; на задачі про розташування без нього точність рівно на рівні вгадування.
І що лишилось невирішеним. Два обмеження ViT ми не прибрали, а лише назвали.
Перше — квадратична ціна. Кількість пар уваги дорівнює квадрату кількості токенів. Для 28×28 з патчем 4 це 2 401 пара, і це дрібниця. Для зображення 1024×1024 з тим самим патчем це вже 65 536 токенів і понад чотири мільярди пар на кожному шарі. Детекція й сегментація саме таких роздільностей і потребують.
Друге — відсутність ієрархії. У згортковій мережі роздільність падає від шару до шару: 28×28 → 14×14 → 7×7, і кожен рівень працює зі своїм масштабом деталей. У ViT усі дванадцять блоків працюють з однією й тією самою сіткою 7×7 або 14×14. Пірамідки, на якій стоять і детекція, і сегментація, просто немає.
Цікаво, що обидві втрати — це саме те, що згортка мала задарма: локальність обмежувала ціну, а пулінг будував ієрархію. Наступна тема, 21 «Swin і гібриди», повертає обидві речі назад у трансформер — не відмовляючись від уваги, а обмежуючи її вікном і додаючи злиття патчів між рівнями. Ми подивимось, скільки від «чистої ідеї» ViT лишається після цього і чи варто воно того.