作者:寒小阳
时间:2016年1月。
出处:http://blog.csdn.net/han_xiaoyang/article/details/50521064
声明:版权所有,转载请联系作者并注明出处
在前一节当中我们讨论了神经网络静态的部分:包括神经网络结构、神经元类型、数据部分、损失函数部分等。这个部分我们集中讲讲动态的部分,主要是训练的事情,集中在实际工程实践训练过程中要注意的一些点,如何找到最合适的参数。
之前的博文我们提到过,我们需要比对数值梯度和解析法求得的梯度,实际工程中这个过程非常容易出错,下面提一些小技巧和注意点:
使用中心化公式,这一点我们之前也说过,使用如下的数值梯度计算公式:
d f ( x ) d x = f ( x + h ) − f ( x − h ) 2 h ( 好 的 形 式 ) d f ( x ) d x = f ( x + h ) − f ( x − h ) 2 h ( 好 的 形 式 ) d f ( x ) d x = f ( x + h ) − f ( x − h ) 2 h (好的形式) df(x)dx=f(x+h)−f(x−h)2h(好的形式)df(x)dx=f(x+h)−f(x−h)2h(好的形式) \frac{df(x)}{dx} = \frac{f(x + h) - f(x - h)}{2h} \hspace{0.1in} \text{(好的形式)} df(x)dx=f(x+h)−f(x−h)2h(好的形式)df(x)dx=f(x+h)−f(x−h)2h(好的形式)dxdf(x)=2hf(x+h)−f(x−h)(好的形式)∇f(x)和梯度下降里看到的一样,是一个梯度向量。直观理解是Hessian矩阵描绘出了损失函数的曲度,因此能让我们更高效地迭代和靠近最低点:乘以Hessian矩阵进行参数迭代会让在曲度较缓的地方,会用更激进的步长更新参数,而在曲度很陡的地方,步伐会放缓一些。因此相对一阶的更新算法,在这点上它还是有很足的优势的。
比较尴尬的是,实际深度学习过程中,直接使用二次迭代的方法并不是很实用。原因是直接计算Hessian矩阵是一个非常耗时耗资源的过程。举个例子说,一个一百万参数的神经网络的Hessian矩阵维度为[1000000*1000000],算下来得占掉3725G的内存。当然,我们有L-BFGS这种近似Hessian矩阵的算法,可以解决内存问题。但是L-BFGS一般在全部数据集上计算,而不像我们用的mini-batch SGD一样在小batch上迭代。现在有很多人在努力研究这个问题,试图让L-BFGS也能以mini-batch的方式稳定迭代更新。但就目前而言,大规模数据上的深度学习很少用到L-BFGS或者类似的二次迭代方法,倒是随机梯度下降这种简单的算法被广泛地使用着。
感兴趣的同学可以参考以下文献:
到目前为止大家看到的学习率更新方式,都是全局使用同样的学习率。调整学习率是一件很费时同时也容易出错的事情,因此大家一直希望有一种学习率自更新的方式,甚至可以细化到逐参数更新。现在确实有一些这种方法,其中大多数还需要额外的超参数设定,优势是在大多数超参数设定下,效果都比使用写死的学习率要好。下面稍微提一下常见的自适应方法(原谅博主底子略弱,没办法深入数学细节讲解):
Adagrad是Duchi等在论文Adaptive Subgradient Methods for Online Learning and Stochastic Optimization中提出的自适应学习率算法。简单代码实现如下:
# 假定梯度为dx,参数向量为x
cache += dx**2
x += - learning_rate * dx / np.sqrt(cache + 1e-8)
其中变量cache
有着和梯度一样的维度,然后我们用这个变量持续累加梯度平方。之后这个值被用作参数更新步骤中的归一化。这种方法的好处是,对于高梯度的权重,它们的有效学习率被降低了;而小梯度的权重迭代过程中学习率提升了。而分母开根号这一步非常重要,不开根号的效果远差于开根号的情况。平滑参数1e-8
避免了除以0的情况。
RMSprop是一种非常有效,然而好像还没有被公开发布的自适应学习率更新方法。有意思的是,现在使用这个方法的人,都引用的大神Geoff Hinton的coursera课程第6节的讲义第29页。RMSProp方法对Adagrad算法做了一个简单的优化,以减缓它的迭代强度,它开方的部分cache做了一个平滑处理,大致的示意代码如下:
cache = decay_rate * cache + (1 - decay_rate) * dx**2
x += - learning_rate * dx / np.sqrt(cache + 1e-8)
这里的decay_rate
是一个手动敲定的超参数,我们通常会在[0.9, 0.99, 0.999]中取值。需要特别注意的是,x+=
这个累加的部分和Adagrad是完全一样的,但是cache
本身是迭代变化的。
另外的方法还有:
下图是上述提到的多种参数更新方法下,损失函数最优化的示意图:
神经网络的训练过程中,不可避免地要和很多超参数打交道,这是我们需要手动设定的,大致包括:
对于大的深层次神经网络而言,我们需要很多的时间去训练。因此在此之前我们花一些时间去做超参数搜索,以确定最佳设定是非常有必要的。最直接的方式就是在框架实现的过程中,设计一个会持续变换超参数实施优化,并记录每个超参数下每一轮完整训练迭代下的验证集状态和效果。实际工程中,神经网络里确定这些超参数,我们一般很少使用n折交叉验证,一般使用一份固定的交叉验证集就可以了。
一般对超参数的尝试和搜索都是在log域进行的。例如,一个典型的学习率搜索序列就是learning_rate = 10 ** uniform(-6, 1)
。我们先生成均匀分布的序列,再以10为底做指数运算,其实我们在正则化系数中也做了一样的策略。比如常见的搜索序列为[0.5, 0.9, 0.95, 0.99]。另外还得注意一点,如果交叉验证取得的最佳超参数结果在分布边缘,要特别注意,也许取的均匀分布范围本身就是不合理的,也许扩充一下这个搜索范围会有更好的参数。
实际工程中,一个能有效提高最后神经网络效果的方式是,训练出多个独立的模型,在预测阶段选结果中的众数。模型融合能在一定程度上缓解过拟合的现象,对最后的结果有一定帮助,我们有一些方式可以得到同一个问题的不同独立模型:
还有一种常用的有效改善模型效果的方式是,对于训练后期,保留几份中间模型权重和最后的模型权重,对它们求一个平均,再在交叉验证集上测试结果。通常都会比直接训练的模型结果高出一两个百分点。直观的理解是,对于碗状的结构,有很多时候我们的权重都是在最低点附近跳来跳去,而没法真正到达最低点,而两个最低点附近的位置求平均,会有更高的概率落在离最低点更近的位置。
cs231n 神经网络训练与注意点