Что делает 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]()
Когда вы используете ваше ядро, вы получите следующий вывод:
, который рассчитывается следующим образом:
- 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).