🔴 Сложный ⏱️ 55 минут

Inception и EfficientNet

📋 Содержание урока

Inception и EfficientNet 🧩

В уроке 336 ты уже мельком встречал имя Inception в общем обзоре классических CNN-архитектур, а урок 337 подробно разобрал skip connections в ResNet — идею, что сеть иногда полезнее учить «ничего не делать», чем заставлять её насильно преобразовывать каждый вход. Сегодня мы возвращаемся к Inception вплотную и разбираем вторую по-настоящему архитектурную идею того же периода: а что, если вместо того чтобы архитектор заранее гадал, какой размер фильтра — 1×1, 3×3 или 5×5 — лучше подойдёт для конкретного слоя, просто применить все варианты параллельно и позволить сети самой решить, какие из них полезнее?

За этой, на первый взгляд, простой идеей параллельных фильтров скрывается вторая, куда менее очевидная: чтобы параллельное применение нескольких свёрток вообще было вычислительно посильным, нужен приём, который сначала кажется тривиальным — свёртка размера 1×1. Она не меняет пространственные размеры карты признаков ни на пиксель, поэтому интуитивно легко подумать, что она «ничего не делает». На деле именно она превращает Inception из красивой, но неподъёмной по вычислениям идеи в архитектуру, которая реально обучалась на железе 2014 года — и её роль как «бутылочного горлышка» (bottleneck), резко сокращающего число каналов перед дорогими свёртками, стала одним из самых влиятельных архитектурных приёмов в истории CNN.

Дальше в этом уроке мы пройдём путь от Inception v1 (GoogLeNet, 2014) через v2, v3 и v4 — по каждой версии кратко, без глубокого погружения в детали, — и придём к архитектуре, которая рвёт шаблон полностью: EfficientNet (2019). Если Inception всё ещё во многом остаётся результатом ручного инженерного творчества — люди придумали параллельные ветки, люди придумали bottleneck, — то EfficientNet задаёт принципиально другой вопрос: а что, если вместо того чтобы вручную добавлять слои или фильтры, систематически, по найденной оптимальной формуле, масштабировать сразу три измерения сети — глубину, ширину и разрешение входного изображения — совместно? Этот подход называется compound scaling (составное масштабирование), и он напрямую связан с темой автоматического поиска архитектур (Neural Architecture Search, NAS), которую ты уже встречал в контексте генетических алгоритмов (урок 296) и целочисленного программирования (урок 284) как общих инструментов оптимизации дискретных и комбинаторных пространств решений.

Для практикующего специалиста по Data Science эта тема — не архивная историческая справка. Вопрос «добавить ли ещё слоёв, ещё каналов или увеличить разрешение входа» встаёт в реальных проектах постоянно, особенно когда модель нужно развернуть на мобильном устройстве или в сервисе с ограниченным бюджетом на инференс. EfficientNet и его принцип compound scaling — это практический урок о том, что наивное «просто добавить больше слоёв» почти никогда не оптимально, а систематически продуманная (или найденная автоматическим поиском) архитектура способна давать значительно лучший результат при том же вычислительном бюджете. Именно поэтому сегодняшний урок — не просто ещё одна архитектура в коллекции, а смена самого способа мышления об архитектурном дизайне.

История

В 2014 году команда исследователей Google — Кристиан Сегеди (Christian Szegedy) и соавторы — представила архитектуру GoogLeNet на том же конкурсе ILSVRC (ImageNet Large Scale Visual Recognition Challenge, «Задача крупномасштабного визуального распознавания ImageNet»), где годом ранее блистала VGGNet своей философией «глубже и только фильтры 3×3» (урок 336). GoogLeNet победила в конкурсе классификации, но пошла принципиально другим путём: вместо одной глубокой последовательной цепочки слоёв она ввела «сеть в сети» — блоки, названные inception-модулями, где несколько свёрток разного размера применяются параллельно к одному и тому же входу, а результаты объединяются. Название «Inception» — отсылка к фильму «Начало» (Inception) Кристофера Нолана и интернет-мему «we need to go deeper» («нужно копнуть глубже») — авторы явно намекали и на глубину сети, и на вложенность идеи «сеть внутри сети». При этом GoogLeNet заметно выигрывала у VGGNet по эффективности: около 5 миллионов параметров против 138 миллионов у VGG-16, при сопоставимой или лучшей точности.

Идея параллельных фильтров не была статичной: в последующие годы та же исследовательская группа выпустила несколько версий Inception, каждая из которых улучшала как точность, так и вычислительную эффективность архитектуры, вводя новые приёмы факторизации свёрток и регуляризации. Эта эволюция шла параллельно с развитием ResNet (урок 337, 2015 год), и в какой-то момент идеи двух архитектур были прямо скрещены — Inception-ResNet добавила skip connections внутрь inception-модулей, объединив лучшее из обоих миров.

Пять лет спустя, в 2019 году, команда Google Brain во главе с Мингксингом Таном (Mingxing Tan) и Куок Ле (Quoc V. Le) задала вопрос совершенно иного порядка. К тому моменту индустрия перепробовала десятки способов сделать сеть точнее: кто-то увеличивал глубину (больше слоёв, как ResNet), кто-то — ширину (больше каналов в каждом слое, как Wide ResNet), кто-то — разрешение входного изображения. Каждый из этих подходов работал в изоляции, но было неясно, есть ли принципиально лучший способ комбинировать все три измерения одновременно. Команда провела систематическое исследование и обнаружила, что масштабирование глубины, ширины и разрешения не независимо, а по фиксированным, найденным эмпирически пропорциям даёт заметно лучший результат, чем масштабирование по одной оси. Так родилось семейство EfficientNet — от EfficientNet-B0 до EfficientNet-B7, — которое при значительно меньшем числе параметров превзошло по точности архитектуры, ранее считавшиеся эталоном эффективности.

Inception-модуль: параллельные фильтры вместо последовательного выбора

Интуиция

Представь, что перед архитектором сети стоит выбор: на этом слое использовать фильтр 1×1, 3×3 или 5×5? Маленький фильтр 1×1 улавливает точечные, локальные закономерности без учёта соседних пикселей. Фильтр 3×3 захватывает небольшую окрестность — линии, углы, простые текстуры. Фильтр 5×5 видит ещё более широкий контекст — крупные формы, комбинации текстур. Проблема в том, что для разных изображений и даже для разных участков одного и того же изображения оптимальный размер рецептивного поля (области входа, влияющей на один выходной нейрон) может быть разным: где-то важна мелкая деталь, где-то — общий контур объекта. Архитектор, вынужденный заранее выбрать один размер фильтра для всего слоя, неизбежно жертвует частью информации.

Inception-модуль решает эту дилемму не выбором, а отказом от выбора: он применяет несколько размеров фильтров параллельно к одному и тому же входу, а затем склеивает (конкатенирует) все результаты по оси каналов в единый выходной тензор. Дальнейшие слои сети получают доступ ко всем масштабам признаков одновременно и сами, в процессе обучения, определяют через веса последующих слоёв, какая комбинация масштабов полезнее для конкретной задачи. Архитектору больше не нужно гадать — сеть решает сама, опираясь на градиентный спуск, а не на интуицию человека.

Формула

Inception-модуль (базовая версия). Пусть на вход подаётся тензор признаков с $C$ каналами. Модуль состоит из нескольких параллельных веток, каждая из которых обрабатывает один и тот же вход независимо:

$$\text{Ветка 1: } 1\times1 \text{ свёртка}, \qquad \text{Ветка 2: } 3\times3 \text{ свёртка}, \qquad \text{Ветка 3: } 5\times5 \text{ свёртка}, \qquad \text{Ветка 4: } 3\times3 \text{ max-pooling}$$

Каждая ветка сохраняет пространственные размеры входа (за счёт padding), но может менять число каналов. Итоговый выход модуля — конкатенация по оси каналов:

$$\text{Output} = \text{Concat}(\text{Ветка}_1, \text{Ветка}_2, \text{Ветка}_3, \text{Ветка}_4)$$

В полной версии GoogLeNet перед дорогими свёртками 3×3 и 5×5, а также после pooling, добавляются 1×1 bottleneck-свёртки — их роль разбирается в следующем разделе отдельно.

Разбор примеров

Пример 1 (форма выхода при конкатенации). Пусть вход имеет пространственный размер $28\times28$ и $192$ канала. Настроим четыре ветки inception-модуля так, чтобы каждая давала на выходе $28\times28$ (за счёт подходящего padding), но с разным числом каналов: ветка 1×1 — $64$ канала, ветка 3×3 — $128$ каналов, ветка 5×5 — $32$ канала, ветка pooling+1×1 — $32$ канала. Тогда итоговый тензор после конкатенации имеет размер $28\times28\times(64+128+32+32) = 28\times28\times256$. Обрати внимание: пространственные размеры ($28\times28$) у всех веток обязаны совпадать — иначе конкатенация по каналам физически невозможна, — а вот число каналов у каждой ветки может быть каким угодно и складывается.

Пример 2 (почему нужен padding, а не просто «применить фильтр»). Если применить к входу $28\times28$ свёртку $5\times5$ без padding, выходной размер уменьшится: по формуле $\lfloor(28-5)/1\rfloor+1=24$, то есть получится $24\times24$ — а ветка $1\times1$ того же входа без padding даст $28\times28$ без изменений. Чтобы такие ветки можно было конкатенировать, для свёртки $5\times5$ нужен padding $p=2$ (по общей формуле $p=(k-1)/2$ для нечётного ядра $k$ при шаге $1$): тогда выходной размер $\lfloor(28+2\cdot2-5)/1\rfloor+1=28$, и все ветки снова синхронизированы по пространственным размерам.

Пример 3 (что видит сеть на разных ветках одновременно — численно через рецептивное поле). Рецептивное поле — это область входного изображения, которая влияет на значение одного выходного пикселя. У ветки $1\times1$ рецептивное поле относительно входа этого модуля — ровно $1\times1$ пиксель (без учёта предыдущих слоёв сети). У ветки $3\times3$ — область $3\times3=9$ пикселей. У ветки $5\times5$ — область $5\times5=25$ пикселей, то есть почти в три раза больше, чем у ветки $3\times3$, и в 25 раз больше, чем у ветки $1\times1$. После конкатенации каждый выходной канал модуля «видел» разный масштаб контекста — от одного пикселя до 25 — и следующий слой сети волен комбинировать эти масштабы как ему выгодно для конкретной задачи, вместо того чтобы полагаться на единственный выбранный архитектором масштаб.

Почему это важно

Параллельные фильтры разного размера — это отказ от жёсткого архитектурного решения в пользу гибкости, которую сеть настраивает сама через обучаемые веса. Тот же принцип «не выбирать заранее, а дать модели выбрать через обучение» ты уже видел в ResNet (урок 337), где skip connection позволяет слою «выбрать» между преобразованием входа и пропуском его без изменений. Inception применяет ту же философию к другому измерению — не «преобразовывать или пропустить», а «какой масштаб фильтра важнее здесь». Именно эта гибкость, а не какой-то один конкретный размер фильтра, объясняет, почему GoogLeNet смогла конкурировать с гораздо более крупными по числу параметров архитектурами.

Роль свёртки 1×1: бутылочное горлышко, экономящее вычисления

Интуиция

Свёртка $1\times1$ выглядит почти как трюк, а не как настоящая операция: она не захватывает никакого пространственного контекста — ни соседних пикселей сверху, ни снизу, ни слева, ни справа, — а лишь пересчитывает значение в каждой точке как взвешенную сумму значений по всем входным каналам в этой же точке. По сути, это полносвязный слой, применённый независимо к каждому пикселю карты признаков, где входом служит не всё изображение, а вектор из $C$ значений одного пикселя по всем каналам. Именно поэтому она может как уменьшать число каналов (если выходных фильтров меньше, чем входных каналов), так и увеличивать его, оставляя пространственные размеры $H\times W$ абсолютно нетронутыми.

Ключевая практическая ценность этой, казалось бы, тривиальной операции — в том, что её можно поставить перед дорогой свёрткой $3\times3$ или $5\times5$, чтобы сначала резко сократить число каналов, а уже потом применить дорогую операцию к гораздо более узкому тензору. Такую конструкцию называют bottleneck (бутылочное горлышко): узкое место, через которое пропускают поток данных, чтобы сэкономить на всём, что после него. Число операций умножения-сложения (MAC) в свёртке линейно зависит от числа входных каналов, поэтому уменьшение каналов, например, с $256$ до $64$ перед свёрткой $5\times5$ сокращает вычисления в этой свёртке не в 4 раза (во сколько уменьшилось число каналов), а куда сильнее — потому что помимо каналов есть ещё пространственный размер ядра $5\times5=25$, который остаётся прежним, но применяется к резко «похудевшему» входу.

Формула

Число параметров (и, пропорционально им, число MAC-операций на один выходной пиксель) свёрточного слоя. Для свёртки с ядром $k\times k$, $C_{in}$ входными и $C_{out}$ выходными каналами (без учёта смещений):

$$\text{Параметров} = k\times k\times C_{in}\times C_{out}$$

Bottleneck-блок Inception. Вместо прямой свёртки $k\times k$ от $C_{in}$ до $C_{out}$ каналов используется композиция из двух свёрток: сначала $1\times1$ от $C_{in}$ до $C_{bottleneck}$ каналов (где $C_{bottleneck}\ll C_{in}$), затем $k\times k$ от $C_{bottleneck}$ до $C_{out}$:

$$\text{Параметров}_{\text{bottleneck}} = \underbrace{1\times1\times C_{in}\times C_{bottleneck}}_{\text{сжатие}} + \underbrace{k\times k\times C_{bottleneck}\times C_{out}}_{\text{дорогая свёртка на узком входе}}$$

Разбор примеров

Пример 1 (прямой численный расчёт экономии — свёртка 5×5, 512 → 256 каналов). Пусть нужно применить свёртку $5\times5$ к входу с $512$ каналами и получить $256$ выходных каналов, напрямую, без bottleneck:

$$\text{Параметров}_{\text{прямо}} = 5\times5\times512\times256 = 25\times131\,072 = 3\,276\,800$$

Теперь вставим bottleneck: сначала $1\times1$ свёртка сжимает $512$ каналов до $64$, затем $5\times5$ свёртка работает уже от $64$ каналов до $256$:

$$\text{Параметров}_{1\times1} = 1\times1\times512\times64 = 32\,768$$

$$\text{Параметров}_{5\times5} = 5\times5\times64\times256 = 25\times16\,384 = 409\,600$$

$$\text{Параметров}_{\text{bottleneck}} = 32\,768 + 409\,600 = 442\,368$$

Экономия: $3\,276\,800 / 442\,368 \approx 7{,}41$ — то есть свёртка с bottleneck требует почти в 7,4 раза меньше параметров (и, соответственно, вычислений), чем прямая свёртка $5\times5$ с тем же числом входных и выходных каналов, при этом рецептивное поле $5\times5$ (то есть способность видеть тот же пространственный контекст) полностью сохраняется.

Пример 2 (та же логика для 3×3, вход поменьше — как в исходном inception-модуле GoogLeNet). Пусть вход $28\times28\times192$, нужно получить $128$ выходных каналов через свёртку $3\times3$. Без bottleneck:

$$3\times3\times192\times128 = 9\times24\,576 = 221\,184 \text{ параметров}$$

С bottleneck через $96$ промежуточных каналов (именно такое число использовала оригинальная GoogLeNet в одной из веток):

$$1\times1\times192\times96 = 18\,432, \qquad 3\times3\times96\times128 = 9\times12\,288 = 110\,592$$

$$\text{Итого: } 18\,432+110\,592 = 129\,024 \text{ параметров}$$

Экономия $221\,184/129\,024\approx1{,}71$ — почти в $1{,}7$ раза меньше. Обрати внимание: для свёртки $3\times3$ выигрыш от bottleneck заметно скромнее, чем для $5\times5$ из примера 1 — потому что $k\times k=9$ для $3\times3$ растёт медленнее, чем $25$ для $5\times5$, а именно квадрат размера ядра определяет, насколько дорого «не сжимать» каналы перед свёрткой.

Пример 3 (что произойдёт без bottleneck на масштабе всей сети — почему GoogLeNet вообще смогла обучаться в 2014 году). GoogLeNet содержит девять inception-модулей подряд, и в каждом — несколько веток с крупными фильтрами. Если бы каждая ветка $5\times5$ применялась напрямую к входу с несколькими сотнями каналов (типичная ситуация в средних и поздних слоях сети), суммарное число параметров и вычислений по всей сети выросло бы в несколько раз — заметная доля общего бюджета архитектуры. Именно систематическая экономия за счёт bottleneck на каждом модуле и позволила GoogLeNet удержать около 5 миллионов параметров — почти в 30 раз меньше, чем 138 миллионов у VGG-16 (урок 336), — при сопоставимой точности на ImageNet.

Почему это важно

Свёртка $1\times1$ — хороший пример того, как операция, которая кажется вырожденной («она же не видит пространственный контекст, зачем она вообще нужна?»), оказывается критически важной именно потому, что решает не задачу «увидеть больше», а задачу «сделать дорогие операции посильными». Понимание bottleneck-конструкции пригодится далеко за пределами Inception: тот же приём — сначала сжать каналы дешёвой $1\times1$ свёрткой, затем сделать основную работу на узком тензоре, затем восстановить исходное число каналов ещё одной $1\times1$ свёрткой — лежит в основе bottleneck-блоков ResNet-50/101/152 (урок 337) и десятков современных мобильных архитектур.

Эволюция Inception: от v1 к v4

Интуиция

Идея параллельных фильтров с 2014 по 2016 год прошла через несколько итераций, каждая из которых решала конкретную практическую проблему предыдущей версии — либо избыточную вычислительную стоимость, либо недостаточную стабильность обучения, либо желание объединить лучшее из Inception и ResNet в одной архитектуре.

Формула

Краткая хронология версий Inception.

  • Inception v1 (GoogLeNet, 2014): базовый inception-модуль с параллельными ветками $1\times1$, $3\times3$, $5\times5$, pooling и bottleneck-свёртками $1\times1$ перед дорогими операциями (разобрано выше).
  • Inception v2–v3 (2015–2016): факторизация крупных свёрток на более мелкие — например, свёртка $5\times5$ заменяется двумя последовательными свёртками $3\times3$ (как в VGGNet, урок 336), а свёртка $n\times n$ заменяется парой асимметричных свёрток $1\times n$ и $n\times1$; добавлена батч-нормализация и вспомогательные классификаторы (auxiliary classifiers) на промежуточных слоях для более стабильного обучения глубокой сети.
  • Inception v4 и Inception-ResNet (2016): упрощение и унификация модулей v1–v3, а в версии Inception-ResNet — добавление skip connections (урок 337) непосредственно внутрь inception-модулей, что ускорило сходимость при обучении.

Разбор примеров

Пример 1 (факторизация 5×5 на два 3×3 — экономия параметров). Свёртка $5\times5$ от $C$ до $C$ каналов требует $5\times5\times C\times C=25C^2$ параметров. Две последовательные свёртки $3\times3$ от $C$ до $C$ каналов (с тем же итоговым рецептивным полем $5\times5$, как ты уже видел на примере VGGNet в уроке 336) требуют $2\times(3\times3\times C\times C)=18C^2$ параметров. При $C=192$: прямая свёртка — $25\times192^2=921\,600$ параметров, две факторизованные — $18\times192^2=663\,552$ параметра. Экономия $921\,600/663\,552\approx1{,}39$ — примерно на $28\%$ меньше параметров при том же рецептивном поле.

Пример 2 (асимметричная факторизация 3×3 на 1×3 и 3×1 — ещё дешевле). Ту же свёртку $3\times3$ от $C$ до $C$ каналов ($9C^2$ параметров) можно заменить последовательностью $1\times3$ и $3\times1$: $(1\times3\times C\times C)+(3\times1\times C\times C)=3C^2+3C^2=6C^2$ параметров. При $C=192$: было $9\times192^2=331\,776$, стало $6\times192^2=221\,184$ — экономия ещё примерно в $1{,}5$ раза относительно уже факторизованной версии из примера 1, и это в дополнение к экономии от bottleneck, разобранной в предыдущем разделе.

Пример 3 (зачем нужны вспомогательные классификаторы — не про экономию, а про стабильность). В глубокой сети из десятков слоёв градиент ошибки, посчитанный на самом последнем слое, должен «дойти» обратным распространением до самых первых слоёв — и по дороге может сильно ослабевать (эта же проблема детально разбиралась в уроке 337 как причина появления skip connections). Inception v1–v3 частично смягчали эту проблему по-своему: к паре промежуточных слоёв сети прикреплялись дополнительные, вспомогательные выходы классификации со своей собственной функцией потерь. Во время обучения градиент от этих промежуточных потерь добавлялся к общему градиенту с небольшим весом (например, $0{,}3$), давая средним слоям сети более сильный и более «свежий» обучающий сигнал напрямую, а не только через длинную цепочку обратного распространения от самого конца сети. На инференсе (то есть при использовании уже обученной модели) вспомогательные классификаторы просто отбрасывались — они существовали исключительно как вспомогательный инструмент обучения.

Почему это важно

Эволюция от v1 к v4 показывает типичный паттерн развития успешной архитектуры: сначала появляется работающая, но затратная идея (параллельные ветки v1), затем инженеры находят способы сделать её дешевле без потери качества (факторизация в v2–v3), а в конце — объединяют её с лучшими идеями конкурирующих архитектур (skip connections из ResNet в Inception-ResNet). Важно другое: несмотря на все улучшения, вся эта эволюция всё ещё держится на ручном инженерном творчестве — люди придумывали, как именно факторизовать свёртку и куда именно поставить вспомогательный классификатор. Именно этот ручной, итеративный процесс проектирования архитектуры EfficientNet и заменяет систематическим поиском, о котором пойдёт речь дальше.

EfficientNet и compound scaling: систематическое масштабирование вместо догадок

Интуиция

К 2019 году индустрия накопила десятки способов сделать сеть мощнее: углубить (добавить слои, как ResNet), расширить (добавить каналов в каждый слой, как Wide ResNet) или увеличить разрешение входного изображения (обучать не на $224\times224$, а на $331\times331$ или крупнее). Каждый из этих трёх рычагов интуитивно понятен по отдельности: больше слоёв — сеть способна выучивать более сложные, иерархически составные признаки; больше каналов — на каждом уровне иерархии доступно больше «слотов» для разных типов признаков; выше разрешение — сеть различает более мелкие детали на изображении. Стандартная практика до EfficientNet заключалась в том, чтобы менять один из этих трёх параметров произвольно, «на глаз», и смотреть, что получится — а чаще всего просто масштабировали только глубину, потому что добавить ещё один слой к архитектуре проще всего технически.

Команда Google Brain задала другой вопрос: что, если увеличивать не один параметр, а все три одновременно, причём не в случайных, а в заранее найденных фиксированных пропорциях? Интуиция здесь такая: если ты увеличиваешь разрешение входа, отдельные объекты на изображении занимают больше пикселей — и сети нужно больше слоёв (глубины), чтобы её рецептивное поле по-прежнему покрывало эти увеличившиеся объекты, а также больше каналов (ширины), чтобы захватывать более мелкие, тонкие детали, которые стали видны на возросшем разрешении. Масштабировать одну ось, игнорируя две другие, — это то же самое, что вложиться только в самый широкий монитор, не подумав ни о видеокарте, ни о скорости интернета: результат ограничен самым слабым, немасштабированным звеном.

Формула

Compound scaling (составное масштабирование) EfficientNet. Введём единый коэффициент масштабирования $\phi\geq0$, который управляет одновременным ростом всех трёх измерений сети:

$$\text{глубина: } d=\alpha^\phi, \qquad \text{ширина: } w=\beta^\phi, \qquad \text{разрешение: } r=\gamma^\phi$$

где $\alpha,\beta,\gamma$ — константы, найденные через небольшой поиск по сетке (grid search) на базовой архитектуре, при ограничении

$$\alpha\cdot\beta^2\cdot\gamma^2\approx2, \qquad \alpha\geq1,\ \beta\geq1,\ \gamma\geq1$$

Ограничение подобрано так, что при увеличении $\phi$ на единицу суммарные вычисления сети (пропорциональные $d\cdot w^2\cdot r^2$, поскольку ширина и разрешение влияют на число операций квадратично, а глубина — линейно) растут примерно в 2 раза. Найденные для EfficientNet-B0 оптимальные значения: $\alpha\approx1{,}2$, $\beta\approx1{,}1$, $\gamma\approx1{,}15$.

Разбор примеров

Пример 1 (проверка ограничения на вычислительный бюджет). Подставим найденные значения в ограничение: $\alpha\cdot\beta^2\cdot\gamma^2 = 1{,}2\times1{,}1^2\times1{,}15^2 = 1{,}2\times1{,}21\times1{,}3225$. Посчитаем по шагам: $1{,}2\times1{,}21=1{,}452$, затем $1{,}452\times1{,}3225\approx1{,}92$. Результат действительно близок к $2$ — это означает, что каждый шаг увеличения $\phi$ на единицу примерно удваивает вычислительные затраты сети, при этом «удвоенный бюджет» распределяется не в одну ось, а пропорционально между глубиной, шириной и разрешением.

Пример 2 (масштабирование от B0 до B3 — конкретные числа). Возьмём базовую модель EfficientNet-B0 с условной глубиной $d_0=18$ слоёв-блоков, шириной, равной базовому числу каналов ($w_0=1{,}0\times$), и разрешением входа $224\times224$. Масштабируем до EfficientNet-B3 с коэффициентом $\phi=3$:

$$d = 1{,}2^3 = 1{,}728, \qquad w = 1{,}1^3 = 1{,}331, \qquad r = 1{,}15^3 \approx 1{,}521$$

Применяя эти множители: глубина растёт до $18\times1{,}728\approx31$ слоя-блока, ширина каналов увеличивается примерно в $1{,}33$ раза, а разрешение входа растёт до $224\times1{,}521\approx341$, то есть примерно $340\times340$ пикселей (на практике разрешение округляется до значения, кратного $32$, поскольку сеть многократно уменьшает пространственные размеры в 2 раза). Обрати внимание, что ни один из трёх параметров не вырос в изоляции — все три увеличились совместно, согласованно, а не по отдельности «наугад».

Пример 3 (почему совместное масштабирование выигрывает у масштабирования одной оси — сравнение вычислительной эффективности). Представь, что вместо совместного масштабирования ты решаешь удвоить вычислительный бюджет сети, увеличивая только глубину в 2 раза (то есть $d=2$, $w=1$, $r=1$). Вычисления вырастут пропорционально $d\cdot w^2\cdot r^2 = 2\times1\times1=2$ — тот же самый бюджет, что и при compound scaling с $\phi=1$ ($d\cdot w^2\cdot r^2\approx1{,}2\times1{,}21\times1{,}3225\approx1{,}92\approx2$). Разница не в потраченном бюджете, а в том, куда он направлен: экспериментально команда EfficientNet показала, что при равном вычислительном бюджете модель, где рост распределён между всеми тремя измерениями, стабильно достигает более высокой точности на ImageNet, чем модель, где весь дополнительный бюджет ушёл в одну ось (только глубину, только ширину или только разрешение). Именно поэтому compound scaling — не просто удобная формула, а результат, подтверждённый прямым сравнением с альтернативами при фиксированном бюджете.

Почему это важно и как это связано с NAS

Ключевой момент, который отличает EfficientNet от Inception по духу: коэффициенты $\alpha$, $\beta$, $\gamma$ не придуманы вручную интуицией инженера, а найдены через небольшой, но систематический перебор по сетке значений на компактной базовой архитектуре, после чего сама базовая архитектура EfficientNet-B0 была получена методом Neural Architecture Search (NAS) — автоматическим поиском архитектуры, который оптимизирует одновременно точность и вычислительную эффективность (число операций с плавающей точкой, FLOPS) как двухкритериальную задачу. Тот же общий принцип — не перебирать архитектуры вручную, а сформулировать поиск как оптимизационную задачу и решить её автоматическим алгоритмом — ты уже встречал в этом курсе в совершенно других контекстах: генетические алгоритмы (урок 296) используют эволюционные операторы (мутацию, скрещивание, отбор) для поиска решений в огромных дискретных пространствах, а целочисленное программирование (урок 284) формализует задачи с дискретными переменными через строгие математические ограничения и целевую функцию. NAS для архитектур нейросетей — это тот же класс задач: пространство возможных архитектур (число слоёв, тип операции в каждом слое, размер фильтров, наличие skip connections) — дискретное и огромное, а целевая функция — точность модели на валидационных данных при заданном бюджете вычислений, которую напрямую нельзя продифференцировать, поэтому для поиска в таких пространствах естественно подходят методы, похожие на генетические алгоритмы, обучение с подкреплением или, как в случае с коэффициентами $\alpha,\beta,\gamma$ EfficientNet, относительно небольшой grid search поверх результатов более масштабного NAS-поиска базовой архитектуры.

Достижение EfficientNet в цифрах говорит само за себя: EfficientNet-B0 достигает точности, сопоставимой с ResNet-50 (урок 337), при этом примерно в 5 раз меньше по числу параметров и примерно в 10 раз меньше по числу операций (FLOPS). А наиболее крупная модель семейства, EfficientNet-B7, на момент публикации превзошла по точности на ImageNet все существовавшие архитектуры, включая гораздо более тяжёлые сети, при этом имея примерно в 8,4 раза меньше параметров, чем ближайший по точности конкурент. Для практика это означает конкретный вывод: если стоит задача развернуть модель на мобильном устройстве или в сервисе с ограниченным бюджетом на инференс, продуманное совместное масштабирование архитектуры почти всегда выигрывает у наивного «добавим ещё слоёв, раз есть свободная память на GPU».

Практика: 30 заданий

Базовые задания (1–10)

Задание 1: Сколько параметров в свёртке $3\times3$ от $128$ входных до $256$ выходных каналов (без bias)?


Задание 2: Свёртка $1\times1$ от $256$ до $64$ каналов. Изменится ли пространственный размер карты признаков $H\times W$?


Задание 3: У inception-модуля четыре ветки с числом выходных каналов $32$, $64$, $16$, $16$ соответственно (все с одинаковым пространственным размером $14\times14$). Каков размер выходного тензора после конкатенации?


Задание 4: Почему все ветки inception-модуля обязаны давать одинаковый пространственный размер выхода?


Задание 5 (машинное обучение): Зачем перед свёрткой $5\times5$ в inception-модуле почти всегда ставят свёртку $1\times1$?


Задание 6: Свёртка $5\times5$ от $256$ до $256$ каналов напрямую против той же операции через bottleneck с $32$ промежуточными каналами. Посчитай оба варианта.


Задание 7: Какие три измерения масштабирует EfficientNet совместно через compound scaling?


Задание 8: По формуле $d=\alpha^\phi$ при $\alpha=1{,}2$ найди коэффициент роста глубины при $\phi=2$.


Задание 9 (машинное обучение): В чём принципиальное отличие подхода EfficientNet от эволюции Inception v1→v4 с точки зрения того, как найдена архитектура?


Задание 10: Проверь: при $\alpha=1{,}2$, $\beta=1{,}1$, $\gamma=1{,}1$ выполняется ли ограничение $\alpha\cdot\beta^2\cdot\gamma^2\approx2$?

Средние задания (11–20)

Задание 11: Свёртка $3\times3$ от $384$ до $384$ каналов напрямую против bottleneck через $96$ промежуточных каналов. Посчитай оба варианта и найди коэффициент экономии.


Задание 12: Inception-модуль: вход $28\times28\times256$. Ветка 1: $1\times1$, $64$ канала. Ветка 2: $1\times1\to32$, затем $3\times3\to64$. Ветка 3: $1\times1\to16$, затем $5\times5\to32$. Ветка 4: MaxPool $3\times3$, затем $1\times1\to32$. Посчитай общее число параметров модуля.


Задание 13: Факторизуй свёртку $5\times5$ от $128$ до $128$ каналов на две свёртки $3\times3$ и посчитай экономию параметров.


Задание 14: Факторизуй свёртку $3\times3$ от $160$ до $160$ каналов на асимметричную пару $1\times3$ и $3\times1$ и посчитай экономию.


Задание 15 (машинное обучение): EfficientNet-B0 имеет базовое разрешение входа $224\times224$. Какое разрешение получится при $\phi=2$, если $\gamma=1{,}15$?


Задание 16: Модель масштабируется с $\phi=0$ до $\phi=4$ при ограничении $\alpha\beta^2\gamma^2\approx2$. Во сколько раз вырастут суммарные вычисления (FLOPS)?


Задание 17: Реализуй на PyTorch простую bottleneck-ветку inception-модуля: $1\times1$ свёртка (сжатие каналов), затем $3\times3$ свёртка.

import torch.nn as nn

class InceptionBottleneckBranch(nn.Module):
    def __init__(self, in_channels, bottleneck_channels, out_channels):
        super().__init__()
        # твой код здесь
        pass

    def forward(self, x):
        # твой код здесь
        pass

Задание 18 (машинное обучение): Почему compound scaling опирается на ограничение $\alpha\cdot\beta^2\cdot\gamma^2\approx2$, а не на что-то вроде $\alpha\cdot\beta\cdot\gamma\approx2$?


Задание 19: Сравни число параметров inception-модуля из задания 12 с гипотетическим модулем, где вместо ветки 3 ($1\times1\to16$, затем $5\times5\to32$) стоит прямая свёртка $5\times5$ от $256$ до $32$ каналов без bottleneck.


Задание 20 (машинное обучение): EfficientNet-B0 показывает точность, сопоставимую с ResNet-50, но с заметно меньшим числом параметров. Объясни, за счёт какого архитектурного принципа (не конкретных цифр) достигается эта эффективность.

Продвинутые задания (21–30)

Задание 21: Inception-модуль без bottleneck: вход $56\times56\times128$, ветка $5\times5\to64$ напрямую. Тот же модуль с bottleneck через $16$ промежуточных каналов. Посчитай оба варианта и итоговую экономию в разах.


Задание 22 (машинное обучение): Почему вспомогательные классификаторы (auxiliary classifiers) в Inception v1–v3 отбрасываются на инференсе, но используются при обучении?


Задание 23: EfficientNet-B0 → EfficientNet-B5 ($\phi=5$), $\alpha=1{,}2$, $\beta=1{,}1$, $\gamma=1{,}15$. Найди множители роста глубины, ширины и разрешения.


Задание 24: Используя результат задания 23 и базовое разрешение $224$, найди примерное разрешение входа EfficientNet-B5.


Задание 25 (машинное обучение): В чём разница между NAS-поиском самой базовой архитектуры EfficientNet-B0 и последующим grid search коэффициентов $\alpha,\beta,\gamma$ для compound scaling? Почему не искали всё сразу одним NAS-поиском?


Задание 26: Сравни суммарное число параметров inception-модуля с четырьмя ветками (как в задании 12, итого $68\,096$) с одиночной свёрткой $3\times3$ от $256$ до $192$ каналов напрямую (без параллельных веток и без bottleneck). Что дороже?


Задание 27: Реализуй на PyTorch полный inception-модуль с четырьмя ветками (аналогично заданию 12).

import torch
import torch.nn as nn

class InceptionModule(nn.Module):
    def __init__(self, in_channels, out_1x1, reduce_3x3, out_3x3, reduce_5x5, out_5x5, pool_proj):
        super().__init__()
        # твой код здесь
        pass

    def forward(self, x):
        # твой код здесь
        pass

Задание 28 (машинное обучение): Мобильное приложение должно классифицировать изображения на устройстве с ограниченной памятью и без GPU. Обоснуй, почему EfficientNet-B0 или B1 предпочтительнее, чем VGG-16 (урок 336) или ResNet-152 (урок 337), даже если у последних выше точность в абсолютных цифрах на большом сервере.


Задание 29: Оцени суммарную экономию параметров для полного inception-модуля из задания 12, если бы все три «дорогие» ветки (3×3, 5×5 и pooling-проекция) применялись без bottleneck-сжатия напрямую от $256$ входных каналов к тем же выходным числам каналов ($64$, $32$, $32$ соответственно).


Задание 30 (машинное обучение): Сформулируй, почему задачу поиска коэффициентов $\alpha,\beta,\gamma$ для compound scaling в принципе можно рассматривать как задачу того же типа, что генетические алгоритмы (урок 296) или целочисленное программирование (урок 284) — приведи конкретное сходство и конкретное отличие.

Частые ошибки

  • Считают свёртку $1\times1$ бесполезной операцией, потому что она «не меняет размер картинки». На деле она не про пространственный размер, а про число каналов: это полносвязный слой, применённый к каждому пикселю независимо, и именно он делает bottleneck-конструкцию возможной (примеры 1–2, раздел про роль $1\times1$).

  • Путают экономию от bottleneck с одинаковым коэффициентом для любого размера ядра. Экономия сильно зависит от $k\times k$: для $5\times5$ выигрыш кратно больше (примерно в $7$–$7{,}4$ раза в разобранных примерах), чем для $3\times3$ (около $1{,}4$–$3{,}6$ раза) — чем крупнее ядро, тем дороже обходится не сжимать каналы заранее.

  • Думают, что параллельные ветки inception-модуля обязательно дают одинаковое число выходных каналов. Число каналов у каждой ветки настраивается независимо и произвольно (см. задание 3) — единственное жёсткое требование — совпадение пространственных размеров $H\times W$ для корректной конкатенации.

  • Считают EfficientNet «просто ещё одной архитектурой из семейства Inception/ResNet», не замечая принципиальной разницы в подходе. Inception v1–v4 — результат ручного архитектурного проектирования и итеративных улучшений; базовая архитектура и коэффициенты масштабирования EfficientNet найдены систематическим автоматическим поиском (NAS и grid search), что делает вклад EfficientNet методологическим, а не только архитектурным.

  • Интерпретируют compound scaling как «увеличение глубины, ширины и разрешения на одинаковый процент». Коэффициенты $\alpha,\beta,\gamma$ в общем случае разные (для EfficientNet $1{,}2$, $1{,}1$ и $1{,}15$ соответственно) — рост по трём осям пропорционален, но не одинаков в процентах; равенство коэффициентов было бы частным, а не общим случаем.

  • Игнорируют квадратичный вклад ширины и разрешения в вычислительный бюджет и считают его линейным по всем трём осям. Ограничение $\alpha\cdot\beta^2\cdot\gamma^2\approx2$ явно показывает, что ширина и разрешение входят в квадрате, а не линейно, как глубина (задание 18) — забыв об этом, легко неправильно оценить, во сколько раз вырастут реальные вычисления при масштабировании.

Главное запомнить

  • Inception-модуль применяет несколько фильтров разного размера ($1\times1$, $3\times3$, $5\times5$) и pooling параллельно к одному входу и объединяет результаты конкатенацией по каналам — сеть сама решает через обучаемые веса, какой масштаб рецептивного поля полезнее, вместо того чтобы архитектор выбирал заранее.

  • Свёртка $1\times1$ — не бесполезная операция, а инструмент bottleneck: она сжимает число каналов перед дорогой свёрткой $3\times3$ или $5\times5$, резко сокращая число параметров и вычислений (в разобранных примерах — в 1,4–7,4 раза) без потери размера рецептивного поля.

  • Экономия от bottleneck растёт вместе с размером ядра последующей свёртки: для $5\times5$ выигрыш заметно больше, чем для $3\times3$, потому что квадрат размера ядра множит эффект от сжатия каналов.

  • Inception эволюционировала от v1 (GoogLeNet, 2014, базовые параллельные ветки) через v2–v3 (факторизация крупных свёрток на более мелкие и асимметричные, вспомогательные классификаторы) к v4 и Inception-ResNet (добавление skip connections внутрь модулей).

  • EfficientNet (Google Brain, 2019) предлагает принципиально другой подход к масштабированию: не добавлять слои или каналы вручную, а совместно увеличивать глубину, ширину и разрешение по формуле $d=\alpha^\phi$, $w=\beta^\phi$, $r=\gamma^\phi$ при ограничении $\alpha\cdot\beta^2\cdot\gamma^2\approx2$ — это и есть compound scaling.

  • Найденные для EfficientNet коэффициенты $\alpha\approx1{,}2$, $\beta\approx1{,}1$, $\gamma\approx1{,}15$ показывают, что глубина масштабируется быстрее всего, а ширина — медленнее всего, поскольку ширина квадратично влияет на вычислительный бюджет.

  • Базовая архитектура EfficientNet-B0 найдена методом Neural Architecture Search (NAS) — тем же классом методов автоматического поиска решений в дискретных пространствах, что генетические алгоритмы (урок 296) и целочисленное программирование (урок 284), только применённым к пространству архитектур нейросетей с целевой функцией «точность при ограниченном бюджете вычислений».

  • EfficientNet-B0 достигает точности, сопоставимой с ResNet-50, при значительно меньшем числе параметров и операций, а EfficientNet-B7 на момент публикации превзошёл по точности более тяжёлые архитектуры, имея заметно меньше параметров — практический пример того, что систематическое масштабирование выигрывает у наивного увеличения одной оси архитектуры.

  • Для развёртывания моделей на устройствах с ограниченными ресурсами (мобильные телефоны, встраиваемые системы) продуманная, сбалансированная архитектура критично важнее максимальной абсолютной точности без учёта вычислительного бюджета.

Связь с темами курса

Этот урок замыкает блок из четырёх уроков про архитектуры свёрточных сетей: урок 335 разобрал базовые операции свёртки и pooling, урок 336 дал общий обзор классических архитектур от LeNet до ResNet, урок 337 подробно разобрал skip connections в ResNet как решение проблемы затухающего градиента в глубоких сетях, а сегодняшний урок довёл эту линию до Inception (параллельные фильтры и bottleneck-приём) и EfficientNet (систематическое совместное масштабирование). Вместе эти четыре урока формируют полную картину того, как индустрия последовательно решала одну и ту же задачу — сделать сеть точнее и одновременно практичнее по вычислениям — четырьмя разными архитектурными идеями: глубиной и простотой (VGGNet), остаточными соединениями (ResNet), параллельными фильтрами с bottleneck (Inception) и систематическим масштабированием, найденным автоматическим поиском (EfficientNet).

Связь с уроками 284 и 296 — не просто формальная аналогия, а содержательная: Neural Architecture Search, лежащий в основе EfficientNet, — это применение общего класса методов поиска в больших дискретных пространствах решений, с которым ты уже работал на примере генетических алгоритмов (урок 296, эвристический поиск через мутацию и отбор) и целочисленного программирования (урок 284, точный поиск через формализованные ограничения). Понимание того, что «архитектура нейросети» — это тоже объект, который можно искать автоматически как решение оптимизационной задачи, а не только придумывать вручную, — важный концептуальный мост между классическим машинным обучением и современным дизайном глубоких моделей.

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

Интересные факты

  • Название Inception — прямая отсылка к фильму «Начало» Кристофера Нолана и интернет-мему «we need to go deeper» («нужно копнуть глубже»): авторы архитектуры буквально включили в одну из своих статей кадр из фильма как шутку, обыгрывая одновременно глубину сети и вложенность идеи «сеть внутри сети».

  • GoogLeNet при почти в 30 раз меньшем числе параметров, чем VGG-16 (5 миллионов против 138 миллионов), победила VGGNet на конкурсе ILSVRC 2014 года — редкий на тот момент пример того, что «более компактная» архитектура обошла заметно более тяжёлую по числу параметров.

  • Компания Google не просто исследовала EfficientNet в лаборатории — модели этого семейства почти сразу стали стандартным выбором baseline-архитектуры (базовой точки отсчёта) для соревнований по компьютерному зрению и продакшен-систем, где важен баланс точности и вычислительного бюджета, именно из-за задокументированного соотношения точность/эффективность.

  • Идея compound scaling кажется очевидной задним числом («конечно, надо масштабировать всё вместе»), но до статьи EfficientNet 2019 года стандартной практикой действительно было масштабировать сети по одной оси за раз — отчасти потому, что найти правильные пропорции для совместного масштабирования без систематического поиска крайне трудно вручную.

Лайфхаки

  • Прежде чем добавлять в архитектуру ещё один слой «на глаз», задай себе вопрос из философии EfficientNet: а что, если вместо только глубины стоит пропорционально увеличить ещё и ширину, и разрешение входа? Часто небольшое сбалансированное увеличение по всем трём осям выгоднее по соотношению точность/вычисления, чем сильное увеличение только одной.

  • Если нужно быстро прикинуть, оправдан ли bottleneck перед конкретной свёрткой, сравни квадрат размера ядра ($k^2$) с коэффициентом сжатия каналов: чем крупнее ядро и чем сильнее сжатие, тем больше выигрыш — для $1\times1$ свёрток bottleneck обычно не нужен вовсе, поскольку экономить там особо не на чем.

  • При выборе предобученной архитектуры для transfer learning на ограниченном железе начинай сравнение не с абсолютной точности на ImageNet, а с соотношением точность/FLOPS и точность/число параметров — по этим метрикам EfficientNet-B0/B1 почти всегда выгоднее устаревших тяжёлых архитектур вроде VGG-16 при сопоставимой практической точности на твоей задаче.

  • Используй библиотеки (torchvision.models, timm) для готовых предобученных весов EfficientNet и Inception вместо переобучения архитектуры с нуля — обе архитектуры хорошо документированы и широко используются как основа для transfer learning в реальных проектах.

  • Когда встречаешь незнакомую архитектуру со сложной параллельной структурой блоков (не только Inception, но и, например, более поздние multi-branch архитектуры), в первую очередь ищи в ней bottleneck-свёртки $1\times1$ — это почти всегда самый быстрый способ понять, где именно архитектор сэкономил вычисления, не жертвуя рецептивным полем.

  • Перед тем как масштабировать собственную архитектуру для нового проекта, посчитай (хотя бы приблизительно, как в примерах этого урока) реальный прирост параметров и FLOPS от планируемого изменения — интуитивные оценки «чуть увеличим сеть» часто расходятся с реальными цифрами в разы, что и продемонстрировано разобранными примерами bottleneck-экономии.

Четыре урока назад мы начали с самой базовой операции свёртки и pooling — и вот теперь ты прошёл весь путь до архитектур, которые действительно работают в продакшене на миллиардах устройств: от глубины и простоты VGGNet, через остаточные соединения ResNet, параллельные фильтры Inception с их bottleneck-экономией — и до EfficientNet, где сама архитектура и способ её масштабирования найдены систематическим автоматическим поиском, а не только человеческой интуицией. Это не просто коллекция исторических имён: за каждым архитектурным решением стоит конкретная инженерная проблема и конкретное, часто изящное решение, которое ты теперь умеешь не просто назвать, а посчитать вручную — в параметрах, в FLOPS, в масштабах экономии. Дальше курс переходит к другому типу данных — последовательностям, — и там ты увидишь, что многие из сегодняшних идей (параллельная обработка, продуманное масштабирование, автоматический поиск архитектур) снова всплывут в совершенно новом контексте.

Понял тему? Закрепи в боте! 🚀

Попрактикуйся на задачах и получи персональные рекомендации от AI

💪 Начать тренировку
💬 Есть вопрос? Спроси бота!