卷积神经网络(Convolutional Neural Networks, CNN)是计算机视觉技术最经典的模型结构,主要知识点包括:卷积、池化、激活函数、批归一化、丢弃法等。
卷积神经网络对特征的提取,既能提取到相邻像素点之间的特征模式,又能保证参数的个数不随图片尺寸变化。典型的卷积神经网络结构中,在输入图片上使用多层卷积和池化层组合,在网络的最后通常会加入一系列全连接层,ReLU激活函数一般加在卷积或者全连接层的输出上,网络中通常还会加入Dropout来防止过拟合。
1、基础术语
1.1、卷积(Convolution)
卷积计算
卷积是数学分析中的一种积分变换的方法,在图像处理中采用的是卷积的离散形式。卷积核(kernel)也被叫做滤波器(filter),假设卷积核的高和宽分别为
k
h
k_h
kh和
k
w
k_w
kw,则称为
k
h
×
k
w
k_h×k_w
kh×kw卷积。卷积核大小是2×2的卷积计算示例过程,如下:
在卷积神经网络中,一个卷积算子除了上面描述的卷积过程之外,还包括加上偏置项的操作。
填充(padding)
卷积输出特征图的尺寸计算方法如下(卷积核的高和宽分别为
k
h
k_h
kh和
k
w
k_w
kw):
H
o
u
t
=
H
−
k
h
+
1
H_{out} = H - k_h + 1
Hout=H−kh+1
W
o
u
t
=
W
−
k
w
+
1
W_{out} = W - k_w + 1
Wout=W−kw+1
为了避免卷积之后图片尺寸变小,通常会在图片的外围进行填充(padding)。
多输入通道、多输出通道和批量操作
多输入通道一般是指彩色图片有RGB三个通道,需要同时计算卷积。假设输入图片的通道数为
C
i
n
C_{in}
Cin,输入数据的形状是
C
i
n
×
H
i
n
×
W
i
n
{C_{in}}\times{H_{in}}\times{W_{in}}
Cin×Hin×Win
卷积操作的输出特征图也会具有多个通道,通常叫做卷积核的个数。
在卷积神经网络的计算中,通常将多个样本放在一起形成一个mini-batch进行批量操作。即输入数据的维度是
N
×
C
i
n
×
H
i
n
×
W
i
n
N×{C_{in}}\times{H_{in}}\times{W_{in}}
N×Cin×Hin×Win;输出特征图的维度是
N
×
C
o
u
t
×
H
o
u
t
×
W
o
u
t
N×{C_{out}}\times{H_{out}}\times{W_{out}}
N×Cout×Hout×Wout
卷积的输出维度计算公式:
h
o
u
t
=
[
h
i
n
+
2
p
a
d
−
k
e
r
n
e
l
S
t
r
i
d
e
]
+
1
h_{out}=\left [ \frac{h_{in}+2pad-kernel}{Stride} \right]+1
hout=[Stridehin+2pad−kernel]+1
1.2、池化(Pooling)
池化是使用某一位置的相邻输出的总体统计特征代替网络在该位置的输出,其好处是当输入数据做出少量平移时,经过池化函数后的大多数输出还能保持不变。由于池化之后特征图会变得更小,如果后面连接的是全连接层,能有效的减小神经元的个数,节省存储空间并提高计算效率。通常有两种方法:平均池化和最大池化。
1.3、ReLU激活函数
在神经网络发展的早期,普遍使用Sigmoid函数做激活函数,而目前用的较多的激活函数是ReLU。这是因为Sigmoid函数在反向传播过程中,容易造成梯度的衰减。在神经网络里,将经过反向传播之后,梯度值衰减到接近于零的现象称作梯度消失现象。
1.4、批归一化(Batch Normalization)
通常情况下会对神经网络的数据进行标准化处理,处理后的样本数据集满足均值为0,方差为1的统计分布,这是因为当输入数据的分布比较固定时,有利于算法的稳定和收敛。对于深度神经网络来说,由于参数是不断更新的,即使输入数据已经做过标准化处理,但是对于比较靠后的那些层,其接收到的输入仍然是剧烈变化的,通常会导致数值不稳定,模型很难收敛。
批归一化方法(BatchNorm)主要思路是在训练时以mini-batch为单位,对神经元的数值进行归一化,使数据的分布满足均值为0,方差为1。
1.5、丢弃法(Dropout)
丢弃法是深度学习中一种常用的抑制过拟合的方法,其做法是在神经网络学习过程中,随机删除一部分神经元,即将其输出设置为0,这些神经元将不对外传递信号。
2、PaddlePaddle(飞桨)代码实现
深度学习框架有pytorch、tensorflow和paddlepaddle等。
飞桨卷积算子图像数据对应的API是paddle.nn.Conv2D,用户可以直接调用API进行计算,也可以在此基础上修改。Conv2D名称中的“2D”表明卷积核是二维的,多用于处理图像数据;Conv3D可以用于处理视频数据(图像的序列)。
类Conv2D常用的参数如下:
- in_channels(int) - 输入图像的通道数。
- out_channels(int) - 卷积核的个数,和输出特征图通道数相同,相当于上文中的 C o u t C_{out} Cout。
- kernel_size(int|list|tuple) - 卷积核大小,可以是整数,比如3,表示卷积核的高和宽均为3 ;或者是两个整数的list,例如[3,2],表示卷积核的高为3,宽为2。
- stride(int|list|tuple,可选) - 步长大小,可以是整数,默认值为1,表示垂直和水平滑动步幅均为1;或者是两个整数的list,例如[3,2],表示垂直滑动步幅为3,水平滑动步幅为2。
- padding(int|list|tuple|str,可选) - 填充大小,可以是整数,比如1,表示竖直和水平边界填充大小均为1;或者是两个整数的list,例如[2,1],表示竖直边界填充大小为2,水平边界填充大小为1。
示例:图像中物体边缘检测
import matplotlib as mpl
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
if __name__ == '__main__':
# 设置卷积核参数
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)
img = Image.open('./taishan.jpg')
# 将读入的图片转化为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)
# 创建卷积算子,输出通道数为1,卷积核大小为3x3,
# 并使用上面的设置好的数值作为卷积核权重的初始化参数
conv = Conv2D(in_channels=3, out_channels=1, kernel_size=[3, 3],
weight_attr=paddle.ParamAttr(initializer=Assign(value=w)))
y = conv(x)
out = y.numpy()
# 绘图:汉字字体
mpl.rcParams['font.sans-serif'] = ['SimHei', 'KaiTi', 'FangSong']
plt.figure(figsize=(10, 6))
f = plt.subplot(121)
f.set_title('原始图片', fontsize=15)
plt.imshow(img)
f = plt.subplot(122)
f.set_title('特征提取', fontsize=15)
plt.imshow(out.squeeze(), cmap='gray')
plt.show()
运行结果,如下: