本专题全是用manet实现的,书籍是动手学深度学习,我想换个角度来分析卷积神经网络,最后有对应的论文和我写的源码,都在我的GIthub上。
接下来的文章中都分为三个问题:解析网络结构,使用的一些技巧,一些总结。当然会有一些自问自答的环节,希望可以尽量清除描述别人的工作(有不同的观点请留言哈)。
图片来自于论文,阅读它可以加强理解。
1 卷积神经网络解决的问题?
答:我想有两个主要的问题。
- 对图像而言,保留相邻像素在垂直和水平方向的信息(保留两个方向上的相关性),比如如果我们将一张图像拉成行向量,势必会丢失垂直方向上的信息,而深度卷积网络输入的是图像,这一点是人们希望的。
- 对于大尺寸的图像,全连接层极容易造成模型过大,占用大量的显存和计算资源。比如,310001000图像,全连接Dense(256),将会占3G显存,这是低效的。通过卷积核可以很好的避免这个问题(深度网络最出彩的就是卷积)。
2 如何比较深刻的了解一个网络?
我觉得至少在学习后,可以大致的把结构讲出来,包括
这个网络有多少层?
用了什么块结构?有什么特点?输入的是什么,输出的是什么?
每层的kernel_size, pool_size, padding, strides
网络有什么特点?
有没有一些trick?
3 LeNet网络结构
- LeNet网络结构比较简单,我们可以划分成三个大块,如图所示,不妨认为是块1, 块2,块3。块1表示一个卷积+一个最大池化,同理块2也是这样的,块三是连续三个全连接层。
- 激活函数是sigmoid,现在relu用的多,因为sigmoid接近0或1有梯度消失和爆炸的情形
- 深度网路是一系列卷积池化堆叠起来的,其中还有一个重要的思想就是模块化操作,正如块1,块2是一摸一样,这也是搭建复杂网络的一个手段(简单的块堆叠)。
上面两个图表示这个结构的框架,第二张图是MxNet的表示,也很形象。接着再进一步分析下。
3.1 LeNet简析
由于是第一次,我会把基本概念简单的重复下,不过我建议,看看《动手学深度学习》第五章106-120页,下面是开源电子书
http://zh.d2l.ai/chapter_convolutional-neural-networks/conv-layer.html
3.2 基本术语
- 网络层数: 卷积层的个数+全连接层。池化层是紧贴最近一个卷积层,不考虑它。这里的话就是1+1+3=5哈
- padding(填充):矩阵有四条边,一般情况下补的是0,理论上pdding有四种。举个例子,5*3的矩阵在右边补了一列0,变成5*4。但是为了计算方便,默认左右补的个数一样,同理上下补的个数也一样,注意:而且进一步,四个方向补的个数都一样,这样也就是补了多少圈零,这是默认的意思,代码中默认为0,即不周围补0
- stride(步幅): 有两个方向,不妨记横着每次跨步为 s w s_w sw, s竖着每次跨步为 s h s_h sh
为了计算方便,记左右补0总数为 p w p_w pw, 记上下补0总数为 p h p_h ph,让我们看看padding和stride的物理意义。
- 输入补[ p w p_w pw, p h p_h ph],输出的宽和高对应增加 p w p_w pw, p h p_h ph
- stride可以以stride倍减少输入尺寸,举例,输入10*10,stride=5,输出2*2。当然有准确的输入到输出计算公式,这里只是比方以stride倍减少。注意:降低模型的复杂度,也是stride存在的意义
重要结论,输入尺寸,padding,stride,输出尺寸的数学表达式
⌊
(
n
h
−
k
h
+
p
h
+
s
h
)
/
s
h
⌋
×
⌊
(
n
w
−
k
w
+
p
w
+
s
w
)
/
s
w
⌋
\lfloor(n_h-k_h+p_h+s_h)/s_h\rfloor \times \lfloor(n_w-k_w+p_w+s_w)/s_w\rfloor
⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋
上面符号是向下取整,很明显可以看出,
s
w
s_w
sw和
s
h
s_h
sh在分母上。
其中
n
h
n_h
nh,
n
w
n_w
nw表示高,宽;
k
h
k_h
kh,
k
w
k_w
kw表示卷积核高,宽,进一步卷积核取奇数且是正方形的
**还要注意的是,
p
h
p_h
ph,
p
w
p_w
pw是上下,左右总和,而padding只是一边的,所以
p
h
p_h
ph=2padding,
p
w
p_w
pw=2padding, **
接下来很多网路都要用到,用到麻木为止。
3.3 逐层解析
我补充了3.2节,不然我觉得既浪费你的时间,也浪费我的时间。
看图说话,
块1的卷积层,输入32*32,输出6@28*28,想想padding,kernel_size,strides是多少
不难看出减少了4*4,不妨分析一个方向。
- 显然strides=1,不为1的话,会以strides倍较少,若为2,结果接近16*16,显然不可能
- 进而-kernel_size+2*0+1=4, 显然kernel_size=5,符合预期的奇数
- 为什么断定padding=0,因为不告诉你默认是0,这是代码的默认行为padding=0,strides=1,不要补,一步一步走
块1的池化层输入6@28*28,输出6@14*14
4. 显然寸尺减半,strides=2, 也就是(28-2+2*0+2)/2=14
5. 池化层用pool_size代替kernel_size,实质一样的,上面的公式同样适用
块2的卷积层,池化层, 发现卷积层减少4,池化层减半,也就是和块1的参数一摸一样
三个全连接层,全连接层只有一超参,神经元的个数,这里分别是120,84,10(10分类,直接和问题挂钩),至于120,84想怎么改就怎么改,还可以去掉120层或84层或全去掉,反正提高test acc。
案例输出:符合预期(下面输入的28*28,数据集不一样),也当一个练习啦
# 一个样本测试(1*1*28*28)
X = nd.random.uniform(shape=(1, 1, 28, 28))
net.initialize()
for layer in net:
X = layer(X)
print(layer.name, 'output shape:\t', X.shape)
3.4 更进一步
我么看看上面的结果,可以认为池化和最靠近的卷积是一层。这样的话,看看pool0,conv1,pool2, dense0
- pool0: 6*12*12=864
- conv1: 16*8*8=1024
- 这两个数值相差不大,举个例子,两个若相等,则第二层通道数是13.5,接下来调参。
- pool1: 16*4*4-256
- dens0:120
- 同理这两个相差也不大
之前,一直没解释通道数这个超参,既然是超参,就没有严格的理论支持,有一些经验。
从上面可以发现,尺寸变小,其通道数会增大,这一点是多数是符合的,但是增加多少,那就要调参了,不过一个初始的想法就是让相邻层变化不大,正如上面的描述。
4 总结
最重要的东西
⌊
(
n
h
−
k
h
+
p
h
+
s
h
)
/
s
h
⌋
×
⌊
(
n
w
−
k
w
+
p
w
+
s
w
)
/
s
w
⌋
\lfloor(n_h-k_h+p_h+s_h)/s_h\rfloor \times \lfloor(n_w-k_w+p_w+s_w)/s_w\rfloor
⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋
- 输入补[ p w p_w pw, p h p_h ph],输出的宽和高对应增加 p w p_w pw, p h p_h ph
- stride可以以stride倍减少输入尺寸,举例,输入10*10,stride=5,输出2*2。当然有准确的输入到输出计算公式,这里只是比方以stride倍减少。注意:降低模型的复杂度,也是stride存在的意义
- 尺寸变小,其通道数会增大,这一点是多数是符合的
- 简单块的堆叠是实现复杂网络的一个手段
5 源码和论文
欢迎访问我的Giuhub,
https://github.com/ve2102388688/myMXNet