keras与scipy不同的2D卷积结果
Different 2D convolution results between keras and scipy
我在尝试调试我的神经网络时发现一些结果难以理解。我尝试使用 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))
在可视化结果时,我发现它们实际上是对称的(点对称围绕中心取模一点偏移)。
.
我尝试了一些经验性的东西,比如转置内核,但它没有改变任何东西。
编辑
感谢@kaya3 的评论,我意识到将内核旋转 180 度就可以了。但是,我仍然不明白为什么我需要这样做才能获得相同的结果。
神经网络(和图像处理)中通常所说的卷积并不完全是convolution, which is what convolve2d
implements, but the similar one of correlation, which is implemented by correlate2d
的数学概念:
res_scipy = correlate2d(image, kernel.T, mode='same')
如果不阅读这两个库的源代码,我无法确定,但是编写卷积算法的直接方法不止一种,显然这两个库以不同的方式实现它。
一种方法是"paint"内核到输出,对于图像的每个像素:
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
另一种方法是 "sum" 输出中每个像素的贡献量:
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 度一样,正如您所观察到的那样。
这两个库都不太可能使用如此简单的算法来进行卷积。对于较大的图像和内核,使用傅里叶变换更有效,应用 convolution theorem。但是这两个库的差异很可能是由类似这样的东西造成的。
我在尝试调试我的神经网络时发现一些结果难以理解。我尝试使用 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))
在可视化结果时,我发现它们实际上是对称的(点对称围绕中心取模一点偏移)。
我尝试了一些经验性的东西,比如转置内核,但它没有改变任何东西。
编辑 感谢@kaya3 的评论,我意识到将内核旋转 180 度就可以了。但是,我仍然不明白为什么我需要这样做才能获得相同的结果。
神经网络(和图像处理)中通常所说的卷积并不完全是convolution, which is what convolve2d
implements, but the similar one of correlation, which is implemented by correlate2d
的数学概念:
res_scipy = correlate2d(image, kernel.T, mode='same')
如果不阅读这两个库的源代码,我无法确定,但是编写卷积算法的直接方法不止一种,显然这两个库以不同的方式实现它。
一种方法是"paint"内核到输出,对于图像的每个像素:
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
另一种方法是 "sum" 输出中每个像素的贡献量:
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 度一样,正如您所观察到的那样。
这两个库都不太可能使用如此简单的算法来进行卷积。对于较大的图像和内核,使用傅里叶变换更有效,应用 convolution theorem。但是这两个库的差异很可能是由类似这样的东西造成的。