Що робить tf.nn.conv2d в tensorflow?


135

Я дивився на документи тензорфлоу 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)

Я розумію шматочки і шматочки згорткових нейронних мереж. Я їх тут вивчав . Але реалізація на тензорфлоу - це не те, чого я очікував. Тож воно порушило питання.

EDIT : Отже, я реалізував набагато простіший код. Але я не можу зрозуміти, що відбувається. Я маю на увазі, як такі результати. Було б дуже корисно, якби хто-небудь міг сказати мені, який процес дає цей результат.

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]]]]

Насправді 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 фільтр розміром 1х1 та 1 канал (розмір - висота х ширина х канали х кількість фільтрів).

Для цього простого випадку отримане 2х2, 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 та фільтр 1x1 мають 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-канальне зображення 3x3 (розмір 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-елементних точкових виробів, де фільтр стирчить повз краю зображення, отримує значення нуля.

Тож кути - це лише суми 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)


@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

Це реалізація згортки на основі матриці Toeplitz?
gkcn

З приводу цього: "Це все ще дає вихідне зображення 5x5, але з 7 каналами (розміром 1x5x5x7). Де кожен канал виробляється одним із фільтрів у наборі." У мене все ще виникають труднощі зрозуміти, звідки 7 каналів? що ви маєте на увазі "фільтри в наборі"? Дякую.
дерек

@mdaoust Привіт, щодо вашого другого прикладу, де the 3x3 image and the 1x1 filter each have 5 channelsя вважаю, що результат відрізняється від ручного розрахованого крапкового продукту.
Тгн Ян

1
@derek У мене те саме питання, чи "output_channel" збігається з "кількістю фільтрів" ??? якщо так, то чому вони називаються "output_channel" у документах з tensorflow?
Вей

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 карти функцій.


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


Таким чином, у пам'яті згортка насправді виконується як множення матриці, що пояснює, чому більші зображення не запускаються обов'язково в більший час обчислень, а натомість, швидше за все, виникають помилки OOM (поза пам'яттю). Чи можете ви пояснити мені, чому 3D-згортка є більш неефективною / ефективною пам’яттю порівняно з 2D згорткою? Наприклад, роблячи 3D-перетворення на [B, H, W, D, C] порівняно з 2D-конвеєрами на [B * C, H, W, D]. Напевно, вони обчислювально коштують однаково?
SomePhysicsStudent
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.