Введение
Конволюция – это математическая операция, в результате которой продукт является интегралом 2-х функций (сигналов), при этом один из сигналов перевернут вверх дном. Например, ниже мы свернули 2 сигнала f(t) и g(t).
Итак, первое, что нужно сделать, это перевернуть сигнал g по горизонтали (180 градусов), а затем перевернуть g на f, умножив и накопив все его значения.
Порядок свертки сигналов не имеет значения для конечного результата, поэтому conv(a,b)==conv(b,a)
В этом случае учтите, что синий сигнал f(τ) является нашим входным сигналом, а g(t) – нашим ядром, термин ядро используется при использовании конверсий для фильтрации сигналов.
Размер выходного сигнала 1D
В случае 1D свертки выходной размер рассчитывается таким образом:
outputSize=(InputSize-KernelSize)+1
Применение сверток
Свертывание при обработке сигналов используется в следующих случаях использования:
Фильтрация сигналов (1D звук, 2D обработка изображения)
Проверьте, насколько один сигнал связан с другим
Поиск закономерностей в сигналах
Простой пример в матлабе и питоне (пронумерованном).
Ниже представлены два сигнала x = (0,1,2,3,4) с w = (1,-1,2).
Делать от руки
Чтобы лучше понять концепцию свертки, давайте возьмем приведенный выше пример из рук. В основном мы будем свертывать 2 сигнала (x,w). Первое, что нужно сделать – перевернуть W по горизонтали (O повернуть на 180 градусов влево)
После этого нам нужно сдвинуть перевернутую W на вход X
Обратите внимание, что в шагах 3,4,5 перевернутое окно полностью находится внутри входного сигнала. Эти результаты называются “действительными” свертками. В тех случаях, когда перевернутое окно не полностью находится во входном окне(X), мы можем считать их нулями или вычислить, что можно вычислить, например, на шаге 1 мы умножаем 1 на ноль, а остальное просто игнорируется.
Входная подкладка
Чтобы сохранить размер результата свертки равным размеру входного сигнала и избежать эффекта, называемого круговой сверткой, мы проставляем сигнал нулями.
Куда вы помещаете нули, зависит от того, что вы хотите сделать, то есть: в 1D случае вы можете скомкатировать их на каждом конце, но в 2D случае вы обычно размещаете их по всему исходному сигналу.
На матлабе можно использовать команду “padarray” для подкладки входа:
>> x
x(:,:,1) =
1 1 0 2 0
2 2 2 2 1
0 0 0 2 1
2 2 2 2 1
2 0 2 2 1
x(:,:,2) =
2 1 0 0 0
0 2 0 1 0
1 0 1 2 0
1 2 0 2 1
1 2 1 2 2
x(:,:,3) =
2 1 1 2 2
1 1 1 0 0
2 0 1 0 2
0 2 0 2 1
0 0 2 1 0
>> padarray(x,[1 1])
ans(:,:,1) =
0 0 0 0 0 0 0
0 1 1 0 2 0 0
0 2 2 2 2 1 0
0 0 0 0 2 1 0
0 2 2 2 2 1 0
0 2 0 2 2 1 0
0 0 0 0 0 0 0
ans(:,:,2) =
0 0 0 0 0 0 0
0 2 1 0 0 0 0
0 0 2 0 1 0 0
0 1 0 1 2 0 0
0 1 2 0 2 1 0
0 1 2 1 2 2 0
0 0 0 0 0 0 0
ans(:,:,3) =
0 0 0 0 0 0 0
0 2 1 1 2 2 0
0 1 1 1 0 0 0
0 2 0 1 0 2 0
0 0 2 0 2 1 0
0 0 0 2 1 0 0
0 0 0 0 0 0 0
Выходной размер для 2D
Пожалуйста, знайте, каков будет размер нашего вывода после выполнения над ним некоторых операций свертки. К счастью, есть удобная формула, которая говорит нам именно об этом.
Если рассматривать свертку входа, пространственных размеров [H, W] с P, с квадратным ядром размера F и с использованием шага S, то выходной размер свертки определяется как:
outputSizeW=(W-F+2P)/S+1 выводSizeH=(H-F+2P)/S+1 вывод
F – это размер ядра, мы обычно используем квадратные ядра, поэтому F – это и ширина, и высота ядра
Реализация конвволюционной операции
В примере ниже будет задействован вход 5x5x3 (LxHx3), с уровнем конвекции со следующими параметрами Stride=2, Pad=1, F=3 (ядро 3×3), и K=2 (два фильтра).
Наш вход имеет 3 канала, поэтому нам нужен вес ядра 3x3x3. У нас есть 2 фильтра (K=2), поэтому в конце у нас будет 2 выходных активации. Далее мы можем вычислить размер этих двух выходов: (5 – 3 + 2)/2 + 1 = 3.
Этот код ниже (ванильная версия) не может быть использован в реальной жизни, потому что он будет медленным, но это хорошо для базового понимания. Обычно библиотеки глубокого изучения делают свертку как одноматричное умножение, используя метод im2col/col2im.
%% n размеры свертки
% Следующий код является просто расширением conv2d_vanila для n размеров.
% Параметры:
% Ввод: Н х Ш х Глубина
% K: ядро F x F x глубина
% S: шажок (сколько пикселей он будет скользить по входу)
% Эта реализация похожа на параметр ‘valid’ при нормальной свертке.
функция outConv = convn_vanilla(input, kernel, S)
% Получить размер ввода в виде строк и столбцов. Весы должны иметь
% та же глубина, что и входной объем (изображение)
[rowsIn, colsIn, ~] = size(input);
% Получить дименсио объем
depthInput = ndims(вход);
% Получить размер ядра, учитывая квадратное ядро всегда.
F = размер(ядро,1);
% % % инициализировать выходы
sizeRowsOut = ((rowsIn-F)/S) + 1;
sizeColsOut = ((rowsIn-F)/S) + 1;
outConvAcc = нули(sizeRowsOut , sizeColsOut, depthInput);
%% Сделать свертку
% Проведите каждый канал на входе с соответствующим каналом ядра,
% на конечной сумме всех результатов канала.
для depth=1:depthInput
% Выберите канал ввода и канал ядра тока
inputCurrDepth = input(:,:, depth);
kernelCurrDepth = kernel(:,:,depth);
% итерация на каждую строку и столбец, (с использованием шага)
для r=1:S:(rowsIn-1)
для c=1:S:(colsIn-1)
% Избегайте выборки из изображения.
если (((c+F)-1) <= colsIn) && ((((r+F)-1) <= rowsIn)
% Выберите окно по вводу громкости (патч)
sampleWindow = inputCurrDepth(r:(r+F)-1,c:(c+F)-1);
% Делать точечный продукт
dotProd = sum(sampleWindow(:) .* kernelCurrDepth(:));
% Результат хранения
outConvAcc(ceil(r/S),ceil(c/S),depth) = dotProd;
конец
конец
конец
конец
% Суммировать элементы над размером входного объема (просуммировать все каналы)
outConv = sum(outConvAcc, depthInput);
конец