1)一次性设置(One time setup)
- 激活函数(Activation functions)
- 数据预处理(Data Preprocessing)
- 权重初始化(Weight Initialization)
- 正则化(Regularization:避免过拟合的一种技术)
- 梯度检查(Gradient checking)
2)动态训练(Training dynamics)
- 跟踪学习过程 (Babysitting the learning process)
- 参数更新 (Parameter updates)
- 超级参数优化(Hyperparameter optimization)
- 批量归一化(BN:Batch Normalization:解决在训练过程中,中间层数据分布发生改变的问题,以防止梯度消失或爆炸、加快训练速度)
3)评估(Evaluation)
- 模型组合(Model ensembles)
(训练多个独立的模型,测试时,取这些模型结果的平均值)
神经网络学习过程本质就是为了:学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低,所以需要使用输入数据归一化方法,使训练数据与测试数据的分布相同。
详细内容参见:激活函数
总结:
1)使用ReLU时,使Learning Rates尽量小
2)尝试使用Leaky ReLU/Maxout/ELU
3)可以使用tanh,但期望不要太高
4)不要使用sigmoid
1)为什么输入数据需要归一化(Normalized Data)?
归一化后有什么好处呢?原因在于神经网络学习过程本质就是为了学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低;另外一方面,一旦每批训练数据的分布各不相同(batch 梯度下降),那么网络就要在每次迭代都去学习适应不同的分布,这样将会大大降低网络的训练速度,这也正是为什么我们需要对数据都要做一个归一化预处理的原因。
对于深度网络的训练是一个复杂的过程,只要网络的前面几层发生微小的改变,那么后面几层就会被累积放大下去。一旦网络某一层的输入数据的分布发生改变,那么这一层网络就需要去适应学习这个新的数据分布,所以如果训练过程中,训练数据的分布一直在发生变化,那么将会影响网络的训练速度。
1)小的随机数
w= 0.01 * np.random.randn(fan_in,fan_out)
2)神经元将饱和,梯度为0
w = 1.0 * np.random.randn(fan_in,fan_out)
3)合理的初始化(Xavier init)
w = np.random.randn((fan_in,fan_out)/np.sqrt(fan_in)
权重初始化是一个重要的研究领域。
1)随机梯度下降法(SGD)对于训练深度网络简单高效,但是它有个毛病,就是需要我们人为的去选择参数,比如学习率、参数初始化、权重衰减系数、Drop out比例等。这些参数的选择对训练结果至关重要,以至于我们很多时间都浪费在这些的调参上。那么使用BN(详见论文《Batch Normalization_ Accelerating Deep Network Training by Reducing Internal Covariate Shift》)之后,你可以不需要那么刻意的慢慢调整参数。
2)神经网络一旦训练起来,那么参数就要发生更新,除了输入层的数据外(因为输入层数据,我们已经人为的为每个样本归一化),后面网络每一层的输入数据分布是一直在发生变化的,因为在训练的时候,前面层训练参数的更新将导致后面层输入数据分布的变化。以网络第二层为例:网络的第二层输入,是由第一层的参数和input计算得到的,而第一层的参数在整个训练过程中一直在变化,因此必然会引起后面每一层输入数据分布的改变。我们把网络中间层在训练过程中,数据分布的改变称之为:“Internal Covariate Shift”。Paper所提出的算法,就是要解决在训练过程中,中间层数据分布发生改变的情况,于是就有了Batch Normalization,这个牛逼算法的诞生。
3)BN的地位:与激活函数层、卷积层、全连接层、池化层一样,BN(Batch Normalization)也属于网络的一层。
4)BN的本质原理:在网络的每一层输入的时候,又插入了一个归一化层,也就是先做一个归一化处理(归一化至:均值0、方差为1),然后再进入网络的下一层。不过文献归一化层,可不像我们想象的那么简单,它是一个可学习、有参数(γ、β)的网络层。
5)归一化公式:
6)如果是仅仅使用上面的归一化公式,对网络某一层A的输出数据做归一化,然后送入网络下一层B,这样是会影响到本层网络A所学习到的特征的。比如我网络中间某一层学习到特征数据本身就分布在S型激活函数的两侧,你强制把它给我归一化处理、标准差也限制在了1,把数据变换成分布于s函数的中间部分,这样就相当于我这一层网络所学习到的特征分布被你搞坏了,这可怎么办?于是文献使出了一招惊天地泣鬼神的招式:变换重构,引入了可学习参数γ、β,这就是算法关键之处:
上面的公式表明,通过学习到的重构参数γ、β,是可以恢复出原始的某一层所学到的特征的。
7)引入了这个可学习重构参数γ、β,让我们的网络可以学习恢复出原始网络所要学习的特征分布。最后Batch Normalization网络层的前向传导过程公式就是:
8)BN层是对于每个神经元做归一化处理,甚至只需要对某一个神经元进行归一化,而不是对一整层网络的神经元进行归一化。既然BN是对单个神经元的运算,那么在CNN中卷积层上要怎么搞?假如某一层卷积层有6个特征图,每个特征图的大小是100*100,这样就相当于这一层网络有6*100*100个神经元,如果采用BN,就会有6*100*100个参数γ、β,这样岂不是太恐怖了。因此卷积层上的BN使用,其实也是使用了类似权值共享的策略,把一整张特征图当做一个神经元进行处理。
9)卷积神经网络经过卷积后得到的是一系列的特征图,如果min-batch sizes为m,那么网络某一层输入数据可以表示为四维矩阵(m,f,w,h),m为min-batch sizes,f为特征图个数,w、h分别为特征图的宽高。在CNN中我们可以把每个特征图看成是一个特征处理(一个神经元),因此在使用Batch Normalization,mini-batch size 的大小就是:m*w*h,于是对于每个特征图都只有一对可学习参数:γ、β。说白了吧,这就是相当于求取所有样本所对应的一个特征图的所有神经元的平均值、方差,然后对这个特征图神经元做归一化。
10) 在使用BN前,减小学习率、小心的权重初始化的目的是:使其输出的数据分布不要发生太大的变化。
11) BN的作用:
1)改善流经网络的梯度
2)允许更大的学习率,大幅提高训练速度:
你可以选择比较大的初始学习率,让你的训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;
3)减少对初始化的强烈依赖
4)改善正则化策略:作为正则化的一种形式,轻微减少了对dropout的需求
你再也不用去理会过拟合中drop out、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;
5)再也不需要使用使用局部响应归一化层了(局部响应归一化是Alexnet网络用到的方法,搞视觉的估计比较熟悉),因为BN本身就是一个归一化网络层;
6)可以把训练数据彻底打乱(防止每批训练的时候,某一个样本都经常被挑选到,文献说这个可以提高1%的精度)。
注:以上为学习过程,在测试时,均值和方差(mean/std)不基于小批量进行计算, 可取训练过程中的激活值的均值。
1)实际测试时,我们依然使用下面的公式:
这里的均值和方差已经不是针对某一个Batch了,而是针对整个数据集而言。因此,在训练过程中除了正常的前向传播和反向求导之外,我们还要记录每一个Batch的均值和方差,以便训练完成之后按照下式计算整体的均值和方差:
上面简单理解就是:对于均值来说直接计算所有batch u值的平均值;然后对于标准偏差采用每个batch σB的无偏估计。最后测试阶段,BN的使用公式就是:
2)BN可以应用于一个神经网络的任何神经元上。文献主要是把BN变换,置于网络激活函数层的前面。在没有采用BN的时候,激活函数层是这样的:
z=g(Wu+b)
也就是我们希望一个激活函数,比如s型函数s(x)的自变量x是经过BN处理后的结果。因此前向传导的计算公式就应该是:
z=g(BN(Wu+b))
其实因为偏置参数b经过BN层后其实是没有用的,最后也会被均值归一化,当然BN层后面还有个β参数作为偏置项,所以b这个参数就可以不用了。因此最后把BN层+激活函数层就变成了:
z=g(BN(Wu))
1)Learning Rate
- Learning Rate太小(如1e-6),cost下降很慢
- Learning Rate太大(如1e-6),cost增长爆炸 (cur cost > 3* original cost)
- 在[1e-3,1e-5]范围内比较合适
2)Mini-batch SGD
Loop:
1. Sample a batch of data
2. Forward prop it through the graph, get loss
3. Backprop to calculate the gradients
4. Update the parameters using gradient
参数优化直观显示图
参数优化的目的是:减少损失(loss), 直至损失收敛(convergence)
Caffe Solver,基于梯度下降的优化方法
for i in range(nb_epochs):
params_grad = evaluate_gradient(loss_function, data, params)
params = params - learning_rate * params_grad
每次基于一个数据样本计算梯度。
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
每次基于n个数据样本计算梯度。
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
优点:
1)减少参数更新的变化, 从而得到更加稳定的收敛
2)使用先进的Deep Learning库,可以高效地计算mini-batch的梯度
注:n一般取[50,256]范围内的数,视具体应用而定。
1)选择合适的Learning Rate是困难的,太小导致收敛慢,太大阻碍收敛或且导致损失函数在最小值附近波动或发散;
2)预先定义的Learning Rate变动规则不能适应数据集的特性;
3)同样的Learning Rate运用到所有的参数更新(后面的AdaGrad, AdaDelta, RMSProp, Adam为解决此问题而生);
4)最小化高度非凸损失函数的羝问题是:避免陷入众多的局部最优值。
关键优点: 利用物体运动时的惯性,加快到达全局最优点的速度,且减少振荡。
关键缺点:球盲目地沿着斜坡向山下滚。
当Loss function的表面曲线的一维比其它维有更多的沟壑时,SGD要跨越此沟壑是困难的,如上图左边所示,SGD沿着沟壑的斜坡振荡,然后犹犹豫豫地向局部最优点前进。
Momentum即动量,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,可以在一定程度上增加稳定性,从而学习地更快,并且还有一定摆脱局部最优的能力:
# Momentum update
V = gama * V + learning_rate * dw # integrate velocity
w -= V # integrate position
就是Momentum,经常取0.5,0.9,或0.99,有时随着时间而变化,从0.5到0.99;表示要在多大程度上保留原来的更新方向,这个值在0-1之间,在训练开始时,由于梯度可能会很大,所以初始值一般选为0.5;当梯度不那么大时,改为0.9。 是学习率,即当前batch的梯度多大程度上影响最终更新方向,跟普通的SGD含义相同。与之和不一定为1。
Momentum的物理解释是:当我们把球推下山时,球不断地累积其动量,速度越来越快(直到其最大速度,如果有空气阻力,如<1),同样的事情发生在参数更新中:梯度保持相同方向的维度的动量不停地增加,梯度方向不停变化的维度的动量不停地减少,因此可以得到更快的收敛速度并减少振荡。
关键优点:一个聪明的球,知道它将到哪儿去,且知道在斜坡向上之前减速。
沿着当前方向,先走一步,然后再看向哪个方向走最快,这样对前方的情况就有了更多地了解,可以做出明智的决策。
w_ahead = w - gama * v
# evaluate dw_ahead (the gradient at w_ahead instead of at w)
v = gama * v + learning_rate * dw_ahead
w -= v
Momentum:
1)计算当前的梯度(上图中:比较小的蓝色向量)
2)沿着更新的累积的梯度方向进行一大跳(上图中:比较大的蓝色向量)
NAG:
1)沿着以前累积的梯度方向进行一大跳 (上图中:棕色向量)
2)在新的位置测量梯度,然后进行校正(上图中:绿色向量)
3)这个有预料的更新可以防止走的太快并导致增加的响应
关键区别:
1)计算梯度的位置不一样
本章描述的方法(AdaGrad、AdaDelta、RMSprop、Adam)专为解决Learning Rate自适应的问题。
前面讨论的基于梯度的优化方法(SGD、Momentum、NAG)的Learning Rate是全局的,且对所有参数是相同的。
参数的有些维度变化快,有些维度变化慢;有些维度是负的斜坡,有些维度是正的斜坡(如鞍点);采用相同的Learning Rate是不科学的,比如有的参数可能已经到了仅需要微调的阶段,但又有些参数由于对应样本少等原因,还需要较大幅度的调动。理想的方案是根据参数每个维度的变化率,采用对应的Learning Rate。
下面讨论如何自适应Learing Rate的方案:AdaGrad、AdaDelta、RMSProp、Adam。
AdaGrad方法给参数的每个维度给出适应的Learning Rate。给不经常更新的参数以较大的Learning Rate, 给经常更新的参数以较小的Learning Rate。Google使用此优化方法“识别Youtube视频中的猫” 。
在AdaGrad中,每个参数在每一次更新时都使用不同的Learming Rate。
其公式如下:
其示意代码如下:
# Assume the gradient dx and parameter vector x
cache += dx**2
x -= learning_rate * dx / (np.sqrt(cache + 1e-8))
learning_rate 是初始学习率,由于之后会自动调整学习率,所以初始值就不像之前的算法那样重要了。而1e-8指一个比较小的数,用来保证分母非0。
其含义是,对于每个参数,随着其更新的总距离增多,其学习速率也随之变慢。
关键优点:1) 解决了AdaGrad Learning Rate单调递减的问题。 (是AdaGrad的扩展)
2) 不需要设置默认的Learning Rate
RMS(Root Mean Squared) : 均方根
Adagrad算法存在三个问题:
1)其学习率是单调递减的,训练后期学习率非常小RMSprop是由Geoff Hinton设计的。RMSprop与AdaDelta的目的一样:解决AdaGrad的Learning Rate逐步消失的问题。
Adam的目的是:为每个参数计算自适应的Learning Rate。
其实际效果与AdaDelta、RMSProp相比,毫不逊色!
SGD optimization on Beale's function
SGD optimization on Long Valley
SGD optimization on Saddle Point
1)总结:
- RMSprop是AdaGrad的扩展,以解决learning rate逐步消失的问题
- RMSprop与AdaDelta相比,AdaDelta在分子更新规则中使用了参数RMS更新,其它相同
- Adam与RMSprop相比,增加了偏差校正和动量
- RMSprop、AdaDelta和Adam是非常类似的算法,在类似的环境下,效果相当
- 从整体上看,Adam目前是最好的选择
2)如果输入数据是稀疏的(sparse),使用adaptive learning-rate(AdaGrad、AdaDelta、RMSprop、Adam)可以获得最好的结果,且不需要调整learning rate;
3)如果你关心快速收敛,你应当选择adaptive learning-rate方法
1)Shuffling:每次迭代前,随机打乱训练样本的顺序
2)Curriculum Learning:把训练样本按某种有意义的方式进行排序,对逐步解决困难问题有效。
为了便于训练,我们经常归一化参数的初始值,通过mean=0, variance=1的高斯分布来初始化参数。在训练过程中,我们不同程度地更新参数,使用参数失去了归一化,这将降低训练速度且放大变化,网络越深问题越严重。
BN为每一个mini-batch重建归一化参数。使模型结构的部分进行归一化,我们可以使用更高的learning rate,且参数初始化要求没哪么高。
此外,BN还作为一个正则化(Regularizer),可以减少或避免使用Dropout。
正则化(Regularizer):是一个用于解决过拟合(Overfitting)问题的一种技术。具体实现方法是在损失函数中增加惩罚因子(参数向量的范数,1范数(L1)或2范数(L2))lambda*N(w)。
在训练时,总是监视验证集的错误率,如果验证集的错误率不能得到改善,应当停止训练。
在前向计算时,随机设置一些神经元的值为0,如下图所示:
示意代码如下:
p = 0.5 # probability of keeping a unit active, higher = less dropout
def train_step(X)
""" X contains the data """
# forward pass for example 3-layer neural network
H1 = np.maximum(0, np.dot(W1, X) + b1)
M1 = np.random.rand(*H1.shape) < p # first dropout mask
H1 *= M1 # drop
H2 = np.maximum(0, np.dot(W2, H1) + b2)
M2 = np.random.rand(*H2.shape) < p # send dropout mask
H2 *= M2 # drop
out = np.dot(W3,H2) + b3
相当于训练多个模型,一个dropout mask对应一个模型,且这些模型共享参数。
在测试时,不需要dropout, 直接计算每层的激活值,然后进行scale作为本层最终输出的激活值,其代码如下:
def predict(X):
# ensembled forward pass
H1 = np.maxmium(0, np.dot(W1,X)+b1) * p # Note: scale the activations
H2 = np.maxmium(0, np.dot(W2,H1)+b2) * p # Note: scale the activations
out = np.dot(W3,H2) + b3