Глава 5a. Bottleneck

Глава 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-блок (смысл, не дословный код):

Bottleneck-блок

Bottleneck: squeeze-expand

Почему 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 / память]

Если бы вы были автором статьи…

  1. Зафиксируйте FLOPs и сравните: 3 блока wide 3×3 vs 6 bottleneck-блоков.
    Что важнее для качества — ширина или глубина при одном бюджете?

  2. С spot-check активаций: визуализируйте каналы до и после reduce.
    Какие признаки умирают первыми при ratio 1/8?

  3. Нужен ли expand ровно к исходной ширине?
    Что будет, если выход блока шире/уже входа (и как тогда жить skip)?

Если даже узкий 3×3 всё ещё дорог — следующий шаг факторизация: depthwise и inverted residual (глава 5b).
А в детекции тот же рефлекс экономии проявится ещё и в том, сколько гипотез боксов сеть таскает с собой.