Сверточная реализация N изображений с М фильтрами с использованием кусочно-скрученных размеров - PullRequest
2 голосов
/ 21 мая 2019

Я буду коротким.Предположим, у меня есть следующие 2 изображения размеров: N (number_of_images) xH (height_of_images) xW (width_of_images) xD (каналы), определенные в numpy как:

input_rgb = np.array(
    [
        [
            [[.7], [.6], [.3]],
            [[.2], [.0], [.0]],
            [[.1], [.2], [.9]]
        ],
        [
            [[.7], [.6], [.3]],
            [[.2], [.0], [.0]],
            [[.1], [.2], [.9]]
        ]
    ])

и следующие 2 ядра размером: M (number_of_kernels) xH (height_of_kernel) xW (width_of_kernel) xD (каналы), определяемый как:

kernel = np.array(
    [
        [
            [[.2], [.1]],
            [[.1], [.7]]
        ],
        [
            [[.9], [.7]],
            [[.1], [.5]]
        ]
    ])

Я хочу выполнить свертку двух вышеуказанных изображений с двумя вышеуказанными ядрами.Для этого я реализовал очень простое решение на основе einsum в numpy, которое прекрасно работает, пока у меня не будет единого изображения.Для одного изображения мой алгоритм выглядит следующим образом:

def convolve_1m(input_image, kernels, padding=0, stride=1):
    image_height, image_width, image_channels = input_image.shape
    number_of_kernels, kernel_height, kernel_width, kernel_depth = kernels.shape
    assert (image_channels == kernel_depth)

    input_image = np.pad(input_image, ((padding, padding), (padding, padding), (0, 0)),
                         mode='constant', constant_values=(0,))
    kernels = np.rot90(kernels, k=2, axes=(1, 2))

    fm_height = (image_height - kernel_height + 2*padding) // stride + 1
    fm_width = (image_width - kernel_width + 2*padding) // stride + 1
    feature_maps = np.zeros(shape=(fm_height, fm_width, number_of_kernels))
    for i in range(fm_height):
        for j in range(fm_width):
            x = input_image[i * stride:i * stride + kernel_height, j * stride:j * stride + kernel_width, :]
            feature_maps[i, j, :] = np.einsum('ijk,mijk', x, kernels)
    return feature_maps

Если я позвоню выше, используя:

convolution = np.array([convolve_1m(input_rgb[0], kernel), convolve_1m(input_rgb[1], kernel)])
print(convolution.shape)
print(convolution)

Я получу результат, который выглядит следующим образом:

(2, 2, 2, 2)
[[[[0.57 0.55]
   [0.45 0.33]]

  [[0.19 0.35]
   [0.2  0.95]]]


 [[[0.57 0.55]
   [0.45 0.33]]

  [[0.19 0.35]
   [0.2  0.95]]]]

Что выглядит идеально, ... по крайней мере, если мои собственные расчеты в порядке на бумаге.Теперь о проблемной части.Это выглядит не очень хорошо, потому что мне нужно восстановить массив np.array в области вызывающей стороны, чтобы я мог передать его следующему сверточному слою.Поэтому вместо того, чтобы иметь его, я попробовал следующий подход:

def convolve(input_images, kernels, padding=0, stride=1):
    number_of_images, image_height, image_width, image_channels = input_images.shape
    number_of_kernels, kernel_height, kernel_width, kernel_depth = kernels.shape
    assert (image_channels == kernel_depth)

    input_images = np.pad(input_images, ((0, 0), (padding, padding), (padding, padding), (0, 0)),
                          mode='constant', constant_values=(0,))
    kernels = np.rot90(kernel, k=2, axes=(1, 2))

    fm_height = (image_height - kernel_height + 2*padding) // stride + 1
    fm_width = (image_width - kernel_width + 2*padding) // stride + 1
    feature_maps = np.zeros(shape=(number_of_images, fm_height, fm_width, number_of_kernels))
    for i in range(fm_height):
        for j in range(fm_width):
            x = input_images[:, i * stride:i * stride + kernel_height, j * stride:j * stride + kernel_width, :]
            feature_maps[:, i, j, :] = np.einsum('nijk,mijk', x, kernels)
    return feature_maps

convolution = convolve(input_rgb, kernel)
print(convolution.shape)
print(convolution)

Однако, несмотря на то, что результаты в порядке, размерность немного странная:

(2, 2, 2, 2)
[[[[0.57 0.57]
   [0.45 0.45]]

  [[0.19 0.19]
   [0.2  0.2 ]]]


 [[[0.55 0.55]
   [0.33 0.33]]

  [[0.35 0.35]
   [0.95 0.95]]]]

Может кто-нибудь помочь мне разобратьсякак сделать так, чтобы описанный выше случай NM работал правильно, используя срезы массива, а не другой для цикла in range, например:

def convolve(input_images, kernels, padding=0, stride=1):
    number_of_images, image_height, image_width, image_channels = input_images.shape
    number_of_kernels, kernel_height, kernel_width, kernel_depth = kernels.shape
    assert (image_channels == kernel_depth)

    input_images = np.pad(input_images, ((0, 0), (padding, padding), (padding, padding), (0, 0)),
                          mode='constant', constant_values=(0,))
    kernels = np.rot90(kernel, k=2, axes=(1, 2))

    fm_height = (image_height - kernel_height + 2*padding) // stride + 1
    fm_width = (image_width - kernel_width + 2*padding) // stride + 1
    feature_maps = np.zeros(shape=(number_of_images, fm_height, fm_width, number_of_kernels))
    for n in range(number_of_images):
        for i in range(fm_height):
            for j in range(fm_width):
                x = input_images[n, i * stride:i * stride + kernel_height, j * stride:j * stride + kernel_width, :]
                feature_maps[n, i, j, :] = np.einsum('ijk,mijk', x, kernels)
    return feature_maps

Хотя это работает и дает правильный результат, я бы хотел получить его без внешнегодля- (п) -loop.

...