Как сделать свертку в ассоциативной? - PullRequest
0 голосов
/ 03 апреля 2019

Дискретная свертка по определению ассоциативна.Но когда я пытаюсь проверить это в pytorch, я не могу найти правдоподобный результат.

Ассоциативный закон равен $ f * (g * \ psi) = (f * g) * \ psi $, поэтомуЯ создаю три дискретные функции с центром в нуле (в виде тензоров) и сворачиваю их с правильными дополнениями нулями, чтобы получить все ненулевые элементы в карте результатов.

import torch
import torch.nn as nn

def test_conv_compst():
    # $\psi$
    inputs = torch.randn((1,4,7,7))
    # $g$
    a = torch.randn((7, 4, 3, 3))
    # $f$
    b = torch.randn((3, 7, 3, 3))
    int_1 = torch.conv2d(inputs, a, padding=2)
    # results obtained by the first order
    res_1 = torch.conv2d(int_1, b, padding=2)

    comp_k = torch.conv2d(a.transpose(1, 0), b, padding=2).transpose(1, 0)
    print(comp_k.shape)
    # results obtained through the second order
    res_2 = torch.conv2d(inputs, comp_k, padding=4)
    print(res_1.shape)
    print(res_2.shape)
    print(torch.max(torch.abs(res_2-res_1)))

Ожидаемый результат заключается в том, что отличие отдва результата незначительны.Но это возвращает:

torch.Size([3, 4, 5, 5])
torch.Size([1, 3, 11, 11])
torch.Size([1, 3, 11, 11])
tensor(164.8044)

1 Ответ

0 голосов
/ 03 апреля 2019

Короче говоря, это из-за пакетирования. Первый аргумент torch.conv2d интерпретируется как [batch, channel, height, width], второй - [out_channel, in_channel, height, width], а вывод - [batch, channel, height, width]. Поэтому, если вы звоните conv2d(a, conv2d(b, c)), вы рассматриваете ведущее измерение b как пакет, а если вы звоните conv2d(conv2d(a, b), c), вы воспринимаете его как out_channels.

Как говорится, у меня сложилось впечатление, что вы спрашиваете о математике здесь, поэтому позвольте мне расширить. Ваша идея в теории верна: свертки являются линейными операторами и должны быть ассоциативными. Однако, поскольку мы предоставляем им ядер , а не фактические матрицы, представляющие линейные операторы, существует некоторое «преобразование», которое должно происходить за кулисами, чтобы ядра правильно интерпретировались как матрицы. Классически это может быть сделано путем построения соответствующих циркулянтных матриц (без учета граничных условий). Если мы обозначим ядра с помощью a, b, c и оператор создания циркулянтной матрицы с помощью M, мы получим M(a) @ [M(b) @ M(c)] = [M(a) @ M(b)] @ M(c), где @ обозначает умножение матрицы на матрицу.

Реализации Convolution возвращают изображение (вектор, ядро, как вы его называете), а не связанную циркулянтную матрицу, которая смехотворно избыточна и в большинстве случаев не помещается в память. Поэтому нам также нужен некоторый оператор циркулянта к вектору V(matrix), который возвращает первый столбец matrix и поэтому является обратным M. В абстрактных математических терминах такие функции, как scipy.signal.convolve (на самом деле correlate, поскольку свертка требует дополнительного переворота одного из входов, который я пропускаю для ясности), реализованы как convolve = lambda a, b: V(M(a) @ M(b)) и, таким образом,

convolve(a, convolve(b, c)) =
                            = V(M(a) @ M(V[M(b) @ M(c)])
                            = V(M(a) @ M(b) @ M(c))
                            = V(M(V[M(a) @ M(b)]) @ M(c))
                            = convolve(convolve(a, b), c)

Я надеюсь, что не потерял вас, это просто преобразование одного в другое, используя тот факт, что V является обратным к M и ассоциативность умножения матриц для перемещения скобок. Обратите внимание, что средняя линия в основном «необработанная» ABC. Мы можем проверить с помощью следующего кода:

import numpy as np
import scipy.signal as sig

c2d = sig.convolve2d

a = np.random.randn(7, 7)
b = np.random.randn(3, 3)
c = np.random.randn(3, 3)

ab = c2d(a, b)
ab_c = c2d(ab, c)

bc = c2d(b, c)
a_bc = c2d(a, bc)

print((a_bc - ab_c).max())

Проблема с PyTorch заключается в том, что первый вход интерпретируется как [batch, channel, height, width], а второй - как [out_channels, in_channels, height, width]. Это означает, что оператор «преобразования» M отличается для первого аргумента и второго аргумента. Назовем их M и N соответственно. Поскольку имеется только один выход, есть только один V, и он может быть обратным либо M, либо N, но не обоим (так как они разные). Если вы перепишете вышеприведенное уравнение, стараясь различить M и N, вы увидите, что в зависимости от вашего выбора, инвертирует ли V одну или другую, вы не можете записать равенство между строками 2 и 3 или 3 и 4.

На практике существует также дополнительная проблема измерения channel, которого нет в классическом определении сверток, однако я предполагаю, что он мог бы иметь дело с одним оператором подъема M для обоих операнды, в отличие от пакетирования.

...