1.调试处理
深度神经网络需要调试的超参数较多,包括:
-
:学习因子
-
:动量梯度下降因子
-
:Adam算法参数
-
#layers:神经网络层数
-
#hidden units:各隐藏层神经元个数
-
learning rate decay:学习因子下降参数
-
mini-batch size:批量训练样本包含的样本个数
超参数之间也有重要性差异。通常来说,学习因子是最重要的超参数,也是需要重点调试的超参数。动量梯度下降因子
、各隐藏层神经元个数#hidden units和mini-batch size的重要性仅次于
。然后就是神经网络层数#layers和学习因子下降参数learning rate decay。最后,Adam算法的三个参数
一般常设置为0.9,0.999和
,不需要反复调试。当然,这里超参数重要性的排名并不是绝对的,具体情况,具体分析。
如何选择和调试超参数?传统的机器学习中,我们对每个参数等距离选取任意个数的点,即网格取点,然后,分别使用不同点对应的参数组合进行训练,最后根据验证集上的表现好坏,来选定最佳的参数。例如有两个待调试的参数,分别在每个参数上选取5个点,这样构成了5x5=25种参数组合,如下图所示:
这种做法在参数比较少的时候效果较好。但是在深度神经网络模型中,我们一般不采用这种均匀间隔取点的方法,比较好的做法是使用随机选择。也就是说,对于上面这个例子,我们随机选择25个点,作为待调试的超参数,如下图所示:
随机化选择参数的目的是为了尽可能地得到更多种参数组合。还是上面的例子,如果使用均匀采样的话,每个参数只有5种情况;而使用随机采样的话,每个参数有25种可能的情况,因此更有可能得到最佳的参数组合。
这种做法带来的另外一个好处就是对重要性不同的参数之间的选择效果更好。假设hyperparameter1为,hyperparameter2为
,显然二者的重要性是不一样的。如果使用第一种均匀采样的方法,
的影响很小,相当于只选择了5个
值。而如果使用第二种随机采样的方法,
和
都有可能选择25种不同值。这大大增加了
调试的个数,更有可能选择到最优值。其实,在实际应用中完全不知道哪个参数更加重要的情况下,随机采样的方式能有效解决这一问题,但是均匀采样做不到这点。
在经过随机采样之后,我们可能得到某些区域模型的表现较好。然而,为了得到更精确的最佳参数,我们应该继续对选定的区域进行由粗到细的采样(coarse to fine sampling scheme)。也就是放大表现较好的区域,再对此区域做更密集的随机采样。例如,对下图中右下角的方形区域再做25点的随机采样,以获得最佳参数。
2.为超参数选择合适的范围
对于某些超参数是可以进行尺度均匀采样的,但是某些超参数需要选择不同的合适尺度进行随机采样。
超参数#layers和#hidden units都是正整数,是可以进行均匀随机采样的,即超参数每次变化的尺度都是一致的(如每次变化为1)。
但是,对于某些超参数,可能需要非均匀随机采样。例如超参数,待调范围是[0.0001, 1]。如果使用均匀随机采样,那么有90%的采样点分布在[0.1, 1]之间,只有10%分布在[0.0001, 0.1]之间。这在实际应用中是不太好的,因为最佳的
值可能主要分布在[0.0001, 0.1]之间,而[0.1, 1]范围内
值效果并不好。因此我们更关注的是区间[0.0001, 0.1],应该在这个区间内细分更多刻度。
通常的做法是将线性轴转换为对数轴,将均匀尺度转化为非均匀尺度,然后再在log scale下进行均匀采样。这样,[0.0001, 0.001],[0.001, 0.01],[0.01, 0.1],[0.1, 1]各个区间内随机采样的超参数个数基本一致,也就扩大了之前[0.0001, 0.1]区间内采样值个数。
一般解法是,如果线性区间为[a, b],令m=log(a),n=log(b),则对应的log区间为[m,n]。对log区间的[m,n]进行随机均匀采样,然后得到的采样值r,最后反推到线性区间,即。
就是最终采样的超参数。相应的Python语句为:
m = np.log10(a)
n = np.log10(b)
r = np.random.rand()
r = m + (n-m)*r
r = np.power(10,r)
除了之外,动量梯度因子
也是一样,在超参数调试的时候也需要进行非均匀采样。一般
的取值范围在[0.9, 0.999]之间,比较难以直接取值。所以一般为
取值,取值范围[0.001, 0.1]。直接进行log变换即可。
假设从0.9000变化为0.9005,那么
基本没有变化。但假设
从0.9990变化为0.9995,那么
前后差别1000。
越接近1,指数加权平均的个数越多,变化越大。所以对
接近1的区间,应该采集得更密集一些。
3.超参数训练的实践:Pandas vs. Caviar
经过调试选择完最佳的超参数并不是一成不变的,一段时间之后(例如一个月),需要根据新的数据和实际情况,再次调试超参数,以获得实时的最佳模型。
在训练深度神经网络时,一种情况是受计算能力所限,一个人只能对一个模型进行训练,调试不同的超参数,使得这个模型有最佳的表现。我们称之为Babysitting one model。另外一种情况是可以对多个模型同时进行训练,每个模型上调试不同的超参数,根据表现情况,选择最佳的模型。我们称之为Training many models in parallel。
因为第一种情况只使用一个模型,所以类比做Panda approach;第二种情况同时训练多个模型,类比做Caviar approach。使用哪种模型是由计算资源、计算能力所决定的。一般来说,对于非常复杂或者数据量很大的模型,使用Panda approach更多一些。
4.正则化网络的激活函数
Batch Normalization可以让调试超参数更加简单,神经网络模型更加“健壮”。即较好模型可接受的超参数范围更大一些,包容性更强,更容易去训练一个深度神经网络。
在训练神经网络时,标准化输入可以提高训练的速度。方法是对训练数据集进行归一化的操作,即将原始数据减去其均值后,再除以其方差
。但是标准化输入只是对输入进行了处理,那么对于神经网络,又该如何对各隐藏层的输入进行标准化处理呢?
其实在神经网络中,第层隐藏层的输入就是第
层隐藏层的输出
。对
进行标准化处理,从原理上来说可以提高
和
的训练速度和准确度。这种对各隐藏层的标准化处理就是Batch Normalization。实际应用中,一般是对
进行标准化处理而不是
,其实差别不是很大。
Batch Normalization对第层隐藏层的输入
做如下标准化处理,忽略上标
:
其中,m是单个mini-batch包含样本个数,是为了防止分母为零,可取值
。这样,使得该隐藏层的所有输入
均值为0,方差为1。
但是,大部分情况下并不希望所有的均值都为0,方差都为1,也不太合理。通常需要对
进行进一步处理:
上式中,和
是learnable parameters,类似于W和b一样,可以通过梯度下降等算法求得。这里,
和
的作用是让
的均值和方差为任意值,只需调整其值就可以了。例如,令:
则,即identity function。可见,设置
和
β为不同的值,可以得到任意的均值和方差。
通过Batch Normalization,对隐藏层的各个进行标准化处理,得到
,替代
。
值得注意的是,输入的标准化处理Normalizing inputs和隐藏层的标准化处理Batch Normalization是有区别的。Normalizing inputs使所有输入的均值为0,方差为1。而Batch Normalization可使各隐藏层输入的均值和方差为任意值。实际上,从激活函数的角度来说,如果各隐藏层的输入均值在靠近0的区域即处于激活函数的线性区域,这样不利于训练好的非线性神经网络,得到的模型效果也不会太好。
5.将Batch Norm拟合进神经网络
对于L层神经网络,经过Batch Norm的作用,整体流程如下:
Batch Norm经常使用在mini-batch上。因为Batch Norm对各隐藏层有去均值的操作,所以这里的常数项
可以消去,其数值效果完全可以由
中的
来实现。因此,在使用Batch Norm时,可以忽略各隐藏层的常数项
。使用梯度下降算法时,分别对
、
和
进行迭代更新。除了传统的梯度下降算法之外,还可以使用动量梯度下降、RMSprop或者Adam等优化算法。
6.Batch Norm为什么奏效
我们可以把输入特征做均值为0,方差为1的规范化处理,来加快学习速度。而Batch Norm也是对隐藏层各神经元的输入做类似的规范化处理。总的来说,Batch Norm不仅能够提高神经网络训练速度,而且能让神经网络的权重W的更新更加“稳健”,尤其在深层神经网络中更加明显。比如神经网络很后面的W对前面的W包容性更强,即前面的W的变化对后面W造成的影响很小,整体网络更加健壮。
如果实际应用的样本与训练样本分布不同,即发生了covariate shift,则一般是要对模型重新进行训练的。在神经网络,尤其是深度神经网络中,covariate shift会导致模型预测效果变差,重新训练的模型各隐藏层的和
均产生偏移、变化。而Batch Norm的作用恰恰是减小covariate shift的影响,限制了在前层的参数更新会影响数值分布的程度,减少了输入值改变的问题,让模型变得更加健壮,鲁棒性更强。Batch Norm减少了各层
、
之间的耦合性,让各层更加独立,实现自我训练学习的效果。也就是说,如果输入发生covariate shift,那么因为Batch Norm的作用,对个隐藏层输出
进行均值和方差的归一化处理,
和
更加稳定,使得原来的模型也有不错的表现。
从另一个方面来说,Batch Norm也起到轻微的正则化(regularization)效果。具体表现在:
-
每个mini-batch都进行均值为0,方差为1的归一化操作
-
每个mini-batch中,对各个隐藏层的
添加了随机噪声,效果类似于Dropout
-
mini-batch越小,正则化效果越明显
但是,Batch Norm的正则化效果比较微弱,正则化也不是Batch Norm的主要功能。batch归一化一次只能处理一个mini-batch上的数据。
7.测试时的Batch Norm
训练过程中,Batch Norm是对单个mini-batch进行操作的,但在测试过程中,可能需要Batch Norm对单个样本进行处理。
首先,回顾一下训练过程中Batch Norm的主要过程:
其中,和
是对单个mini-batch中所有m个样本求得的。如果只有一个样本,求均值和方差没有意义,需要对
和
进行估计。理论上可以将所有训练集放入最终的神经网络模型中,然后将每个隐藏层计算得到的
和
直接作为
和
来使用。但是,实际应用中一般使用指数加权平均的方法来预测测试过程单个样本的
和
。
指数加权平均的做法很简单,对于第层隐藏层,考虑所有mini-batch在该隐藏层下的
和
,然后用指数加权平均的方式来预测得到当前单个样本的
和
。这样就实现了对测试过程单个样本的均值和方差估计。最后,再利用训练过程得到的
和
值计算出各层的
值。
8.softmax回归
目前介绍的都是二分类问题,神经网络输出层只有一个神经元,表示预测输出是正类的概率
,
则判断为正类,
则判断为负类。
对于多分类问题,用C表示种类个数,神经网络中输出层就有C个神经元,即。其中,每个神经元的输出依次对应属于该类的概率,即
。为了处理多分类问题,我们一般使用Softmax回归模型。C=2,softmax回归等于logistic回归。Softmax回归模型输出层的激活函数如下所示:
输出层每个神经元的输出对应属于该类的概率,满足:
所有的,即
,维度为(C, 1)。
下面给出几个简单的线性多分类的例子:
9.训练一个softmax分类器
先来看一下softmax classifier的loss function。举例来说,假如C=4,某个样本的预测输出和真实输出
为:
从值来看,
,概率最大,而真实样本属于第2类,因此该预测效果不佳。定义softmax classifier的loss function为:
然而,由于只有当时,
,其它情况下,
。所以,上式中的
可以简化为:
所有m个样本的cost function为:
其预测输出向量即
的维度为(4, m)。
softmax classifier的反向传播过程仍然使用梯度下降算法,其推导过程与二元分类有一点点不一样。因为只有输出层的激活函数不一样,先推导:
对于所有m个训练样本:
可见的表达式与二元分类结果是一致的,虽然推导过程不太一样。然后就可以继续进行反向传播过程的梯度下降算法了,推导过程与二元分类神经网络完全一致。
10.深度学习框架
深度学习框架有很多,例如:
-
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)
11.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))
session.run(train)
print(session.run(w))
for i in range(1000):
session.run(train)
print(session.run(w))
>>0.0
>>0.1
>>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))
session.run(train, feed_dict=(x:coefficients))
print(session.run(w))
for i in range(1000):
session.run(train, feed_dict=(x:coefficients))
print(session.run(w))
>>0.0
>>0.1
>>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),服务器,移动设备等等。