卷积神经网络
这篇博客主要介绍更复杂的网络结构怎样去实现?在上一篇博客我们实现了一个非常简单的串行的神经网络结构,里面用了两个卷积层,两个池化层,然后是一个全连接层,最后得到相应的输出。
下面我们看一种更为复杂的,不是串行结构的网络。第一种网络结构是 GoogleNet(如下图),其中蓝色块表示卷积,红色块表示池化,黄色块表示 Softmax,绿色的块表示其它比如拼接层等等。
那么我们看到这样一种网络结构,很多小伙伴可能就傻眼了,如果按照之前的方法一个一个去定义,什么时候才能把这个网络写完,写完之后代码量可想而知。我们在定义网络的时候,在编程语言中,想要减少代码量首先就得减少代码冗余,减少代码冗余有两种常用的方法:第一种在面向过程的程序设计中使用函数减少代码冗余,第二种就是在面向对象的程序设计中使用类减少代码冗余。
那么我们首先得观察一下这个网络中是否存在相似的结构以致于把这些相似的结构封装成函数或类。
上面用红框圈出的部分都是相同的,那么我们就可以将该部分封装起来。这个部分在 GoogleNet 中叫做 Inception。下面我们就来看看 Inception 模块是怎样实现的?下面是该模块的图示,该模块主要由四个分支构成,每个分支中使用了不同的 kernel-size。
为什么 Inception 要设计成这样呢?因为我们在构造神经网络的时候有一些超参数是很难选择的,比如卷积核的大小。所以这个 GoogleNet 的出发点就是不知道哪个 kernel-size 最好,那么就在一个模块里面把几种卷积核都使用一遍,然后将它们的结果叠加在一起,假设 3x3 的卷积核比较好,相应的 3x3 的权重就会大一些,其它的权重就会变小。所以它是提供了几种候选的卷积神经网络的配置,将来通过训练自动的找出当前最优的卷积的组合。
我们再来看看模块中的 Concatenate ,它的意思是将张量进行拼接。如下图所示,在进行 Concatenate 之前输出的是四个张量,我们需要把这些张量拼接成一个张量。而且做 Concatenate 时,必须要保证每个图像的宽度和高度是相同的。比如说,我们最后输出的张量都是 ( b , c , w , h ) (b, c, w, h) (b,c,w,h),那么从下图中四条路径求出的张量唯一不同的可以是 c c c,但是 w w w 和 h h h 必须保持一致。
我们可以观察到,上图中 1x1 的卷积核比较多。下面我举一个简单的例子,展示 1x1 的卷积核的计算过程。下面有一个单层的通道和一个 1x1 的卷积核做卷积,那么计算如下:
如果输入有多个通道,那么就需要给每个通道配一个 1x1 的卷积核。
注意做完卷积之后,还需要进行求和。
从上面的结果我们可以得出,我们从一个 3 通道的输入变成了一个 1 通道的输出,所以 1x1 的卷积核主要的工作就是改变通道的数量,它可以从
C
1
C_1
C1 变为
C
2
C_2
C2,你可能就会有这样的疑问,为什么要使用 1x1 的卷积核,直接做卷积不就可以了?
下面我们来看看为什么要使用 1x1 的卷积核?首先我们看这么一个运算,我们的输入有 192 个通道,图像大小为 28x28,然后我们用一个 5x5 的卷积核做卷积。
那么最后要进行浮点数的运算如下:
那么进行一个卷积运算需要 1亿2千万次运算。我们在神经网络中,实际上我们最大的问题就是运算量太大,我们经常需要想办法降低运算量,所以就提出了用 1x1 的卷积核来降低运算量。还是之前的输入和输出,只不过在中间加了一层 1x1 的卷积。
看上去好像比之前的网络结构更加的复杂,但是我们实际上看一下运算量。
最后计算量为 1千200万,是之前运算量的 1/10。
下面我们来看看 Inception 到底是怎样实现的?用具体的代码来分析。为了方便观察,我们将图旋转了一下,来看每一个分支怎样来实现。
这里面需要注意一点,由于每个分支最后输出的张量的
w
w
w 和
h
h
h 一定要是相同的,所以在做卷积的时候,一定要保证卷积前后图像大小不变,所以在用 3x3 的卷积核做卷积时,需要加上 padding=1;用 5x5 的卷积核做卷积时,需要加上 padding=2。
最后,我们需要将四个途径的输出沿着通道的维度拼接成一个张量,这个拼接的过程就是 Concatenate。
Concatenate 的代码如下:
代码中的 dim=1 表示沿着通道数进行拼接,因为最后输出的四个张量都是
(
b
,
c
,
w
,
h
)
(b, c, w, h)
(b,c,w,h),
b
b
b 为 0,
c
c
c 为 1,
w
w
w 为 2,
h
h
h 为 3。
我们将整个 Inception 的代码整合到一起就得到了 Inception 类:
构造网络的代码如下:
下面我们介绍另外一种使用非常多的主干网络叫做 Residual net,它的思想就是我们把 3x3 的卷积一直不停的堆下去,它的性能会不会变好?他们做实验发现,对于 CIFAR-10 数据集来说,在测试数据集中 20 层的卷积的性能要比 56 层的卷积要好,在测试数据集中也是如此。
当然这有可能是因为堆积了 50 层,模型变得越复杂,可能是发生了过拟合或其它一些情况,其中有一种可能叫做梯度消失。
那怎么解决梯度消失的问题呢?
平时的神经网络就是一层叠一层,首先是权重层,然后经过激活就会得到输出。
在 Residual net 中,加了一个跳连接。
为什么说加了一层跳连接就可以解决梯度消失的问题呢?因为
H
(
x
)
=
F
(
x
)
+
x
H(x) = F(x) + x
H(x)=F(x)+x,当在求
H
(
x
)
H(x)
H(x) 对
x
x
x 的导数时,当
F
(
x
)
F(x)
F(x) 对
x
x
x 的导数很小时,趋近于 0,但是后面加了一个
x
x
x 对
x
x
x 的导数 1,所以
H
(
x
)
H(x)
H(x) 对
x
x
x 的导数整体就趋近于 1,很好的解决了梯度消失的问题。
下面我就在 MNIST 数据集上添加相应的 Residual Network,在做 Residual 时,必须保证输入和输出的维度是一样的。
下面我们看上面流程图中 Residual Block 怎么来实现?
构造好 Residual Block 之后,我们就可以在网络中添加 Residual Block。
在下面这篇论文中有很多关于 Residual Block 的设计。
比如说:
具体代码见 14 卷积神经网络(进阶).ipynb