CNN 다운 샘플링시의 위치 어긋남 문제와 간단한 해소 방법

소개



일반적인 CNN에서는 어떤 형태로 다운 샘플링이 수행됩니다.
종래는 2x2 풀링을 스트라이드 2x2로 사용되고 있었습니다만, 최근에는 3x3 컨벌루션이나 3x3 풀링을 스트라이드 2x2로 사용되는 것이 많은 것 같습니다.

이러한 다운 샘플링을 할 때 약간의 함정이되는 것이 좌표 계산입니다.
각종 딥 러닝 프레임 워크에서는 Convolution의 패딩 방식으로 "SAME"모드를 지정할 수 있다고 생각하지만, 나이브에 이것을 사용하면 다운 샘플링 레이어마다 수신 영역의 위치가 출력에 대해 어긋나 간다 라는 문제가 있습니다.
일반적인 딥 모델을 사용하고 있는 동안에는 영향은 적습니다만, 고속화를 위해서 네트워크를 소형화 하거나 하면(자) 이 문제가 표출해 옵니다.

문제 재현



다음과 같은 모델이 있다고 가정합니다.
h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="same")(x)
h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="same")(h)
h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="same")(h)
y = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="same")(h)

이 때 수용 영역은 다음과 같습니다.
출력이 왼쪽 위와 오른쪽 아래에 있을 때를 비교하면 출력 좌표에 대해 수용 영역의 위치가 어긋나 있음을 알 수 있습니다.

출력 셀:

해당 수용 영역:


해결책



다운 샘플링의 횟수를 미리 고려하여 처음에 패딩을 해 버리는 것으로, 간단하게 해소할 수 있습니다.
덧붙여 입력 화상 사이즈가 짝수라면 1픽셀의 어긋남은 나오므로, 그 점은 주의해 주세요.
h = ZeroPadding2D(((7, 8), (7, 8)))(x)
h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="valid")(h)
h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="valid")(h)
h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="valid")(h)
y = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="valid")(h)

마찬가지로 시각화합니다. 낫습니다
출력 셀:

해당 수용 영역:


검증 코드



검증에 사용한 코드를 기재해 둡니다.

cnn_receptive_field.py
from keras.layers import *
from keras import backend as K
import cv2

height, width = 128, 128
layers = 4
cell = 2**layers

# 受容領域を可視化するためのモデル
m = Input(batch_shape=(1, height//cell, width//cell, 1))
x = K.ones((1, height, width, 1))

# "same" mode
# h = x
# for i in range(layers):
#     h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="same")(h)

# padding
h = ZeroPadding2D(((cell//2-1, cell//2), (cell//2-1, cell//2)))(x)
for i in range(layers):
    h = Conv2D(1, kernel_size=(3, 3), strides=(2, 2), padding="valid")(h)

d = Lambda(lambda x: x*m)(h)
g = K.gradients(d, x)

func = K.function([m], g)


# 可視化実施
count = 0
for i in range(height//cell):
    for j in range(width//cell):
        mask = np.zeros((1, height//cell, width//cell, 1))
        mask[0, i, j, 0] = 1
        grad = func([mask])[0]

        receptive_field = np.zeros_like(grad)
        receptive_field[grad != 0] = 1

        cv2.imshow("mask", mask[0])
        cv2.imshow("receptive_field", receptive_field[0])
        cv2.waitKey(0)

        # cv2.imwrite(f"img/mask{count:04d}.png", (mask[0]*255).astype(np.uint8))
        # cv2.imwrite(f"img/field{count:04d}.png",
        #             (receptive_field[0]*255).astype(np.uint8))
        # count += 1

좋은 웹페이지 즐겨찾기