反卷积神经网络中的反卷积是指,通过测量输出和已知输入重构未知输入的过程,其并不具备学习能力,仅仅是用于可视化一个已经训练好的卷积神经网络模型,没有学习的过程。
看一下VGG-16反卷积神经网络的结构,VGG-16是一个深度神经网络模型,其反卷积过程就是将中间数据,按照前面卷积,池化等变化过程,完全相反的做一遍,从而得到类似原输入的数据。
反卷积的原理
反卷积可以理解为卷积操作的逆操作,但是不要认为它能够复原出卷积操作的输入值,它仅仅是将卷积变换的过程中的步骤反向变换一次而已,通过将卷积核转置(并不是矩阵的转置),与卷积后得结果再做一遍卷积,所以它还叫转置卷积。虽然其不能还原出原来卷积的样子,但还是有类似的效果,只是将小部分缺失信息最大化的恢复应用
一般可以用来信道均衡、图像恢复、语音识别、地震学、无损探伤等未知输入估计和过程辨识方面的问题更多就是充当可视化的作用。反卷积的操作步骤
1)首先将卷积核反转(并不是转置,而是上下左右方向进行递序操作)2)再将卷积结果作为输入,做补0的扩充操作,即往每一个元素后面补0,这一步是根据步长来的,对每一个元素 沿着步长的方向补(步长-1)个0,
3)在扩充后的输入基础上再对整体补0。以原始输入的shape作为输出,按照卷积padding规则,计算padding的 补0位置及个数(统一按照padding='SAME', 步长为1),得到的补0位置要上下和左右各自颠倒一下
4)将补0后得卷积结果作为真正的输入,反转后的卷积核为filter,进行步长为1的卷积操作
看下图理解一下过程:
TensorFlow中的操作函数
def conv2d_transpose( value, # 代表通过卷积操作之后的Tensor,一般用 NHWC 类型 filter, # 卷积核 output_shape, # 输出的Tensor形状也是个4维Tensor,value参数的原数据形状 strides, # 步长 padding="SAME", # 代表原数据生成value时使用的补0方式,是用来检查输入形状和输出形状是否合规的 data_format="NHWC", # 神经网络中在图像处理方面常用的类型,N--个数,H--高,W--宽,C--通道数 name=None)在TensorFlow源码中,反卷积操作其实是使用 gen_nn_ops.conv2d_backprop_input 函数最终实现,即 TensorFlow 中利用了卷积操作在反向传播的处理函数中做反卷积操作,即卷及操作的反向传播就是反卷积操作。
看上图反卷积过程的代码实现:
import tensorflow as tf # 模拟数据 img = tf.Variable(tf.constant(1., shape=[1, 4, 4, 1])) filter_ = tf.Variable(tf.constant([1., 0, -1, -2], shape=[2, 2, 1, 1])) con_s = tf.nn.conv2d(img, filter_, strides=[1, 2, 2, 1], padding='SAME') con_v = tf.nn.conv2d(img, filter_, strides=[1, 2, 2, 1], padding='VALID') print(con_s.shape) print(con_v.shape) # 反卷积 con_ts = tf.nn.conv2d_transpose(con_v, filter_, output_shape=[1, 4, 4, 1], strides=[1, 2, 2, 1], padding='SAME') con_tv = tf.nn.conv2d_transpose(con_v, filter_, output_shape=[1, 4, 4, 1], strides=[1, 2, 2, 1], padding='VALID') with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print('con_s:\n', sess.run([con_s, filter_])) print('con_v:\n', sess.run([con_v])) print('con_ts:\n', sess.run([con_ts])) print('con_tv:\n', sess.run([con_tv])) # con_s: # [ # array([[ # [[-2.],[-2.]], # [[-2.],[-2.]] # ]], dtype=float32), # array([ # [ # [[ 1.]],[[ 0.]] # ], # [ # [[-1.]],[[-2.]] # ] # ], dtype=float32) # ] # con_v: # [array([[ # [[-2.],[-2.]], # [[-2.],[-2.]] # ]], dtype=float32)] # con_ts: # [array([[ # [[-2.],[ 0.],[-2.],[ 0.]], # [[ 2.],[ 4.],[ 2.],[ 4.]], # [[-2.],[ 0.],[-2.],[ 0.]], # [[ 2.],[ 4.],[ 2.],[ 4.]] # ]], dtype=float32)] # con_tv: # [array([[ # [[-2.],[ 0.],[-2.],[ 0.]], # [[ 2.],[ 4.],[ 2.],[ 4.]], # [[-2.],[ 0.],[-2.],[ 0.]], # [[ 2.],[ 4.],[ 2.],[ 4.]] # ]], dtype=float32)]
既然有反卷积,那么有反池化吗??那肯定是。。。有的
反池化
反池化原理
反池化是池化的逆操作,无法通过池化的结果还原出全部的原始数据。池化的过程只是保留主要的信息,去除部分信息。想从池化后的这些主要信息恢复全部信息,则会存在信息缺失,这时只能通过补位来实现最大程度的信息完整。池化有最大池化与平均池化,则反池化也会对应这两个操作:
1) 平均池化首先还原成原来的大小,然后将池化结果中的每个值都填入其对应于原始数据区域中的相应位置
2)最大池化要求在池化过程中记录最大激活的坐标位置,然后在反池化时,只把池化过程中最大值所在位置坐标 的值激活,其他的值置为0
TensorFlow中并没有反池化的操作函数。也不支持输出最大激活值的位置,但是有个池化的反向传播函数 tf.nn.max_pool_with_argmax,该函数可以输出位置
import tensorflow as tf # 重新定义最大池化函数 def max_pool_with_argmax(net, stride, padding='SAME'): """ 函数首先调用 max_pool_with_argmax 函数获得每个最大值的位置 mask, 再将反向传播的 mask 梯度计算停止,接着调用 tf.nn.max_pool 函数 计算最大池化操作,然后返回结果 """ _, _mask_ = tf.nn.max_pool_with_argmax(net, ksize=[1, stride, stride, 1], strides=[1, stride, stride, 1], padding=padding) _mask_ = tf.stop_gradient(_mask_) net = tf.nn.max_pool(net, ksize=[1, stride, stride, 1], strides=[1, stride, stride, 1], padding=padding) return net, _mask_ def unpool(net, mask, stride): """ 定义最大反池化函数 """ ksize = [1, stride, stride, 1] input_shape = net.get_shape().as_list() # 计算 new shape output_shape = (input_shape[0], input_shape[1]*ksize[1], input_shape[2]*ksize[2], input_shape[3]) # 计算索引 one_like_mask = tf.ones_like(mask) batch_range = tf.reshape(tf.range(output_shape[0], dtype=tf.int64), shape=[input_shape[0], 1, 1, 1]) b = one_like_mask*batch_range y = mask // (output_shape[2]*output_shape[3]) x = mask % (output_shape[2]*output_shape[3]) // output_shape[3] feature_range = tf.range(output_shape[3], dtype=tf.int64) f = one_like_mask * feature_range # 转置索引 updates_size = tf.size(net) indices = tf.transpose(tf.reshape(tf.stack([b, y, x, f]), [4, updates_size])) values = tf.reshape(net, [updates_size]) ret = tf.scatter_nd(indices, values, output_shape) return ret # 测试 img = tf.constant([ [[0., 4.], [0., 4.], [0., 4.], [0., 4.]], [[1., 5.], [1., 5.], [1., 5.], [1., 5.]], [[2., 6.], [2., 6.], [2., 6.], [2., 6.]], [[3., 7.], [3., 7.], [3., 7.], [3., 7.]] ]) img = tf.reshape(img, [1, 4, 4, 2]) pooling = tf.nn.max_pool(img, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME') encode, mask = max_pool_with_argmax(img, 2) img2 = unpool(encode, mask, 2) with tf.Session() as sess: print('image:\n', sess.run(img)) print('pooling:\n', sess.run(pooling)) result, mask0 = sess.run([encode, mask]) print('result:\n', result) print('mask0:\n', mask0) print('img2:\n', sess.run(img2)) # pooling: # [[ # [[1. 5.] [1. 5.]] # [[3. 7.] [3. 7.]] # ]] # result: # [[ # [[1. 5.] [1. 5.]] # [[3. 7.] [3. 7.]] # ]] # mask0: # [[ # [[ 8 9] [12 13]] # [[24 25] [28 29]] # ]] # img2: # [[ # [[0. 0.] [0. 0.] [0. 0.] [0. 0.]] # [[1. 5.] [0. 0.] [1. 5.] [0. 0.]] # [[0. 0.] [0. 0.] [0. 0.] [0. 0.]] # [[3. 7.] [0. 0.] [3. 7.] [0. 0.]] # ]]
代码中的函数都是可以直接拿来用的。