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

135

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

# 1: Выравнивает фильтр в 2-D матрицу с формой

[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]]]]
Shubhashis
источник
На самом деле cudnn по умолчанию включен в GPU tf.nn.conv2d(), поэтому данный метод вообще не используется, когда мы используем TF с поддержкой GPU, если use_cudnn_on_gpu=Falseэто не указано явно.
gkcn

Ответы:

59

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


В самом простом примере нет отступов и шага = 1. Давайте предположим, что вы inputи kernelесть: введите описание изображения здесь

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

  • 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

Функция conv2d TF рассчитывает свертки в пакетах и ​​использует немного другой формат. Для входа это [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)

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


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

Сальвадор Дали
источник
Хороший пример, однако некоторые ссылки не работают.
Silgon
1
@silgon, к сожалению, это потому, что SO решила не поддерживать функцию документации, которую они сначала создали и рекламировали.
Сальвадор Дали
161

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


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

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


Теперь давайте попробуем больше каналов:

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')

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


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

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х3 (размер 1х3х3х1). Каждое из этих значений представляет собой сумму 9, 5-элементных точечных произведений.

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

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

Теперь с "ЖЕ" обивка:

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-элементных точечных продуктов, в которых фильтр выходит за край изображения, получает значение ноль.

Таким образом, углы являются только суммами 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 каналов, но только 3х3 (размер 1х3х3х7).

Это связано с тем, что вместо центрирования фильтров в каждой точке изображения фильтры центрируются в каждой другой точке изображения с шагом (шагом) шириной 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)

mdaoust
источник
@ZijunLost Нет, документы утверждают, что первый и последний элемент должен быть 1.Must have strides[0] = strides[3] = 1. For the most common case of the same horizontal and vertices strides, strides = [1, stride, stride, 1].
JohnAllen
Это основанная на матрице Теплица реализация свертки?
gkcn
Относительно этого: «Это по-прежнему дает выходное изображение 5x5, но с 7 каналами (размер 1x5x5x7). Где каждый канал создается одним из фильтров в наборе?», Мне все еще трудно понять, откуда 7 каналов? что значит "фильтры в наборе"? Спасибо.
Дерек
@mdaoust Привет, относительно вашего второго примера, где the 3x3 image and the 1x1 filter each have 5 channels, я считаю, результат отличается от рассчитанного вручную точечного произведения.
Тгн Ян
1
@derek У меня тот же вопрос, "output_channel" совпадает с "количеством фильтров" ??? если так, почему они названы "output_channel" в документах тензорного потока?
Вэй
11

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

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 карт объектов.

Val9265
источник
8

Я пытался реализовать 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, были очень близкие результаты (но эта реализация медленнее). Я надеюсь, это поможет вам.

Артем Ященко
источник
0

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

karaspd
источник
Таким образом, в памяти свертка фактически выполняется как матричное умножение, которое объясняет, почему большие изображения не всегда работают с большим временем вычислений, а вместо этого с большей вероятностью могут столкнуться с ошибкой OOM (нехватка памяти). Можете ли вы объяснить мне, почему трехмерная свертка более неэффективна / эффективна по сравнению с двумерной сверткой? Например, выполнение 3D конвоя на [B, H, W, D, C] по сравнению с 2D конвоном на [B * C, H, W, D]. Конечно, они в вычислительном отношении стоят одинаково?
SomePhysicsStudent