Различные результаты 2D свертки между керасом и сципионом - PullRequest
2 голосов
/ 07 ноября 2019

Мне было трудно понять некоторые результаты при попытке отладки моей нейронной сети. Я пытался выполнить некоторые вычисления в автономном режиме, используя scipy (1.3.0), и у меня нет таких же результатов, как с keras (2.3.1) с бэкэндом tensorflow (1.14.0). Вот минимальный воспроизводимый пример:

from keras.layers import Conv2D, Input
from keras.models import Model
import numpy as np
from scipy.signal import convolve2d

image = np.array([[-1.16551484e-04, -1.88735046e-03, -7.90571701e-03,
        -1.52302440e-02, -1.55315138e-02, -8.40757508e-03,
        -2.12123734e-03, -1.49851941e-04],
       [-1.88735046e-03, -3.05623915e-02, -1.28019482e-01,
        -2.46627569e-01, -2.51506150e-01, -1.36146188e-01,
        -3.43497843e-02, -2.42659380e-03],
       [-7.90571701e-03, -1.28019482e-01, -5.06409585e-01,
        -6.69258237e-01, -6.63918257e-01, -5.31925797e-01,
        -1.43884048e-01, -1.01644937e-02],
       [-1.52302440e-02, -2.46627569e-01, -6.69258296e-01,
         2.44587708e+00,  2.72079444e+00, -6.30891442e-01,
        -2.77190477e-01, -1.95817426e-02],
       [-1.55315138e-02, -2.51506120e-01, -6.63918316e-01,
         2.72079420e+00,  3.01719952e+00, -6.19484246e-01,
        -2.82673597e-01, -1.99690927e-02],
       [-8.40757508e-03, -1.36146188e-01, -5.31925797e-01,
        -6.30891442e-01, -6.19484186e-01, -5.57167232e-01,
        -1.53017864e-01, -1.08097391e-02],
       [-2.12123734e-03, -3.43497805e-02, -1.43884048e-01,
        -2.77190447e-01, -2.82673597e-01, -1.53017864e-01,
        -3.86065207e-02, -2.72730505e-03],
       [-1.49851941e-04, -2.42659380e-03, -1.01644937e-02,
        -1.95817426e-02, -1.99690927e-02, -1.08097391e-02,
        -2.72730505e-03, -1.92666746e-04]], dtype='float32')

kernel = np.array([[ 0.04277903 ,  0.5318366  ,  0.025291916],
       [ 0.5756132  , -0.493123   ,  0.116359994],
       [ 0.10616145 , -0.319581   , -0.115053006]], dtype='float32')

print('Mean of original image', np.mean(image))

## Scipy result

res_scipy = convolve2d(image, kernel.T, mode='same')

print('Mean of convolution with scipy', np.mean(res_scipy))

## Keras result

def init(shape, dtype=None):
    return kernel[..., None, None]
im = Input((None, None, 1))
im_conv = Conv2D(1, 3, padding='same', use_bias=False, kernel_initializer=init)(im)
model = Model(im, im_conv)

model.compile(loss='mse', optimizer='adam')

res_keras = model.predict_on_batch(image[None, ..., None])

print('Mean of convolution with keras', np.mean(res_keras))

При визуализации результатов я обнаружил, что они действительно симметричны (точечная симметрия вокруг центра по небольшому смещению). imagescipy result, on the right the keras result, both with the same scale">.

Я пробовал что-то эмпирическое, например, транспонировать ядро, но ничего не изменилось.


РЕДАКТИРОВАТЬ Благодаря комментарию @ kaya3 японял, что вращение ядра на 180 градусов сделало свое дело. Тем не менее, я до сих пор не понимаю, зачем мне это делать, чтобы получить те же результаты.

Ответы [ 2 ]

2 голосов
/ 07 ноября 2019

То, что обычно называют сверткой в ​​нейронных сетях (и обработкой изображений), не совсем математическая концепция свертки , которую реализует convolve2d, но похожая корреляция , которая реализуется correlate2d:

res_scipy = correlate2d(image, kernel.T, mode='same')
1 голос
/ 07 ноября 2019

Я не знаю наверняка, не читая исходный код для этих двух библиотек, но существует более одного простого способа написания алгоритма свертки, и, очевидно, эти две библиотеки реализуют его по-разному.

Один из способов - «нарисовать» ядро ​​на выходе для каждого пикселя изображения:

from itertools import product

def convolve_paint(img, ker):
    img_w, img_h = len(img[0]), len(img)
    ker_w, ker_h = len(ker[0]), len(ker)
    out_w, out_h = img_w + ker_w - 1, img_h + ker_h - 1
    out = [[0]*out_w for i in range(out_h)]
    for x,y in product(range(img_w), range(img_h)):
        for dx,dy in product(range(ker_w), range(ker_h)):
            out[y+dy][x+dx] += img[y][x] * ker[dy][dx]
    return out

Другой способ - «сложить» суммы, вносимые в каждый пиксель на выходе:

def convolve_sum(img, ker):
    img_w, img_h = len(img[0]), len(img)
    ker_w, ker_h = len(ker[0]), len(ker)
    out_w, out_h = img_w + ker_w - 1, img_h + ker_h - 1
    out = [[0]*out_w for i in range(out_h)]
    for x,y in product(range(out_w), range(out_h)):
        for dx,dy in product(range(ker_w), range(ker_h)):
            if 0 <= y-dy < img_h and 0 <= x-dx < img_w:
                out[y][x] += img[y-dy][x-dx] * ker[dy][dx]
    return out

Эти две функции выдают одинаковый вывод. Однако обратите внимание, что у второго есть y-dy и x-dx вместо y+dy и x+dx. Если второй алгоритм записан с + вместо -, как это может показаться естественным, то результаты будут такими, как если бы ядро ​​поворачивалось на 180 градусов, как вы уже видели.

Вряд ли какая-либо из библиотек использует такой простой алгоритм для свертки. Для больших изображений и ядер более эффективно использовать преобразование Фурье, применяя теорему о свертке . Но разница между этими двумя библиотеками может быть вызвана чем-то похожим на это.

...