Глава 2. Свёртка

Глава 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.
Мы получили масштаб, сдвиг и иерархию — ровно те свойства, без которых компьютерное зрение на реальных фото не взлетает.


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

  1. Почему 3×3, а не 11×11 с самого начала?
    Большое ядро сразу даёт большое поле зрения. Чем вы платите? Когда большой kernel оправдан?

  2. Что будет, если убрать weight sharing, но оставить локальные окна?
    Это всё ещё «почти свёртка» или уже другой зверь? Для каких данных sharing вреден?

  3. Свертка предполагает, что статистика кадра примерно одинакова везде.
    Придумайте домен, где это ложь (медицина? документы? небо/земля в автопилоте). Какую архитектуру вы бы предложили вместо чистого sharing?

В следующей главе мы посмотрим на ту же операцию глазами обработки сигналов: изображение как спектр, фильтр как частотная маска, обучение — как автоматический подбор того, что раньше рисовали руками.