Глава 9. Оптимизация на пределе: CSP и блоки C2f
Самый быстрый способ выполнить работу — не делать часть этой работы вообще.
У нас уже есть:
- обучаемый глубокий backbone (residual + norm);
- многомасштабная шея (FPN/PAN);
- одностадийная голова.
На бумаге детектор жив.
На устройстве он может всё ещё не влезать в бюджет миллисекунд.
Следующий вопрос уже не «как вообще детектировать», а:
Где мы платим FLOPs и память за повтор вычислений,
которые не дают пропорционального качества?
9.1. Избыточность вычислений и градиентов
Посмотрите на обычный residual-стек.
В каждый блок заходит широкий тензор — скажем, 256 каналов.
Все каналы проходят тяжёлые свёртки. Потом результат складывается с входом.
Возникают две подозрительные вещи:
- Вычислительная: мы всегда платим полную цену за полную ширину.
- Обучающая: разные слои часто учат похожие признаки; градиенты дублируются.
Гипотеза:
А нужно ли всем каналам участвовать в тяжёлом F на каждом этапе?
Если нет — можно разрезать поток.
9.2. Разделяй и конкатенируй: CSP
Cross Stage Partial (CSP) предлагает грубо такой контракт:
- разделить входные каналы на две части;
- одну часть прогнать через блок преобразований;
- вторую провести транзитом почти без изменений;
- склеить (concat) на выходе.
Что обычно получаем:
| Эффект | Почему |
|---|---|
| Меньше FLOPs | Тяжёлая ветка уже |
| Меньше памяти | Меньше промежуточных широких активаций |
| Часто не хуже / лучше точность | Разнообразие: «свежие» и «переработанные» признаки рядом |
| Богаче градиентные маршруты | Не всё проходит через одну кашу преобразований |
Это прямой родственник идеи residual, но с другим акцентом:
- residual: «добавь поправку к полному x»;
- CSP: «не все каналы обязаны идти в дорогую переработку».
Обмен явный:
Жертвуем симметрией «все каналы равны в каждом блоке»
ради скорости и разнообразия представлений.
9.3. От CSP к C3 и C2f в YOLO-линии
В прикладных детекторах идея CSP обросла конкретными формами.
C3 (линия YOLOv5 и окружение)
Практическая упаковка CSP: входная свёртка, частичный проход через bottleneck-стек, склейка, выходная свёртка.
Инженерный смысл важнее названия: частичный тяжёлый путь стал стандартным кирпичом хребта.
C2f (линия YOLOv8 и дальше)
Буква f — про более «быстрый/богатый» сбор признаков.
Вместо одной финальной склейки двух половин блок собирает промежуточные состояния внутренних bottleneck’ов и конкатенирует их более гранулированно — «слоёный пирог» признаков.
Интуиция C2f:
Слушать не только итоговый вывод ветки,
но и промежуточные аргументы — иногда там живёт полезный масштаб признака.
Не нужно боготворить аббревиатуру. Нужно видеть рычаг:
ширина ветки
глубина bottleneck-стека
что именно конкатенируем на выходе
Именно этими рычагами семейство YOLO крутит баланс mAP ↔ latency под разные железки (n/s/m/l/x и т.д.).
flowchart TD
A[Широкий тензор] --> B{Все каналы в тяжёлый F?}
B -->|да| C[Дорого, много дублирования]
B -->|нет, CSP| D[Часть транзитом]
D --> E[C3: простой partial + bottlenecks]
D --> F[C2f: partial + сбор промежуточных срезов]
E --> G[Лучше FPS при той же глубине]
F --> G
9.4. Исследовательский вопрос, который стоит прожить
В конце ранних черновиков этой книги уже звучал хороший вопрос:
Если деление пополам помогает, почему не на 10 частей?
Почему не менять пропорцию на каждом слое?
Возможные ответы (проверяемые, не догматические):
- Железо любит простые регулярные формы — половина/кратность каналов хорошо ложится на реализации.
- Слишком узкая тяжёлая ветка начинает душить выразительность.
- Слишком много веток/конкатов раздувает память на склейках и съедает выигрыш.
- Сладкая точка ищется абляциями на конкретных датасетах и размерах модели; «пополам» — сильный baseline, не закон природы.
Вот это и есть стиль Research Engineer: не заучить «C2f лучше», а уметь поставить эксперимент «что будет, если split ratio = 0.25 / 0.5 / 0.75».
9.5. Собираем детектор как конструктор компромиссов
К концу Части III картина должна сложиться без магии брендов:
Современный одностадийный детектор
| Блок | Роль |
|---|---|
| Backbone | Глубокие признаки дёшево (Res + CSP/C2f) |
| SPPF | Разный контекст на глубоком уровне |
| Neck | FPN + PAN — дружба смысла и геометрии |
| Head | Сетка / много масштабов; часто decoupled |
| Loss | IoU-семейство + классификация (+ objectness…) |
Главный навык, ради которого писалась эта часть:
Смотреть на блок и спрашивать не «как называется в YAML»,
а «какую боль закрыли и какой обмен приняли».
Именно из таких вопросов рождаются следующие архитектуры — в том числе те, где шею или голову начинают разбавлять attention и трансформерными энкодерами.
Если бы вы были автором статьи…
-
Абляция split ratio: 1/4, 1/2, 3/4 при фиксированном бюджете FLOPs.
Что важнее для mAP мелких объектов? -
C2f без конката промежуточных срезов — оставьте только выход последнего bottleneck.
Сколько FPS вернёте и сколько mAP отдадите? -
Перенесите CSP-идею в Neck, а не в Backbone.
Имеет ли смысл partial-обработка на FPN-слияниях, или там каналы и так узкое место другое?
Часть III закрыта. Дальше книга уходит в другой кризис — уже не про пространство картинки, а про последовательности и контекст: почему рекуррентность упёрлась в GPU и как внимание переписало правила.