卷积神经网络(CNN)
卷积神经网络 是一种具有 局部连接、权重共享 等特性的 深层前馈神经网络(文末附CNN的Python
实现(基于Keras
))
文章目录
一、概念
1. 前言
卷积神经网络最早用于解决图像信息,再用全连接前馈网络来处理图像时,会有以下问题
- 参数过多:若图像大小为 100 x 100 x 3(高度100, 宽度100,RGB 三个颜色通道),第一个隐藏层的每个神经元到输入层都有 100 x 100 x 3 = 30000 个互相独立的连接,每个连接都对应一个权重参数,导致训练效率低,易过拟合。
- 局部不变性特征:自然图像中的物体一般都有局部不变性特征,如尺度缩放,平移、旋转等操作不影响其语义信息,而全连接前馈网络很难提取到这些特征,一般需要 数据增强 来提高性能。
目前的卷积神经网络一般是由卷积层、汇聚层和全连接层交叉堆叠而成的前馈神经网络。卷积神经网络有三个结构上的特性:局部连接、权重共享、汇聚[^ 2]。这使得卷积神经网络具有一定程度上的平移、缩放和旋转不变性。和前馈神经网络相比,卷积神经网络参数更少。
卷积神经网络学习到的模式具有 平移不变性[^ 1]:如图像在右下角学习到某个模式后,它可以在任何地方识别这个模式,如左上角。因此它只需要更少的训练样本就可以学到具有泛化能力的数据表示。
它还可以学到 模式的空间层次结构[^ 1],第一个卷积层将学到较小的局部模式,第二个卷积层将学到由第一层特征组成的更大模式,以此类推。这使得卷积神经网络可以更有效地学习越来越复杂、越来越抽象的视觉概念。
2. 卷积
卷积 ( Convolution ),是分析数学中的一种重要的运算,在信号处理或图像处理中,经常使用一维或二维卷积。
2.1 一维卷积
经常用在信号处理中,用于计算信号的延迟累积,假设一个信号发生器每个时刻
t
t
t 产生一个信号
x
t
x_t
xt ,其信息衰减率为
w
k
w_k
wk,即在
k
−
1
k - 1
k−1 个时间步长后,信息为原来的
w
k
w_k
wk 倍,假设
w
1
=
1
,
w
2
=
1
2
,
w
3
=
1
4
w_1 = 1, w_2 = \frac{1}{2}, w_3 = \frac{1}{4}
w1=1,w2=21,w3=41,那么在时刻
t
t
t 收到的信号
y
t
y_t
yt 为当前时刻产生的信息和以前时刻延迟信息的叠加[^ 2]:
y
t
=
1
×
x
t
+
1
2
×
x
t
−
1
+
1
4
×
x
t
−
2
=
w
1
×
x
t
+
w
2
×
x
t
−
1
+
w
3
×
x
t
−
2
=
∑
k
=
1
3
w
k
x
t
−
k
+
1
y_t = 1 \times x_t + \frac{1}{2} \times x_{t-1} + \frac{1}{4} \times x_{t-2} \\ \qquad \, = w_1 \times x_t + w_2 \times x_{t-1} + w_3 \times x_{t-2} \\ = \sum\limits^3_{k=1}w_kx_{t-k+1} \qquad \qquad \qquad \;
yt=1×xt+21×xt−1+41×xt−2=w1×xt+w2×xt−1+w3×xt−2=k=1∑3wkxt−k+1
把
w
n
w_n
wn 称为 滤波器(Filter) 或 卷积核(Convolution Kernel),假设滤波器长度为K,它和一个信号序列的卷积为
y
t
=
∑
K
=
1
K
w
k
x
t
−
k
+
1
y_t = \sum\limits^K_{K=1}w_kx_{t-k+1}
yt=K=1∑Kwkxt−k+1
可以通过设计不同的滤波器来提取信号序列的不同特征,如滤波器为
w
=
[
1
/
K
,
…
,
1
/
K
]
w=[1/K,\dots,1/K]
w=[1/K,…,1/K] 时,卷积相当于信号序列的 简单移动平均(窗口大小为K),(这个名词有点想到了 ARIMA 模型,后续接触到 ARIMA 的学习后如果找到联系点会进行更新)。
简单来说,假如有一个长度为 n n n 的序列,经过一个长度为 m m m 的卷积核,卷积后的长度为 n − m + 1 n - m + 1 n−m+1:( y 1 y_1 y1 是第1个到第m个数据的加权和, y 2 y_2 y2 是第二个到第 m + 1 m+1 m+1 个数据的加权和,以此类推。。。)
2.2 二维卷积
给定一个图像
X
∈
R
M
×
N
X \in R^{M \times N}
X∈RM×N 和一个滤波器
W
∈
R
U
×
V
W \in R^{U \times V}
W∈RU×V,一般
U
<
M
,
V
<
N
U<M,V<N
U<M,V<N,其卷积为[^ 2]
y
i
j
=
∑
u
=
1
U
∑
v
=
1
V
w
u
v
x
i
−
u
+
1
,
j
−
v
+
1
y_{ij} = \sum\limits^U_{u=1}\sum\limits^V_{v=1}w_{uv}x_{i-u+1, j-v+1}
yij=u=1∑Uv=1∑Vwuvxi−u+1,j−v+1
输入信息
X
X
X 和滤波器
W
W
W 的 二维卷积 定义为
Y
=
W
∗
X
Y = W * X
Y=W∗X
其中
∗
*
∗ 表示卷积运算,千万不要当成了矩阵乘法。
如果还没懂的话,举个例子,给定一个图像
P
P
P,图像大小为 5x5,值随便设吧:
[
1
1
1
1
1
−
1
0
−
3
0
1
2
1
1
−
1
0
0
−
1
1
2
1
1
2
1
1
1
]
\begin{bmatrix} 1 & 1 & 1 & 1 &1 \\ -1 & 0 &-3 & 0 & 1 \\ 2 & 1 & 1 & -1 & 0 \\ 0 & -1 & 1 & 2 & 1 \\ 1 & 2 & 1 & 1 & 1 \end{bmatrix}
⎣⎢⎢⎢⎢⎡1−1201101−121−311110−12111011⎦⎥⎥⎥⎥⎤
定义二维卷积核
W
W
W:
[
1
0
0
0
0
0
0
0
−
1
]
\begin{bmatrix} 1 & 0 & 0 \\ 0 & 0 & 0 \\ 0 & 0 & -1 \end{bmatrix}
⎣⎡10000000−1⎦⎤
一一相乘,则卷积后的第一个值为:
y
11
=
[
1
1
1
−
1
0
−
3
2
1
1
]
∗
W
=
1
×
1
+
1
×
0
+
1
×
0
+
−
1
×
0
+
0
×
0
+
−
3
×
0
+
2
×
0
+
1
×
0
+
1
×
−
1
=
0
y_{11} = \begin{bmatrix} 1 & 1 & 1\\ -1 & 0 &-3 \\ 2 & 1 &1 \end{bmatrix} * W = 1 \times 1 + 1 \times 0 + 1 \times 0 + -1 \times 0 + 0 \times 0 + -3 \times 0 + 2 \times 0 + 1 \times 0 +1 \times -1 = 0
y11=⎣⎡1−121011−31⎦⎤∗W=1×1+1×0+1×0+−1×0+0×0+−3×0+2×0+1×0+1×−1=0
卷积后的矩阵为
[
0
−
2
−
1
2
2
4
−
1
0
0
]
\begin{bmatrix} 0 & -2 & -1 \\2 & 2 & 4 \\ -1 & 0 & 0 \end{bmatrix}
⎣⎡02−1−220−140⎦⎤
矩阵大小为
(
M
−
U
+
1
)
×
(
N
−
V
+
1
)
(M-U+1)\times(N-V+1)
(M−U+1)×(N−V+1) ,因此,5 x 5 的矩阵经过 3 x 3 的卷积核后,得到了一个 3 x 3 的矩阵。一幅图像经过卷积操作后得到的结果称为 卷积映射。
3. 卷积的变种
Padding:填充,在输入向量两端进行补零。
设卷积层的输入神经元个数为 M M M,卷积大小为 K K K,步长为 S S S,在输入两端填补 P P P 个0,那么卷积层的神经元数量为 ( M − K + 2 P ) / S + 1 (M-K+2P)/S + 1 (M−K+2P)/S+1
- 窄卷积:步长为1,两端不补零 P = 0 P=0 P=0,卷积后输出商都为 M − K + 1 M-K+1 M−K+1
- 宽卷积:步长为1,两端补零 P = K − 1 P=K-1 P=K−1,卷积后输出长度为 M + K − 1 M+K-1 M+K−1
- 等宽卷积:步长为1,两端补零 P = ( K − 1 ) / 2 P = (K-1) /2 P=(K−1)/2,卷积后输出长度为 M M M
二、卷积神经网络
卷积神经网络一般由卷积层、汇聚层和全连接层构成。
1. 卷积和全连接
在全连接前馈神经网络中,如果第 l l l 层由 M l M_l Ml 个神经元,第 l − 1 l-1 l−1 层由 M l − 1 M_{l-1} Ml−1 个神经元,连接边由 M l × M l − 1 M_l \times M_{l-1} Ml×Ml−1 个参数。当 M l M_l Ml 和 M l − 1 M_{l-1} Ml−1 都很大时,权重矩阵的参数非常多,训练效率会非常低。
如果采用卷积来代替全连接,第
l
l
l 层的净输入
z
(
l
)
z^{(l)}
z(l) 为第
l
−
1
l - 1
l−1 层活性值
a
(
l
−
1
)
a^{(l-1)}
a(l−1) 和卷积核
w
l
∈
R
K
w^{l} \in R^K
wl∈RK 的卷积,用
⨂
\bigotimes
⨂ 来表示卷积操作 :
z
(
l
)
=
w
(
l
)
⨂
a
(
l
−
1
)
+
b
(
l
)
z^{(l)} = w^{(l)} \bigotimes a^{(l-1)} + b^{(l)}
z(l)=w(l)⨂a(l−1)+b(l)
接下来解释本文开头提到的概念:
- 局部连接:在卷积层中每一个神经元都只和下一层中的某个局部窗口内的神经元相连,构成一个局部连接网络,由原来的 M l × M l − 1 M_l \times M_{l-1} Ml×Ml−1 个连接变为 M l × K M_l \times K Ml×K, K K K 为卷积核大小。
- 权重共享:作为参数的卷积核 W ( l ) W^{(l)} W(l) 对于第 l l l 层的所有的神经元都是相同的,权重共享可以理解为一个卷积核只捕捉输入数据中的一种特定的局部特征。因此如果需要提取多种特征就需要使用多个不同的卷积核。
- 特征映射:一幅图像在经过卷积提取到的特征,每个特征可以作为一类抽取的图像特征,为了提高卷积网络的表示能力,可以在每一层使用多个不同的特征映射。
2. 汇聚层
也叫子采样层,或者池化层。其作用是进行特征选择,降低特征数量,从而减少参数数量。或者说,你可以理解成超清4K变成马赛克的过程。
常用的汇聚函数有两种:
- 最大汇聚:对于一个区域,选择这个区域内所有神经元的最大活性值作为这个区域的表示,比如一个 (2,2)的最大汇聚:
[ 5 4 3 7 ] ⇒ m a x P o o l i n g 7 \begin{bmatrix} 5 & 4 \\ 3 & 7 \end{bmatrix} \xRightarrow[max]{Pooling} 7 [5347]Poolingmax7
- 平均汇聚:取区域内所有神经元活性值的平均值,还是以上个例子为例:
[ 5 4 3 7 ] ⇒ m e a n P o o l i n g 4.75 \begin{bmatrix} 5 & 4 \\ 3 & 7 \end{bmatrix} \xRightarrow[mean]{Pooling} 4.75 [5347]Poolingmean4.75
在末尾会用 keras
实例来实现一个简单的卷积神经网络。
3. 卷积网络的结构
目前常用的卷积网络由卷积块和全连接层构成,一个 卷积块 为连续 M M M 个卷积层(通常使用 ReLU 激活函数) 和 b b b 个汇聚层,一个卷积网络中可以堆叠 N N N 个连续的卷积块,然后后面接着 K K K 个全连接层。
目前,卷积网络的整体结构趋向于使用更小的卷积核以及更深的结构。
4. 卷积神经网络的反向传播算法
4.1 汇聚层
当第
l
−
1
l-1
l−1 层是汇聚层时,因为汇聚层是下采样操作,
l
−
1
l-1
l−1 层的每个神经元的误差项
δ
\delta
δ 对应于第
l
l
l 层的相应特征映射的一个区域,
l
l
l 岑的第
p
p
p 个特征映射中的每个神经元都有一条边和
l
−
1
l-1
l−1 层的第
p
p
p 个特征映射中的一个神经元相连,根据链式法则,第
l
l
l 层的一个特征映射的误差项
δ
(
l
,
p
)
\delta ^{(l, p)}
δ(l,p),只需要将
l
+
1
l+1
l+1 层对应的特征映射的误差项
δ
(
l
+
1
,
p
)
\delta ^{(l+1, p)}
δ(l+1,p) 进行上采样操作,再和
l
l
l 层特征映射的激活值偏导数逐元素相乘,就得到了
δ
(
l
,
p
)
\delta ^{(l, p)}
δ(l,p),第
l
l
l 层的第
p
p
p 个特征映射的误差项
δ
(
l
,
p
)
\delta ^{(l, p)}
δ(l,p) 的具体推导过程如下:
δ
(
l
,
p
)
=
∂
L
∂
Z
(
l
,
p
)
=
∂
X
(
l
,
p
)
∂
Z
(
l
,
p
)
∂
Z
(
l
+
1
,
p
)
∂
X
(
l
,
p
)
∂
L
∂
Z
(
l
+
1
,
p
)
=
f
l
′
(
Z
(
l
,
p
)
)
⨀
u
p
(
δ
(
l
+
1
,
p
)
)
\begin{aligned} \delta ^{(l, p)} &= \frac{\partial L}{\partial Z^{(l, p)}} \\ &= \frac{\partial X^{(l, p)}}{\partial Z^{(l, p)}} \frac{\partial Z^{(l+1, p)}}{\partial X^{(l, p)}} \frac{\partial L}{\partial Z^{(l+1, p)}} \\ &= f_l'(Z^{(l, p)}) \bigodot up (\delta^{(l+1, p)}) \end{aligned}
δ(l,p)=∂Z(l,p)∂L=∂Z(l,p)∂X(l,p)∂X(l,p)∂Z(l+1,p)∂Z(l+1,p)∂L=fl′(Z(l,p))⨀up(δ(l+1,p))
f l ′ f_l' fl′: 为第 l l l 层使用的激活函数
u p up up:上采样函数
与汇聚层中使用的下采样操作刚好相反,如果下采样是 最大汇聚,误差项 δ ( l + 1 , p ) \delta ^{(l+1, p)} δ(l+1,p) 中每个值会直接传递到上一层对应区域中的最大值所对应的神经元,该区域内中其他神经元的误差项都设为0,如果下采样是 平均汇聚,误差 δ ( l + 1 , p ) \delta ^{(l+1, p)} δ(l+1,p) 中每个值会被平均分配到上一层对应区域中的所有神经元上。
4.2 卷积层
当
l
+
1
l + 1
l+1 层为卷积层时,假设特征映射净输入
Z
(
l
+
1
)
∈
R
M
′
×
N
′
×
P
Z^{(l+1)} \in R^{M' \times N' \times P}
Z(l+1)∈RM′×N′×P ,其中第
p
(
1
≤
p
≤
P
)
p(1 \le p \le P)
p(1≤p≤P) 个特征映射净输入
Z
(
l
+
1
,
p
)
=
∑
d
=
1
D
W
(
l
+
1
,
p
,
d
)
⨂
X
(
l
,
d
)
+
b
(
l
+
1
,
p
)
Z^{(l+1, p)} = \sum\limits^D_{d=1} W^{(l+1, p, d)} \bigotimes X^{(l, d)} + b^{(l+1, p)}
Z(l+1,p)=d=1∑DW(l+1,p,d)⨂X(l,d)+b(l+1,p)
在这里,你应该对CNN有了一个基本的概念,可以做一些简单的图像分类任务,但如果要向计算机视觉方向发展,了解这些还远远不够。
三、基于卷积神经网络的手写数字识别
在前一篇文章中已经使用了基于 Keras
的全连接神经网络来进行手写数字识别,训练200次后,其准确率达到了 95.59%
接下来使用卷积神经网络来进行识别,看看准确率是否会有提升(对神经网络基础不熟的可以看看我的上篇文章(BP神经网络的简单实现):
from keras.layers import *
from keras.models import *
# 这里依旧使用序贯模型(不清楚这个的可以去看我上一篇文章)
model = Sequential()
# 加入3个卷积层和2个最大池化层,激活函数通常都是使用的ReLu
# 卷积核大小为 3x3,池化区域为 2x2,对上文概念不清楚的可以通过这些代码示例来了解它的工作流程
# Conv2D的第一个参数是使用的过滤器的数量,即使用32个不同的特征映射
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
# 看看现在网络的结构
model.summary()
输出
:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 32) 320
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 11, 11, 64) 18496
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 64) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 3, 3, 64) 36928
=================================================================
Total params: 55,744
Trainable params: 55,744
Non-trainable params: 0
_________________________________________________________________
经过第一个 3x3 的卷积核后,图像由之前的 28x28 变成了 26x26,即是 28 − 3 + 1 = 26 28-3+1=26 28−3+1=26 ,接下来进入 2x2 的池化区域(
max_pooling2d
),每一个 2x2 的区域都转换成了它的最大值,即 2x2 变成了 1x1,所以它的维度减少了一半。
# Flatten 的作用是将输入展平,可以看到之前图像已经变成了3x3x64的形状
model.add(Flatten())
# 加入全连接层,依旧使用的 ReLU 激活函数
model.add(Dense(64, activation='relu'))
# 多分类问题,手写数字有10个类别,因此输出层神经元设为10,激活函数为 softmax
model.add(Dense(10, activation='softmax'))
# 再来看看现在的网络结构
model.summary()
网格结构:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 32) 320
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 11, 11, 64) 18496
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 64) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 3, 3, 64) 36928
_________________________________________________________________
flatten (Flatten) (None, 576) 0
_________________________________________________________________
dense (Dense) (None, 64) 36928
_________________________________________________________________
dense_1 (Dense) (None, 10) 650
=================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________
模型搭建完成,开始导入数据:
from keras.datasets import mnist
from keras.utils.np_utils import to_categorical
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
# 编译网络,损失函数设为交叉熵损失函数,评分为准确率,优化器为 RMSprop
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
# 开始训练,训练5次,每次更新64批
his = model.fit(train_images, train_labels, epochs=5, batch_size=64)
看看训练效果:
Epoch 1/5
938/938 [==============================] - 31s 21ms/step - loss: 0.3822 - accuracy: 0.8751
Epoch 2/5
938/938 [==============================] - 20s 21ms/step - loss: 0.0508 - accuracy: 0.9846
Epoch 3/5
938/938 [==============================] - 19s 21ms/step - loss: 0.0317 - accuracy: 0.9899
Epoch 4/5
938/938 [==============================] - 19s 21ms/step - loss: 0.0260 - accuracy: 0.9920
Epoch 5/5
938/938 [==============================] - 21s 22ms/step - loss: 0.0186 - accuracy: 0.9939
可以看出五次之后,损失函数已经降到了0.0186,准确率高达99.39%,之前使用全连接神经网络训练50次准确率才达到95.59%,因此可以看出卷积神经网络在图像识别比全连接神经网络具有先天的优势。(你也可以将卷积神经网络训练更多次以求得更好的识别效果,但没必要,我将该卷积神经网络训练50次后其准确率大概提升到了99.7%,但其实15次之后准确率已经开始上下波动了,5次的时候其提升效果最明显,再往后面可能就会产生过拟合问题了
。
四、参考文献
[^ 1] :[美]弗朗索瓦·肖莱,《Python深度学习》[M],北京市丰台区成寿寺路11号;人民邮电出版社,2018年8月第一版.
[^ 2] :邱锡鹏,《神经网络与深度学习》[M],北京市西城区百万庄大街22号;机械工业出版社,2020年6月第一版