一边Upsample一边Convolve:Efficient Sub-pixel-convolutional-layers详解

前言

这篇文章介绍<Is the deconvolution layer the same as a convolutional layer?>论文中提出的一种结合上升采样upsample和卷积操作的的一种方法,称之为Sub-piexl convolution。这种方法作用于低分率图像得然后通过其操作得到高分辨率图像,其实说白了就是上升采样,我们之前做过的maxpooling、average-pooling和transposed-convolution都有类似的效果,也就是将一个尺寸较小的图像通过算法使之变为尺寸较大的图像,如下图。 (cs231n课程课件截图)

相比于其他的上升采样操作,本文介绍的这个算法在相同运算速度下,不仅具有比maxpooling和averagepooling有对位置的记忆功能,而且相比Transposed convolution具有精度更高的优点。

思想描述

文章将这个算法描述为:LR network,即低分辨卷积网络。文章拿来与之作对比的是HR network,高分辨卷积网络,一般HR network是现将低分辨力的图像进行二次插值变换后然后对变换后的图像再进行卷积网络。像HR network是先将图像进行upsample后才进行卷积,而文中的这个算法操作则是在upsample的过程中对图像就进行了卷积。

这个算法的实现流程如上图,举个例子,实现的功能就是将一个1×1的image通过Sub-pixel操作将其变为

的高分辨率图像,但是这个实现过程不是直接产生这个高分辨率图像而是先得到

个通道特征图然后通过周期筛选(periodic shuffing)得到这个高分辨率的图像,其中r为upscaling factor,也就是图像扩大倍率。

知识铺垫

在说明这个具体的算法流程之前,我们先对几个知识回顾一下:

Transposed convolution and sub-pixel convolution layers

上图是一个简单的1D的卷积网络,x为输入y为输出,通过滤波器f来实现这个过程,x的size为8,f为4,y为5,x中灰色的方块表示用0进行padding。在f权重中的灰色方块代表f中某些值与x中的0进行了相乘。这个图就是1D卷积的过程,从x到y。

上图左面是一个转置卷积(stride为2),右面是sub-pixel convolution(stride为1/2),作用在1D上。

通过观察可以很容易的看到左边x为5,y为8,f为4,y中灰色部分代表被截去的部分,而右边通过对x进行部分 取值达到 过滤效果。通过比较两个卷积方法可以发现:如果把右边的fliter进行reverse(倒转),会发现两边得到的y值相同(自己可以试着加一下)。也就是说在参数(filter中权重值)可以学习的情况下,右边的操作和左边的操作完全相同。

Deconvolution layer vs Convolution in LR

接下来我们展示的是:在LR space中用一个简单的卷积参数为(o* r* r, i,k,k)-(输出通道,输入通道,kernel width,kernel height)的卷积操作,和deconvolution,其中卷积参数为((o, i ,k * r,k * r),k为正数)的作用是相同的。

我们用2D来进行演示,input为(1,4,4),kernel为(1,1,4,4),假设我们upsample的比率为2,我们要得到(1,8,8)的输出。

如上图,我们首先通过fractional indices从原input中创建一个sub-pixel图像,其中白色的像素就是原input中的像素(在LR sapce中),灰色是通过zero padding而来的。

用一个(1,1,4,4)的卷积核来和刚才生成的sub-pixel图像进行卷积,首先发现卷积核和sub-pixel图像中非零的像素进行了第一次有效卷积(图中紫色像素代表被激活的权重),然后我们将sub-pixels整体向右移动一格,让卷积核在进行一次卷积,会发现卷积核中蓝色的像素权重被激活,同理绿色和红色(注意这里是中间的那个8×8的sub-pixel图像中的白色像素点进行移动,而每次卷积的方式都相同)。

最后我们输出得到HR图像,HR图像和sub-pixel图像的大小是一致的,我们将其涂上颜色,颜色代表这个卷积核中权重和sub-pixel图像中哪个像素点进行了卷积(也就是哪个权重对对应的像素进行了贡献)。

需要注意的是,这是一个(1,1,4,4)的卷积核,上面每个权重皆为独立地被激活,也就是说我们可以轻易地将其分成(4,1,2,2)的卷积核(如上图)。这个操作是可逆的因为每个卷及权重在操作过程中都是独立的。

这样,这里不适用(1,1,4,4)的卷积核,而是使用(4,1,2,2)的卷积核,,对sub-pixel图像直接在LR space中进行卷及操作(如上图)。再将得到的(4,4,4)的输出进行周期筛选(periodic shuffling)将其reshape为HR image。

这个图片展示了将左面图像上方的(9,32,3,3)卷积层转化为底部的(1,32,9,9)卷积层,转化过程正如右方所示。也就是说,上方的卷积层对一个32通道的图像进行卷积然后周期筛选后得到的图像和使用下方的卷积层进行反卷积(deconvolution)得到的结果相同。

这个卷积操作是很灵活的,看一下之前的1D的卷积操作,可以将滤波器f=(1,2,3,4)更换为f1=(2,4)以及f2=(1,3),然后产生y1 = f1 * x和y2 = f2 * 2其中代表卷积操作。通过对y1和y2进行组合得到最终的结果y。还记得上面谈说过: “在LR space中用一个简单的卷积参数为(o r* r,i,k,k)-(输出通道,输入通道,kernel width,kernel height)的卷积操作,和deconvolution,其中卷积参数为((o,i,k * r,k * r),k为正数)的作用是相同的。” 这里的f我们一般都以(k* r)来表示,但其实这个f可以为任意值,加入f=(1,2,3),我们就可以分成f=(2),f=(1,3),同样,得到结果组合起来即可。

核心思想以及结论

现在回到最开始说的那个操作,上图是这个算法的整个过程。通过上面的一系列论述,我们可以得到deconvolition layer的操作和在LR中输出

的convolution操作得到的结果是一样的(d代表维度)。也就是说,得到

个通道的这个卷积过程和在它之前得到

个特征图的操作是一样的。

在上面的Hidden convolutional layers中,看倒数第二个layer,有n个feature maps。我们现在可以知道,在LR space中,通过一个放大倍率为2的upsampling去学习表示n feature maps和在HR space中,通过一个n/4个feature maps去表示的效果是一样的。想象在相同运行速度下,一个n=32(LR space),一个n/4=8个feature maps(HR space),在LR 下的network比在HR network下的表示能力是很强大的。

举个实际的例子,LR network(32 x 32 x 3 x 3 x W/2 x H/2)的复杂度和HR中的(8 x 8 x 6 x 6 x W x H)的复杂度是一样的(W和H代表image的宽和高)。特征图中存留的信息也是一样的:LR(l x 32 x w/2 x h/2)和HR(l x 8 x w x h),其中l代表layer的数量。接受域来说,在原始input LR space中都是一样的,但是LR network的参数(l x 32 x 32 x 3 x 3)比HR network(l x 8 x 8 x 6 x 6)的多,也就是说,LR中,network的卷积比HR中先对input进行upsample的卷积有很强的表示学习能力。

所以有时候先对原始input进行upsample然后再进行卷积并不好,至少在表示学习能力并没有sub-pixel convolution效果好。

实现代码(摘自Deepfake-keras实现版本)

class PixelShuffler(Layer):
	def __init__(self, size=(2, 2), data_format=None, **kwargs):
		super(PixelShuffler, self).__init__(**kwargs)
		self.data_format = conv_utils.normalize_data_format(data_format)
		self.size = conv_utils.normalize_tuple(size, 2, 'size')  # (2,2)

	def call(self, inputs):

		input_shape = K.int_shape(inputs)
		if len(input_shape) != 4:
			raise ValueError('Inputs should have rank ' +
							 str(4) +
							 '; Received input shape:', str(input_shape))

		if self.data_format == 'channels_first':
			batch_size, c, h, w = input_shape
			if batch_size is None:
				batch_size = -1
			rh, rw = self.size
			oh, ow = h * rh, w * rw
			oc = c // (rh * rw)

			out = K.reshape(inputs, (batch_size, rh, rw, oc, h, w))
			out = K.permute_dimensions(out, (0, 3, 4, 1, 5, 2))
			out = K.reshape(out, (batch_size, oc, oh, ow))
			return out

		elif self.data_format == 'channels_last':
			batch_size, h, w, c = input_shape
			if batch_size is None:
				batch_size = -1
			rh, rw = self.size
			oh, ow = h * rh, w * rw
			oc = c // (rh * rw)

			out = K.reshape(inputs, (batch_size, h, w, rh, rw, oc))
			out = K.permute_dimensions(out, (0, 1, 3, 2, 4, 5))
			out = K.reshape(out, (batch_size, oh, ow, oc))
			return out

	def compute_output_shape(self, input_shape):

		if len(input_shape) != 4:
			raise ValueError('Inputs should have rank ' +
							 str(4) +
							 '; Received input shape:', str(input_shape))

		if self.data_format == 'channels_first':
			height = input_shape[2] * self.size[0] if input_shape[2] is not None else None
			width = input_shape[3] * self.size[1] if input_shape[3] is not None else None
			channels = input_shape[1] // self.size[0] // self.size[1]

			if channels * self.size[0] * self.size[1] != input_shape[1]:
				raise ValueError('channels of input and size are incompatible')

			return (input_shape[0],
					channels,
					height,
					width)

		elif self.data_format == 'channels_last':
			height = input_shape[1] * self.size[0] if input_shape[1] is not None else None
			width = input_shape[2] * self.size[1] if input_shape[2] is not None else None
			channels = input_shape[3] // self.size[0] // self.size[1]

			if channels * self.size[0] * self.size[1] != input_shape[3]:
				raise ValueError('channels of input and size are incompatible')

			return (input_shape[0],
					height,
					width,
					channels)

参考资料: https://arxiv.org/pdf/1603.07285v1.pdf https://arxiv.org/abs/1609.07009

此文由腾讯云爬虫爬取,文章来源于Oldpan博客

欢迎关注Oldpan博客公众号,持续酝酿深度学习质量文:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户2442861的专栏

Python下opencv使用笔记(三)(图像的几何变换)

下面介绍的图像操作假设你已经知道了为什么需要用矩阵构造才能实现了(上面那个博客有介绍为什么)。那么关于偏移很简单,图像的平移,沿着x方向tx距离,y方向ty距离...

631
来自专栏算法channel

全面总结机器学习项目和面试中几乎绕不开的决策树

决策树是一种常见的机器学习算法,它的思想十分朴素,类似于我们平时利用选择做决策的过程。

890
来自专栏技术与生活

从一条曲线谈损失函数优化方法

找到生成最小值的一组参数的算法被称为优化算法。我们发现随着算法复杂度的增加,则算法倾向于更高效地逼近最小值。我们将在这篇文章中讨论以下算法:

652
来自专栏Pulsar-V

OpenCV图像处理基础(一)

图像处理基础,等大小图片相似度匹配 基于RGB通道的像素点相似度匹配算法思路 ? def compare_by_RGB(image_1,image_2): ...

34910
来自专栏人工智能的秘密

算法教程:能够体现文本语义关系的关键词提取算法

关键词提取能让我们快速地了解一篇文章。在信息爆炸的时代,能够有效提取文本的关键词,对于快速、及时、高效地获取信息是非常有帮助的。本文介绍一种能够体现文本语义关系...

2840
来自专栏用户3246163的专栏

2.1 统计基础

主要用在线性回归的时候来估计b1 unbiasedness: 估计的残差是随机的 efficiency:对比其他估计样本残差最小 consistency:样本增...

1193
来自专栏张蓓的专栏

机器学习:基于网格的聚类算法

俗话说:“物以类聚,人以群分”,在机器学习中,聚类算法是一种无监督分类算法。聚类算法很多,包括基于划分的聚类算法(如:kmeans),基于层次的聚类算法(如:B...

3.3K0
来自专栏菩提树下的杨过

机器学习笔记(3):多类逻辑回归

仍然是 动手学尝试学习系列的笔记,原文见:多类逻辑回归 — 从0开始 。 这篇的主要目的,是从一堆服饰图片中,通过机器学习识别出每个服饰图片对应的分类是什么(比...

3658
来自专栏IT派

经典!构建你的第一个神经网络识别数字

在Keras环境下构建多层感知器模型,对数字图像进行精确识别。模型不消耗大量计算资源,使用了cpu版本的keras,以Tensorflow 作为backende...

3475
来自专栏PPV课数据科学社区

机器学习系列:(四)从线性回归到逻辑回归

从线性回归到逻辑回归 在第2章,线性回归里面,我们介绍了一元线性回归,多元线性回归和多项式回归。这些模型都是广义线性回归模型的具体形式,广义线性回归是一种灵活的...

4116

扫码关注云+社区