深度学习之解构卷积
1. 卷积神经网络组成
卷积神经网络(Convolutional Neural Network)在深度学习特别是机器视觉(CV)任务中有着重要的作用。Yann Lecun在1998年第一次提出了基于卷积神经网络的手写字符识别网络LeNet-5,采用了基于梯度的反向传播算法来训练,模型包括简单的2个卷积层(2个pooling层)和3个全连接。由于算力的问题,LeNet-5没有得到推广,直到2012年AlexNet网络配合GPU算力的支持,以卷积神经网络为基础的深度学习开始普及。
上图可知,LeNet-5已经具备基本的卷积神经网络的雏形了,卷积层负责提取特征,全连接层负责分类识别。卷积神经网络的组成部分如下:
- 卷积层
- pooling层:下采样层(max pooling和avg pooling)
- 激活函数
- 全连接层
其中卷积层以扫窗的方式,通过卷积核(Conv kernel)对图像进行卷积操作,提取局部的特征;每个卷积层可以多个卷积核,每个核对应一个输出通道(feature map),可以提取不同的特征,而共享一个卷积核的参数;卷积核的值,是通过学习得到。卷积操作来源数字图像处理的卷积即点乘后加和,涉及参数有卷积核大小kernel_size
填充padding
卷积移动步长stride
。
卷积神经网络相比传统人工神经网络有如下特点:
- 连接稀疏(sparsity of connections):卷积操作是提取局部的信息,共享一个卷积核的参数,减少参数量。同时这符合视觉的特点,人眼不需要一次查看整张图,可以只看部分图,就可以做出辨识
- 参数共享(parameters sharing):不同位置的相同特征可以用同一个卷积核进行提取特征
- pooling下采样层:降低维度,减少参数。也符合视觉的特点,一副图通过下采样2倍后,并不影响人眼做出辨识
卷积神经网络有效的提取特征的同时,大大的降低了参数量,提高了效率,配合GPU高效的计算能力,使得深度学习不断的普及。
以下为卷积神经网络提取到特点示例:
随着深度学习不断发展,出现了不同类型的卷积操作,本文和大家一起分享当前主流的卷积操作和实现,主要有:
- 空洞卷积
- 1*1 卷积
- 反卷积
- 深度可分离卷积
- 可变形卷积
- 3D卷积
2. 卷积操作
2.1 空洞卷积
通常,一个3x3的卷积核具备3*3的感受野(receptive field),不断的堆叠卷积层,逐渐加大感受野,感受野越大提取到特征接收的信息越多。为了增加单个卷积核的感受野,Multi-scale context aggregation with dilated convolutions提出了空洞卷积。空洞卷积顾名思义就是在常规卷积核中加入空洞(补0)。如下图所示,a是普通3x3卷积,b是空洞率为2的3x3卷积核,具备7x7的感受野;c是空洞率为4的3x3卷积核,具备15x15的感受野。
在实现上,有效的kernel value还是3x3的值,只是在做点乘操作时,点乘的对应的feature map值的位置改变了。这样就可以达到不改变参数量的情况下,增大感受野。
空洞卷积除了增大感受野的同时,配合普通卷积可以捕捉不同感受野的信息,获取了多尺度信息,对于一些检测和分割任务来说是很重要的。
import torch
from torch import nn as nn
input = torch.randn(1, 256, 12, 12)
dilation_conv = nn.Conv2d(256, 64, kernel_size=3, stride=1, padding=0, dilation=2, groups=1, bias=True, padding_mode='zeros')
output = dilation_conv(input)
output.size()
# torch.Size([1, 64, 8, 8])
2.2 1x1卷积
卷积层中的卷积核基本是3x3, 5x5,1x1卷积最早是在Network in Network中提出,后来在googlenet的Inception系列和Reset中被广泛应用。一个维度为(h, w, c)的输入feature map, 经过(1, 1, cout)卷积核后,得到(h, w, cout)。可知,1x1的卷积和普通卷积在计算熵没什么区别,但是1x1有它独特的特点:
- 改变输出维度:当cout>c, 达到升维目的;当cout<c,达到减维目的;可以方便的改变维度的数量而保存大小不变
- 通道融合:根据卷积操作(点乘后汇总),1x1卷积其实是将所有的channel上的值汇总,达到通道融合的特点。
Inception v2 中的1x1卷积:
Resnet中1x1卷积:
import torch
from torch import nn as nn
input = torch.randn(1, 256, 12, 12)
one_by_one_conv = nn.Conv2d(256, 64, kernel_size=1, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
output = one_by_one_conv(input)
output.size()
# torch.Size([1, 64, 12, 12])
2.3 反卷积或转置卷积
反卷积顾名思义是卷积的反操作,比较科学说法是转置卷积(transposed convolution operator), 通常应用于上采样操作比如超分任务,分割任务等等。 普通卷积如果没有padding情况下,feature map的尺度是不断的变小的(降采样),而转置卷积是上采样,尺度变大,所以才说是反卷积。
from torch import nn as nn
input = torch.randn(1, 16, 12, 12)
downsample = nn.Conv2d(16, 16, 3, stride=2, padding=1)
upsample = nn.ConvTranspose2d(16, 16, 3, stride=2, padding=1)
h = downsample(input)
h.size()
# torch.Size([1, 16, 6, 6])
output = upsample(h, output_size=input.size())
output.size()
# torch.Size([1, 16, 12, 12])
2.4 深度可分离卷积
深度可分离卷积是在Xception提出,在MoblieNet 系列等轻量级模型中广泛应用,主要是为了减少参数量。
对于输入是channel是M的feature map, 想提取N个输出channel的结果,普通卷积如下图所示是采用N个卷核(Dk, Dk),由于输入M个channel,所以实际上每个卷积的维度是(Dk,Dk, M),可知参数量为DkDkM*N:
而深度可分离卷积,首先对卷积核维度进行分离,即每个卷积的维度是(Dk,Dk, M)分离成M个(Dk,Dk,1),分别对应到的M个channel的每一个channel,这样分别相乘计算卷积,这样得到的是输出的channel是M,参数量是DkDkM,此时的问题是输出channel不对,而且原始输入的channel之间是分离计算的。
此时就要对特征进行融合和输出想要的channel数,这个时候1x1卷积出场了。pointwise操作利用N个1x1卷积,就生成channel为N的feature map。而这一步的参数量是MN,全部的参数量( DkDkM + MN),相比DkDkM*N,参数量降低的非常多。
pytorch实现其实很简单,分离卷积通过分组(groups)的方式
import torch
from torch import nn as nn
input = torch.randn(1, 256, 12, 12)
dw_conv = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=0, dilation=1, groups=256, bias=False, padding_mode='zeros')
one_by_one_conv = nn.Conv2d(256, 64, kernel_size=1, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
dw_output = dw_conv(input)
print(dw_output.size())
# torch.Size([1, 256, 10, 10])
output = one_by_one_conv(dw_output)
output.size()
# torch.Size([1, 64, 10, 10])
2.5 可变形卷积
普通的卷积通常是规则形状的,对于分割或目标检测任务来说物体的形状通常是不规则的,deformable convolution可变形卷积(Deformable Convolutional Networks)通过增加位置偏移使得卷积核值的位置变得不规则,来提取更有效的不规则特征,能够自动调整尺度或者感受野。
从上图可知,可变形卷积对普通卷积核的每个位置增加一个方向参数,使得同样参数情况下,可以学习不规则的形状,同时也会有可变的感受野。在实现上,通过一个卷积来学习偏离的值(offset),进行卷积的时候,普通的卷积核和offset值,进行卷积操作。可想而知,offset是位置偏移,可能是小数,这样卷积核位置就无法和feature map的值一一对应,此时采用插值算法进行对应位置来进行卷积操作,如下图所示
具体实现基于cuda的方式来实现,参见:https://github.com/4uiiurz1/pytorch-deform-conv-v2
2.6 3D卷积
从前面章节的卷积都是二维卷积(2D)对应的维度为(h, w, c), 而3D卷积就是多一个维度(t, h, w, c);3D卷积通常用在视频图像处理上,在空间维度上增加一个时间维度,比较出名的算法是C3D(Learning Spatiotemporal Features with 3D Convolutional Networks)。2D和3D的区别如下图,输出是立体:
动态操作如下(来自网络):
import torch
from torch import nn as nn
# With square kernels and equal stride
m = nn.Conv3d(16, 33, 3, stride=2)
# non-square kernels and unequal stride and with padding
m = nn.Conv3d(16, 33, (3, 5, 2), stride=(2, 1, 1), padding=(4, 2, 0))
input = torch.randn(20, 16, 10, 50, 100)
output = m(input)
output.size()
# torch.Size([20, 33, 8, 50, 99])
3. 总结
本文和大家分享卷积神经网络中各种不同的卷积操作和作用,希望对大家有帮助,总结如下:
- CNN三剑客:卷积层+pooling下采样+激活函数(非线性)
- 空洞卷积:增大感受野,多尺度信息
- 1*1 卷积:方便的工具卷积,进行通道融合
- 反卷积:上采样卷积
- 深度可分离卷积:先分离,再融合,降低参数
- 可变形卷积:给卷积加上位置偏差,结合插值进行卷积,识别可变形态
- 3D卷积:给2D卷积增加一维