Hyperparameter tuning
Tuning process
深度神经网络需要调试的超参数(Hyperparameters)较多,包括:
-
:学习因子
-
:动量梯度下降因子
-
: Adam算法参数
-
#layers:神经网络层数
-
#hidden units:隐藏层神经元个数
-
learning rate decay:学习因子下降参数
-
mini-batch size:批量训练样本包含的样本个数
超参数之间也有重要性差异。
- 最重要:学习因子
- 次重要:动量梯度下降因子 ,各隐藏层神经元个数#hidden units,mini-batch size
- 次次重要:神经网络层数#layers和学习因子下降参数learning rate decay
- 最不重要:Adam算法参数的 :一般分别设为0.9,0.99, ,一般不调试改变
当超参数数量相对较少时,左上图的取参方法较为实用。但是在深度神经网络模型中,我们习惯采用右上图的随机选择模式。也就说我们随机选择25个点,作为待调试的超参数。
举一个极端的例子,假设hyperparameter1为α,hyperparameter2为ε,显然二者的重要性是不一样的。如果使用第一种均匀采样的方法,ε的影响很小,相当于只选择了5个α值。而如果使用第二种随机采样的方法,ε和α都有可能选择25种不同值。这大大增加了α调试的个数,更有可能选择到最优值。其实,在实际应用中完全不知道哪个参数更加重要的情况下,随机采样的方式能有效解决这一问题,但是均匀采样做不到这点。
如果在某一区域找到一个效果好的点,应将关注点放到点附近的小区域内继续寻找。
Using an appropriate scale to pick hyperparameters
在超参数选择的时候,一些超参数是在一个范围内进行均匀随机取值,如隐藏层神经元结点的个数、隐藏层的层数等,他们都是正整数,是可以进行均匀随机采样的,即超参数每次变化的尺度都是一致的(如每次变化为1,犹如一个刻度尺一样,刻度是均匀的)。但是有一些超参数的选择做均匀随机取值是不合适的,这里需要按照一定的比例在不同的小范围内进行均匀随机取值,以学习率α的选择为例,在0.001,…,1范围内进行选择:
如上图所示,如果在 0.001,…,1的范围内进行进行均匀随机取值,则有90%的概率 选择范围在 0.1∼1之间,而只有10%的概率才能选择到0.001∼0.1之间,显然是不合理的。
所以在选择的时候,在不同比例范围内进行均匀随机取值,如0.0001∼0.001、0.001∼0.01、0.01∼0.1、0.1∼1范围内选择。
代码实现
r = -4 * np.random.rand() # r in [-4,0]
learning_rate = 10 ** r
#
一般的,如果在 之间的范围内进行按比例的选择,则 , 。
同样,在使用指数加权平均的时候,超参数β也需要用上面这种方法进行选择.一般β的取值范围在[0.9, 0.999]之间,那么1−β的取值范围就在[0.001, 0.1]之间。那么直接对1−β在[0.001, 0.1]区间内进行log变换即可。
这里解释下为什么β也需要像α那样做非均匀采样。假设β从0.9000变化为0.9005,那么 基本没有变化。但假设β从0.9990变化为0.9995,那么 前后差别1000。β越接近1,指数加权平均的个数越多,变化越大。所以对β接近1的区间,应该采集得更密集一些。
Hyperparameters tuning in practice: Pandas vs. Caviar
在超参数调试的实际操作中,我们需要根据我们现有的计算资源来决定以什么样的方式去调试超参数,进而对模型进行改进。下面是不同情况下的两种方式:
- 在计算资源有限的情况下,使用第一种,仅调试一个模型,每天不断优化;
- 在计算资源充足的情况下,使用第二种,同时并行调试多个模型,选取其中最好的模型。
##Batch Normalization
Normalizing activations in a network
在Logistic Regression 中,将输入特征进行归一化,可以加速模型的训练。那么对于更深层次的神经网络,我们是否可以归一化隐藏层的输出 或者经过激活函数前的 ,以便加速神经网络的训练过程?答案是肯定的。
常用的方式是将隐藏层的经过激活函数前的 进行归一化。
Batch Norm 的实现
以神经网络中某一隐藏层的中间值为例:
:
其中,m是单个mini-batch包含样本个数,ε是为了防止分母为零,可取值
。这样,使得该隐藏层的所有输入
均值为0,方差为1。
但是,大部分情况下并不希望所有的
均值都为0,方差都为1,也不太合理。通常需要对
进行进一步处理:
上式中,γ和β是learnable parameters,类似于W和b一样,可以通过梯度下降等算法求得。这里,γ和β的作用是让z̃ (i)的均值和方差为任意值,只需调整其值就可以了。例如,令:
则
,即identity function。可见,设置γ和β为不同的值,可以得到任意的均值和方差。
这样,通过Batch Normalization,对隐藏层的各个
zl进行标准化处理,得到
,替代
。
值得注意的是,输入的标准化处理Normalizing inputs和隐藏层的标准化处理Batch Normalization是有区别的。Normalizing inputs使所有输入的均值为0,方差为1。而Batch Normalization可使各隐藏层输入的均值和方差为任意值。实际上,从激活函数的角度来说,如果各隐藏层的输入均值在靠近0的区域即处于激活函数的线性区域,这样不利于训练好的非线性神经网络,得到的模型效果也不会太好。这也解释了为什么需要用γ和β来对 作进一步处理。
Fitting Batch Norm into a neural network
我们已经知道了如何对某单一隐藏层的所有神经元进行Batch Norm,接下来将研究如何把Batch Norm应用到整个神经网络中。
对于L层神经网络,经过Batch Norm的作用,整体流程如下:
实际上,Batch Norm经常使用在mini-batch上,这也是其名称的由来。
值得注意的是,因为Batch Norm对各隐藏层 有去均值的操作,所以这里的常数项 可以消去,其数值效果完全可以由 中的β来实现。因此,我们在使用Batch Norm的时候,可以忽略各隐藏层的常数项 。在使用梯度下降算法时,分别对 , 和 进行迭代更新。除了传统的梯度下降算法之外,还可以使用我们之前介绍过的动量梯度下降、RMSprop或者Adam等优化算法。
Why does Batch Norm work?
First Reason
首先Batch Norm 可以加速神经网络训练的原因和输入层的输入特征进行归一化,从而改变Cost function的形状,使得每一次梯度下降都可以更快的接近函数的最小值点,从而加速模型训练过程的原理是有相同的道理。
只是Batch Norm 不是单纯的将输入的特征进行归一化,而是将各个隐藏层的激活函数的激活值进行的归一化,并调整到另外的分布。
Second Reason
Batch Norm 可以加速神经网络训练的另外一个原因是它可以使权重比网络更滞后或者更深层。
如下图所示,提供的所有猫的训练样本都是黑猫。然后,用这个训练得到的模型来对各种颜色的猫样本进行测试,测试的结果可能并不好。其原因是训练样本不具有一般性(即不是所有的猫都是黑猫),这种训练样本(黑猫)和测试样本(猫)分布的变化称之为covariate shift。
在神经网络,尤其是深度神经网络中,covariate shift会导致模型预测效果变差,重新训练的模型各隐藏层的
和
均产生偏移、变化。而Batch Norm的作用恰恰是减小covariate shift的影响,让模型变得更加健壮,鲁棒性更强。Batch Norm减少了各层
之间的耦合性,让各层更加独立,实现自我训练学习的效果。也就是说,如果输入发生covariate shift,那么因为Batch Norm的作用,对个隐藏层输出
进行均值和方差的归一化处理,
和
更加稳定,使得原来的模型也有不错的表现。针对上面这个黑猫的例子,如果我们使用深层神经网络,使用Batch Norm,那么该模型对花猫的识别能力应该也是不错的。
Batch Norm 正则化效果
Batch Norm还有轻微的正则化效果。
这是因为在使用Mini-batch梯度下降的时候,每次计算均值和偏差都是在一个Mini-batch上进行计算,而不是在整个数据样集上。这样就在均值和偏差上带来一些比较小的噪声。那么用均值和偏差计算得到的 也将会加入一定的噪声。
所以和Dropout相似,其在每个隐藏层的激活值上加入了一些噪声,(这里因为Dropout以一定的概率给神经元乘上0或者1)。所以和Dropout相似,Batch Norm 也有轻微的正则化效果。
这里引入一个小的细节就是,如果使用Batch Norm ,那么使用大的Mini-batch如256,相比使用小的Mini-batch如64,会引入更少的噪声,那么就会减少正则化的效果。
Batch Norm at test time
训练过程中,我们是在每个Mini-batch使用Batch Norm,来计算所需要的均值μ和方差 。但是在测试的时候,我们需要对每一个测试样本进行预测,无法计算均值和方差。
此时,我们需要单独进行估算均值μ和方差 。通常的方法就是在我们训练的过程中,对于训练集的Mini-batch,使用指数加权平均,当训练结束的时候,得到指数加权平均后的均值μ和方差 ,而这些值直接用于Batch Norm公式的计算,用以对测试样本进行预测。
Multi-class classification
Softmax Regression
目前我们介绍的都是二分类问题,神经网络输出层只有一个神经元,表示预测输出ŷ是正类的概率P(y=1|x),ŷ >0.5则判断为正类,ŷ <0.5则判断为负类。
对于多分类问题,用C表示种类个数,神经网络中输出层就有C个神经元,即
。其中,每个神经元的输出依次对应属于该类的概率,即P(y=c|x)。为了处理多分类问题,我们一般使用Softmax回归模型。Softmax回归模型输出层的激活函数如下所示:
输出层每个神经元的输出
对应属于该类的概率,满足:
所有的
,即ŷ,维度为(C, 1)。
下面给出几个简单的线性多分类的例子:
如果使用神经网络,特别是深层神经网络,可以得到更复杂、更精确的非线性模型。
Training a softmax classifier
理解Softmax
为什么叫做Softmax?我们以前面的例子为例,由 到 的计算过程如下:
通常我们判定模型的输出类别,是将输出的最大值对应的类别判定为该模型的类别,也就是说最大值为的位置1,其余位置为0,这也就是所谓的“hardmax”。而Sotfmax将模型判定的类别由原来的最大数字5,变为了一个最大的概率0.842,这相对于“hardmax”而言,输出更加“soft”而没有那么“hard”。
Sotfmax回归 将 logistic回归 从二分类问题推广到了多分类问题上。
Softmax 的Loss function
在使用Sotfmax层时,对应的目标值y以及训练结束前某次的输出的概率值ŷ分别为:
y= ,ŷ =
Sotfmax使用的Loss function为:
在训练过程中,我们的目标是最小化Loss function,由目标值我们可以知道,
,所以代入L(ŷ ,y)中,有:
所以为了最小化Loss function,我们的目标就变成了使得
的概率尽可能的大。
也就是说,这里的损失函数的作用就是找到你训练集中的真实的类别,然后使得该类别相应的概率尽可能地高,这其实是最大似然估计的一种形式。
对应的Cost function如下:
Softmax 的梯度下降
在Softmax层的梯度计算公式为:
Introduction to programming frameworks
Deep learning frameworks
深度学习框架有很多,例如:
- Caffe/Caffe2
- CNTK
- DL4J
- Keras
- Lasagne
- mxnet
- PaddlePaddle
- TensorFlow
- Theano
- Torch
一般选择深度学习框架的基本准则是:
- Ease of programming(development and deployment)
- Running speed
- Truly open(open source with good governance)
实际应用中,我们应该根据自己的需求选择最合适的深度学习框架。
Tensorflow
这里简单介绍一下最近几年比较火的一个深度学习框架:TensorFlow。
举个例子来说明,例如cost function是参数w的函数:
如果使用TensorFlow对cost function进行优化,求出最小值对应的w,程序如下:
import numpy as np
import tensorflow as tf
w = tf.Variable(0,dtype=tf.float32)
#cost = tf.add(tf.add(w**2,tf.multiply(-10,w)),25)
cost = w**2 - 10*w +25
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost)
init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print(session.run(w))
>>0.0
session.run(train)
print(session.run(w))
>>0.1
for i in range(1000):
session.run(train)
print(session.run(w))
>>4.99999
TensorFlow框架内可以直接调用梯度下降优化算法,不需要我们自己再写程序了,大大提高了效率。在运行1000次梯度下降算法后,w的解为4.99999,已经非常接近w的最优值5了。
针对上面这个例子,如果对w前的系数用变量x来代替,程序如下:
import numpy as np
import tensorflow as tf
cofficients = np.array([[1.],[-10.],[25.]])
w = tf.Variable(0,dtype=tf.float32)
x = tf.placeholder(tf.float32,[3,1])
#cost = tf.add(tf.add(w**2,tf.multiply(-10,w)),25)
#cost = w**2 - 10*w +25
cost = x[0][0]*w**2 + x[1][0]*w + x[2][0]
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost)
init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print(session.run(w))
>>0.0
session.run(train, feed_dict=(x:coefficients))
print(session.run(w))
>>0.1
for i in range(1000):
session.run(train, feed_dict=(x:coefficients))
print(session.run(w))
>>4.99999
结果跟之前是一样的。除此之外,我们还可以更改x即cofficients的值,而得到不同的优化结果w。
另外,上段程序中的:
session = tf.Session()
session.run(init)
print(session.run(w))
有另外一种写法:
with tf.Session() as session:
session.run(init)
print(session.run(w))
TensorFlow的最大优点就是采用数据流图(data flow graphs)来进行数值运算。图中的节点(Nodes)表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。而且它灵活的架构让你可以在多种平台上展开计算,例如台式计算机中的一个或多个CPU(或GPU),服务器,移动设备等等。