欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

番外篇2.2:图像处理与深度学习-CNN的发展和结构

程序员文章站 2022-07-07 11:10:28
...

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提出)。
这个神经网络的组成如图下:
番外篇2.2:图像处理与深度学习-CNN的发展和结构
番外篇2.2:图像处理与深度学习-CNN的发展和结构
这个神经网络首次引入卷积层,但它的池化器是下采样的。后面的cnn多以卷积+池化+非线性**作为典型特征,人称机器学习三剑客。

2 AlexNet

AlexNet是2012 ImageNet比赛的冠军,深度为8层。
它使用ReLU作为非线性**函数,使用了后面常用的最大池化。而且使用了dropout来防止过拟合,用softmax作为输出层。
图像分类的样本都是将分类对象置于中心的,虽然池化考虑到了特征的相对空间关系,但是物体有太大的平移或旋转时分类效果难以保证,所以有时候训练时需要做数据扩增。AlexNet也使用了数据扩增。而且还利用了GPU来缩短了训练时间。
番外篇2.2:图像处理与深度学习-CNN的发展和结构
番外篇2.2:图像处理与深度学习-CNN的发展和结构

3 VGG

VGG是2014年的ImageNet分类的亚军,物体检测冠军,使用了更小的卷积核(3x3),并且连续多层组合使用。
番外篇2.2:图像处理与深度学习-CNN的发展和结构
这篇论文的重要结论就是,越深精度就越高。
连续3个3x3的卷积层(步长1)能获得和一个7x7的卷积层等效的感知域(receptive fields),而深度的增加在增加网络的非线性时减少了参数(3*3^2 vs 7^2)。自VGG之后,大家都在用小卷积层,甚至有人分解卷积核。
但是,VGG只是简单的堆叠卷积层,且卷积核太深(最多达512),特征太多,导致其参数猛增,搜索空间太大,正则化困难,因而其精度也并不是最高的,在推理时也相当耗时,和GoogLeNet相比性价比十分之低。

4 GoogleNet

引入了一个概念,叫做Inception module,一个输入进来,直接分四步进行处理,这四步处理完后深度直接进行叠加。在不同的尺度上对图片进行操作。大量运用1*1的convolution,可以灵活控制输出维度,极大地降低了参数数量。这个模块是GoogleNet以更深的网络和更高的效率取得更好的结果的重要原因。
番外篇2.2:图像处理与深度学习-CNN的发展和结构
番外篇2.2:图像处理与深度学习-CNN的发展和结构
这个模块后面也有改善,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提出了一种减轻网络训练负担的残差学习框架,这种网络比以前使用过的网络本质上层次更深。其明确地将这层作为输入层相关的学习残差函数,而不是学习未知的函数。学习残差与原始特征相比更容易,也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。这种方式又称为短路连接。
番外篇2.2:图像处理与深度学习-CNN的发展和结构
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的,比如下图所示。
番外篇2.2:图像处理与深度学习-CNN的发展和结构

学习了一下大牛们的博客、包括做的项目、比赛,实际上很多都是调用成熟的结构,训练自己的参数,调整一些层、参数等,或者采用两种架构自己来做集成工作,加入一些小tricks等,还是蛮有意思的。。希望自己也能有机会摸摸这方面的东西~
后面可能会总结图像检测中深度学习方面的东西,包括R-CNN等~