Глава 2. Свертка как оптимальный фильтр
Если бы изображения были случайными наборами пикселей, свёртка никогда бы не появилась.
Она существует только потому, что реальные изображения обладают сильной локальной корреляцией.
В прошлой главе brute force умер честно: слишком много параметров, нет понятия «сосед», нет иерархии.
Теперь вопрос другой — не «почему Dense плохо», а «какую операцию поставить вместо него».
Мы по-прежнему не открываем учебник.
Мы выводим операцию из ограничений.
2.1. Локальные закономерности
Возьмите любое фото. Не нейросеть — просто фото.
Пиксель почти никогда не независим от соседей.
Небо рядом с небом. Шерсть рядом с шерстью. Край объекта — это резкий переход в маленькой окрестности, а не магия, размазанная по всему кадру.
Фрагмент реальной картинки (условно):
12 14 13 80 82
11 12 15 79 81
10 13 14 78 80
11 12 70 76 77
12 68 72 74 75
Слева — гладкая область. Справа — другая. Граница локальна.
Это не эстетическое наблюдение. Это статистическое свойство мира, на котором можно строить архитектуру.
Аксиома, которую мы принимаем:
Значение в точке
(x, y)почти полностью объясняется небольшой окрестностью вокруг неё.
Дальние пиксели важны для смысла сцены, но не для первичного признака «край / текстура / пятно».
Как только аксиома принята, полная связность становится расточительством: мы платим весами за связи, которых в данных почти нет.
Значит, первый кандидат на операцию — локальное окно.
Пока это ещё не «свёртка из учебника». Это просто инженерное решение: не смотри на весь кадр сразу.
2.2. Weight Sharing: один фильтр на весь кадр
Локальность сама по себе ещё не спасает.
Если для каждой позиции кадра завести свои веса окна 3×3, параметров станет меньше, чем у Dense, но всё ещё слишком много — и, главное, модель снова не будет знать, что «вертикальный край» слева и справа — одна и та же вещь.
Вторая аксиома:
Полезные локальные паттерны повторяются по всему изображению.
Детектор вертикальной линии в левом верхнем углу должен уметь работать и в правом нижнем.
Отсюда — разделение весов (weight sharing):
один и тот же набор коэффициентов окна применяется во всех позициях.
Без sharing С sharing
позиция (0,0): свои 9 весов позиция (0,0): фильтр W
позиция (0,1): свои 9 весов позиция (0,1): тот же W
позиция (1,0): свои 9 весов позиция (1,0): тот же W
... ...
параметры ~ H × W × 9 параметры = 9 (+ bias)
Для кадра 640×480 это разница между сотнями тысяч локальных весов и десятком чисел на один фильтр.
Мы совершили обмен:
| Отдали | Получили |
|---|---|
| Право учить «особый фильтр для каждого пикселя» | Кратное сокращение параметров |
| Полную пространственную свободу | Инвариантность к сдвигу «встроена» |
Именно этот обмен делает операцию масштабируемой.
2.3. Как считается ответ окна
Зафиксируем механику — без мистики.
Есть входное окно и фильтр одного размера.
В каждой клетке перемножаем, затем складываем. Получаем одно число — отклик фильтра в этой позиции.
Сдвинули окно на шаг — получили следующее число.
Пройшли весь кадр — получили карту признаков (feature map): не картинку для глаз, а карту «насколько фильтр здесь сработал».
Несколько фильтров — несколько карт:
Почему фильтры обучаемые, а не вручную как Собель?
Потому что мы не хотим снова попасть в ловушку hand-crafted features из предистории детекции. Пусть данные скажут, какие локальные шаблоны полезны для задачи. Архитектура задаёт форму свободы; обучение заполняет коэффициенты.
2.4. Stride, padding и цена дискретизации
Как только операция стала скользящим окном, появляются инженерные ручки.
Stride (шаг).
Шаг 1 — плотная карта. Шаг 2 — карта вдвое меньше по каждой оси: меньше вычислений, больше потеря точной локализации.
Padding.
Без дополнения краёв карта сжимается на каждом слое «сама собой». Иногда это удобно. Иногда — нет: края кадра начинают жить по другим правилам, чем центр.
Вход 5×5, ядро 3×3, stride 1, padding 0 → выход 3×3
Вход 5×5, ядро 3×3, stride 1, padding 1 → выход 5×5
Вход 5×5, ядро 3×3, stride 2, padding 1 → выход 3×3
Уже здесь виден будущий конфликт детекции (мы вернёмся к нему в Части III):
Каждое уменьшение разрешения экономит FLOPs
и одновременно убивает мелкие объекты.
Свертка решила проблему параметров.
Она не отменила проблему масштаба. Она лишь отложила её.
2.5. Инвариантность к сдвигу — что именно мы получили
Важная тонкость, которую часто произносят слишком громко.
Свертка с разделением весов даёт эквивариантность к сдвигу на уровне карт признаков:
Сдвинули объект на входе → отклик на карте сдвигается так же.
Классификатору потом всё равно понадобится слой, который соберёт «где бы объект ни был» в один ответ (pooling, глобальный average и т.п.). Но фундамент уже другой: детектор края не надо учить заново в каждом углу.
flowchart LR
A[Изображение] --> B[Локальное окно]
B --> C[Общие веса W]
C --> D[Карта признаков]
D --> E[Сдвиг объекта на входе]
E --> F[Тот же сдвиг отклика на карте]
Сравните с главой 1: там сдвиг рождал «новую вселенную» в пространстве весов.
Здесь сдвиг — это сдвиг по координатам карты, а не смена личности признака.
2.6. Несколько слоёв подряд: откуда берётся иерархия
Одного слоя мало.
Первый слой с маленьким полем зрения видит края и простые текстуры.
Второй слой, глядя на карты первого, собирает углы и простые формы.
Глубже — части объектов. Ещё глубже — объекты.
Это не магия «глубокого обучения» как бренда. Это следствие того, что рецептивное поле растёт, а признаки переиспользуются:
Иерархия, которой не хватало Dense, появляется как побочный эффект стека локальных операций — при условии, что сеть вообще сможет обучаться на такой глубине.
(Спойлер Части II: на большой глубине её снова почти убьёт другая болезнь.)
2.7. Маленькая честность про название
В классической математике свёртка двух функций включает отражение одного из ядер.
В глубоком обучении библиотеки чаще считают кросс-корреляцию: окно накладывается без зеркалирования.
Почему всех это устраивает?
Потому что веса обучаемые. Если оптимальный фильтр «должен быть зеркальным», оптимизатор просто выучит зеркальные коэффициенты. Название «convolution» осталось исторически; инженерный смысл — локальный скользящий фильтр с общими весами.
Про связь с Фурье («свёртка во времени = произведение спектров») — отдельная глава. Там свёртка перестанет быть «окошком в цикле for» и станет утверждением о частотах. Пока достаточно механики и обмена.
2.8. Итог обмена
| Brute force | Свёртка |
|---|---|
| Смотрит на весь кадр | Смотрит в локальное окно |
| Свои веса в каждой точке | Одни веса на весь кадр |
| Сдвиг = новый объект | Сдвиг = сдвиг карты признаков |
| Иерархия не заложена | Иерархия растёт со стеком |
| Параметры взрываются | Параметры растут с числом фильтров |
Свёртка — не «умный слой».
Свёртка — это встроенное знание о мире, записанное в граф вычислений.
Мы отказались от универсальности Dense.
Мы получили масштаб, сдвиг и иерархию — ровно те свойства, без которых компьютерное зрение на реальных фото не взлетает.
Если бы вы были автором статьи…
-
Почему 3×3, а не 11×11 с самого начала?
Большое ядро сразу даёт большое поле зрения. Чем вы платите? Когда большой kernel оправдан? -
Что будет, если убрать weight sharing, но оставить локальные окна?
Это всё ещё «почти свёртка» или уже другой зверь? Для каких данных sharing вреден? -
Свертка предполагает, что статистика кадра примерно одинакова везде.
Придумайте домен, где это ложь (медицина? документы? небо/земля в автопилоте). Какую архитектуру вы бы предложили вместо чистого sharing?
В следующей главе мы посмотрим на ту же операцию глазами обработки сигналов: изображение как спектр, фильтр как частотная маска, обучение — как автоматический подбор того, что раньше рисовали руками.