Что делает tf.nn.conv2d в тензорном потоке?

Я смотрел на документы тензорного потока около tf.nn.conv2d здесь. Но я не могу понять, что он делает или чего он пытается достичь. Он говорит о документах,

# 1: сглаживает фильтр на двумерной матрице с формой

[filter_height * filter_width * in_channels, output_channels].

Теперь, что это делает? Это умножение по типу или просто однократное умножение матрицы? Я также не мог понять два других пункта, упомянутых в документах. Я написал их ниже:

# 2: извлекает патчи изображения из входного тензора для формирования виртуального тензора формы

[batch, out_height, out_width, filter_height * filter_width * in_channels].

# 3: для каждого патча справа умножает матрицу фильтра и вектор паттерна изображения.

Было бы очень полезно, если бы кто-нибудь мог привести пример, кусок кода (чрезвычайно полезный), возможно, и объяснить, что там происходит, и почему операция такая.

Я пробовал кодирование небольшой порции и распечатывал форму операции. Тем не менее, я не могу понять.

Я пробовал что-то вроде этого:

op = tf.shape(tf.nn.conv2d(tf.random_normal([1,10,10,10]), 
              tf.random_normal([2,10,10,10]), 
              strides=[1, 2, 2, 1], padding='SAME'))

with tf.Session() as sess:
    result = sess.run(op)
    print(result)

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

ИЗМЕНИТЬ: Итак, я реализовал гораздо более простой код. Но я не могу понять, что происходит. Я имею в виду, как результаты выглядят так. Было бы очень полезно, если бы кто-нибудь мог сказать мне, какой процесс дает этот результат.

input = tf.Variable(tf.random_normal([1,2,2,1]))
filter = tf.Variable(tf.random_normal([1,1,1,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
init = tf.initialize_all_variables()
with tf.Session() as sess:
    sess.run(init)

    print("input")
    print(input.eval())
    print("filter")
    print(filter.eval())
    print("result")
    result = sess.run(op)
    print(result)

Выход

input
[[[[ 1.60314465]
   [-0.55022103]]

  [[ 0.00595062]
   [-0.69889867]]]]
filter
[[[[-0.59594476]]]]
result
[[[[-0.95538563]
   [ 0.32790133]]

  [[-0.00354624]
   [ 0.41650501]]]]

Ответ 1

2D свертка вычисляется аналогично вычислению 1D свертки: вы скользите ядром по входным данным, вычисляете поэлементное умножение и суммируете их. Но вместо того, чтобы ваше ядро /ввод был массивом, здесь они являются матрицами.


В самом простом примере нет отступов и шага = 1. Предположим, что ваш input и kernel: enter image description here

Когда вы используете ваше ядро, вы получите следующий вывод: enter image description here, который рассчитывается следующим образом:

  • 14 = 4 * 1 + 3 * 0 + 1 * 1 + 2 * 2 + 1 * 1 + 0 * 0 + 1 * 0 + 2 * 0 + 4 * 1
  • 6 = 3 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 0 * 1 + 1 * 0 + 2 * 0 + 4 * 0 + 1 * 1
  • 6 = 2 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 2 * 1 + 4 * 0 + 3 * 0 + 1 * 0 + 0 * 1
  • 12 = 1 * 1 + 0 * 0 + 1 * 1 + 2 * 2 + 4 * 1 + 1 * 0 + 1 * 0 + 0 * 0 + 2 * 1

Функция TF conv2d вычисляет свертки в пакетах и использует немного другой формат. Для входа это [batch, in_height, in_width, in_channels] для ядра это [filter_height, filter_width, in_channels, out_channels]. Поэтому нам нужно предоставить данные в правильном формате:

import tensorflow as tf
k = tf.constant([
    [1, 0, 1],
    [2, 1, 0],
    [0, 0, 1]
], dtype=tf.float32, name='k')
i = tf.constant([
    [4, 3, 1, 0],
    [2, 1, 0, 1],
    [1, 2, 4, 1],
    [3, 1, 0, 2]
], dtype=tf.float32, name='i')
kernel = tf.reshape(k, [3, 3, 1, 1], name='kernel')
image  = tf.reshape(i, [1, 4, 4, 1], name='image')

После этого свертка рассчитывается по формуле:

res = tf.squeeze(tf.nn.conv2d(image, kernel, [1, 1, 1, 1], "VALID"))
# VALID means no padding
with tf.Session() as sess:
   print sess.run(res)

И будет эквивалентен тому, который мы рассчитали вручную.


Для примеров с отступами/шагами, посмотрите здесь.

Ответ 2

Хорошо, я думаю, что это простейший способ объяснить все это.


Ваш пример - 1 изображение, размер 2x2, с 1 каналом. У вас есть 1 фильтр, размер 1x1 и 1 канал (размер - высота x ширина x каналов x количество фильтров).

В этом простом случае получаемое изображение 2x2, 1 канал (размер 1x2x2x1, количество изображений x высота x ширина x x каналов) является результатом умножения значения фильтра на каждый пиксель изображения.


Теперь попробуйте еще несколько каналов:

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

Здесь изображение 3x3 и фильтр 1x1 имеют по 5 каналов. Полученное изображение будет 3x3 с 1 каналом (размер 1x3x3x1), где значение каждого пикселя является точечным произведением по каналам фильтра с соответствующим пикселем во входном изображении.


Теперь с фильтром 3x3

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

Здесь мы получаем изображение 1x1 с 1 каналом (размер 1x1x1x1). Значение представляет собой сумму 9, 5-элементных точечных продуктов. Но вы можете просто назвать это 45-элементным точечным продуктом.


Теперь с большим изображением

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

Выход представляет собой 3-х канальное 1-канальное изображение (размер 1x3x3x1). Каждое из этих значений представляет собой сумму 9, 5-элементных точечных продуктов.

Каждый вывод производится центрированием фильтра на одном из 9 центральных пикселей входного изображения, так что ни один из фильтров не торчит. Ниже x представлены центры фильтров для каждого выходного пикселя.

.....
.xxx.
.xxx.
.xxx.
.....

Теперь с надписью "SAME":

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

Это дает выходное изображение 5x5 (размер 1x5x5x1). Это делается центрированием фильтра в каждой позиции на изображении.

Любой из 5-элементных точечных продуктов, в которых фильтр торчит за краем изображения, получает значение 0.

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


Теперь с несколькими фильтрами.

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

Это все равно дает выходное изображение 5x5, но с 7 каналами (размер 1x5x5x7). Где каждый канал создается одним из фильтров в наборе.


Теперь с шагами 2,2:

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

Теперь результат по-прежнему имеет 7 каналов, но только 3x3 (размер 1x3x3x7).

Это связано с тем, что вместо центрирования фильтров в каждой точке изображения фильтры центрируются в каждой другой точке изображения, делая шаги (шаги) ширины 2. Ниже x представляет центр фильтра для каждого выходной пиксель на входном изображении.

x.x.x
.....
x.x.x
.....
x.x.x

И, конечно, первое измерение ввода - это количество изображений, поэтому вы можете применить его к пакету из 10 изображений, например:

input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

Выполняет одну и ту же операцию для каждого изображения независимо, в результате получается стопка из 10 изображений (размер 10x3x3x7)

Ответ 3

Чтобы добавить к другим ответам, вы должны подумать о параметрах в

filter = tf.Variable(tf.random_normal([3,3,5,7]))

как "5", соответствующее количеству каналов в каждом фильтре. Каждый фильтр представляет собой 3D-куб с глубиной 5. Глубина фильтра должна соответствовать глубине входного изображения. Последний параметр, 7, следует рассматривать как количество фильтров в партии. Просто забудьте о том, что это 4D, и представьте себе, что у вас есть набор или пакет из 7 фильтров. Что вы делаете, так это создание 7 фильтрующих кубов с размерами (3,3,5).

Гораздо проще визуализировать в области Фурье, так как свертка становится точечным умножением. Для входного изображения размеров (100, 100,3) вы можете переписать размеры фильтра как

filter = tf.Variable(tf.random_normal([100,100,3,7]))

Чтобы получить одну из 7 карт выходных функций, мы просто выполняем точечное умножение куба фильтра с кубом изображения, затем суммируем результаты по размеру каналов/глубины (здесь это 3), сворачиваем на карту характеристик 2d (100,100). Сделайте это с каждым кубом фильтра, и вы получите 7 2D-карт.

Ответ 4

Я попытался реализовать conv2d (для моего изучения). Ну, я написал это:

def conv(ix, w):
   # filter shape: [filter_height, filter_width, in_channels, out_channels]
   # flatten filters
   filter_height = int(w.shape[0])
   filter_width = int(w.shape[1])
   in_channels = int(w.shape[2])
   out_channels = int(w.shape[3])
   ix_height = int(ix.shape[1])
   ix_width = int(ix.shape[2])
   ix_channels = int(ix.shape[3])
   filter_shape = [filter_height, filter_width, in_channels, out_channels]
   flat_w = tf.reshape(w, [filter_height * filter_width * in_channels, out_channels])
   patches = tf.extract_image_patches(
       ix,
       ksizes=[1, filter_height, filter_width, 1],
       strides=[1, 1, 1, 1],
       rates=[1, 1, 1, 1],
       padding='SAME'
   )
   patches_reshaped = tf.reshape(patches, [-1, ix_height, ix_width, filter_height * filter_width * ix_channels])
   feature_maps = []
   for i in range(out_channels):
       feature_map = tf.reduce_sum(tf.multiply(flat_w[:, i], patches_reshaped), axis=3, keep_dims=True)
       feature_maps.append(feature_map)
   features = tf.concat(feature_maps, axis=3)
   return features

Надеюсь, я сделал это правильно. Проверено на MNIST, имело очень близкие результаты (но эта реализация медленнее). Надеюсь, это поможет вам.

Ответ 5

В дополнение к другим ответам, conv2d работает в c++ (cpu) или cuda для машин gpu, которые требуют определенного выравнивания и преобразования данных и использования умножения матрицы gemmBLAS или cuBLAS (cuda).