Что делает операция "conv2d transpose ()" TensorFlow?

документация conv2d_transpose() операция четко не объясняет, что она делает:

транспонирование conv2d.

эта операция иногда называется "деконволюция" после Сети Deconvolutional, но на самом деле является транспонированием (градиентом) conv2d, а не деконволюции.

Я просмотрел бумагу, на которую указывает док, но это не помогло.

что это операция do и каковы примеры того, почему вы хотите ее использовать?

4 ответов


Это лучшее объяснение, которое я видел в интернете, как работает транспонирование свертки здесь.

Я дам свое собственное краткое описание. Он применяет свертку с дробным шагом. Другими словами, расстояние между входными значениями (с нулями) для применения фильтра к области, которая потенциально меньше размера фильтра.

Что касается того, почему один хотел бы использовать его. Его можно использовать как своего рода upsampling с выученными весами в отличие от билинейная интерполяция или другой фиксированной форме апсемплинга.


вот еще одна точка зрения с точки зрения "градиентов", т. е. почему документация TensorFlow говорит conv2d_transpose() это "на самом деле транспонирование (градиент) conv2d, а не фактической деконволюции". для получения более подробной информации о фактических вычислениях, выполненных в conv2d_transpose, я настоятельно рекомендую в этой статье, начиная со страницы 19.

Четыре Взаимосвязанные Функции

на tf.nn, есть 4 тесно связанных и довольно запутанные функции для 2d свертки:

  • tf.nn.conv2d
  • tf.nn.conv2d_backprop_filter
  • tf.nn.conv2d_backprop_input
  • tf.nn.conv2d_transpose

резюме одного предложения:они все просто 2d свертки. Их различия заключаются в упорядочении входных аргументов, вращении ввода или транспонировании, шагах (включая размер дробного шага), прокладках и т. д. С tf.nn.conv2d в руке, одно может снабдить все 3 других ops путем преобразовывать входные сигналы и изменение conv2d аргументов.

Настройки Проблема

  • вперед и назад расчета:
# forward
out = conv2d(x, w)

# backward, given d_out
=> find d_x?
=> find d_w?

в прямом вычислении мы вычисляем свертку входного изображения x фильтр w и в результате out. В обратном вычислении предположим, что нам дано d_out, который является градиентом w.r.т. out. Наша цель-найти d_x и d_w, которые являются градиентом w.r.т. x и w соответственно.

для простоты обсуждения будем считать:

  • весь размер шага, чтобы быть 1
  • все in_channels и out_channels are 1
  • использовать VALID обивка
  • нечетный размер фильтра, это позволяет избежать некоторых асимметричных проблем формы

Короткий Ответ:

концептуально, с предположениями выше, мы имеем следующее отношения:

out = conv2d(x, w, padding='VALID')
d_x = conv2d(d_out, rot180(w), padding='FULL')
d_w = conv2d(x, d_out, padding='VALID')

здесь rot180 это 2d-матрица, повернутая на 180 градусов (левый-правый флип и флип сверху вниз),FULL означает " применить фильтр везде, где он частично перекрывается с входом "(см. Феано документы). Отмечает, что это справедливо только с вышеуказанными предположениями, однако, можно изменить аргументы conv2d, чтобы обобщить его.

ключевые моменты:

  • входной градиент d_x свертка выходного градиента d_out и массой w С некоторыми изменениями.
  • градиент веса d_w является сверткой входного x и выходной градиент d_out с некоторыми изменениями.

Ответ

теперь давайте приведем фактический пример рабочего кода о том, как использовать 4 функции выше для вычисления d_x и d_w дано d_out. Это показывает, как conv2d, conv2d_backprop_filter, conv2d_backprop_input, и conv2d_transpose есть связаны друг с другом. пожалуйста, найдите полные сценарии здесь.

вычислений d_x в 4-разному:

# Method 1: TF's autodiff
d_x = tf.gradients(f, x)[0]

# Method 2: manually using conv2d
d_x_manual = tf.nn.conv2d(input=tf_pad_to_full_conv2d(d_out, w_size),
                          filter=tf_rot180(w),
                          strides=strides,
                          padding='VALID')

# Method 3: conv2d_backprop_input
d_x_backprop_input = tf.nn.conv2d_backprop_input(input_sizes=x_shape,
                                                 filter=w,
                                                 out_backprop=d_out,
                                                 strides=strides,
                                                 padding='VALID')

# Method 4: conv2d_transpose
d_x_transpose = tf.nn.conv2d_transpose(value=d_out,
                                       filter=w,
                                       output_shape=x_shape,
                                       strides=strides,
                                       padding='VALID')

вычислений d_w 3 различными способами:

# Method 1: TF's autodiff
d_w = tf.gradients(f, w)[0]

# Method 2: manually using conv2d
d_w_manual = tf_NHWC_to_HWIO(tf.nn.conv2d(input=x,
                                          filter=tf_NHWC_to_HWIO(d_out),
                                          strides=strides,
                                          padding='VALID'))

# Method 3: conv2d_backprop_filter
d_w_backprop_filter = tf.nn.conv2d_backprop_filter(input=x,
                                                   filter_sizes=w_shape,
                                                   out_backprop=d_out,
                                                   strides=strides,
                                                   padding='VALID')

смотрите полный скрипты для реализации tf_rot180, tf_pad_to_full_conv2d, tf_NHWC_to_HWIO. В сценариях мы проверяем, что конечные выходные значения разных методов одинаковы; реализация numpy также доступно.


conv2d_transpose () просто переносит веса и переворачивает их на 180 градусов. Затем применяется стандартный conv2d (). "Транспонирует "практически означает, что он изменяет порядок" столбцов " в Тензоре Весов. Пожалуйста, проверьте пример ниже.

здесь есть пример, который использует свертки со stride=1 и padding='SAME'. Это простой случай, но то же самое рассуждение может быть применено и к другим случаям.

говорят, что мы есть:

  • вход: MNIST изображение 28x28x1, shape = [28,28,1]
  • сверточный слой: 32 фильтра 7x7, форма Весов = [7, 7, 1, 32], name = W_conv1

если мы выполним свертку ввода, то активации будут иметь форму: [1,28,28,32].

 activations = sess.run(h_conv1,feed_dict={x:np.reshape(image,[1,784])})

где:

 W_conv1 = weight_variable([7, 7, 1, 32])
 b_conv1 = bias_variable([32])
 h_conv1 = conv2d(x, W_conv1, strides=[1, 1, 1, 1], padding='SAME') + b_conv1

для получения "деконволюции" или "транспонированной свертки" мы можем использовать conv2d_transpose() для активации свертки в таким образом:

  deconv = conv2d_transpose(activations,W_conv1, output_shape=[1,28,28,1],padding='SAME')

или с помощью conv2d () нам нужно транспонировать и переворачивать веса:

  transposed_weights = tf.transpose(W_conv1, perm=[0, 1, 3, 2])

здесь мы меняем порядок "колумов" с [0,1,2,3] на [0,1,3,2].Итак, из [7, 7, 1, 32] получим тензор с формой=[7,7,32,1]. Затем переворачиваем гири:

  for i in range(n_filters):
      # Flip the weights by 180 degrees
      transposed_and_flipped_weights[:,:,i,0] =  sess.run(tf.reverse(transposed_weights[:,:,i,0], axis=[0, 1]))

тогда мы можем вычислить свертку с conv2d () как:

  strides = [1,1,1,1]
  deconv = conv2d(activations,transposed_and_flipped_weights,strides=strides,padding='SAME')

и мы получим тот же результат, что и раньше. Также очень такой же результат можно получить с conv2d_backprop_input (), используя:

   deconv = conv2d_backprop_input([1,28,28,1],W_conv1,activations, strides=strides, padding='SAME')

результаты показаны здесь:

тест conv2d (), conv2d_tranposed () и conv2d_backprop_input ()

мы видим, что результаты совпадают. Чтобы увидеть это лучше, пожалуйста, проверьте мой код по адресу:

https://github.com/simo23/conv2d_transpose

здесь я реплицирую вывод функции conv2d_transpose (), используя стандартный conv2d ().


одно приложение для conv2d_transpose масштабируется, вот пример, который объясняет, как это работает:

a = np.array([[0, 0, 1.5],
              [0, 1, 0],
              [0, 0, 0]]).reshape(1,3,3,1)

filt = np.array([[1, 2],
                 [3, 4.0]]).reshape(2,2,1,1)

b = tf.nn.conv2d_transpose(a,
                           filt,
                           output_shape=[1,6,6,1],
                           strides=[1,2,2,1],
                           padding='SAME')

print(tf.squeeze(b))

tf.Tensor(
[[0.  0.  0.  0.  1.5 3. ]
 [0.  0.  0.  0.  4.5 6. ]
 [0.  0.  1.  2.  0.  0. ]
 [0.  0.  3.  4.  0.  0. ]
 [0.  0.  0.  0.  0.  0. ]
 [0.  0.  0.  0.  0.  0. ]], shape=(6, 6), dtype=float64)