У попередній темі ми порахували AlexNet і VGG по шарах і побачили дві речі. Перша відома всім: глибина працює, і VGG16 із тринадцятьма згортками краща за AlexNet із пʼятьма. Друга відома менше: 123 642 856 із 138 мільйонів ваг VGG16 сидять у трьох повнозвʼязних шарах голови, тобто мережа, яку памʼятають як глибоку згорткову, на 89.4 % складається зі звичайних повнозвʼязних ваг.
Обидві ці речі виправила одна архітектура, і виправила одним прийомом. Її звати
ResNet — від residual network, «залишкова мережа». У цій темі
ми розберемо, що саме зламалось, коли мережі спробували зробити ще глибшими, і чому
рядок out = out + x це полагодив.
resnet18 через torchvision. Кожне число тут — з нашого
власного заміру, жодне не взяте на віру.ОбіцянкаОбіцянка глибини
До 2015 року історія згорткових мереж читалася як одна пряма лінія. AlexNet, 2012 — пʼять згорткових шарів. VGG16, 2014 — тринадцять. Точність росла разом із глибиною, і пояснення було зрозумілим: кожен шар додає рівень абстракції. Перші шари бачать межі, середні — кутки й текстури, глибші — частини предметів. Більше шарів — довший ланцюжок абстракцій.
З цього випливав очевидний наступний крок: узяти VGG, дописати ще двадцять шарів і отримати ще кращу мережу. Спробували — і не вийшло.
Мережа з більшою кількістю шарів давала гіршу точність. Причому гіршою вона була не тільки на перевірочних даних, яких не бачила, а й на самих навчальних, які бачила тисячі разів. Це вже не схоже на жодну з відомих проблем, і саме через це явище дістало власне імʼя — деградація (degradation).
Спершу перевіримо, що це справді відбувається. На нашій задачі, з нашим кодом, із числами, які ти можеш відтворити.
ВимірЩо показує вимір
Задача — та сама, що в блоці 2: шість класів фігур 28×28 (коло, квадрат, ромб, кільце, хрест, трикутник), згенерованих формулами, зі зсувом центра до чотирьох пікселів і гаусовим шумом. Шістсот прикладів на навчання, триста на перевірку. Шість класів означає, що вгадування навмання дає 0.167 — це наша підлога, нижче не буває.
Мережу будуємо з однакових блоків. Кожен блок — це дві згортки 3×3 на шістнадцяти каналах, після кожної батчнорм (тема 11), після першої ReLU, після другої теж ReLU. Перед блоками стоїть один згортковий шар, який зменшує картинку з 28×28 до 7×7; такий вхідний шар прийнято звати стемом (stem) — він приймає сирий кадр і віддає блокам уже стиснуту карту ознак. Після блоків — глобальне усереднення й один лінійний шар на шість класів. Глибину міряємо в згорткових шарах: один у стемі плюс дві на блок. Один блок — три шари, дванадцять блоків — двадцять пʼять.
Тепер найважливіше в цьому досліді. Ми порівнюємо дві мережі, які відрізняються рівно одним знаком «плюс»:
def forward(self, x):
out = self.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out = out + x # цього рядка немає в простій мережі
return self.relu(out)
Однакова кількість згорток, однакова кількість батчнормів, однакова кількість ReLU, однакова кількість ваг до останнього числа. Ми зміряли: на глибині 25 обидві мережі мають рівно 56 342 параметри. Різниця лише в тому, що залишкова мережа додає до виходу блоку його ж вхід. Це важливо: якби мережі відрізнялись ще чимось, висновок нічого не вартував би.
Навчання: SGD із моментом 0.9, швидкість навчання 0.005, двадцять епох, батч 64. Кожен рядок — середнє з трьох прогонів із різними зернами випадковості, бо на такій маленькій задачі один прогін занадто шумний, щоб на нього спиратись.
| глибина | параметрів | проста, навч | проста, тест | залишкова, навч | залишкова, тест |
|---|---|---|---|---|---|
| 3 шари | 4 950 | 0.807 | 0.759 | 0.876 | 0.821 |
| 5 шарів | 9 622 | 0.926 | 0.868 | 0.934 | 0.869 |
| 9 шарів | 18 966 | 0.872 | 0.798 | 1.000 | 0.943 |
| 17 шарів | 37 654 | 0.798 | 0.717 | 1.000 | 0.960 |
| 25 шарів | 56 342 | 0.567 | 0.530 | 1.000 | 0.954 |
Прочитай ліву половину таблиці згори вниз. Проста мережа виходить на найкращу навчальну точність на пʼяти шарах — 0.926. Далі кожні додані шари роблять гірше: 0.872 на девʼяти, 0.798 на сімнадцяти, 0.567 на двадцяти пʼяти. Мережа з двадцяти пʼяти шарів і 56 тисяч ваг вивчила навчальні дані гірше, ніж мережа з пʼяти шарів і 9.6 тисячі ваг — маючи вшестеро більше ваг і ту саму задачу.
Тепер праву половину. Залишкова мережа з тих самих блоків росте монотонно й з девʼяти шарів упирається в стелю: 1.000 на навчальних даних на глибинах 9, 17 і 25. Перевірочна точність — 0.943, 0.960, 0.954. Мережа не ламається.
Деградація глибини
Веди повзунок по глибині. Суцільні лінії — навчальна точність, пунктирні — перевірочна. Рожеві — проста мережа, бірюзові — залишкова.
Не перенавчанняЦе не перенавчання
Найперша думка, коли глибша модель дає гіршу перевірочну точність, — це перенавчання. Модель більша, ваг більше, вона завчила навчальні дані напамʼять і не узагальнює. Ця думка правильна в девʼяти випадках із десяти, і саме тому її треба відкинути акуратно, а не махнути рукою.
Перенавчання має точний підпис. Навчальна точність висока, часто одиниця; перевірочна нижча; розрив між ними росте. Модель уміє відтворити те, що бачила, і не вміє нічого крім.
Подивись на рядок «25 шарів» у нашій таблиці. Навчальна точність простої мережі — 0.567. Перевірочна — 0.530. Розрив між ними 0.037, тобто його практично немає. Мережа не завчила навчальні дані — вона їх не вивчила. На тих самих шістсот прикладах, які бачила двадцять разів поспіль, вона помиляється майже в половині випадків.
Порівняй із мережею на пʼять шарів: 0.926 навчальної проти 0.868 перевірочної. Ось там розрив є, і це нормальний невеликий надлишок памʼяті над узагальненням.
Друга думка, яка приходить наступною, — зникомий градієнт (vanishing gradient). Це відома проблема глибоких мереж: коли сигнал помилки йде назад крізь багато шарів, він множиться на похідні й може згасати до нуля, так що перші шари нічого не вчаться. Але в нашій мережі після кожної згортки стоїть батчнорм, а він саме для того й придуманий, щоб тримати масштаб сигналу в межах. Ми зміряємо градієнт у розділі 6 і побачимо: з ним справді щось не так, але зовсім не те, чого чекаєш.
ПарадоксПарадокс тотожності
Тепер найважливіший аргумент теми, і він не потребує ні коду, ні математики — лише уважності.
Візьмімо нашу мережу на пʼять шарів, яка дала 0.926 на навчальних даних. Тепер збудуймо мережу на двадцять пʼять шарів так: перші пʼять шарів такі самі, а решта двадцять — додаткові.
Питання: чи існує такий набір ваг для цих двадцяти додаткових шарів, при якому двадцятипʼятишарова мережа поводиться точно так само, як пʼятишарова?
Так, існує. Досить, щоб кожен зайвий шар навчився тотожного перетворення — тобто просто передавав свій вхід на вихід незмінним. Тоді двадцять зайвих шарів разом не роблять нічого, сигнал проходить крізь них наскрізь, і мережа дає рівно той самий результат, що й коротша.
Із цього випливає висновок, який виглядає залізобетонним:
Словами: глибша мережа не може бути гіршою за коротшу, бо вона містить коротшу в собі як окремий випадок. У найгіршому разі оптимізація знайде саме цей випадок і зупиниться. Гірше бути не повинно в принципі.
А тепер наш вимір: 0.567 проти 0.926. Розрив — 0.359, і він не на користь глибшої.
Парадокс тотожності
Обери, скільки шарів дописати до найкращої простої мережі. Ліворуч — що каже теорія, праворуч — що показав вимір.
Логіка бездоганна, вимір теж. Отже, помилкове припущення сховане десь між ними — і воно ось яке: ми припустили, що оптимізація знайде тотожне перетворення, якщо воно оптимальне.
Не знайде. Спробуй уявити, що потрібно, щоб згортка 3×3 із батчнормом вивчила тотожність. Ядро має стати одиничним імпульсом: одиниця рівно в центрі свого каналу й нулі у всіх інших 143 позиціях (16 каналів × 9 позицій, мінус одна). Батчнорм має підібрати γ і β так, щоб не змінити масштаб. Це не «просто нічого не робити» — це дуже конкретна, дуже вузька точка у просторі ваг, і градієнтний спуск не має жодної причини цілитись саме в неї.
Отже, проблема не у виразності мережі, а в оптимізації. Потрібний розвʼязок існує, але шлях до нього не проглядається зі стартової точки.
ІдеяТотожність задарма
Коли задача звучить як «потрібне перетворення існує, але його важко знайти», є два шляхи: шукати краще або зробити так, щоб шукати не довелось. ResNet обрала друге.
Ідея в одному рядку. Нехай F(x) — те, що рахує блок із двох згорток. Замість того щоб брати вихід блоку як є, додамо до нього вхід:
Розберемо кожен символ. x — те, що прийшло в блок: карта ознак, наприклад 16 каналів по 7×7. F(x) — те, що порахували дві згортки з батчнормами всередині блоку; форма така сама, 16 на 7×7. y — вихід блоку. Знак «плюс» — звичайне поелементне додавання двох тензорів однакової форми: перше число до першого, друге до другого.
Тепер подивись, що змінилося. Раніше блок мав вивчити яким має бути вихід. Тепер він вчить що додати до входу. Різницю між бажаним виходом і входом називають залишком (residual) — звідси й назва архітектури.
Щоб глибша мережа не була гіршою за коротшу, зайвий блок має вміти нічого не робити. Позначмо бажаний вихід блоку H(x). «Нічого не робити» означає H(x) = x.
Дві згортки з батчнормами мають самі відтворити тотожність. Це та сама вузька точка у просторі ваг, про яку йшлося вище.
Тепер тотожність досягається, коли внутрішня частина видає нуль. А нуль — це те, куди ваги приходять самі: досить, щоб вони поменшали. Ваговий спад (weight decay), який і так стоїть у кожному оптимізаторі, тягне ваги саме туди.
Тотожність перестала бути особливою точкою, яку треба вгадати. Вона стала станом спокою — тим, що вийде, якщо блок нічого не робитиме. Глибина більше не карає: зайвий блок або приносить користь, або дешево вимикається.
Порахуймо це руками на чотирьох числах, щоб не лишалось абстракцією. Хай на вхід блоку прийшов маленький шматок карти ознак: x = [2, 0, 1, 3]. Хай згортки всередині видали F(x) = [0.1, −0.2, 0, 0.05] — маленькі числа, бо ваги ще майже не навчились.
- Звичайний блок віддасть далі [0.1, −0.2, 0, 0.05]. Від початкового [2, 0, 1, 3] не лишилось нічого: інформація зникла на одному шарі.
- Залишковий блок віддасть [2.1, −0.2, 1, 3.05]. Вхід дійшов до виходу майже неушкодженим, блок його трохи підправив.
Це і є зміст скіп-зʼєднання: сигнал за замовчуванням проходить, а шар лише вносить поправку.
ГрадієнтЩо це робить із градієнтом
Досі ми говорили про прямий прохід — про те, як дані йдуть від входу до виходу. Але навчання відбувається у зворотному, і саме там скіп-зʼєднання видно найкраще.
Спершу словами, без похідних. Коли мережа помилилась, сигнал про помилку йде назад: від останнього шару до першого, і кожен шар по дорозі його перетворює. Якщо на кожному кроці сигнал трохи зменшується, до перших шарів дійде майже нуль, і вони не навчатимуться. Якщо на кожному кроці він трохи зростає, до перших шарів дійде щось величезне, і вони почнуть смикатись від кожної дрібниці.
Тепер формально, і це єдина похідна в темі. Похідна — це відповідь на питання «наскільки зміниться вихід, якщо трохи змінити вхід». Позначається значком ∂ (кругле «д»), і запис ∂y/∂x читається «як швидко y змінюється зі зміною x». Похідна суми дорівнює сумі похідних — це шкільне правило, і воно тут вирішує все:
Одиниця в кінці — і є скіп-зʼєднання. Що б не сталося з градієнтом усередині блоку, до неї додається чиста одиниця: частина сигналу проходить крізь блок незміненою. У звичайному блоці цієї одиниці немає, там просто ∂y/∂x = ∂F/∂x, і якщо цей множник систематично більший або менший за одиницю, за двадцять блоків він підноситься до двадцятого степеня.
Зміряймо. Беремо мережу з дванадцяти блоків (25 шарів), подаємо один батч із 64 зображень, рахуємо помилку, робимо зворотний прохід — і дивимось на норму градієнта першої згортки в кожному блоці. Норма — це просто довжина вектора: корінь із суми квадратів усіх чисел градієнта. Одне число, що каже «наскільки сильно цей шар зараз штовхають».
| блок | проста мережа | залишкова |
|---|---|---|
| 1 (біля входу) | 9.077 | 1.480 |
| 4 | 2.584 | 0.609 |
| 8 | 0.688 | 0.435 |
| 12 (біля виходу) | 0.1797 | 0.350 |
| перший / останній | 50.5× | 4.2× |
Норма градієнта по шарах
Перемкни архітектуру. Стовпчики — норма градієнта в кожному з дванадцяти блоків, зліва вхід, справа вихід. Шкала логарифмічна.
БлокБлок ResNet по деталях
Тепер зберімо блок повністю й розберімося з деталями, які виглядають дрібницями, а насправді ними не є.
class Block(nn.Module):
def __init__(self, channels):
self.conv1 = nn.Conv2d(channels, channels, 3, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(channels)
self.conv2 = nn.Conv2d(channels, channels, 3, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(channels)
def forward(self, x):
out = relu(self.bn1(self.conv1(x))) # перша половина: згортка, BN, ReLU
out = self.bn2(self.conv2(out)) # друга: згортка і BN, ReLU поки нема
out = out + x # скіп-зʼєднання
return relu(out) # і аж тепер ReLU
Три деталі, які варто прокоментувати окремо.
Згортки без зсуву (bias=False). Батчнорм одразу після
згортки віднімає середнє по батчу, тож будь-який доданий зсув він тут-таки й прибирає.
Тримати параметр, дію якого наступний шар скасовує, — марна витрата. Для нашого блоку на
16 каналів це економія 32 чисел із 4 672, дрібниця; для ResNet-50 — десятки тисяч.
ReLU стоїть після додавання, а не до нього. Це та деталь, повз яку легко пройти, а вона визначає, що блок узагалі здатен зробити. ReLU обнуляє все відʼємне. Якщо поставити її перед додаванням, гілка F(x) віддаватиме лише невідʼємні числа, і блок зможе тільки додавати — ніколи не віднімати. Сигнал тоді росте від блоку до блоку й ніколи не спадає. Ми зміряли середнє значення активацій на старті: у канонічному варіанті воно росте від 0.91 у першому блоці до 2.36 у дванадцятому, а якщо перенести ReLU перед додаванням (в інтерактиві 4 це режим «скіп після ReLU») — від 1.12 до 5.42. За вдвічі швидшого розгону на дванадцяти блоках на пʼятдесяти буде вже погано.
Скіп нічого не коштує. У ньому немає жодної ваги: це операція додавання двох тензорів. Наш блок на 16 каналів має 4 672 параметри — дві згортки 2 × 16 × 16 × 3 × 3 = 4 608 плюс два батчнорми 2 × 2 × 16 = 64, — і рівно стільки ж їх у простому блоці. Скіп додає трохи памʼяті (вхід блоку треба тримати до самого додавання) і кілька відсотків часу, але жодного параметра.
Куди йде додавання
Три варіанти проводки одного й того самого блоку. Дивись, де стоїть додавання відносно останньої ReLU і що з цього виходить.
НульНульова ініціалізація
У розділі 5 ми сказали: тотожність досягається, коли F(x) = 0. Це відкриває дешевий прийом, який радять усі практичні посібники з ResNet: зробити так, щоб на самому старті кожен блок уже видавав нуль.
Робиться це одним рядком: γ (гамма) останнього батчнорму в блоці ставимо в нуль.
Нагадаю, що робить батчнорм: він нормалізує число, а потім множить на γ і додає β. Ці два параметри мережа вчить сама, і за замовчуванням вони починають з γ = 1, β = 0. Якщо поставити γ = 0, вихід батчнорму стає нулем незалежно від входу. Отже, F(x) = 0, і блок віддає relu(0 + x), що дорівнює просто x — бо x прийшов із попереднього ReLU й уже невідʼємний.
Ми це перевірили прямо, а не на віру: перевірка коштує один рядок,
torch.allclose(block(x), x). Він відповідає True для блоку
з нульовим γ і False для звичайного, де максимальне відхилення виходу
від входу становить 0.65.
Що це дає. Глибока залишкова мережа на старті поводиться як неглибока: усі блоки прозорі, працює тільки стем і голова. Масштаб сигналу не пливе: ми зміряли середнє активацій по дванадцяти блоках, і воно тримається на 0.73 від першого до останнього, тоді як у звичайної залишкової мережі росте з 0.91 до 2.36. Глибина «вмикається» поступово, у міру того як γ відходить від нуля.
Нульова ініціалізація останнього BN
Перемкни старт. Стовпчики — середня активація на виході кожного з дванадцяти блоків до першого кроку навчання.
ФормиКоли форми не збігаються
Досі всі наші блоки мали однаковий вхід і вихід: 16 каналів по 7×7 усередині, 16 каналів по 7×7 назовні. Тому F(x) + x просто працює: два тензори однакової форми додаються поелементно.
Але справжня мережа так не будується. За правилом із теми 09 сторона карти час від часу зменшується вдвічі, а кількість каналів подвоюється. У той момент вхід блоку має форму, скажімо, 64 канали по 56×56, а вихід — 128 каналів по 28×28. Додати їх не можна: різні розміри.
Розвʼязок ResNet — пропустити сам скіп крізь проєкцію: згортку 1×1 з кроком 2. Згортка 1×1 (ми розбирали її в темі 07) не дивиться на сусідів — вона лише перемішує канали в кожній точці окремо. Крок 2 пропускає кожен другий піксель по обох осях, зменшуючи сторону вдвічі. Разом це рівно та операція, яка потрібна: змінити кількість каналів і сторону, не роблячи нічого змістовного.
Ws — ваги цієї згортки 1×1. Скіп перестав бути безкоштовним, тож порахуймо, скільки він тепер коштує. Згортка 1×1 без зсуву з cin у cout каналів має cin × cout ваг, плюс батчнорм після неї додає ще 2 × cout.
У справжній resnet18 проєкція потрібна рівно тричі — на трьох переходах
між стадіями. Ми порахували всі три:
| перехід | канали | проєкція, ваг | увесь блок, ваг | частка |
|---|---|---|---|---|
| layer2[0] | 64 → 128 | 8 448 | 230 144 | 3.67 % |
| layer3[0] | 128 → 256 | 33 280 | 919 040 | 3.62 % |
| layer4[0] | 256 → 512 | 132 096 | 3 673 088 | 3.60 % |
| разом | — | 173 824 | 11 689 512 | 1.49 % |
Тобто вся тотожність, яку ResNet купує проєкціями, коштує півтора відсотка
мережі. Решта скіпів — а їх у resnet18 пʼять із восьми — не коштує
нічого взагалі.
Коли скіпу потрібна проєкція
Перемикай перехід між стадіями. Дивись, чи збігаються форми на двох гілках і скільки коштує їх узгодити.
torchvision.models.resnet18(weights=None), вхід
3×224×224. Верхня гілка — дві згортки 3×3, нижня — скіп. Коли форми збігаються, скіп
намальовано лінією без коробки: він не має жодної ваги.ГорлоПляшкове горло
Наш блок із двох згорток 3×3 звуть звичайним (basic block), і саме на ньому побудовані ResNet-18 і ResNet-34. Але ResNet-50 і глибші влаштовані інакше, і причина суто арифметична.
Згортка 3×3 із c каналів у c каналів має c × c × 9 ваг — тобто росте як квадрат кількості каналів. На 64 каналах це 36 864, на 512 — 2 359 296, у шістдесят чотири рази більше. Стос глибиною в пʼятдесят шарів на широких каналах просто не влізе нікуди.
Прийом називається пляшкове горло (bottleneck) і складається з трьох згорток замість двох:
1×1 : c → c/4 стиснути канали вчетверо — дешево, бо ядро одиничне 3×3 : c/4 → c/4 уся просторова робота — на вчетверо вужчому місці 1×1 : c/4 → c розтиснути назад до потрібної ширини
Уся просторова робота — та сама згортка 3×3, яка й бачить сусідів, — робиться на вчетверо меншій кількості каналів. Дорога згортка отримує в шістнадцять разів менше ваг (бо c² стало (c/4)² ), а дешеві 1×1 по краях коштують мало.
| каналів | звичайний блок | пляшкове горло | у скільки менше |
|---|---|---|---|
| 64 | 73 984 | 4 544 | 16.3× |
| 256 | 1 180 672 | 70 400 | 16.8× |
| 512 | 4 720 640 | 280 064 | 16.9× |
| 2048 | 75 505 664 | 4 462 592 | 16.9× |
Останній рядок і є відповіддю на питання «навіщо». ResNet-50 закінчується стадією на 2048 каналів. На звичайних блоках вона важила б 226 516 992 ваги — більше, ніж уся VGG16 разом із її головою. На пляшкових горлах та сама стадія важить 14 964 736, у пʼятнадцять разів менше. Уся ResNet-50 разом виходить на 25 557 032 параметри — при пʼятдесяти трьох згорткових шарах це впʼятеро легше за VGG16 із тринадцятьма.
Звичайний блок проти пляшкового горла
Веди повзунок по ширині каналів. Обидва блоки мають однакові вхід і вихід — різна лише внутрішня будова.
ResNet18ResNet18 по шарах
Зберімо все докупи на справжній мережі. torchvision будує її локально,
нічого не завантажуючи:
import torchvision.models as models model = models.resnet18(weights=None) # архітектура є, ваг з ImageNet немає
Будова: один згортковий шар 7×7 на початку, потім чотири стадії по два блоки, потім глобальне усереднення й один лінійний шар. Разом 20 згорткових шарів і 11 689 512 параметрів.
| стадія | блоків | каналів | крок першого блоку |
|---|---|---|---|
| conv1 | — | 3 → 64 | 2 (плюс пулінг 2) |
| layer1 | 2 | 64 → 64 | 1 |
| layer2 | 2 | 64 → 128 | 2 |
| layer3 | 2 | 128 → 256 | 2 |
| layer4 | 2 | 256 → 512 | 2 |
| голова | — | 512 → 1000 | — |
А тепер головне число, заради якого ми сюди йшли ще з теми 13. Порахуймо, де ці 11.7 мільйона лежать:
| мережа | усього ваг | згорткових шарів | у згортках | у голові |
|---|---|---|---|---|
| AlexNet | 61 100 840 | 5 | 4.0 % | 96.0 % |
| VGG16 | 138 357 544 | 13 | 10.6 % | 89.4 % |
| ResNet18 | 11 689 512 | 20 | 95.5 % | 4.4 % |
| ResNet34 | 21 797 672 | 36 | 97.6 % | 2.4 % |
| ResNet50 | 25 557 032 | 53 | 91.8 % | 8.0 % |
Пропорція перевернулась. У VGG16 голова тримає 123 642 856 ваг, у ResNet18 — 513 000, у 241 раз менше. Мережа стала вчетверо глибшою за AlexNet — і водночас у дванадцять разів легшою за VGG16. Глибшою за саму VGG16 вона лише в півтора раза: уся економія тут у вазі, не в довжині.
І зробили це не скіп-зʼєднання. Зробило глобальне усереднення з теми 08: замість
Flatten, що перетворює 512×7×7 на рядок із 25 088 чисел і віддає його
повнозвʼязному шару на 4096 виходів, ResNet усереднює кожну карту в одне число й
віддає лінійному шару рядок із 512. Один шар замість трьох, 512 входів замість
25 088.
Ще одне число варте уваги. Батчнорми в ResNet18 займають 0.08 % параметрів — 9 600 ваг з одинадцяти з половиною мільйонів. Шар, без якого глибока мережа не навчається взагалі, важить менше однієї тисячної.
ПідсумокПідсумок і що далі
Що ми зміряли в цій темі:
- Деградація існує, і ми її відтворили. Проста мережа з 25 згорткових шарів дала 0.567 навчальної точності проти 0.926 у мережі з пʼяти. Та сама мережа зі скіп-зʼєднаннями — 1.000.
- Це не перенавчання. На глибині 25 навчальна точність простої мережі 0.567, перевірочна 0.530: розрив 0.037. Мережа не завчила дані напамʼять, вона їх не вивчила.
- Парадокс тотожності. Глибша мережа містить коротшу в собі, тож гіршою бути не може — а на вимірі гірша на 0.359. Отже, проблема в оптимізації, а не у виразності.
- Скіп вирівнює градієнт. Розкид норми градієнта між першим і останнім блоком: 50.5 раза в простій мережі, 4.2 раза в залишковій. Похідна суми пропускає одиницю крізь скіп незміненою.
- Скіп безкоштовний. Мережі на глибині 25 мають рівно по 56 342 параметри обидві. Проєкції 1×1 у справжній resnet18 коштують 1.49 % ваг.
- Пляшкове горло дає 16.9 раза. На 2048 каналах звичайний блок важив би 75 505 664 ваги, блок із горлом — 4 462 592.
- Друга революція — голова. 95.5 % ваг ResNet18 у згортках проти 10.6 % у VGG16. Голова схудла з 123 642 856 ваг до 513 000.
Скіп-зʼєднання пішло далі за ResNet
Прийом виявився надто корисним, щоб лишитись деталлю однієї архітектури. Сьогодні важко знайти глибоку мережу без нього:
- U-Net для сегментації — скіпи звʼязують шари стиснення з шарами відновлення, повертаючи дрібні деталі, втрачені при пулінгу. Про це — блок 6 курсу.
- Трансформери — кожен блок уваги й кожен повнозвʼязний підблок обгорнуті в x + …. Без цього трансформер на дванадцять шарів не навчається. Про це — блок 4.
- DenseNet пішла далі й зʼєднала кожен шар з усіма наступними; ідея та сама, тільки замість додавання — склеювання каналів.
Що далі
ResNet зняла обмеження на глибину й заразом викинула важку голову. Мережа стала глибокою, точною і при цьому вдванадцятеро легшою за VGG16.
Але «легша за VGG16» — це слабка похвала. ResNet-50 — це 25 557 032 ваги, і сама лише її остання стадія, яку ми щойно порахували, важить 14 964 736 — більше, ніж уся ResNet18 разом. І це вже після того, як пляшкове горло зрізало з неї пʼятнадцять шістнадцятих. На сервері з відеокартою це дрібниця. На телефоні, який має вкластися в кадр відео й не розрядити батарею, це неможливо.
Отже, наступне питання не «як зробити глибше», а «як зробити дешевше» — і відповідь виявиться такою ж несподіваною, як і скіп-зʼєднання. Про це — тема 15, Inception, MobileNet, EfficientNet.