Что делает операция 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().