番外篇2.2:图像处理与深度学习-CNN的发展和结构
ok,我可是没有暂停啊!
在说cnn之前,要先总结卷积层、池化层、**函数、全连接层等层的作用。
0.1 卷积层
卷积层其实就是对输入进来的图像做卷积,这层和上一层不是全连接,而是只有卷积核大小的区域连接,它的作用是模拟不同神经元对同样的刺激会有不同的反应,这个反应就是卷积,不同点就在于卷积核的不同。
卷积的时候会使图像缩小,为了方便并且不丢失边缘区域的信息,我们在图像外面加几圈0(3*3卷积核就加一圈,5*5就加两圈,以此类推),保证处理后的图像大小不变。而有时候我们扫描的是一个三维图像(rgb),我们所用的卷积核也是一个三维的,可能对每个通道信息的刺激反应也不相同。
卷积核和全连接层相比,一个显著的优点就是省内存。(因为连接的比较少嘛)
还有经过卷积核卷积后的图片叫做特征图,理论上卷积核越多,特征图越多,精度也会越高,不过运算量也会相应增大。具体多少个合适呢?具体情况具体分析,我不太清楚,不敢乱说,希望知道的人可以解答一下w
0.2 池化层
听着还是很厉害的,不过以2*2的池化层举例,其实就是2*2的矩阵并成一个元素,有下采样、上采样、平均采样等多种采样方法,具体操作如名字所示,取最大最小平均值。
池化层的作用,首先我认为肯定有缓解内存压力的作用,这个操作不影响channel数,而且可以较为高效的进行数据压缩。
0.3 全连接层
全连接层的每一个输出与上层的每一个节点相连,用来把前边提取到的特征综合起来。由于其全相连的特性,一般全连接层的参数也是最多的。它将从父层(特征图)那里得到的高维数据铺平以作为输入、进行一些非线性变换(用**函数作用)、然后将结果输进跟在它后面的各个普通层构成的系统中。
很多地方说,全连接层在整个cnn中起到了分类器的作用,深以为然。然而全连接层参数冗余,(占整个网络80%左右)ResNet等已经使用了全局平均池化取代FC,来融合深度特征,仍用softmax等损失函数作为目标函数来指导学习过程。这种方法通常也有较好的预测性能。好像也有用svd来减少参数的。
0.4 **函数
**函数主要有sigmoid,tanh,ReLU三种,还有ReLU的几个变体。
sigmoid函数是f(x) = 1/(1+exp(-x)),由于其单增以及反函数单增等性质,Sigmoid函数常被用作神经网络的阈值函数,将变量映射到0,1之间。这个函数曾经比较流行,可以想象成一个神经元的放电率,导数大的位置为敏感区,平滑的地方叫抑制区。
当输入稍微远离坐标原点,函数梯度变得很小,况且还可能经过很多个sigmod函数,最后会导致权重w对损失函数几乎没影响,这种情况叫做梯度饱和(梯度弥散)。
还有此函数输出不是以0为中心的,这样会使权重更新效率降低。不过有一个变体,是把这个值减0.5,不过上一个问题还是没法解决。而且指数运算对于计算机来说也很慢。
tanh就是双曲正切,f(x) = (exp(x)-exp(-x))/(exp(x)+exp(-x))。
它把变量映射到-1,1之间,除了输出是以0为中心以外,几乎包括了sigmoid函数的所有缺点。
ReLU函数是f(x)=max(0,x),目前算是比较火的**函数。它有一些优点:
当输入为正数的时候,不存在梯度饱和问题;
只有线性变化,计算快很多。
当然,缺点也是有的:
当输入是负数的时候,ReLU是完全不被**的,这就表明一旦输入到了负数,ReLU就会死掉。这样在前向传播过程中,还不算什么问题,有的区域是敏感的,有的是不敏感的。但是到了反向传播过程中,输入负数,梯度就会完全到0,这个和sigmod函数、tanh函数有一样的问题;
我们发现ReLU函数的输出要么是0,要么是正数,这也就是说,ReLU函数也不是以0为中心的函数。
第一个问题,有几种变体可以解决:
首先有PReLU,函数是f(x)=max(ax,x),这个a是一个很小的数,保证不会死掉就可以。当a=0.01,叫做Leaky ReLU,当a从高斯分布中随机产生,叫Random ReLU。
还有ELU,函数是f(x)=a(exp(x)-1) (x<=0), x (x>0).。这种方法平均**均值趋近与0,对噪声更有鲁棒性。但由于引入了指数,所以计算量又变大了。当ELU乘一个系数,就变成了SELU,本质上差不多。
还有swish和maxout,我还没搞太懂,不过公式可以给出来:
swish是f(x) = x*sigmoid(βx),这个β可以是训练的参数,也可以是常数。Swish 具备无上界有下界、平滑、非单调的特性。Swish 在深层模型上的效果优于 ReLU。例如,仅仅使用 Swish 单元替换 ReLU 就能把 Mobile NASNetA 在 ImageNet 上的 top-1 分类准确率提高 0.9%,Inception-ResNet-v 的分类准确率提高 0.6。
Maxout可以看做是在深度学习网络中加入一层**函数层,包含一个参数k.这一层相比ReLU,sigmoid等,其特殊之处在于增加了k个神经元,然后输出**值最大的值.
优点:
Maxout的拟合能力非常强,可以拟合任意的凸函数。
Maxout具有ReLU的所有优点,线性、不饱和性。
同时没有ReLU的一些缺点。如:神经元的死亡。
缺点:
从上面的**函数公式中可以看出,每个神经元中有两组(w,b)参数,那么参数量就增加了一倍,这就导致了整体参数的数量激增。
(出处:https://www.cnblogs.com/makefile/p/activation-function.html)
好好好,直接进入主题:
1 LeNet5
为啥叫LeNet5呢?因为这个神经网络只有5层,然后提出的人叫LeCun(1994提出)。
这个神经网络的组成如图下:
这个神经网络首次引入卷积层,但它的池化器是下采样的。后面的cnn多以卷积+池化+非线性**作为典型特征,人称机器学习三剑客。
2 AlexNet
AlexNet是2012 ImageNet比赛的冠军,深度为8层。
它使用ReLU作为非线性**函数,使用了后面常用的最大池化。而且使用了dropout来防止过拟合,用softmax作为输出层。
图像分类的样本都是将分类对象置于中心的,虽然池化考虑到了特征的相对空间关系,但是物体有太大的平移或旋转时分类效果难以保证,所以有时候训练时需要做数据扩增。AlexNet也使用了数据扩增。而且还利用了GPU来缩短了训练时间。
3 VGG
VGG是2014年的ImageNet分类的亚军,物体检测冠军,使用了更小的卷积核(3x3),并且连续多层组合使用。
这篇论文的重要结论就是,越深精度就越高。
连续3个3x3的卷积层(步长1)能获得和一个7x7的卷积层等效的感知域(receptive fields),而深度的增加在增加网络的非线性时减少了参数(3*3^2 vs 7^2)。自VGG之后,大家都在用小卷积层,甚至有人分解卷积核。
但是,VGG只是简单的堆叠卷积层,且卷积核太深(最多达512),特征太多,导致其参数猛增,搜索空间太大,正则化困难,因而其精度也并不是最高的,在推理时也相当耗时,和GoogLeNet相比性价比十分之低。
4 GoogleNet
引入了一个概念,叫做Inception module,一个输入进来,直接分四步进行处理,这四步处理完后深度直接进行叠加。在不同的尺度上对图片进行操作。大量运用1*1的convolution,可以灵活控制输出维度,极大地降低了参数数量。这个模块是GoogleNet以更深的网络和更高的效率取得更好的结果的重要原因。
这个模块后面也有改善,Inception-v2使用了Batch Normalization;Inception-v3有两方面改进,一是像VGG一样,使用了连续的3x3卷积核代替更大的卷积核;另一方面进一步使用了Depthwise Convolution分解卷积。
回到正题,这个网络把正确率升高到95%-96%,比人类要高。
def Conv2d_BN(x, nb_filter,kernel_size, padding='same',strides=(1,1),name=None):
if name is not None:
bn_name = name + '_bn'
conv_name = name + '_conv'
else:
bn_name = None
conv_name = None
x = Conv2D(nb_filter,kernel_size,padding=padding,strides=strides,activation='relu',name=conv_name)(x)
x = BatchNormalization(axis=3,name=bn_name)(x)
return x
def Inception(x,nb_filter):
branch1x1 = Conv2d_BN(x,nb_filter,(1,1), padding='same',strides=(1,1),name=None)
branch3x3 = Conv2d_BN(x,nb_filter,(1,1), padding='same',strides=(1,1),name=None)
branch3x3 = Conv2d_BN(branch3x3,nb_filter,(3,3), padding='same',strides=(1,1),name=None)
branch5x5 = Conv2d_BN(x,nb_filter,(1,1), padding='same',strides=(1,1),name=None)
branch5x5 = Conv2d_BN(branch5x5,nb_filter,(1,1), padding='same',strides=(1,1),name=None)
branchpool = MaxPooling2D(pool_size=(3,3),strides=(1,1),padding='same')(x)
branchpool = Conv2d_BN(branchpool,nb_filter,(1,1),padding='same',strides=(1,1),name=None)
x = concatenate([branch1x1,branch3x3,branch5x5,branchpool],axis=3)
return x
def GoogLeNet():
inpt = Input(shape=(224,224,3))
#padding = 'same',填充为(步长-1)/2,还可以用ZeroPadding2D((3,3))
x = Conv2d_BN(inpt,64,(7,7),strides=(2,2),padding='same')
x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)
x = Conv2d_BN(x,192,(3,3),strides=(1,1),padding='same')
x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)
x = Inception(x,64)#256
x = Inception(x,120)#480
x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)
x = Inception(x,128)#512
x = Inception(x,128)
x = Inception(x,128)
x = Inception(x,132)#528
x = Inception(x,208)#832
x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)
x = Inception(x,208)
x = Inception(x,256)#1024
x = AveragePooling2D(pool_size=(7,7),strides=(7,7),padding='same')(x)
x = Dropout(0.4)(x)
x = Dense(1000,activation='relu')(x)
x = Dense(1000,activation='softmax')(x)
model = Model(inpt,x,name='inception')
return model
(代码抄自:https://www.cnblogs.com/skyfsm/p/8451834.html)
5 ResNet
ResNet由何凯明何博士在2015提出。ResNet解决了深度CNN模型难训练的问题。
当网络过深时,网络的准确度会出现饱和,甚至下降。我们知道深层网络存在着梯度消失或者爆炸的问题,这使得深度学习模型很难训练。
ResNet提出了一种减轻网络训练负担的残差学习框架,这种网络比以前使用过的网络本质上层次更深。其明确地将这层作为输入层相关的学习残差函数,而不是学习未知的函数。学习残差与原始特征相比更容易,也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。这种方式又称为短路连接。
ResNet直接使用stride=2的卷积做下采样(卷积同时也缩小了图像),并且用global average pool层替换了全连接层。ResNet直接使用stride=2的卷积做下采样,并且用global average pool层替换了全连接层。
抄了一下ResNet50在tensorflow上的实现,改成了keras的,会方便好多~
不过这个代码规范性太差了,我改动了一下(源代码网址:https://blog.csdn.net/u013709270/article/details/78838875):
def Conv2d_BN(x, nb_filter,kernel_size, strides=(1,1), padding='same',name=None):
if name is not None:
bn_name = name + '_bn'
conv_name = name + '_conv'
else:
bn_name = None
conv_name = None
x = Conv2D(nb_filter,kernel_size,padding=padding,strides=strides,activation='relu',name=conv_name)(x)
x = BatchNormalization(axis=3,name=bn_name)(x)
return x
def Conv_Block(inpt,nb_filter,kernel_size,strides=(1,1), with_conv_shortcut=False):
x = Conv2d_BN(inpt,nb_filter=nb_filter[0],kernel_size=(1,1),strides=strides,padding='same')
x = Conv2d_BN(x, nb_filter=nb_filter[1], kernel_size=(3,3), padding='same')
x = Conv2d_BN(x, nb_filter=nb_filter[2], kernel_size=(1,1), padding='same')
if with_conv_shortcut:
shortcut = Conv2d_BN(inpt,nb_filter=nb_filter[2],strides=strides,kernel_size=kernel_size)
x = add([x,shortcut])
return x
else:
x = add([x,inpt])
return x
def ResNet50():
inpt = Input(shape=(224,224,3))
x = ZeroPadding2D((3,3))(inpt)
x = Conv2d_BN(x,nb_filter=64,kernel_size=(7,7),strides=(2,2),padding='valid')
x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)
x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3),strides=(1,1),with_conv_shortcut=True)
x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)
x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)
x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)
x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3))
x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3))
x = AveragePooling2D(pool_size=(7,7))(x)
x = Flatten()(x)
x = Dense(1000,activation='softmax')(x)
model = Model(inputs=inpt,outputs=x)
return model
6 DenseNet
这种架构实际上也是运用了ResNet中短路的思想,它任何一层的输出都可以作为该层之后任何一层的输入。不同的是,它的多个输入是进行拼接。为了解决拼接后维数过大的问题,使用1*1卷积核进行降维。DenseNet可以充分利用不同卷积层所产生的特征值,参数量进一步降低。不过内存占用好多!恐怖如斯
(不过我还没搞太懂~)
需要明确一点,dense connectivity 仅仅是在一个dense block里的,不同dense block 之间是没有dense connectivity的,比如下图所示。
学习了一下大牛们的博客、包括做的项目、比赛,实际上很多都是调用成熟的结构,训练自己的参数,调整一些层、参数等,或者采用两种架构自己来做集成工作,加入一些小tricks等,还是蛮有意思的。。希望自己也能有机会摸摸这方面的东西~
后面可能会总结图像检测中深度学习方面的东西,包括R-CNN等~
上一篇: libvlc 使用mediaplayer 播放rtsp
下一篇: Oracle基础操作