Что делает операция TensorFlow `conv2d_transpose()`?

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

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

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

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

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

Ответы

Ответ 1

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

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

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

Ответ 2

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

Четыре связанные функции

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

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

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

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

  • Прямые и обратные вычисления:

    # forward
    out = conv2d(x, w)
    
    # backward, given d_out
    => find d_x?
    => find d_w?
    

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

  • Для удобства обсуждения мы предполагаем
    • Весь размер шага 1
    • Все in_channels и out_channels являются 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 означает "применять фильтр везде, где он частично перекрывается с входом" (см. anano docs). Заметьте, что это справедливо только с вышеуказанными предположениями, однако, можно изменить аргументы 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 четырьмя различными способами:

# Method 1: TF 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 тремя различными способами:

# Method 1: TF 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. В сценариях мы проверяем, что конечные выходные значения разных методов одинаковы; также доступна многократная реализация.

Ответ 3

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