目录:
1、感受野
2、优化函数
3、激活函数
4、loss 函数。
前言
文章有点长,内容有点丰富,基本涵盖整个深度卷积神经网络,涉及
网络中的感受野、激活函数、loss函数以及各种相关知识。
Part 一、 感受野 receptive field
receptive filed 中文叫感受野,是卷积神经网络中最重要的概念之一,各类目标检测任务都是基于不同的感受野而设计的,比如SSD(single shot detection)融合高分辨率的低层特征做检测解决小目标检测问题,因为小物体随着网络层数加深感受野变大在最后的feature maps中消失,又比如YOLO对成群的物体检测效果较差,天上飞的大雁群,地上跑的羊群,由于感受野变大,成群的物体会连成一片进行检测,导致检测异常,回归的boundingbox有可能每次运行的结果都不同。
感受野是什么?
The receptive field is defined as the region in the input space
that a particular CNN’s feature is looking at (i.e. be affected by).
翻译过来就是,在当前的feature map上,单个像素点对应原始输入图像的区域大小,拿原图来说,感受野就是1,单个像素点对应原图的区域就是这个像素所以是1,现在如果通过一个3*3的卷积核得到新的feature map,那么这个特征图上每个像素点对应原图上一个3*3区域,感受野就是3,原图和第一层卷积后的特征图的感受野比较直白,后面层感受野的计算稍显复杂,但还是有规律可循的。
层层卷积和池化的过程是下采样的过程,这里的小采样是指分辨率逐渐变小的过程,所以无论是卷积或者池化操作后得到的特征图上的特征值虽然在当前空间上位置上是连接在一起的,但是如果对应到原图它们之间都是有一定间隔的这个又叫做jump,这个jump跟进行卷积或池化操作时的步长有关。此外,当前层的感受野是前面所有层感受野堆叠起来的,所以计算当前第n层感受野需要计算前n-1层的感受野,前n-1层的感受野通过计算前n-2层感受野得到,这个计算过程是一个递归过程。
1、原始输入尺寸n,jump j0=1,r0=1,start0=0.5
# r0 表示原始输入图像的感受野
# start0表示当前特征图上的第一个值在原图中的位置
# nout 表示输出特征图的尺寸
特征图宽和高的计算方式:
p 代表padding的规模,可以是1,2等整数也可以是‘valid’或‘same',’valid‘表示padding的规模是0,’same‘表示padding的规模要保证卷积后的特征图和原图一样大小,k是卷积核的尺寸,s代表步长。
当前层的jump在上一层的特征图中,计算公式为:
其中, 表示当前层的上一层特征图中存在的jump, 表示经过当前层的卷积后得到的特征图中的jump,也即为下一层计算感受野所需要引入的jump。
感受野的计算公式:
是当前层的感受野大小, 是但前层的上一层的感受野大小,k是卷积核尺寸。
最后是关于特征图上第一个元素在原始输入中的空间位置:
图一是这个过程的描述示例。
在参考资料[1]中有实现这个过程的python代码,计算的是AlexNet中每一层的上述参数,可以根据实际需要进行更改,代码不复杂。
Part 二、优化函数 optimizer functions
假设你对机器学习或者深度学习有一定了解,二者模型参数的学习方式都是梯度下降,而不同的优化函数改变的是参数的调整方向以及量级,至于为什么是梯度下降这里简单解释一下:
1、假设模型服从函数: 这里的是向量
2、假设有大量的训练样本,模型通过不断的调整使得对于样本中的代入函数中的输出是或者是无限趋近。
3、如何确定训练得到的模型参数满足2中的要求或者评估模型的效果呢
4、引入loss函数,,其中是训练样本对应的label,模型通过最下化的值得到最优解。
5、如果是凸函数则通过4得到全局最优解,否则是局部最优解
6、求解4中最优解的方法是基于梯度下降的,因为无论是局部最优解还是全局最优解一般都满足梯度为0的条件,所以参数要根据当前梯度的下降方向的反方向进行调整,而调整的尺度由不同的optimizer functions决定。
结合图一进行说明,假设也即图中的函数曲线是凸函数,xi是待求的最优解,如果参数theta已经取到xi此时的梯度为0,得到全局最优解,参数停止更新,模型训练完成。如果通过计算得到当前的梯度小于0,也即theta在xi的左边,那么theta的调整方向是向右调整,如果此时的梯度大于0,即theta在xi的右边说明theta越过了最优解必须向回拉,theta要向左调整,所以参数调整的方向始终与梯度方向相反,而至于调整的步长则由不同的优化算法定义。
下面会介绍几种常见的优化算法。
Batch gradient descent
1、模型训练n个epochs,初始化参数theta,学习率
2、在每个epoch训练完成后,计算一次cost function的梯度
3、更新theta
简单用代码表示:
for i in range(nb_epochs):
params_grad = evaluate_gradient(loss_function, data, params)
params = params - learning_rate * params_grad
该算法可以保证凸函数收敛到全局最优解,非凸函数收敛到局部最优解,缺点是梯度下降缓慢因为计算完整个训练数据梯度仅仅执行了一次更新操作,并且对于当前epoch没有加载到内存中的训练数据没有进行任何更新和学习操作。解释一下,因为CPU,GPU的内存资源有限假设16G,如果训练样本集过大比如20G,那么每个epoch最多只能取16G的训练数据,换句话说只对这16G训练数据计算了梯度以及更新参数,虽然通过多个epoch可以覆盖所有的训练样本,但会导致模型收敛非常缓慢,效率较低。
Stochastic gradient descent
随机梯度下降算法,你可能会对它的简写SGD比较熟悉。每导入一条训练数据,更新一次参数,优点是可以在线实时学习参数避免Batch gradient descent中冗余计算(在更新参数前重复计算相似样本的梯度),SGD训练一次更新一次参数可以避免这种冗余计算,使得每次更新都是有效的,因为在Batch gradient descent中计算完所有样本的梯度进行一次参数更新,假设更新后的参数对汽车特征敏感,那么不包含汽车的样本前一次计算的梯度则是冗余的。(个人理解,如果有不同理解可以在评论区讨论)另外,这种更新方式也比Batch gradient descent运行的更快。
就像硬币的两面,SGD的缺点是由参数更新过于频繁引起的。SGD会在最优解附近振荡,增加了收敛到最优点的难度。所以为了解决这个问题,mini-batch SGD应运而生。顾名思义,mini-batch SGD计算一个mini-batch训练样本的梯度进行一次参数更新,这样即保留了SGD的优点又避免了参数频繁更新带来的振荡,现在tensorflow、keras等框架中的SGD通常都是mini-batch SGD,并且会用到下文提到的Momentum技术。
SGD:
for i in range(nb_epochs):
np.random.shuffle(data)
for example in data:
params_grad = evaluate_gradient(loss_function, example, params)
params = params - learning_rate * params_grad
mini-batch SGD:
for i in range(nb_epochs):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
params_grad = evaluate_gradient(loss_function, batch, params)
params = params - learning_rate * params_grad
就像硬币的两面,mini-batch也是有缺点的。
1、初始学习率存在于完整训练过程的每一次参数更新中,所以初始学习率的设置尤为重要,太小导致收敛速度较慢,太大会导致在最优解附近振荡甚至不收敛。
2、虽然可以通过设置学习率的schedules避免1中的问题,但是schedules如何设置以及根据不同的数据集进行调整都是一个艰难的工作
3、所有参数的更新步长都是一样的,对于稀疏数据特征由于各个特征出现的频率不同,无法根据特征出现的频率更新参数,频次较高的特征设置小步长更新,极少出现的特征设置较大步长的更新。
4、SGD很难逃离高度非凸函数的鞍点,并且容易收敛到非凸函数的局部次优点。
Momentum
中文叫动量,是给SGD加速的方法使其能够快速的沿着相关方向前进并且减少振荡。在原来参数更新的步长基础上,加上一个当前时刻之前累积的动量,如果当前梯度方向和上一时刻相关,更新的步长变大,如果方向发生变化,步长开始减小,以此获得更快的收敛速度减少振荡。
参数更新公式:,通常设置为0.9.
Nesterov accelerated gradient
考虑SGD+Monentum,当梯度方向发生变化时,此时是从最优解的左侧跃到右侧或者相反,都是跳过最优解后才会逐步向最优解靠近,那么有没有可能在寻找最优解的时候,时刻关注自身的位置在接近最优解时调整参数更新的步长最终停留在最优解。Nesterov accelerated gradient (NAG)提供了这种可能性。前文提到Monentum方法会根据当前时刻之前累积的动量更新参数,那么通过计算可以得到参数在下一个位置的估计值,通过计算参数在下一个位置的梯度加上之前累积的动量进行参数更新。
参数更新公式:,通常设置为0.9。
图二是Monentum 和NAG的更新步长
Momentum:蓝色线条
1、首先计算当前参数的梯度,较短的蓝线段
2、计算前面累积的动量,较长的蓝线段
3、1和2相加得到本次参数更新步长
NAG:
1、首先计算前面累积的动量,左起第一条棕色线条
2、然后计算当前梯度和校正量
3、1和2相加得到本次参数更新步长左起第一条绿色线段
不难看出Momentum、NAG等都是SGD的加速版本,采用不同的策略加快SGD的收敛速度减少振荡,但是不同的参数使用的都是相同的更新步长,是否可以根据各个参数的重要性确定其步长呢?下面要介绍的方法使用不同的策略根据参数本身的性质决定更新步长。
Adagrad
Adagrad可以调整参数的学习率,用小步长更新特征出现频次较高的参数,用大步长更新出现频次较低的参数,在稀疏数据上表现较好,并且可以提高SGD(mini-batch SGD之后提高的SGD都是这个)的鲁棒性。
参数更新公式:
其中,是初始学习率,默认设置为0.01,是一个平滑项避免分母是0,取值是1e-8,是一个对角矩阵,假设有N项,那么是一个N*N的对角矩阵,对角矩阵上的第i个元素是t+1时刻之前所有梯度的平方和。那么这个方法是如何调整根据特征出现的频次调整参数的更新步长的呢?
假设对应的是出现频次较高的特征,那么通过累积会得到一个较大的值,从而得到一个较小的更新步长,反之出现频次较低的特征会得到一个较大的更新步长。
就像硬币的两面,这个方法也是有缺点的。其一仍然需要设置一个初始学习率,其二任何数的平方都是非负的,所以通过累加中对角线上的元素会越来越大,导致某些参数的更新步长快速的逼近无穷小量导致无法继续学习相对应的特征。
Adadelta
Adadelta是Adagrad的改进版本,主要改进有:1、在一个窗口w中对梯度进行求和不是对之前所有的梯度平方求和;2、不再存放w之前的梯度,用对先前所有梯度均值(使用RMS即均方根值实现)的一个指数衰减作为代替的实现方法。
更新公式如下:
1、将累计梯度信息从全部历史梯度变为当前时间向前的一个窗口期内的累积:
通常设置为0.9
这里解释一下为什么是指数衰减:
(1)
(2)
(3)
..................................
的系数
的系数
的系数
....................................
梯度平方的系数是呈指数衰减的,当t比较大时前面的梯度会越来越小累积到后面的值趋近于0,相当于只进行了当前时间向前一个窗口期内的累积。
2、参数更新公式
由于分母项是梯度的均方根误差准则,所以可以简写为:
上面这个公式是RMSprop的参数更新公式,。
但是此时仍然需要设置一个初始学习率,所以还需要进一步的调整。
借鉴牛顿法的思想,牛顿法的迭代步长是二阶近似的解析解,不需要手动指定学习率,高阶牛顿法的迭代步长是Hessian矩阵。
最后的更新公式为:
中间的推导过程感兴趣的可以参考参考其他资料。
Adam
Adam也是一种根据参数自行调整学习率的算法,与Adadelta和RMSprop不同的是,Adam不仅计算历史梯度平方的指数衰减平均,同时计算历史梯度的指数衰减平均,也就是说同时引入了梯度的方差和均值。
由于,初始值都是0,所以在初始时间步长中它们偏向0,尤其是在衰减率较小时,为了解决这个问题,用经过偏斜校正的的一阶矩估计以及的二阶矩估计来代替原来的,进行参数更新。
经过实验验证,Adam要优于其他自适应学习率算法。
Part 三、激活函数 Activation Functions
激活函数是深度学习模型中重要的组件之一,决定了网络的输出,是从输入到输出的映射函数,决定网络的收敛速度、复杂数据的学习能力。
激活函数往往都是非线性的,并且会把输出归一化到(0,1)或者(-1,1),非线性保证了模型能够学习复杂的数据结构,计算并学习可以用来表示问题模型的几乎所有函数,并提供准确的预测结果。最简单的线性回归,表示线性回归模型的函数
是一个一次函数。输入x到输出y是线性映射的关系,但是如果实际问题模型是更为复杂的映射关系,但是又不确定具体的映射关系时就需要引进非线性的激活函数: ,其中 (sigmoid函数),当然也可以是别的非线性激活函数,这样就实现了输入x到输出y的非线性映射,从而可以学习更为复杂的数据结构和函数关系,拟合真实的数据分布。输出的归一化可以减少计算量、加速训练过程,模型更快的收敛。
下面介绍几种常用的激活函数:
SigMoid
函数形式:
优点:
对于任意输入x,输出y被映射到(0,1),由于sigmoid函数梯度连续,所以输出的y值不会存在异常跃迁,从函数图中可以看出当输入x>2,或者x<-2时,y~=1,y~=0,所以sigmoid函数在输出时可以保证干净的预测,不是0就是1,这也对应了激活函数的意义,有选择的让一些神经元处于激活状态,沉默另一部分。
缺点:
1、梯度消失,当x趋于无穷大或者无穷小时,由于sigmoid函数的梯度趋于0,所以此时模型的输出基本不会发生变化,模型不再进行任何参数更新学习;sigmoid函数的最大梯度值是0.25,所以对于深层网络(VGG16、ResNet50等),在反向传播的过程中,经过链式求导法则,模型的梯度在传播的过程中趋向0,导致模型不能进行任何参数学习。总的来说,就是对于浅层模型,sigmoid还是一个不错的选择,但是对于深层模型,sigmoid会导致梯度消失。
2、输出不是以0位中心的。
3、计算比较耗时。
TanH
函数形式:
tanh激活函数的形状和sigmoid类似,函数的值域不同,tanh取值(-1,1),并且是以0为中心的(zero-centered)。
优点:
1、sigmoid的所有优点
2、zero-centered,值域分布在0附近,可以更轻松地对具有强负,中性和强正值的输入进行建模。
缺点:
sigmoid的所有缺点。
ReLU
函数形式
优点:
1、计算高效,网络能够更快的收敛
2、非线性,函数可微分,能够进行反向传播
3、没有梯度消失的问题(在正区间)
缺点:
Dyling ReLu问题:当输入接近0或为负时,函数的梯度是0,网络将无法进行反向传播,无法进行参数的更新学习。
Leaky ReLU
函数形式:
优点:
1、避免了Dying ReLu问题:在负区域激活函数具有较小的正斜率,因此即使对于负输入值,它也能够进行反向传播。
2、跟ReLU一样
缺点:
1、输出和输入无法对齐:无法为负输入值提供一致的预测。
Parametric ReLU
函数形式:
优点:
1、允许学习输入为负值部分的斜率:此函数提供负数部分的斜率作为参数,因此,可以进行反向传播并学习最合适的值。
2、ReLu,Leaky ReLU的所有优点
缺点:
性能不稳定,对于不同的问题可能会有所不同,时灵时不灵,没有ReLu稳定。
SoftMax
函数形式:
优点:
1、能够处理多类别问题:将每个类别的输出归一化到(0,1),然后除以它们的和,得到输入值属于特定类别的可能性。
2、用在模型的输出层,进行多分类任务。
缺点:
1、存在类间竞争问题,尤其是类别数目较多时,导致整体类别预测概率较低。
Swish
函数形式:
Swish 是Google研究人员发现的一种新的自我控制的激活函数,它在相同的情境下的计算效率比ReLU更好,ImageNet上的对比实验表明,Swish将分类精度提高了0.6-0.9。
Part 四、Loss函数
待续。。。
参考资料:
1、 A guide to receptive field arithmetic for Convolutional Neural Networks