点击左上方蓝字关注我们
本栏目由百度飞桨RD们联手精心打造,对深度学习的知识点进行由浅入深的剖析和讲解。大家可视它为深度学习百科(面试秘籍也是可以哒),当然也可收藏为深度学习查询手册~
大家好,六小桨第2期和大家见面啦,我是助教唐僧!就在上期,我们被杠了!居然有人质疑我们的算术不好,因为明明只有5个主桨人,哪来的六小桨……但是难道助教就不算人嘛?掩面哭泣~好吧,戏有点多了。
在上期中,主桨人见见为大家讲解了卷积的基本概念和计算。但在实际应用中,我们要处理的问题要复杂很多,因此,本期主桨人见见带来的是内容是:卷积的批量计算及应用案例。
卷积的批量计算
一张彩色图片有RGB三个通道,因此在对彩色图片进行处理时,需要处理多输入通道的场景,相应的输出特征图往往也会具有多个通道,而且在神经网络的计算中常常是把一个批次的样本放在一起计算,所以卷积算子需要具有批量处理多输入和多输出通道数据的功能。
多输入通道场景
当输入数据有多个通道时,对应的卷积核也应该有相同的通道数。假设输入图片的通道数为,输入数据的形状是
。
对每个通道分别设计一个2维数组作为卷积核,卷积核数组的形状是
。
对任一通道
,分别用大小为
的卷积核在大小为
的二维数组上做卷积。
将这
个通道的计算结果相加,得到的是一个形状为
的二维数组。
应用示例
上面的例子中,卷积核通常是2维数组,但实际上一张图片往往含有RGB三个通道,要计算卷积的输出结果,卷积核的形式也会发生变化。假设输入图片的通道数为3,输入数据的形状是,计算过程如 图1 所示。
对每个通道分别设计一个2维数组作为卷积核,卷积核数组的形状是
。
对任一通道
,分别用大小为
的卷积核在大小为
的二维数组上做卷积。
将这3个通道的计算结果相加,得到的是一个形状为
的二维数组。
多输出通道场景
如果我们希望检测多种类型的特征,我们可以使用多个卷积核进行计算。所以一般来说,卷积操作的输出特征图也会具有多个通道,这时我们需要设计
个维度为
的卷积核,卷积核数组的维度是
。
对任一输出通道
,分别使用上面描述的形状为
的卷积核对输入图片做卷积。
将这
个形状为
的二维数组拼接在一起,形成维度为
的三维数组。
应用示例
假设输入图片的通道数为3,我们希望检测2种类型的特征,因此需要输出数据的通道数为2,这时我们需要设计2个维度为的卷积核,卷积核数组的维度是
,计算过程如 图2 所示。
对任一输出通道
,分别使用上面描述的形状为
的卷积核对输入图片做卷积。
将这个形状为
的二维数组拼接在一起,形成维度为
的三维数组。
批量操作
在卷积神经网络的计算中,通常将多个样本放在一起形成一个mini-batch进行批量操作,即输入数据的维度是。由于会对每张图片使用同样的卷积核进行卷积操作,卷积核的维度是
,那么,输出特征图的维度就是
。
应用示例
假设我们输入数据的维度是,卷积核的维度与上面多输出通道的情况一样,仍然是
,输出特征图的维度是
。计算过程如 图3 所示。
1 x 1 卷积
1 x 1 卷积,与标准卷积完全一样,唯一的特殊点在于卷积核的尺寸是1 x 1 ,也就是不考虑输入数据局部信息之间的关系,而把关注点放在不同通道间。当输入矩阵的尺寸为3 x 3 ,通道数也为3时,使用4个1 x 1卷积核进行卷积计算(多输出通道场景),最终就会得到与输入矩阵尺寸相同,通道数为4的输出矩阵,如 图4 所示。
1 x 1 卷积的作用
1. 实现信息的跨通道交互与整合。考虑到卷积运算的输入输出都是3个维度(宽、高、通道),所以1 x 1 卷积实际上就是对每个像素点,在不同的通道上进行线性组合,从而整合不同通道的信息。
2. 对卷积核通道数进行降维和升维,减少参数量。经过1 x 1 卷积后的输出保留了输入数据的原有平面结构,通过调控通道数,从而完成升维或降维的作用。
3. 利用1 x 1 卷积后的非线性激活函数,在保持特征图尺寸不变的前提下,大幅增加非线性因素。
应用示例
1 x 1 卷积在GoogLeNet[1]中的应用
GoogLeNet是2014年ImageNet比赛的冠军,它的主要特点是网络不仅有深度,还在横向上具有“宽度”。由于图像信息在空间尺寸上的巨大差异,如何选择合适的卷积核来提取特征就显得比较困难了。空间分布范围更广的图像信息适合用较大的卷积核来提取其特征;而空间分布范围较小的图像信息则适合用较小的卷积核来提取其特征。为了解决这个问题,GoogLeNet提出了一种被称为Inception模块的方案。如 图5 所示:
图5 (a)是Inception模块的设计思想,使用3个不同大小的卷积核对输入图片进行卷积操作,并附加最大池化,将这4个操作的输出沿着通道这一维度进行拼接,构成的输出特征图将会包含经过不同大小的卷积核提取出来的特征,从而达到捕捉不同尺度信息的效果。Inception模块采用多通路(multi-path)的设计形式,每个支路使用不同大小的卷积核,最终输出特征图的通道数是每个支路输出通道数的总和,这将会导致输出通道数变得很大,尤其是使用多个Inception模块串联操作的时候,模型参数量会变得非常大。
为了减小参数量,Inception模块使用了图5(b)中的设计方式,在3 x 3 和5 x 5 的卷积层之前,增加1 x 1的卷积层来控制输出通道数;在最大池化层后面增加1 x 1卷积层减小输出通道数。我们这里可以简单计算一下Inception模块中使用1 x 1 卷积前后参数量的变化,这里以GoogleNet中的Inception 5(a)模块为例,输入通道数=192,1 x 1 卷积的输出通道数
=64,3 x 3 卷积的输出通道数
=128,5 x 5 卷积的输出通道数
=32,则图5(a)中的结构所需的参数量为:
1 x 1 x 192 x 64 + 3 x 3 x 192 x 128 + 5 x 5 x 192 x 32 = 387072
图5(b)中在3 x 3 卷积前增加了通道数=96的1 x 1卷积,在5 x 5 卷积前增加了通道数
=16的 1 x 1 卷积,同时在max pooling后增加了通道数
=32的 1 x 1 卷积,参数量变为:
1 x 1 x 192 x 64 + 1 x 1 x 192 x 96 +1 x 1 x 192 x 16 +3 x 3 x 96 x 128 +5 x 5 x 16 x 32 + 1 x 1 x 192 x 32 = 163328
可见,1 x 1卷积可以在不改变模型表达能力的前提下,大大减少所使用的参数量。
1 x 1 卷积在ResNet[2]中的应用
随着深度学习的不断发展,模型的层数越来越多,网络结构也越来越复杂。但是增加网络的层数之后,训练误差往往不降反升。由此,Kaiming He等人提出了残差网络ResNet来解决上述问题。ResNet是2015年ImageNet比赛的冠军,将识别错误率降低到了3.6%,这个结果甚至超出了正常人眼识别的精度。在ResNet中,提出了一个非常经典的结构—残差块(Residual block)。
残差块具体设计方案如 图6 所示。不同规模的残差网络中使用的残差块也并不相同,对于小规模的网络,残差块如图6(a)所示,但是对于稍大的模型,使用图6(a)的结构会导致参数量非常大,因此从ResNet50以后,都是使用图6(b)的结构。图6(b)中的这种设计方案也常称作瓶颈结构(BottleNeck)。1 x 1的卷积核可以非常方便的调整中间层的通道数,在进入3 x 3的卷积层之前减少通道数(256->64),经过该卷积层后再恢复通道数(64->256),可以显著减少网络的参数量。这个结构(256->64->256)像一个中间细,两头粗的瓶颈,所以被称为“BottleNeck”。
我们这里可以简单计算一下残差块中使用1 x 1 卷积前后参数量的变化,为了保持统一,我们令图6中的两个结构的输入输出通道数均为256,则图6 (a)中的结构所需的参数量为:
3 x 3 x 256 x 256 x 2 = 1179648
而图6(b)中采用了1 x 1 卷积后,参数量为:
1 x 1 x 256 x 64 + 3 x 3 x 64 x 64 + 1 x 1 x 64 x256 = 69632
同样,1 x 1卷积大大减少所使用的参数量。
卷积应用案例
案例1:简单的黑白边界检测
下面是使用卷积算子Conv2D完成一个图像边界检测的任务。如 图7 所示,图像左边为光亮部分,右边为黑暗部分,需要检测出光亮跟黑暗的分界处。
图8 卷积核
具体代码如下所示,结果输出在下方的图案中,如 图9 所示。
import matplotlib.pyplot as plt
import numpy as np
import paddle
from paddle.nn import Conv2D
from paddle.nn.initializer import Assign
%matplotlib inline
# 创建初始化权重参数w
w = np.array([1, 0, -1], dtype='float32')
# 将权重参数调整成维度为[cout, cin, kh, kw]的四维张量
w = w.reshape([1, 1, 1, 3])
# 创建卷积算子,设置输出通道数,卷积核大小,和初始化权重参数
# kernel_size = [1, 3]表示kh = 1, kw=3
# 创建卷积算子的时候,通过参数属性weight_attr指定参数初始化方式
# 这里的初始化方式时,从numpy.ndarray初始化卷积参数
conv = Conv2D(in_channels=1, out_channels=1, kernel_size=[1, 3],
weight_attr=paddle.ParamAttr(
initializer=Assign(value=w)))
# 创建输入图片,图片左边的像素点取值为1,右边的像素点取值为0
img = np.ones([50,50], dtype='float32')
img[:, 30:] = 0.
# 将图片形状调整为[N, C, H, W]的形式
x = img.reshape([1,1,50,50])
# 将numpy.ndarray转化成paddle中的tensor
x = paddle.to_tensor(x)
# 使用卷积算子作用在输入图片上
y = conv(x)
# 将输出tensor转化为numpy.ndarray
out = y.numpy()
f = plt.subplot(121)
f.set_title('input image', fontsize=15)
plt.imshow(img, cmap='gray')
f = plt.subplot(122)
f.set_title('output featuremap', fontsize=15)
# 卷积算子Conv2D输出数据形状为[N, C, H, W]形式
# 此处N, C=1,输出数据形状为[1, 1, H, W],是4维数组
# 但是画图函数plt.imshow画灰度图时,只接受2维数组
# 通过numpy.squeeze函数将大小为1的维度消除
plt.imshow(out.squeeze(), cmap='gray')
plt.show()
# 查看卷积层的权重参数名字和数值
print(conv.weight)
# 参看卷积层的偏置参数名字和数值
print(conv.bias)
案例2:图像中物体边缘检测(图像锐化)
上面展示的是人为构造的简单图片,使用卷积网络检测图片明暗分界处的示例。对于真实的图片,我们也可以使用合适的卷积核来检测物体的边缘轮廓。如 图10所示。
观察输出特征图跟原图之间的对应关系,如下代码所示,输出图像如图12所示。
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import paddle
from paddle.nn import Conv2D
from paddle.nn.initializer import Assign
img = Image.open('./img/example1.jpg')
# 设置卷积核参数
w = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]], dtype='float32')/8
w = w.reshape([1, 1, 3, 3])
# 由于输入通道数是3,将卷积核的形状从[1,1,3,3]调整为[1,3,3,3]
w = np.repeat(w, 3, axis=1)
# 创建卷积算子,输出通道数为1,卷积核大小为3x3,
# 并使用上面的设置好的数值作为卷积核权重的初始化参数
conv = Conv2D(in_channels=3, out_channels=1, kernel_size=[3, 3],
weight_attr=paddle.ParamAttr(
initializer=Assign(value=w)))
# 将读入的图片转化为float32类型的numpy.ndarray
x = np.array(img).astype('float32')
# 图片读入成ndarry时,形状是[H, W, 3],
# 将通道这一维度调整到最前面
x = np.transpose(x, (2,0,1))
# 将数据形状调整为[N, C, H, W]格式
x = x.reshape(1, 3, img.height, img.width)
x = paddle.to_tensor(x)
y = conv(x)
out = y.numpy()
plt.figure(figsize=(20, 10))
f = plt.subplot(121)
f.set_title('input image', fontsize=15)
plt.imshow(img)
f = plt.subplot(122)
f.set_title('output feature map', fontsize=15)
plt.imshow(out.squeeze(), cmap='gray')
plt.show()
案例3:图像均值模糊
对一张输入图像,如图13所示,我们尝试使用卷积算子实现图像均值模糊任务。
实现代码如下所示,输出图像如图15所示。
import paddle
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
from paddle.nn import Conv2D
from paddle.nn.initializer import Assign
# 读入图片并转成numpy.ndarray
# 换成灰度图
img = Image.open('./img/example2.jpg').convert('L')
img = np.array(img)
# 创建初始化参数
w = np.ones([1, 1, 5, 5], dtype = 'float32')/25
conv = Conv2D(in_channels=1, out_channels=1, kernel_size=[5, 5],
weight_attr=paddle.ParamAttr(
initializer=Assign(value=w)))
x = img.astype('float32')
x = x.reshape(1,1,img.shape[0], img.shape[1])
x = paddle.to_tensor(x)
y = conv(x)
out = y.numpy()
plt.figure(figsize=(20, 12))
f = plt.subplot(121)
f.set_title('input image')
plt.imshow(img, cmap='gray')
f = plt.subplot(122)
f.set_title('output feature map')
out = out.squeeze()
plt.imshow(out, cmap='gray')
plt.show()
本期内容详细介绍了深度学习中卷积的批量多通道计算以及1x1卷积,最后介绍了卷积的应用案例。下一期将由主桨人步步高为大家带来优化器的讲解,感兴趣的记得关注『听六小桨讲AI』系列内容,下期见~~
如果您有话对『六小桨』说,欢迎在文章下留言:)
参考文献
[1] Going deeper with convolutions
[2] Deep Residual Learning for Image Recognition
回顾往期:
如在使用过程中有问题,可加入官方QQ群进行交流:778260830。
如果您想详细了解更多飞桨的相关内容,请参阅以下文档。
·飞桨官网地址·
https://www.paddlepaddle.org.cn/
·飞桨开源框架项目地址·
GitHub: https://github.com/PaddlePaddle/Paddle
Gitee: https://gitee.com/paddlepaddle/Paddle
????长按上方二维码立即star!????
飞桨(PaddlePaddle)以百度多年的深度学习技术研究和业务应用为基础,是中国首个开源开放、技术领先、功能完备的产业级深度学习平台,包括飞桨开源平台和飞桨企业版。飞桨开源平台包含核心框架、基础模型库、端到端开发套件与工具组件,持续开源核心能力,为产业、学术、科研创新提供基础底座。飞桨企业版基于飞桨开源平台,针对企业级需求增强了相应特性,包含零门槛AI开发平台EasyDL和全功能AI开发平台BML。EasyDL主要面向中小企业,提供零门槛、预置丰富网络和模型、便捷高效的开发平台;BML是为大型企业提供的功能全面、可灵活定制和被深度集成的开发平台。
???? 近期直播 ????
END