Что такое сверточная нейросеть и какую задачу решает свертка
Сверточная нейросеть (CNN) строит представление изображения через 2D-свертку. Ядро, небольшая матрица весов, скользит по входу, поэлементно умножает свои веса на значения под ним и суммирует произведения в один выходной пиксель. Затем ядро повторяет ту же процедуру для каждой позиции и превращает одну двумерную матрицу в другую двумерную матрицу признаков. Разбор базовой механики свертки описывает операцию ровно в такой последовательности.
Ключевое свойство: каждый выходной признак складывается из входных признаков примерно из той же локации. Ядро 3x3 объединяет 9 пикселей, ядро 5x5 - 25. Размер ядра напрямую задает, сколько входных значений попадает в одно выходное.
Дальше по порядку: пошаговая механика с ручным расчетом, экономия параметров, padding и stride, многоканальные фильтры и смещение, линейность свертки и hard prior, receptive field с визуальной иерархией, ограничения подхода.
Как работает 2D-свертка: ядро скользит по изображению
Операция раскладывается на четыре шага.
- Накладываем ядро на окно входа того же размера.
- Поэлементно умножаем веса ядра на значения окна.
- Складываем все произведения и получаем одно число, выходной пиксель.
- Сдвигаем ядро на stride (по умолчанию на 1 пиксель) и повторяем, пока не пройдем все окна.
Ручной пример. Вход 5x5:
1 0 2 1 0 0 1 3 2 1 2 1 0 1 2 1 2 1 0 1 0 1 2 1 0
Ядро 3x3, которое реагирует на вертикальный перепад яркости:
1 0 -1 1 0 -1 1 0 -1
Берем верхнее левое окно:
1 0 2 0 1 3 2 1 0
Считаем построчно: 1*1 + 0*0 + 2*(-1) = -1; 0*1 + 1*0 + 3*(-1) = -3; 2*1 + 1*0 + 0*(-1) = 2. Сумма: -1 - 3 + 2 = -2. Первый пиксель выхода равен -2.
Окно со второй по четвертую строку и со второго по четвертый столбец дает другое значение: (1*1 + 3*0 + 2*(-1)) + (1*1 + 0*0 + 1*(-1)) + (2*1 + 1*0 + 0*(-1)) = -1 + 0 + 2 = 1.
Полный проход по входу 5x5 дает матрицу 3x3: 9 выходных значений из 25 входных. Без padding размер уменьшается, потому что каждый проход съедает по одному пикселю с каждой стороны, а за границей матрицы значений для ядра просто нет.
Почему сверточный слой требует на порядок меньше параметров, чем полносвязный
Для того же примера: 25 входных значений и 9 выходных. Полносвязный слой связал бы каждый вход с каждым выходом и получил матрицу 25*9 = 225 весов. Сверточный слой обходится 9 весами ядра, в 25 раз меньше. Тот же источник приводит именно этот расчет.
Экономия берется из двух свойств:
- локальность связей: выходной нейрон смотрит не на все входы, а только на окно под ядром;
- связывание параметров (weight sharing): одно и то же ядро применяется во всех позициях, веса не дублируются.
С ростом картинки разрыв увеличивается. Сверточный слой 3x3 на 64 входных и 64 выходных каналах содержит 3*3*64*64 = 36 864 веса. Полносвязный слой на карте 32x32x64 (65 536 входов) с тем же числом выходов потребовал бы около 4,3 млрд весов.
Оговорка: меньше параметров не означает меньше вычислений. Каждый выходной пиксель требует K*K умножений, и на большом изображении суммарное число операций остается заметным. Экономия идет по весам, а значит по памяти и по объему данных, нужных для обучения.
Padding и stride: как управлять размером выхода свертки
Padding добавляет по краям фиктивные пиксели, обычно нулевые. Ядро при скольжении ставит настоящий пиксель в центр, а часть весов попадает на фиктивные значения за границей, поэтому выход сохраняет размер входа: матрица 5x5 с padding=1 после ядра 3x3 снова дает 5x5. В разборе padding описан так же, с тем же примером.
Stride задает шаг скольжения. При stride=2 ядро перепрыгивает через пиксель: выход становится меньше, вычислений меньше, поле зрения растет быстрее.
Размер выхода считается по формуле:
out = floor((W - K + 2P) / S) + 1
W - сторона входа, K - размер ядра, P - padding, S - stride. Числовые примеры:
- 5x5, ядро 3x3, P=0, S=1: floor((5-3)/1)+1 = 3, выход 3x3.
- 5x5, ядро 3x3, P=1, S=1: floor((5-3+2)/1)+1 = 5, выход 5x5, как на входе.
- 5x5, ядро 3x3, P=1, S=2: floor((5-3+2)/2)+1 = 3, выход 3x3 при вдвое меньшем числе окон.
- 224x224, ядро 7x7, P=3, S=2: floor((224-7+6)/2)+1 = 112, так устроен первый слой многих классических архитектур.
Для нечетного ядра padding (K-1)/2 сохраняет размер при stride=1: ядру 3x3 нужен P=1, ядру 5x5 нужен P=2. Этот режим называют same padding.
Практический смысл: padding сохраняет информацию у краев (без него граничные пиксели участвуют меньше внутренних), stride уменьшает разрешение и нагрузку, а заодно расширяет поле зрения следующих слоев.
Многоканальная свертка: чем фильтр отличается от ядра
Цветное изображение имеет три входных канала, и на входе слоя лежит тензор H x W x Cin, а не одна матрица.
Ядро, двумерная матрица весов для одного входного канала. Фильтр, набор ядер по числу входных каналов: для RGB фильтр 3x3 состоит из трех ядер 3x3 и содержит 27 весов.
Фильтр проходит по всем входным каналам сразу, результаты по каналам складываются в одно число, и к этой сумме добавляется смещение (bias), обучаемый скаляр на выходной канал. Один фильтр формирует один выходной канал. Число выходных каналов равно числу фильтров.
Пример: вход 32x32x3, 16 фильтров 3x3, padding=1, stride=1. Выход 32x32x16. Параметров: 16 * (3*3*3) + 16 = 448.
В PyTorch тензор весов слоя Conv2d имеет форму (out_channels, in_channels, kH, kW), а смещение хранится отдельным вектором длиной out_channels. Если bias отключить, слой останется линейным без сдвига; так иногда делают перед нормализацией, которая сама вносит сдвиг.
Почему свертка остается линейным преобразованием с эквивалентной матрицей весов
Каждый выходной пиксель, линейная комбинация входных значений. Весь слой записывается как умножение на матрицу: y = Wx + b. Матрица W получается огромной, но почти целиком заполнена нулями: ненулевые элементы стоят только там, где вход попадает под ядро. Ненулевые значения повторяются, потому что одно и то же ядро работает во всех позициях.
Такая структура и есть hard prior, жесткое априорное ограничение модели. Сеть заранее лишена возможности связать пиксель из левого верхнего угла с пикселем из правого нижнего через один слой и обязана применять одно правило ко всем позициям. Ограничение сужает пространство гипотез, снижает риск переобучения на малых данных и задает трансляционную эквивариантность.
Нелинейность появляется после свертки: в активациях вроде ReLU и в pooling. До них стек сверточных слоев эквивалентен одной большой линейной операции.
Отсюда практический смысл для transfer learning. Ранние слои с фиксированным приором выучивают края, направленные перепады и простые текстуры, а такие признаки полезны в разных задачах. Веса, обученные на ImageNet, часто переносят на медицинские снимки, документы и промышленную дефектоскопию, дообучая последние слои. Ограничение: перенос работает, когда статистика новых данных близка к исходной. При сильном сдвиге, например при другой модальности или радикально другом масштабе объектов, выигрыш падает и сети нужна полноценная настройка большего числа слоев.
Receptive field: как сеть расширяет поле зрения
Receptive field, область входа, которая влияет на один нейрон конкретного слоя. У первого слоя с ядром 3x3 поле равно 3x3. У второго слоя 3x3 нейрон смотрит на выход первого, а через него на 5x5 исходных пикселей. Три слоя 3x3 дают 7x7.
Расчет ведут двумя числами: r, размер поля, и j, расстояние между соседними нейронами в координатах входа. Формулы: r_out = r_in + (k - 1) * j_in и j_out = j_in * s. На старте r=1, j=1.
| Слой | Ядро k | Stride s | Поле r | Шаг j |
|---|---|---|---|---|
| 1 | 3 | 1 | 3 | 1 |
| 2 | 3 | 1 | 5 | 1 |
| 3 | 3 | 2 | 7 | 2 |
| 4 | 3 | 1 | 11 | 2 |
| 5 | 3 | 2 | 15 | 4 |
| 6 | 3 | 1 | 23 | 4 |
Из таблицы видно, как работает масштаб. Обычные свертки 3x3 добавляли по 2 пикселя за слой. Stride=2 удвоил шаг, и следующие слои стали прибавлять 4 пикселя, потом 8. К шестому слою поле reaches 23x23, хотя ядро везде одно и то же 3x3.
Pooling считается по той же формуле, где k, размер окна. Слой 1x1 поле зрения не расширяет: он смешивает каналы в одной точке. Поэтому глубина и stride, основные инструменты управления полем зрения, а сокращение разрешения идет как побочный эффект.
Визуальная иерархия: от краев до целых объектов на примере GoogLeNet
Малые поля зрения ранних слоев видят края и перепады яркости. Средние слои собирают из них углы, дуги, повторяющиеся текстуры. Глубокие слои с полем в десятки и сотни пикселей отвечают за части объектов и объекты целиком.
GoogLeNet (Inception), победитель ILSVRC 2014, показала, как работать с несколькими масштабами внутри одного блока. Инцепшн-модуль содержит параллельные ветви: свертку 1x1, свертку 3x3, свертку 5x5 и max-pooling 3x3. Выходы ветвей склеиваются по оси каналов, и следующий слой получает признаки сразу трех масштабов.
Ветви 3x3 и 5x5 предварены свертками 1x1, которые сжимают число каналов. Свертка 1x1 здесь работает как линейное смешивание каналов в каждом пикселе: она снижает объем вычислений, не трогая пространственное разрешение и не расширяя поле зрения.
Смысл конструкции в том, что объекты разного размера требуют разного эффективного поля зрения. Масштаб не выбирается вручную: блок держит несколько вариантов параллельно, а какие из них сработают, решает обучение.
Ограничения CNN: adversarial examples и отличие от человеческого зрения
Adversarial example, вход, отличающийся от исходного на малую величину, которую человек не замечает, но модель уверенно относит его к другому классу. Классическая схема атаки: x' = x + ε * sign(∇x L), где ε небольшая константа, а градиент функции потерь по входу показывает, в какую сторону сдвигать каждый пиксель.
Устойчивость таких атак заложена в самой архитектуре. Слой линеен, поэтому малое изменение на входе предсказуемо меняет выход; на изображении 224x224 с десятками тысяч пикселей мелкие сдвиги складываются в заметный сдвиг логитов. Hard prior заставляет сеть опираться на локальные корреляции, а не на целостное представление объекта, и этой локальной статистикой можно управлять целенаправленно.
Человеческое зрение работает иначе: выделяет контур и форму объекта, устойчиво к шуму и мелким искажениям. CNN агрегирует локальные отклики и сильнее опирается на текстуры, поэтому уверенно ошибается там, где человек сомневается.
Защита остается открытой задачей. Состязательное обучение, то есть добавление adversarial-примеров в тренировочный набор, повышает устойчивость к конкретному типу атаки, но обычно снижает точность на чистых данных и не гарантирует защиту от новых схем. Численных замеров устойчивости здесь нет: они зависят от модели, датасета и метода атаки.
Что важно запомнить о работе сверточной нейросети
- 2D-свертка: ядро скользит по входу, поэлементно умножает веса, суммирует в один пиксель и повторяет для всех позиций.
- Экономия параметров: 25 входов и 9 выходов дают 225 весов у полносвязного слоя против 9 у свертки; причина в локальности связей и связывании параметров.
- Padding сохраняет размер выхода, stride его уменьшает: out = floor((W - K + 2P)/S) + 1.
- Ядро работает с одним входным каналом, фильтр объединяет ядра по всем входным каналам; число фильтров равно числу выходных каналов, смещение добавляется на канал.
- Свертка линейна, hard prior из нулей и общих весов объясняет переносимость ранних признаков; нелинейность дают активации и pooling.
- Receptive field растет с глубиной и stride: шесть слоев 3x3 с двумя stride=2 дают поле 23x23.
- Ограничения: adversarial examples и расхождение с человеческим зрением остаются нерешенными.
Практические выводы для тех, кто собирает или отлаживает CNN:
- Считайте параметры и операции отдельно: свертка экономит память, но не обязательно FLOPs.
- Для сохранения разрешения берите P=(K-1)/2 при stride=1; для даунсэмплинга переходите на stride=2, а не на большое ядро.
- Проверяйте формы тензоров по формуле выхода до запуска обучения, это ловит основную часть ошибок сборки модели.
- Для переноса выбирайте веса с домена, близкого к вашей задаче; при сильном сдвиге размораживайте больше слоев.
- Оценивайте устойчивость к возмущениям отдельно от обычной точности, если модель работает там, где возможны целенаправленные атаки.
Начать стоит с ручного расчета одной свертки на бумаге: после него формулы размера выхода и receptive field перестают выглядеть магией и становятся рабочим инструментом.