Глава 5a. Bottleneck: зачем сужать каналы перед работой
Большинство вычислений в свёртке приходится не на «ум», а на ширину тензора.
Если сначала сузить, посчитать, потом расширить — можно купить глубину дешевле.
Глава 5 дала shortcut.
Этого мало для практического ResNet-50/101: наивный стек широких 3×3 на 256–512 каналах сжигает бюджет раньше, чем вы набираете полезную глубину.
Bottleneck — ответ на вопрос не «как обучить глубже», а «как сделать каждый блок дешевле».
5a.1. Где живут FLOPs в свёртке
Грубая оценка для Conv 3×3 без учёта трюков ядра:
FLOPs ~ H × W × K² × C_in × C_out
При фиксированном разрешении и ядре цена растёт как произведение каналов.
Удвоили C_in и C_out — примерно учетверили работу.
Отсюда инженерная мысль:
Если тяжёлая пространственная свёртка неизбежна,
пусть она работает на узком тензоре.
5a.2. Конструкция: 1×1 → 3×3 → 1×1
Классический bottleneck-блок (смысл, не дословный код):
Почему 1×1 вообще что-то делает?
Он не смотрит на соседей по пространству. Он смешивает каналы и меняет размерность — дешёвый «адаптер ширины».
Обмен:
| Получили | Чем заплатили |
|---|---|
| Существенно меньше FLOPs на блок | Узкое место может стать информационным горлышком |
| Можно набрать больше блоков при том же бюджете | Лишние слои/нормы в блоке, сложнее граф |
| Тот же «внешний» интерфейс каналов для skip | Нужно следить, чтобы bottleneck не задушил признаки |
5a.3. Сравнение на пальцах
Допустим, без bottleneck два слоя 3×3 на 256 каналах:
2 × (H·W·9·256·256) = 2 · 9 · 65536 · H·W
С bottleneck 256→64→64→256:
H·W·1·256·64 + H·W·9·64·64 + H·W·1·64·256
Порядок величин: узкий 3×3 дешевле широкого примерно в (256/64)² = 16 раз по канальной части — именно поэтому трюк работает, даже с двумя 1×1 вокруг.
Точные числа зависят от реализации и fused kernels, но направление обмена устойчиво: пространственная работа должна быть узкой.
5a.4. Связь с CSP и Mobile-блоками
Bottleneck — родственник идей, которые встретятся дальше:
| Идея | Рычаг экономии |
|---|---|
| Bottleneck | Сузить → посчитать → расширить |
| CSP (гл. 9) | Не все каналы пускать в тяжёлую ветку |
| Depthwise separable | Разделить пространственную и канальную работу ещё жёстче |
| Inverted residual (MobileNetV2) | Иногда выгоднее расширять внутри, а снаружи держать узко |
Не заучивайте зоопарк. Держите один вопрос:
Где в блоке самая дорогая операция — и обязана ли она видеть полную ширину?
5a.5. Когда bottleneck вреден
Узкое горлышко — не бесплатный обед.
- Слишком сильное сжатие (256→16) убивает выразительность:
3×3не из чего собирать. - На очень маленьких картах признаков (детекция, глубокие уровни) выигрыш по FLOPs может съесть overhead запусков ядер.
- Если задача живёт на тонких канальных различиях, агрессивный reduce стирает их до пространственной стадии.
Практическое правило архитектора:
Bottleneck — это ставка:
«смесь каналов дешёвым1×1сохранит всё нужное для узкого3×3».
Если ставка не отыгрывается на ваших данных — ratio сжатия надо ослабить, а не «добавить ещё слоёв».
flowchart TD
A[Широкий тензор] --> B{Где считать 3x3?}
B -->|на полной ширине| C[Дорого, просто]
B -->|в bottleneck| D[Дешевле, риск горлышка]
D --> E[Подкрутить ratio 1/2, 1/4, 1/8]
E --> F[Абляции: mAP / FPS / память]
Если бы вы были автором статьи…
-
Зафиксируйте FLOPs и сравните: 3 блока wide
3×3vs 6 bottleneck-блоков.
Что важнее для качества — ширина или глубина при одном бюджете? -
С spot-check активаций: визуализируйте каналы до и после reduce.
Какие признаки умирают первыми при ratio 1/8? -
Нужен ли expand ровно к исходной ширине?
Что будет, если выход блока шире/уже входа (и как тогда жить skip)?
Если даже узкий 3×3 всё ещё дорог — следующий шаг факторизация: depthwise и inverted residual (глава 5b).
А в детекции тот же рефлекс экономии проявится ещё и в том, сколько гипотез боксов сеть таскает с собой.