🔴 Сложный ⏱️ 55 минут

Свёрточные нейронные сети

📋 Содержание урока

Свёрточные нейронные сети 🖼️

В уроках 326–329 ты разобрал полносвязную сеть (MLP) до последнего винтика: каждый нейрон слоя соединён со всеми нейронами предыдущего слоя, и весь слой целиком вычисляется одной матричной операцией $a=f(Wx+b)$. Этот блок — универсальный строительный кирпич, способный в принципе приблизить любую непрерывную функцию (теорема универсальной аппроксимации, урок 328). Но у попытки применить его напрямую к изображению есть проблема, которая на практике оказывается фатальной задолго до того, как речь зайдёт о точности модели.

Возьми скромное по современным меркам изображение $224\times224$ пикселя в трёх цветовых каналах (RGB) — стандартный входной размер для многих архитектур компьютерного зрения. Развёрнутое в вектор, оно даёт $224\times224\times3=150\,528$ чисел на входе. Если первый скрытый слой полносвязной сети содержит всего $1000$ нейронов, число весов только этого одного слоя составит $150\,528\times1000\approx150$ миллионов параметров — и это без единого скрытого слоя после него, без bias-векторов, без учёта того, что реальные сети видят изображения куда крупнее. Дело не только в количестве памяти под такую матрицу весов: полносвязный слой ещё и полностью игнорирует то, что пиксели изображения — не произвольный набор чисел, а объекты, расположенные в пространстве, где близкие пиксели с высокой вероятностью связаны друг с другом (образуют край, текстуру, часть объекта), а далёкие — как правило, нет. Полносвязный слой рассматривает пиксель в левом верхнем углу и пиксель в правом нижнем углу совершенно одинаково — как два ничем не примечательных входа, которые нужно связать отдельным обучаемым весом с каждым нейроном следующего слоя, не делая скидку на то, что пространственная близость обычно значит куда больше, чем случайная удалённая пара точек.

Свёрточные нейронные сети (Convolutional Neural Networks, CNN) решают обе проблемы одним и тем же архитектурным приёмом. Вместо того чтобы каждый нейрон смотрел на всё изображение целиком, нейрон смотрит только на маленький локальный участок — окно размером, скажем, $3\times3$ или $5\times5$ пикселей. И вместо того чтобы обучать для каждой позиции окна собственный, никак не связанный с соседними набор весов, CNN использует один и тот же набор весов (фильтр) для всех позиций изображения сразу. Эта же самая операция «маленькое окно весов, скользящее по входу и всюду использующее одни и те же веса» называется свёрткой (convolution) — отсюда и название всей архитектуры. Именно свёртка и есть тема сегодняшнего урока: как она вычисляется вручную число за числом, почему локальная связность и разделяемые веса делают CNN на порядки экономнее по параметрам, как padding и stride управляют размером результата и что вообще происходит, когда изображение проходит через pooling-слой.

Сегодняшний урок — первый в подблоке про CNN и полностью фундаментальный: он не касается конкретных архитектур вроде ResNet или Inception (это уроки 336 и далее), а разбирает саму операцию свёртки и её ключевые строительные детали настолько подробно, чтобы каждое число в примере ниже можно было пересчитать вручную и убедиться, что оно совпадает. Без этого фундамента любая более сложная архитектура компьютерного зрения будет выглядеть чёрным ящиком — с ним же за любым nn.Conv2d в PyTorch будет стоять конкретный, понятный тебе на уровне арифметики механизм.

История

Идея, что зрительная система должна обрабатывать изображение локально и иерархически, восходит к нейрофизиологии задолго до появления CNN как инженерной архитектуры. В 1980 году японский учёный Кунихико Фукусима (Kunihiko Fukushima) предложил неокогнитрон (neocognitron) — модель, вдохновлённую экспериментами Дэвида Хьюбела и Торстена Визела над зрительной корой кошек: те обнаружили, что отдельные нейроны зрительной коры реагируют не на всё поле зрения сразу, а на строго ограниченную локальную область (так называемое рецептивное поле), причём одни нейроны реагируют преимущественно на вертикальные линии, другие — на горизонтальные, третьи — на определённые углы наклона. Неокогнитрон впервые воплотил эту идею в вычислительной модели: слои простых клеток, каждая из которых реагирует на локальный паттерн в ограниченной области входа, чередовались со слоями сложных клеток, обеспечивающих устойчивость к небольшим сдвигам изображения. Обучение в неокогнитроне не опиралось на градиентный спуск в современном понимании, но архитектурная идея — локальные рецептивные поля, организованные в иерархию слоёв, — оказалась пророческой почти буквально: это и есть каркас, на котором строится любая современная CNN.

Настоящий практический прорыв случился спустя почти десятилетие, когда Ян Лекун (Yann LeCun), работая в Bell Labs, соединил идею локальных рецептивных полей с алгоритмом обратного распространения ошибки (backpropagation), который к 1986 году уже стал практичным инструментом обучения многослойных сетей (урок 328). В 1989 году Лекун опубликовал первую свёрточную сеть, обучаемую end-to-end через backpropagation, для распознавания рукописных цифр на почтовых индексах, а к 1998 году довёл архитектуру до системы LeNet-5 — сети из чередующихся свёрточных и pooling-слоёв, завершающихся полносвязными слоями, которая успешно распознавала рукописные цифры в реальных банковских системах обработки чеков в США. LeNet-5 ввела в практику ровно тот шаблон, вокруг которого построен весь сегодняшний урок: свёртка находит локальные паттерны, pooling уменьшает пространственный размер и добавляет устойчивость к сдвигам, а стек таких пар слоёв строит иерархию признаков всё возрастающей сложности.

Между LeNet-5 и современным бумом CNN лежит более десяти лет относительного затишья — не из-за недостатка идей, а из-за нехватки данных и вычислительной мощности: обучение глубоких CNN на больших изображениях было попросту непрактичным на процессорах конца 1990-х и начала 2000-х. Перелом наступил в 2012 году, когда AlexNet — CNN, обученная на GPU на миллионах изображений датасета ImageNet, — выиграла ежегодный конкурс распознавания изображений ImageNet, вдвое обойдя по точности все предыдущие подходы, основанные на вручную сконструированных признаках. Именно этот момент историки машинного обучения называют началом современной эры глубокого обучения в компьютерном зрении. Но фундаментальный строительный блок — операция свёртки, которую ты разберёшь ниже число за числом, — не изменился с 1989 года: это та же самая арифметика, что и в первой сети Лекуна, только выполняемая на несопоставимо большем масштабе данных и вычислений.

Операция свёртки

Интуиция

Представь, что у тебя есть маленькое окошко фиксированного размера — скажем, $3\times3$ клетки — которое ты прикладываешь к левому верхнему углу изображения. В каждой позиции окна ты умножаешь число под каждой клеткой окна на число из самого изображения, стоящее в той же клетке, складываешь все девять произведений в одно число — и это число становится одним пикселем результата. Затем окно сдвигается на одну позицию вправо, и вся процедура повторяется: снова девять произведений, снова одна сумма, снова один пиксель результата. Когда окно доходит до правого края строки, оно возвращается к левому краю, но сдвигается на одну строку вниз, и процесс продолжается, пока окно не пройдёт всё изображение.

Именно это скользящее окно и называется фильтром или ядром (kernel), а числа внутри него — весами фильтра, которые в CNN не задаются вручную, а обучаются через backpropagation точно так же, как веса полносвязного слоя. Результат применения фильтра ко всему изображению — новое, как правило меньшее по размеру изображение, которое называется картой признаков (feature map): в каждой её точке записано число, показывающее, насколько сильно локальный паттерн из фильтра совпал с локальным содержимым изображения именно в этой позиции. Если фильтр «настроен» реагировать на вертикальные границы, то в тех местах исходного изображения, где действительно проходит вертикальная граница между тёмной и светлой областью, значение на карте признаков будет большим по модулю; там, где изображение однородно или граница горизонтальна, значение будет близким к нулю.

Формула

Двумерная свёртка (дискретная, в том виде, как она используется в CNN). Пусть входное изображение — матрица $I$ размера $H\times W$, а фильтр — матрица $K$ размера $k_h\times k_w$ (обычно $k_h=k_w=k$, квадратный фильтр). Значение выходной карты признаков в позиции $(i,j)$ вычисляется как

$$S(i,j) = \sum_{m=0}^{k_h-1}\sum_{n=0}^{k_w-1} I(i+m,\,j+n)\cdot K(m,n)$$

то есть: поэлементное произведение фильтра $K$ и участка изображения $I$ того же размера, наложенного начиная с позиции $(i,j)$, просуммированное в одно число. Строго говоря, эта операция в инженерной практике CNN называется «свёрткой» по историческим причинам, хотя математически ближе к операции взаимной корреляции (cross-correlation) — настоящая свёртка в математическом смысле требует ещё и разворота фильтра на 180°. Для обучаемых весов, которые сеть подбирает сама, это различие не имеет практического значения: она с равным успехом подберёт нужные веса в любом из двух соглашений.

Разбор примеров

Пример 1 (полный численный пример — свёртка изображения $5\times5$ фильтром $3\times3$ без padding, шаг $1$). Возьмём простое чёрно-белое изображение $5\times5$, где явно закодирована вертикальная граница: левые два столбца тёмные (значение $0$), правые два столбца светлые (значение $255$), а средний столбец — переходный (значение $128$):

$$I=\begin{pmatrix}0&0&128&255&255\\0&0&128&255&255\\0&0&128&255&255\\0&0&128&255&255\\0&0&128&255&255\end{pmatrix}$$

Возьмём фильтр Собеля (Sobel filter) для обнаружения вертикальных границ — классический, десятилетиями используемый в классическом (не нейросетевом) компьютерном зрении фильтр, который реагирует на резкий переход яркости слева направо:

$$K=\begin{pmatrix}-1&0&1\\-2&0&2\\-1&0&1\end{pmatrix}$$

Без padding выход имеет размер $(5-3+1)\times(5-3+1)=3\times3$ (формула размера выхода разобрана ниже, в разделе про padding и stride). Посчитаем значение в позиции $(0,0)$ — фильтр накладывается на верхний левый угол изображения, то есть на подматрицу строк $0$–$2$ и столбцов $0$–$2$:

$$I_{0:3,0:3}=\begin{pmatrix}0&0&128\\0&0&128\\0&0&128\end{pmatrix}$$

Поэлементное произведение с $K$ и сумма: $(-1)\cdot0+0\cdot0+1\cdot128+(-2)\cdot0+0\cdot0+2\cdot128+(-1)\cdot0+0\cdot0+1\cdot128 = 128+256+128=512$.

Позиция $(0,1)$ — сдвигаем окно на один столбец вправо, теперь под фильтром столбцы $1$–$3$: $I_{0:3,1:4}=\begin{pmatrix}0&128&255\\0&128&255\\0&128&255\end{pmatrix}$. Сумма: $(-1)\cdot0+1\cdot255+(-2)\cdot0+2\cdot255+(-1)\cdot0+1\cdot255=255+510+255=1020$.

Позиция $(0,2)$ — столбцы $2$–$4$: $I_{0:3,2:5}=\begin{pmatrix}128&255&255\\128&255&255\\128&255&255\end{pmatrix}$. Сумма: $(-1)\cdot128+1\cdot255+(-2)\cdot128+2\cdot255+(-1)\cdot128+1\cdot255 = -128+255-256+510-128+255=508$.

Поскольку все пять строк изображения идентичны, все три строки выходной карты признаков тоже идентичны. Итоговая карта признаков $3\times3$:

$$S=\begin{pmatrix}512&1020&508\\512&1020&508\\512&1020&508\end{pmatrix}$$

Результат наглядно демонстрирует, зачем нужен именно такой фильтр: максимальное значение ($1020$) приходится ровно на средний столбец выхода — то есть на ту позицию, где фильтр целиком «накрывает» переходную область между тёмной и светлой частью изображения, где перепад яркости слева направо максимален. По краям (столбцы $0$ и $2$ выхода) значение заметно меньше, потому что фильтр там захватывает участок с менее резким перепадом. Именно так фильтр Собеля «на глаз» видит: не всё изображение, а конкретно вертикальные границы, причём чем резче переход яркости, тем сильнее отклик.

Пример 2 (тот же фильтр Собеля на диагональной границе — фильтр не универсален). Возьмём изображение с диагональной, а не вертикальной границей — верхний левый треугольник тёмный, нижний правый светлый:

$$I=\begin{pmatrix}0&0&255\\0&255&255\\255&255&255\end{pmatrix}$$

Применим фильтр Собеля $K$ из примера 1 (тот же самый, $3\times3$) к этому изображению $3\times3$ без padding — выход будет единственным числом $1\times1$: $(-1)\cdot0+0\cdot0+1\cdot255+(-2)\cdot0+0\cdot255+2\cdot255+(-1)\cdot255+0\cdot255+1\cdot255 = 255+510-255+255=765$. Отклик получился большим, но меньшим, чем был бы для чисто вертикальной границы такой же контрастности (для сравнения — на чисто вертикальной границе такого же перепада яркости от $0$ до $255$ отклик достиг бы $1020$, как в примере 1). Диагональная граница активирует вертикальный фильтр Собеля лишь частично — фильтр «настроен» специфически под одно направление паттерна, и именно поэтому в реальных CNN используется не один фильтр, а сразу много (обычно десятки или сотни в одном слое), каждый из которых в процессе обучения самостоятельно настраивается на свой характерный паттерн — не только вертикальные и горизонтальные границы, но и углы, текстуры, цветовые пятна.

Пример 3 (счёт операций одной свёртки — во что это обходится вычислительно). Для карты признаков размера $3\times3$ из примера 1 при фильтре $3\times3$ на каждую выходную позицию требуется $3\times3=9$ умножений и $8$ сложений (плюс необязательный bias). Всего выходных позиций $9$, значит на всю свёртку уходит $9\times9=81$ умножение. Масштабируем на реалистичный первый слой CNN: вход $224\times224\times3$ (цветное изображение), $64$ фильтра размера $3\times3\times3$ (фильтр обязан иметь ту же глубину, что и вход — подробнее в следующем разделе), выход $222\times222\times64$ при шаге $1$ без padding. Число умножений: $222\times222\times64\times(3\times3\times3)\approx222\times222\times64\times27\approx85{,}2$ миллиона умножений — и это только для одного-единственного свёрточного слоя из десятков, из которых состоит реальная сеть. Такая арифметика идеально распараллеливается на GPU (каждая выходная позиция вычисляется независимо от остальных), что и объясняет, почему CNN массово обучают именно на видеокартах.

Почему это важно

Свёртка — это единственная новая операция, которую нужно освоить, чтобы понимать CNN: всё остальное в архитектуре (pooling, несколько фильтров на слой, чередование слоёв) — надстройки поверх неё. Численный пример с фильтром Собеля показывает не абстрактную формулу, а конкретный механизм: фильтр — это, по сути, маленький детектор конкретного локального паттерна, и его отклик тем сильнее, чем точнее участок изображения совпадает с тем, на что фильтр «настроен». В классическом компьютерном зрении такие фильтры (Собеля, Лапласа, Гаусса и десятки других) веками подбирались вручную инженерами по одному под конкретную задачу. Центральная идея CNN — перестать подбирать веса фильтров руками и вместо этого обучать их через backpropagation точно так же, как веса полносвязного слоя, позволяя сети самой найти те паттерны, которые полезны для решения конкретной задачи классификации или детекции.

Локальная связность и разделяемые веса

Интуиция

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

Во-первых, локальная связность (local connectivity, иногда — sparse connectivity): каждое значение на выходной карте признаков зависит только от маленького локального окна входа (например, $3\times3$), а не от всего изображения целиком, как в полносвязном слое. Это прямое инженерное воплощение биологической идеи Фукусимы и Лекуна: раз полезные паттерны в изображениях (границы, углы, текстуры) обычно локальны, нейрону не нужно «видеть» всё изображение сразу, чтобы их найти.

Во-вторых, разделяемые веса (shared weights, иногда — parameter sharing): один и тот же фильтр (один и тот же набор весов) применяется во всех позициях изображения. В примере со свёрткой $5\times5$ фильтром $3\times3$ выше все девять выходных значений вычислены одним и тем же набором из девяти чисел фильтра — просто наложенным на разные участки входа. Это резко отличается от полносвязного слоя, где для каждого выходного нейрона (для каждой позиции результата) обучается собственный, полностью независимый набор весов.

Формула

Число обучаемых параметров одного свёрточного слоя. Пусть входной тензор имеет глубину (число каналов) $C_{in}$, слой содержит $C_{out}$ фильтров размера $k\times k$ (каждый фильтр обязан иметь ту же глубину $C_{in}$, что и вход, чтобы «накрыть» все входные каналы сразу). Тогда число параметров равно

$$P_{conv} = \underbrace{(k\times k\times C_{in})}_{\text{веса одного фильтра}}\times C_{out} + \underbrace{C_{out}}_{\text{bias — по одному на фильтр}}$$

Критически важно: это число не зависит от пространственного размера входа $H\times W$ — сколь угодно большое изображение обрабатывается тем же самым числом параметров, потому что один и тот же фильтр переиспользуется во всех позициях.

Разбор примеров

Пример 1 (прямое сравнение — свёрточный слой против полносвязного на одном и том же входе). Возьмём вход $32\times32\times3$ (маленькое цветное изображение, как в классическом датасете CIFAR-10) и слой на выходе $32\times32\times64$ (для CNN — сохраняем пространственный размер с помощью padding, разберём ниже; для полносвязного слоя выход тоже разворачиваем в $32\times32\times64=65\,536$ выходных чисел, чтобы сравнение было честным по объёму выходной информации).

Полносвязный слой: каждый из $65\,536$ выходных нейронов соединён с каждым из $32\times32\times3=3\,072$ входных чисел. Параметров: $3\,072\times65\,536 + 65\,536 = 201\,326\,592+65\,536 \approx 201{,}4$ миллиона.

Свёрточный слой с $64$ фильтрами $3\times3$ и той же глубиной входа $C_{in}=3$: $P_{conv}=(3\times3\times3)\times64+64 = 27\times64+64=1\,728+64=1\,792$ параметра.

Разница — свёрточный слой использует в $201\,391\,872 / 1\,792 \approx 112\,384$ раза меньше параметров, при этом покрывая своими $64$ фильтрами всё то же самое изображение целиком, просто применяя каждый фильтр многократно, в каждой позиции. Это не примерная, а точная иллюстрация того, зачем нужны локальная связность и разделяемые веса вместе: локальная связность уменьшает число входов, влияющих на одно выходное значение (с $3\,072$ до $27$), а разделяемые веса позволяют не обучать отдельный такой маленький набор весов для каждой из $32\times32=1\,024$ пространственных позиций выхода, а переиспользовать один и тот же.

Пример 2 (что теряется при переходе к CNN — и почему это оправданная цена). Полносвязный слой в принципе способен выучить, что пиксель в левом верхнем углу важен только в комбинации с пикселем в правом нижнем углу, — сколь угодно нелокальную и «неструктурированную» зависимость, если она действительно присутствует в данных. Свёрточный слой с маленьким фильтром физически не может выучить такую зависимость за одну операцию — каждое выходное значение видит только локальное окно. Однако для абсолютного большинства задач компьютерного зрения такие дальние, структурно случайные зависимости пикселей крайне редки и не несут полезной информации, тогда как локальные паттерны (края, текстуры) встречаются повсеместно и переиспользуются в разных частях изображения — котёнок в левом верхнем углу фотографии и котёнок в правом нижнем углу другой фотографии распознаются одними и теми же признаками «мех», «глаз», «ухо», вне зависимости от того, в какой части кадра они оказались. Резкое сокращение числа параметров — это не потеря выразительности там, где она реально нужна для изображений, а удаление именно тех степеней свободы, которые для изображений практически никогда не были полезны.

Пример 3 (эта же операция в коде — torch.nn.Conv2d). В PyTorch свёрточный слой создаётся так:

import torch
import torch.nn as nn

conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1, stride=1)
x = torch.randn(1, 3, 32, 32)  # (batch, channels, height, width)
y = conv(x)                     # форма (1, 64, 32, 32) при padding=1

print(sum(p.numel() for p in conv.parameters()))  # 1792 — совпадает с примером 1

Аргумент in_channels — это $C_{in}$ из формулы выше, out_channels — число фильтров $C_{out}$, kernel_size — размер фильтра $k$. Вызов conv.parameters() подтверждает ровно то число параметров, которое посчитано вручную в примере 1: PyTorch не делает здесь ничего магического — это тот же самый набор фильтров, каждый из которых независимо обучается через градиентный спуск, применяясь ко всем позициям входа с одними и теми же весами.

Почему это важно

Локальная связность и разделяемые веса — не два отдельных трюка, а два взгляда на одну и ту же архитектурную идею, и именно она объясняет, почему CNN вообще можно было обучить на реальных изображениях, а не только на игрушечных датасетах низкого разрешения. Сокращение параметров с сотен миллионов до тысяч на слой — это не просто экономия памяти: меньше параметров означает меньше риск переобучения (урок 333 про Dropout и регуляризацию) при том же объёме данных, быстрее обучение и возможность обучать по-настоящему глубокие сети — десятки и сотни слоёв, каждый из которых добавляет CNN относительно немного новых параметров. Без разделяемых весов современные архитектуры компьютерного зрения с миллионами изображений в обучающей выборке были бы попросту невозможны.

Padding и stride

Интуиция

В примере со свёрткой $5\times5$ фильтром $3\times3$ выходная карта признаков сжалась до $3\times3$ — изображение стало меньше просто оттого, что фильтр физически не может «зацепиться» за края так же, как за центр: угловой пиксель участвует лишь в одном скользящем положении фильтра, тогда как центральный пиксель — в девяти. Если применить несколько таких свёрток подряд без каких-либо мер, изображение будет стремительно сжиматься слой за слоем, а информация на краях будет систематически недоучитываться. Padding (набивка) решает эту проблему буквально: вокруг изображения добавляется рамка из дополнительных пикселей (чаще всего нулей — отсюда термин zero padding), так что угловые и краевые пиксели исходного изображения тоже оказываются в центре какого-то положения фильтра, а размер выхода можно контролировать напрямую, вплоть до сохранения точно того же размера, что был у входа.

Stride (шаг) — это второй параметр, управляющий тем же самым результатом с другой стороны: он задаёт, на сколько позиций сдвигается фильтр при переходе к следующему положению. В примерах выше шаг всегда был равен $1$ — окно сдвигалось на один пиксель. Если задать шаг $2$, окно будет перепрыгивать через одну позицию, пропуская промежуточные вычисления, — выходная карта признаков получится примерно вдвое меньше по каждому измерению. Padding и stride вместе дают полный контроль над пространственным размером выхода на каждом слое CNN — от точного сохранения размера входа до его целенаправленного уменьшения в несколько раз за один слой.

Формула

Размер выхода свёртки с учётом padding и stride. Пусть вход имеет размер $H\times W$, фильтр — $k\times k$, padding (число добавленных пикселей рамки с каждой стороны) — $p$, stride — $s$. Тогда размер выходной карты признаков по каждому измерению равен

$$H_{out} = \left\lfloor \frac{H - k + 2p}{s} \right\rfloor + 1, \qquad W_{out} = \left\lfloor \frac{W - k + 2p}{s} \right\rfloor + 1$$

Частный случай «same»-padding (выход того же пространственного размера, что и вход, при $s=1$): $p = \dfrac{k-1}{2}$, что требует нечётного $k$ (отсюда широко распространённая практика выбирать фильтры нечётного размера — $3\times3$, $5\times5$).

Разбор примеров

Пример 1 (проверка формулы на уже посчитанном вручную примере). В примере со свёрткой $5\times5$ фильтром $3\times3$ без padding и с шагом $1$: $H=5$, $k=3$, $p=0$, $s=1$. По формуле: $H_{out}=\lfloor(5-3+0)/1\rfloor+1=\lfloor2\rfloor+1=3$. Это в точности совпадает с полученной вручную картой признаков $3\times3$ — формула не добавляет ничего нового, а лишь обобщает то, что уже было пересчитано число за числом.

Пример 2 (same-padding — сохранение размера входа). То же изображение $5\times5$, тот же фильтр $3\times3$, но теперь с padding $p=(3-1)/2=1$: вокруг изображения $5\times5$ добавляется рамка из нулей толщиной в один пиксель, так что фактический размер, к которому применяется фильтр, становится $7\times7$. По формуле: $H_{out}=\lfloor(5-3+2\cdot1)/1\rfloor+1=\lfloor4\rfloor+1=5$ — выход снова $5\times5$, тот же размер, что и вход. Это и есть смысл «same»-padding: он позволяет складывать свёрточные слои один за другим, не теряя пространственный размер на каждом шаге, и откладывать уменьшение размера до специально выделенных для этого мест — обычно до pooling-слоёв (следующий раздел) или свёрток с шагом больше $1$.

Пример 3 (stride $2$ — контролируемое уменьшение размера). Вход $32\times32$, фильтр $3\times3$, padding $p=1$ (same-padding для $k=3$), но теперь шаг $s=2$. По формуле: $H_{out}=\lfloor(32-3+2\cdot1)/2\rfloor+1=\lfloor31/2\rfloor+1=\lfloor15{,}5\rfloor+1=15+1=16$. Изображение $32\times32$ превращается в $16\times16$ за один свёрточный слой — ровно вдвое меньше по каждому измерению. Такой приём (свёртка с шагом $2$ вместо шага $1$) — стандартная альтернатива pooling-слою для уменьшения пространственного размера, используемая, например, в архитектуре ResNet (урок 337): вместо отдельного pooling-слоя уменьшение размера «встраивается» прямо в свёрточный слой за счёт увеличенного шага.

Почему это важно

Padding и stride — это не декоративные настройки, а инструмент прямого управления архитектурой: без padding глубокая сеть из десятков свёрточных слоёв быстро «съедает» изображение до тривиально маленького размера, а без возможности задавать шаг больше $1$ не было бы простого способа целенаправленно и предсказуемо уменьшать пространственный размер там, где это нужно архитектору сети. Формула размера выхода — рутинный, но обязательный расчёт при проектировании любой CNN: несовпадение ожидаемого и фактического размера тензора на стыке слоёв — одна из самых частых практических ошибок при написании кода на PyTorch, и без понимания этой формулы её невозможно быстро продиагностировать.

Pooling-слои и иерархия признаков

Интуиция

Pooling-слой решает ту же задачу — уменьшение пространственного размера — но принципиально иначе, чем свёртка с большим шагом: у pooling-слоя нет обучаемых весов вообще, только фиксированное правило агрегации значений внутри окна. Самый распространённый вариант — max pooling: окно (обычно $2\times2$) скользит по карте признаков и в каждой позиции оставляет только максимальное значение из окна, отбрасывая остальные. Менее распространённый, но тоже используемый вариант — average pooling, который вместо максимума берёт среднее значение окна.

Зачем это вообще нужно, если размер можно уменьшать и сверткой с шагом $2$? У max pooling есть особое дополнительное свойство — инвариантность к небольшим сдвигам (translation invariance): если интересующий сеть паттерн (например, край объекта) сдвинулся на один-два пикселя внутри окна pooling, максимальное значение окна с высокой вероятностью останется тем же самым или почти тем же, потому что max pooling реагирует на «сработал ли сильный признак где-то в этой области», а не на его точную позицию с точностью до пикселя. Это ровно то свойство, которое нужно для классификации изображений: для ответа на вопрос «есть ли на фото кошка» неважно, сдвинуто ли изображение кошки на два пикселя влево или вправо, — а для более точных задач вроде сегментации изображения (точное определение границ объекта попиксельно) это же свойство, наоборот, может быть нежелательным, поэтому такие архитектуры используют pooling куда осторожнее.

Формула

Max pooling. Пусть входная карта признаков — матрица $F$, окно размера $k\times k$, шаг $s$ (для pooling шаг обычно равен размеру окна, чтобы области не пересекались). Значение выхода в позиции $(i,j)$:

$$P(i,j) = \max_{0\le m Average pooling — та же формула с заменой $\max$ на среднее арифметическое значений окна. Размер выхода вычисляется той же формулой, что и для свёртки, при $p=0$: $H_{out}=\lfloor(H-k)/s\rfloor+1$.

Разбор примеров

Пример 1 (полный численный пример max pooling $2\times2$ с шагом $2$). Возьмём карту признаков $4\times4$ (например, результат предыдущего свёрточного слоя после активации ReLU):

$$F=\begin{pmatrix}1&3&2&4\\2&8&1&3\\5&2&6&1\\1&4&2&7\end{pmatrix}$$

Разбиваем на четыре непересекающихся окна $2\times2$: левое верхнее $\begin{pmatrix}1&3\\2&8\end{pmatrix}$ — максимум $8$; правое верхнее $\begin{pmatrix}2&4\\1&3\end{pmatrix}$ — максимум $4$; левое нижнее $\begin{pmatrix}5&2\\1&4\end{pmatrix}$ — максимум $5$; правое нижнее $\begin{pmatrix}6&1\\2&7\end{pmatrix}$ — максимум $7$. Итоговый результат — карта $2\times2$:

$$P=\begin{pmatrix}8&4\\5&7\end{pmatrix}$$

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

Пример 2 (инвариантность к сдвигу на числах). Возьмём то же левое верхнее окно $\begin{pmatrix}1&3\\2&8\end{pmatrix}$ с максимумом $8$ в правом нижнем углу окна, и вариант с этим же значением $8$, сдвинутым в другую позицию внутри того же окна: $\begin{pmatrix}8&3\\2&1\end{pmatrix}$. В обоих случаях max pooling выдаст одно и то же значение $8$ — несмотря на то, что положение «сильного» пикселя внутри окна изменилось. Именно в этом и заключается устойчивость к небольшим сдвигам: пока сдвиг не выводит важный признак за пределы окна целиком, итоговый результат pooling не меняется, хотя точная позиция признака внутри изображения могла немного сместиться.

Пример 3 (average pooling на тех же данных — сравнение с max). Применим average pooling с тем же окном $2\times2$ и шагом $2$ к той же карте признаков $F$ из примера 1: среднее левого верхнего окна $(1+3+2+8)/4=3{,}5$, правого верхнего — $(2+4+1+3)/4=2{,}5$, левого нижнего — $(5+2+1+4)/4=3$, правого нижнего — $(6+1+2+7)/4=4$. Результат:

$$P_{avg}=\begin{pmatrix}3{,}5&2{,}5\\3&4\end{pmatrix}$$

Сравнение с max pooling показывает разницу в характере агрегации: average pooling сглаживает все значения окна, сохраняя больше информации об общем «фоне», но размывая пиковые отклики сильных признаков; max pooling, наоборот, сохраняет самый сильный сигнал, но полностью теряет остальную информацию окна. На практике для промежуточных слоёв классификационных CNN max pooling используется значительно чаще именно потому, что задача классификации обычно опирается на наличие сильных, отчётливых признаков, а не на их усреднённый фон. В PyTorch обе операции доступны как torch.nn.MaxPool2d(kernel_size=2, stride=2) и torch.nn.AvgPool2d(kernel_size=2, stride=2) соответственно.

Иерархия признаков — прямое продолжение урока 326

В уроке 326 было введено общее понятие иерархии признаков — идея, что глубокая сеть строит представления возрастающей сложности слой за слоем. Стек из чередующихся свёрточных и pooling-слоёв CNN — это самая наглядная, буквально визуализируемая реализация этой идеи для изображений. Первый свёрточный слой, как в примере с фильтром Собеля выше, обучается реагировать на простейшие локальные паттерны — края определённой ориентации, пятна однородного цвета, простые текстурные градиенты. Каждый следующий слой принимает на вход уже не исходное изображение, а карту признаков предыдущего слоя, поэтому его фильтры комбинируют уже найденные простые паттерны в более сложные: сочетания краёв образуют углы и простые формы, сочетания форм — части объектов (глаз, колесо, оконная рама), а самые глубокие слои сети реагируют уже на присутствие целых объектов или крупных их частей (лицо, автомобиль, здание). При этом каждый следующий слой видит не буквально больший участок исходного изображения в пикселях, а больший участок с точки зрения того, сколько исходных пикселей повлияло на каждое его значение, — это понятие называется рецептивным полем (receptive field) и растёт слой за слоем по мере того, как свёртки и pooling последовательно уменьшают пространственный размер, сохраняя при этом всё большую эффективную область обзора.

Почему это важно

Pooling-слои — не опциональное украшение архитектуры, а инструмент, который одновременно решает две задачи: контролируемо уменьшает вычислительную и параметрическую нагрузку следующих слоёв (меньшая карта признаков — меньше вычислений в следующей свёртке) и добавляет сети устойчивость к незначительным вариациям во входных данных, что критично для обобщения на новые, ранее не виденные изображения. А иерархия признаков, которую строит весь стек свёрточных и pooling-слоёв вместе, — это не метафора, а буквально измеримое, визуализируемое явление: исследователи компьютерного зрения регулярно визуализируют, на что именно реагируют фильтры разных слоёв обученной CNN, и раз за разом наблюдают ровно ту прогрессию от простого к сложному, которая описана выше. Именно эта иерархия и есть причина, по которой CNN так хорошо работают для изображений: она напрямую использует то, что сложные визуальные объекты в реальном мире действительно состоят из простых локальных паттернов, скомбинированных иерархически, а не собраны из случайного, неструктурированного набора пикселей.

Практика: 30 заданий

Базовые задания (1–10)

Задание 1: Дано изображение $3\times3$: $\begin{pmatrix}1&2&3\\4&5&6\\7&8&9\end{pmatrix}$ и фильтр $3\times3$: $\begin{pmatrix}1&0&1\\0&1&0\\1&0&1\end{pmatrix}$. Найди результат свёртки без padding (выход $1\times1$).


Задание 2: Изображение $4\times4$, фильтр $2\times2$, без padding, шаг $1$. Каким будет размер выходной карты признаков?


Задание 3 (машинное обучение): Что такое карта признаков (feature map) и откуда она берётся?


Задание 4: Изображение $5\times5$, фильтр $3\times3$, padding $p=1$, шаг $s=1$. Найди размер выхода.


Задание 5: Карта признаков $2\times2$: $\begin{pmatrix}3&9\\1&5\end{pmatrix}$. Примени max pooling $2\times2$ с шагом $2$.


Задание 6: Та же карта признаков из задания 5. Примени average pooling $2\times2$ с шагом $2$.


Задание 7 (машинное обучение): В чём принципиальное отличие свёрточного слоя от полносвязного с точки зрения того, какие входные пиксели влияют на одно выходное значение?


Задание 8: Изображение $6\times6$, фильтр $3\times3$, без padding, шаг $2$. Найди размер выхода.


Задание 9: Слой имеет $16$ фильтров размера $3\times3$, вход с глубиной $C_{in}=3$. Посчитай число обучаемых параметров слоя (с учётом bias).


Задание 10 (машинное обучение): Зачем в CNN применяют pooling-слои, если размер можно уменьшить и свёрткой с шагом больше $1$?

Средние задания (11–20)

Задание 11: Изображение $5\times5$ из примера урока: $\begin{pmatrix}0&0&128&255&255\\0&0&128&255&255\\0&0&128&255&255\\0&0&128&255&255\\0&0&128&255&255\end{pmatrix}$, фильтр Собеля $\begin{pmatrix}-1&0&1\\-2&0&2\\-1&0&1\end{pmatrix}$. Посчитай значение выхода в позиции $(1,1)$ (окно строк $1$–$3$, столбцов $1$–$3$).


Задание 12 (машинное обучение): Почему фильтр Собеля для вертикальных границ (столбцы $-1,0,1$) даёт слабый отклик на изображении с чисто горизонтальной границей?


Задание 13: Транспонированный фильтр Собеля для горизонтальных границ: $\begin{pmatrix}-1&-2&-1\\0&0&0\\1&2&1\end{pmatrix}$. Примени его к окну $\begin{pmatrix}0&0&0\\128&128&128\\255&255&255\end{pmatrix}$ (горизонтальный переход сверху вниз).


Задание 14: Вход $28\times28$ (как в MNIST), фильтр $5\times5$, без padding, шаг $1$. Найди размер выхода.


Задание 15 (машинное обучение): Слой $A$ — полносвязный слой со входом $28\times28=784$ и выходом $784$ (столько же нейронов, сколько входов). Слой $B$ — свёрточный слой с $1$ фильтром $5\times5$, глубина входа $1$. Сравни число параметров.


Задание 16: Карта признаков $4\times4$: $\begin{pmatrix}2&1&0&3\\4&6&2&1\\1&0&5&2\\3&2&1&8\end{pmatrix}$. Примени max pooling $2\times2$ с шагом $2$.


Задание 17: Вход $64\times64$, свёрточный слой с padding $p=2$, фильтр $k=5$, шаг $s=1$. Проверь, действительно ли это «same»-padding (сохраняет размер входа).


Задание 18 (машинное обучение): Первый слой CNN содержит $32$ фильтра $3\times3$ на входе с $3$ каналами (RGB). Второй слой содержит $64$ фильтра $3\times3$, принимающих выход первого слоя. Посчитай параметры второго слоя.


Задание 19: Вход $16\times16$, свёрточный слой с $k=3$, $p=1$, $s=2$. Найди размер выхода.


Задание 20 (машинное обучение): Объясни своими словами, почему max pooling даёт инвариантность к небольшим сдвигам, а average pooling — в меньшей степени.

Продвинутые задания (21–30)

Задание 21: Вычисли полную карту признаков $2\times2$ для изображения $4\times4$: $\begin{pmatrix}1&2&0&1\\0&1&2&0\\2&0&1&1\\1&1&0&2\end{pmatrix}$ и фильтра $3\times3$: $\begin{pmatrix}1&0&-1\\1&0&-1\\1&0&-1\end{pmatrix}$, без padding, шаг $1$.


Задание 22 (машинное обучение): У тебя CNN из трёх свёрточных слоёв $3\times3$ подряд с same-padding и шагом $1$ (размер пространственно не меняется). Оцени, какого размера участок исходного изображения влияет на одно значение после третьего слоя (рецептивное поле).


Задание 23: Слой с $8$ фильтрами $5\times5$, $C_{in}=3$, за ним max pooling $2\times2$ с шагом $2$. Вход $32\times32\times3$. Найди форму тензора после свёртки (padding $p=2$, same) и после pooling.


Задание 24 (машинное обучение): Почему фильтр в свёрточном слое всегда имеет ту же глубину, что и вход (например, $3$ для RGB-изображения), хотя пространственный размер фильтра ($k\times k$) намного меньше пространственного размера входа?


Задание 25: CNN для классификации изображений $64\times64\times3$ на $10$ классов состоит из: свёрточного слоя ($16$ фильтров $3\times3$, same-padding) → max pooling $2\times2$ → свёрточного слоя ($32$ фильтра $3\times3$, same-padding) → max pooling $2\times2$ → полносвязного слоя на $10$ выходов. Посчитай форму тензора на выходе последнего pooling-слоя (перед разворачиванием в вектор).


Задание 26 (машинное обучение): Для сети из задания 25 посчитай число параметров полносвязного слоя, принимающего развёрнутый выход последнего pooling-слоя ($8\,192$ числа) и выдающего $10$ классов. Сравни с числом параметров обоих свёрточных слоёв вместе.


Задание 27: Изображение $7\times7$, требуется получить выход $3\times3$ свёрткой с фильтром $3\times3$ и padding $p=0$. Найди необходимый шаг $s$ (целое число).


Задание 28 (машинное обучение): Объясни, почему увеличение числа фильтров в свёрточном слое (например, с $16$ до $64$) не меняет размер этого фильтра по горизонтали и вертикали, но существенно увеличивает число параметров слоя.


Задание 29: Карта признаков $6\times6$ обрабатывается average pooling $3\times3$ с шагом $3$ (окна не пересекаются). Дано верхнее левое окно $\begin{pmatrix}9&3&6\\0&6&3\\3&0&6\end{pmatrix}$. Найди значение average pooling для этого окна.


Задание 30 (машинное обучение): Спроектируй (без кода, только рассуждение) первый свёрточный слой CNN для входа $128\times128\times3$, чтобы на выходе получить карту признаков $128\times128\times32$ (сохранить пространственный размер, задать $32$ фильтра). Укажи $k$, $p$, $s$ и посчитай число параметров.

Частые ошибки

  • Путают понятия «свёртка» и «фильтр», говоря «применили свёртку» без уточнения, каким конкретно фильтром. Свёртка — это операция (скользящее окно, поэлементное умножение и суммирование), а фильтр — конкретный набор весов, к которому эта операция применяется; один и тот же слой обычно содержит сразу много разных фильтров, и каждый даёт свою отдельную карту признаков.

  • Забывают, что фильтр обязан иметь ту же глубину, что и вход. Ошибка вида «применили фильтр $3\times3$ к цветному изображению» без уточнения, что фактический размер фильтра — $3\times3\times3$ (задание 24), часто приводит к неверному подсчёту параметров или ошибкам формы тензора в коде.

  • Считают, что padding обязательно означает «искажение» изображения. На самом деле padding нулями просто добавляет рамку вокруг изображения, не изменяя ни одного исходного пикселя, — его единственная цель — контролировать размер выхода и дать краевым пикселям столько же «внимания» фильтра, сколько получают центральные.

  • Путают stride в свёртке со stride в pooling, ожидая одинакового поведения. В свёрточном слое шаг чаще всего равен $1$ (пространственное сжатие делают через padding и отдельный pooling или редкие свёртки с шагом $2$), тогда как в pooling-слое шаг практически всегда равен размеру окна (чтобы области не пересекались) — смешение этих двух ролей приводит к неверному расчёту размеров тензоров.

  • Считают, что max pooling всегда лучше average pooling. Max pooling действительно чаще используется в классификационных сетях, но average pooling (в частности, глобальный average pooling — усреднение по всей карте признаков целиком) широко применяется в конце современных архитектур (например, в ResNet, урок 337) именно там, где важно агрегировать всю пространственную информацию, а не только пиковый отклик.

  • Не проверяют размер выхода перед написанием кода и получают ошибки несовпадения форм тензоров на стыке слоёв. Формула $H_{out}=\lfloor(H-k+2p)/s\rfloor+1$ (раздел про padding и stride) — рутинный, но обязательный расчёт для каждого слоя при проектировании CNN; пропуск этого шага — самый частый источник ошибок формы при первом самостоятельном написании архитектуры на PyTorch.

Главное запомнить

  • Свёртка — это операция скользящего окна (фильтра): в каждой позиции окна происходит поэлементное умножение фильтра на участок входа и суммирование в одно число выходной карты признаков (feature map).

  • Полный численный пример этого урока — фильтр Собеля $\begin{pmatrix}-1&0&1\\-2&0&2\\-1&0&1\end{pmatrix}$, применённый к изображению с вертикальной границей, — показывает, что фильтр даёт максимальный отклик ровно там, где перепад яркости совпадает с паттерном, на который он настроен.

  • Локальная связность: каждое значение на выходной карте признаков зависит только от маленького локального окна входа, а не от всего изображения целиком, как в полносвязном слое.

  • Разделяемые веса: один и тот же фильтр применяется во всех пространственных позициях изображения — это резко (на конкретном примере из урока в $112\,384$ раза) сокращает число параметров по сравнению с полносвязным слоем того же логического назначения.

  • Число параметров свёрточного слоя $P=(k\times k\times C_{in})\times C_{out}+C_{out}$ не зависит от пространственного размера входа — один и тот же слой обрабатывает изображения любого разрешения тем же числом весов.

  • Padding (рамка из нулей вокруг изображения) и stride (шаг сдвига фильтра) вместе управляют размером выхода по формуле $H_{out}=\lfloor(H-k+2p)/s\rfloor+1$; same-padding при $k$ нечётном и $s=1$ достигается при $p=(k-1)/2$.

  • Max pooling выбирает максимум из локального окна карты признаков, что даёт инвариантность к небольшим сдвигам; average pooling берёт среднее окна и сглаживает результат сильнее, чем сохраняет пиковые отклики.

  • Стек чередующихся свёрточных и pooling-слоёв строит иерархию признаков: ранние слои реагируют на простые локальные паттерны (границы, текстуры), поздние — на части объектов и целые объекты (прямое продолжение общей идеи иерархии признаков из урока 326).

  • torch.nn.Conv2d и torch.nn.MaxPool2d — прямая программная реализация операций свёртки и max pooling, разобранных в этом уроке вручную число за числом; параметры конструктора (in_channels, out_channels, kernel_size, padding, stride) соответствуют ровно тем обозначениям, что использованы в формулах.

Связь с темами курса

Этот урок напрямую опирается на понятие иерархии признаков, впервые введённое в общих чертах в уроке 326 про искусственные нейронные сети: там было заявлено, что глубина сети позволяет строить представления возрастающей сложности, а сегодняшний урок показал, как именно эта идея реализуется в CNN буквально — слой за слоем, от границ и текстур до частей объектов и целых объектов. Понимание backpropagation (урок 330) остаётся неизменным и для CNN: веса фильтров — точно такие же обучаемые параметры, как веса полносвязного слоя, и они подстраиваются тем же самым алгоритмом обратного распространения ошибки, только с учётом того, что один и тот же вес фильтра используется многократно в разных позициях (что математически лишь означает суммирование градиентов по всем позициям, где этот вес участвовал в forward pass).

Следующий урок (336) переходит от фундаментальных операций к готовым, проверенным временем архитектурам, построенным на этих операциях, — классическим CNN вроде LeNet, AlexNet и VGG. Каждая из них — это конкретный, проверенный на практике рецепт того, сколько свёрточных и pooling-слоёв использовать, каких размеров фильтры выбирать и как их чередовать, чтобы построенная сегодня иерархия признаков действительно работала на реальных изображениях. Понимание padding, stride, pooling и числа параметров, полученное сегодня, — необходимый словарь для того, чтобы читать и понимать архитектурные схемы этих сетей, а не просто запоминать их названия.

Интересные факты

  • Неокогнитрон Фукусимы 1980 года появился благодаря экспериментам Хьюбела и Визела над зрительной корой кошек — работе, за которую они получили Нобелевскую премию по физиологии и медицине в 1981 году; таким образом, ключевая архитектурная идея CNN восходит к нобелевской нейрофизиологии, а не к инженерии как таковой.

  • Термин «свёртка» в названии CNN исторически не вполне точен: операция, которую реализует nn.Conv2d, математически ближе к взаимной корреляции, а не к свёртке в строгом математическом смысле (для настоящей свёртки фильтр нужно было бы развернуть на 180°) — но поскольку веса фильтра всё равно обучаются автоматически, сети совершенно всё равно, в каком именно направлении «читать» фильтр, и терминология закрепилась именно как «свёртка».

  • LeNet-5 Яна Лекуна работала в реальных банковских системах США по автоматическому распознаванию рукописных сумм на чеках уже в конце 1990-х годов — то есть CNN нашли коммерческое промышленное применение почти за полтора десятилетия до триумфа AlexNet в 2012 году, который принято считать «стартом» эпохи глубокого обучения.

  • Фильтр Собеля, использованный в качестве примера в этом уроке, был предложен ещё в 1968 году Ирвином Собелем — задолго до появления обучаемых сверточных сетей — и десятилетиями применялся как один из стандартных инструментов классического компьютерного зрения; CNN не изобрели саму идею такого фильтра, а научились подбирать веса подобных фильтров автоматически под конкретную задачу вместо ручного проектирования.

Лайфхаки

  • Перед написанием кода архитектуры CNN всегда сначала распиши на бумаге форму тензора после каждого слоя, используя формулу $H_{out}=\lfloor(H-k+2p)/s\rfloor+1$, — несовпадение форм на стыке слоёв откроется в коде PyTorch как исключение только во время запуска, а на бумаге его видно заранее.

  • Если сомневаешься, какое значение padding задать, начни с same-padding ($p=(k-1)/2$ для нечётного $k$) — это самый частый выбор в промежуточных слоях, откладывающий уменьшение пространственного размера на явные pooling-слои или свёртки с увеличенным шагом, а не размывающий его по всей архитектуре незаметно.

  • Считая число параметров свёрточного слоя вручную, всегда явно выписывай все четыре множителя формулы $(k\times k\times C_{in})\times C_{out}+C_{out}$ по отдельности — самая частая арифметическая ошибка на практике заключается в том, что забывают домножить на глубину входа $C_{in}$, получая заниженное в разы число.

  • Для быстрой проверки, правильно ли фильтр «видит» нужный паттерн, попробуй применить его вручную (как в примерах урока) к паре контрастных мини-примеров — изображению с вертикальной границей и изображению с горизонтальной границей; если отклики сильно отличаются в ожидаемую сторону, фильтр ведёт себя так, как задумано.

  • При отладке собственной CNN печатай форму тензора (.shape в PyTorch) после каждого слоя во время первого прогона на игрушечном примере — это быстрее и надёжнее, чем пересчитывать все размеры в голове, особенно когда слоёв в архитектуре становится больше пяти-шести.

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

Сегодня ты прошёл путь от одной операции — скользящего окна с поэлементным умножением и суммированием — до понимания, почему именно эта операция вместе с pooling-слоями лежит в основе практически любой современной архитектуры компьютерного зрения. Ты вручную пересчитал отклик фильтра Собеля на конкретных числах, убедился, зачем нужны локальная связность и разделяемые веса на примере с разницей в сто с лишним тысяч раз в числе параметров, и разобрался, как padding, stride и pooling вместе управляют тем, как информация сжимается и обобщается слой за слоем. Всё это — не абстрактная теория, а тот самый набор строительных блоков (nn.Conv2d, nn.MaxPool2d), из которых в следующем уроке ты соберёшь первые полноценные, проверенные временем архитектуры CNN.

Понял тему? Закрепи в боте! 🚀

Попрактикуйся на задачах и получи персональные рекомендации от AI

💪 Начать тренировку
💬 Есть вопрос? Спроси бота!