Padding и stride в свёрточных сетях: как считать размер выходного тензора

На чтение
6 мин
Дата обновления
05.11.2026

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

Ниже используется соглашение PyTorch для батча изображений: N × C × H × W. Здесь N — количество примеров, C — каналы, H — высота, W — ширина. Разбор относится к обычной Conv2d и MaxPool2d с оговорёнными параметрами; транспонированная свёртка требует другой формулы.

Что именно меняют kernel, padding и stride

Представьте окно, которое последовательно прикладывается к изображению. kernel_size определяет его размер. stride задаёт шаг между соседними положениями окна. padding добавляет границу вокруг исходной области. При стандартном padding_mode у Conv2d это нули; другие поддерживаемые режимы меняют значения на границе, но не превращают каналы в пространственные координаты.

dilation задаёт расстояние между точками ядра. При dilation=1 ядро 3 × 3 занимает область 3 × 3; при dilation=2 те же три позиции вдоль оси охватывают пять входных позиций. Размер весов при этом не становится 5 × 5: увеличивается эффективный охват, а не число точек ядра.

Для одной оси удобно сначала вычислить эффективный размер окна: K_eff = D × (K − 1) + 1. Здесь K — kernel_size, D — dilation. После этого формула выглядит короче:

L_out = floor((L_in + 2 × P − K_eff) / S) + 1

P — симметричный padding с каждой стороны оси, S — stride. floor означает округление вниз. Формула не требует, чтобы деление было целым: неполный следующий шаг просто не создаёт ещё одну позицию обычной свёртки. Если окно вообще не помещается в дополненный вход, корректного положительного выхода для такого слоя нет.

Первый расчёт по клеткам

Возьмём одну ось длиной 7, ядро 3, шаг 2, без дополнения и dilation=1. Окно начинается в позициях 0, 2 и 4 и покрывает соответственно 0–2, 2–4 и 4–6. Получаются три значения. Подстановка даёт floor((7 − 3)/2) + 1 = 3.

Для длины 8 при тех же параметрах результат также равен 3: последнее допустимое начало остаётся 4. Начало 6 потребовало бы позиции 8 за пределами входа. Именно поэтому уменьшение через stride не всегда равно привычному делению размера пополам с округлением, выбранным на глаз.

Теперь добавим padding=1 к длине 7. Дополненная ось имеет длину 9; результат floor((7 + 2 − 3)/2) + 1 = 4. Это не обещание, что сеть станет лучше: дополнение меняет геометрию и участие границы в вычислениях, а качество модели проверяется отдельно.

L_inK / S / P / DK_effL_out
323 / 1 / 0 / 1330
323 / 1 / 1 / 1332
323 / 2 / 1 / 1316
313 / 2 / 1 / 1316
323 / 1 / 2 / 2532

Высота, ширина и число каналов — три разных вопроса

У Conv2d параметры можно задавать одним числом либо парой. Одно число применяется к обеим пространственным осям; в паре первое значение относится к высоте, второе — к ширине. Поэтому квадратное ядро не делает прямоугольное изображение квадратным.

Авторский пример: вход 4 × 3 × 31 × 47 и слой Conv2d(3, 16, kernel_size=3, stride=2, padding=1). Для высоты получаем floor((31 + 2 − 3)/2) + 1 = 16. Для ширины — floor((47 + 2 − 3)/2) + 1 = 24. Итоговая форма: 4 × 16 × 16 × 24.

Четвёрка не изменилась, поскольку это размер батча. Тройка входных каналов должна соответствовать in_channels слоя, а шестнадцать выходных каналов задаются out_channels. В стандартной свёртке с groups=1 фильтр использует все входные каналы; не нужно отдельно умножать пространственный размер выхода на три.

Для неквадратного случая kernel_size=(3, 5), stride=(2, 3), padding=(1, 2), dilation=1 на входе 31 × 47 высота будет 16, а ширина floor((47 + 4 − 5)/3) + 1 = 16. Совпадение размеров здесь получилось из выбранных параметров, а не из обязательного свойства Conv2d.

Почему same не стоит переносить между библиотеками по памяти

В документации PyTorch padding='valid' соответствует отсутствию дополнения, а padding='same' сохраняет пространственную форму входа. Для Conv2d этот режим поддерживает только stride=1. Поэтому сочетание same и шага 2 нельзя считать универсальным сокращением для произвольного уменьшения изображения.

Для нечётного эффективного ядра и шага 1 симметричное дополнение P=(K_eff−1)/2 сохраняет размер. Например, K=3, D=2 даёт K_eff=5 и P=2. Если эффективное ядро чётное, одинакового целого дополнения с обеих сторон может быть недостаточно для сохранения размера; не подменяйте режим same произвольным округлением этого выражения.

Когда переносите архитектуру, фиксируйте конкретный API и его правила. Даже одинаковые названия аргументов не освобождают от проверки порядка осей, способа дополнения и допустимого шага. Особенно легко ошибиться, если исходный пример использовал другой порядок каналов.

Pooling: похожая арифметика, другие значения и параметры

У MaxPool2d пространственная формула при ceil_mode=False также использует floor. Но если stride не задан, он по умолчанию равен kernel_size. У Conv2d значение stride по умолчанию равно 1. Эта небольшая разница способна незаметно изменить всю цепочку размеров.

Дополнение MaxPool2d в документации описано как отрицательная бесконечность: это существенно для максимума среди отрицательных значений. Не объясняйте pooling как свёртку с обучаемыми весами или как обязательное добавление нулей. Число каналов обычный пространственный max pooling сохраняет.

При ceil_mode=True действуют отдельные правила допустимых окон: допускается выход за границу для окон, начинающихся во входе или левом дополнении, а начинающиеся в правом дополнении игнорируются. Для базового расчёта ниже этот режим выключен. Механически заменить floor на ceil без проверки граничных условий — ненадёжный способ.

Считаем цепочку до Linear

ЭтапПараметрыФорма
ВходЧетыре RGB-изображения4 × 3 × 31 × 47
Conv2d3 → 16, K=3, S=2, P=14 × 16 × 16 × 24
MaxPool2dK=2, S=2, P=0, ceil_mode=False4 × 16 × 8 × 12
Conv2d16 → 32, K=3, S=1, P=04 × 32 × 6 × 10
Flatten с оси 132 × 6 × 104 × 1920

Если следующий слой Linear получает все эти признаки, его in_features для данного размера входа равен 1920. Батч в произведение не входит: каждому изображению соответствует собственный вектор. Изменение входного разрешения может изменить длину вектора, поэтому число 1920 не является универсальной настройкой сети.

Небольшой калькулятор для проверки записей

def out_size(length, kernel, stride=1, padding=0, dilation=1):
    if min(length, kernel, stride, dilation) <= 0 or padding < 0:
        raise ValueError("Invalid geometry")
    effective = dilation * (kernel - 1) + 1
    if length + 2 * padding < effective:
        raise ValueError("Kernel does not fit")
    return (length + 2 * padding - effective) // stride + 1

assert out_size(31, 3, 2, 1) == 16
assert out_size(47, 3, 2, 1) == 24
assert out_size(32, 3, 1, 2, 2) == 32
assert 32 * out_size(8, 3) * out_size(12, 3) == 1920

Калькулятор использует целочисленные размеры, симметричное дополнение и обычное округление вниз. Он проверяет арифметику примера, но не заменяет ограничения конкретного слоя и не реализует ceil_mode. Для реальной модели дополнительно пропустите тестовый тензор через выбранные слои и сравните shape после каждого этапа с таблицей.

Если формы разошлись, ищите первый неверный переход: порядок H/W, забытый stride у pooling, dilation или число добавленных пикселей. Исправление последнего Linear без выяснения причины скрывает ошибку архитектуры, а последовательная запись размеров показывает, где именно она появилась.