主要介绍训练神经网络的一些技巧,是对cs231n spring 2017 课程 lecture 6、7两节内容的一个整理。
激活函数
激活函数的作用就是引入一个非线性变换,起到一个激活作用。常见的激活函数有sigmoid,tanh,ReLU,Leaky ReLU等。下面分别介绍。
sigmoid
sigmoid的表达式是 σ(x)=1/(1+exp(-x))。其图像如下图所示:它有两个明显的缺点使得现在已经不适用了:
1、当x过大或者过小时,曲线趋于饱和,使得梯度很小,可能导致很少数据被激活。而且这也给参数初始化造成困难,参数初始化过大或者过小都不好。
2、它不过原点,一般来说我们希望激活函数能过原点。当然,这相比第一个缺点来说,负面影响会小一点。
tanh
tanh其实就是将sigmoid函数变换到以0为中心,它和sigmoid函数一样有着饱和的问题,不过有所改进的是它没有sigmoid函数的第二个缺点。其表达式是:tanh(x)=2σ(2x)-1,图像如下所示:
ReLU
过去几年比价流行的激活函数是ReLU,其表达式非常简单:f(x)=max(0,x),图像如图所示:
相比之前的激活函数,ReLU函数有以下特点:
优点:计算简单快速,实现容易。而且没有趋于饱和的问题。
缺点:由于在x小于0时,其数值是0,这可能造成有些神经元会在训练过程中由于参数更新使得永久不能被激活。如果学习率设置得比较高,最后可能会有高达40%的神经元不会被激活。
Leaky ReLU
为了解决ReLU函数部分神经元不能起作用的问题,在x小于0的部分引入一个比较小的斜率(一般设置为0.01)。这在有些场合是比ReLU函数表现好的。
Maxout
另一种激活函数形式是这样的:
它是对ReLU和LeakyReLU的推广,它有着ReLU函数的优点,却不存在ReLU函数的缺点。不过相对而言,其参数个数会翻倍。
使用建议
优先选用ReLU,如果效果不好,可以尝试Leaky ReLU或者Maxout,也可以尝试tanh(但是它的效果在绝大多数情况会比ReLU/Maxout差)。sigmoid就千万别用了,已经被时代抛弃~
数据预处理
主要有3中常用的数据预处理方法,对于数据矩阵X,大小是[NxD],N是数据数量,D是维度。
Mean subtraction
这是最常用的一种方法。就是做一个减去平均值的处理。在python中这样实现:
X-=np.mean(X,axis=0)
Normalization
做法是把数据维度规范化,一种做法是将所有数据除以标准差:X/=np.std(X,axis=0);另一种做法是规范化使得最大值为1,最小值为-1。
下图中左边是原始数据,中间是做Mean subtraction处理,右边是做Normalization(具体方法是除以标准差的做法)处理。
PCA and Whitening
PCA(主成分分析)和Whtening的做法都是先利用Mean subtraction方法将数据零对称,然后计算协方差矩阵,将协方差矩阵进行特征值分解之后再进一步处理。
它们相同的步骤是:
之后,PCA的处理是用主成分代替整个数据已达到降维的目的,可以以最小的精度损失代价换取较快的计算。
而Whitening的处理方式是:
值得注意的是:我们在实现的过程中为了防止除零,在分母加了一个小常数,这样带来了一个问题就是它放大了噪声的影响。这个问题可以通过将这个常数增大来减轻。下图中左边是原始数据,中间和右边分别是PCA和Whitening的处理结果。
具体例子
下面是对CIFAR-10中的一些数据进行预处理结果。
实际使用
实际上在卷积网络中,是不使用PCA和Whitening作为预处理的。而Mean subtraction和Normalization通常是必须要做的。另外值得注意的一点是:预处理步骤中的一些统计信息的计算,比如求均值等,只能在训练集上计算,然后应用到测试集上。
权重初始化
训练神经网络的一个很重要的步骤是参数的初始化工作,理想的初始权重是满足高斯分布。主要有以下几种初始化的方法,下面将分别讨论。
陷阱:全部初始化为0
如果全部初始化为0的话,那么每个神经元都会有相同的输出,这样在反向传播的过程中它们也会有相同的梯度,最后就会执行相同的参数更新。那么最终的优化后的参数也会全部都是一样的,很明显这样的网络是没有价值的。所以这种初始化方式是错误的。
随机小数初始化
另一种方式就是随机小数初始化,W=0.01*np.random.randn(D,H)。但是事实上这种初始化方式并没有更好的表现。因为如果一个神经网络层具有很小的权重,那么计算出的梯度也会逐渐。考虑一个10层的网络结果,有500个神经元,tanh作为激活函数。实验中出现,随着层数增加,最后就会出现绝大多数参数为0的情况,这又遇到了和全初始化为零一样的问题。有一种设想是改变随机数前面的系数从0.01改为1,这样在实践中又会造成几乎所有神经元的饱和,其值不是-1就是1,而梯度都为0。
用sqrt(N)校正
初始化为W=np.random.randn(n)/sqrt(n),这在以tanh为激活函数的时候有比较好的效果,但是在以ReLU为激活函数的时候效果就不太好了。其实目前在实践中最推荐的初始化方式是W=np.random.randn(n)/sqrt(2/n),激活函数为ReLU。
偏置量的初始化
最常用的方式就是将偏置初始化为0,当然也有做法将偏置统一初始化为一个小数,比如0.01。
Batch Normalization
参考
batch normalization 论文
quaro
参考1
论文笔记:Batch Normalization
视频讲解
Regularization(正则化)
正则化的目的就是防止过拟合。主要有以下几种方式。
L2 regularization
这是一种比较常用的正则化方法,对每个权重的平方都进行正则化惩罚,常常设置为0.5λWW,λ是一个超参数,前面加一个0.5的系数是为了方便反向传播的时候求导为λW。
L1 regularization
L1 regularization 也是常用的正则化方法,也可以同时使用这两种方法,设置惩罚项为
λ1abs(W)+λ2W*W。
Max norm constraints
正则化的另一种形式是强制每个神经元的权重向量的绝对上限,并使用预测的梯度下降来强制约束。这种方法的好处就是即便学习率设置得很高,网络也不会出现问题,因为参数更新受到极大限制。
Dropout
这是一种比较新的防止过拟合的正则化的方法。通过每次执行参数更新时随机抛弃每一层的一定数量的神经元来达到防止过拟合的目的。决定神经元是否激活的概率p是一个超参数。具体实现的时候需要注意只是在训练的时候需要执行dropout,在测试的时候并不dropout,所以测试时还需要做一个尺度变换,乘上一个p。但是实践中是在训练时执行一次Inverted dropout,而在测试中不做任何处理。
偏置正则化
偏置的正则化是不常见的,因为它们不通过乘法相互作用与数据交互,对于最终的输出没有什么影响。然而,在实际应用中(通过适当的数据预处理),正则化偏差很少会导致性能显着降低。
实践技巧
实践中常用的是L2 regularization,其系数λ通过交叉验证来设置。也可以将其和dropout相结合,dropout的概率p一般设置为0.5。
Loss Function
loss function就是对ground truth和我们的预测结果之间差异的度量,我们在训练中做的事情就是使得其最小化。我们需要针对不同的场景选择适合的loss function以达到我们的目的。
Classfication
在分类任务中,我们常用的loss function有 SVM loss:
其形式如下:
实际应用中也有使用它的平方项达到较好效果的例子。
另外一种常见的是Softmax loss,如下所示:
实际上当分类任务中标签太多时,更推荐使用Hierarchical Softmax loss的计算方法,具体细节见
参考
Attribute classfication
上面介绍的loss function都是针对一个物品只有一个标签的情况,对于一个物体有多个标签的情况,我们需要用另外一种度量方法。解决这种的一个比较好的方法是独立地为每个单独的属性构建一个二进制分类器。例如,每个类别的二进制分类器将独立采用以下形式:
其中y(ij)的值取决于第i个物体是否属于类别j,如果是,其值为1,否则为-1。
这种损失的替代方案是独立地为每个属性训练逻辑回归分类器。二元逻辑回归分类器只有两个类(0,1),并且将类1的概率计算为:
这可以最终简化得到
这种方法看起来麻烦,实际上它的梯度计算非常简单,为:
Regression
这类任务需要预测一个东西的准确值,比如预测房价等。常常使用L1 loss或者L2 loss。实际上,L2 loss相比softmax loss更难优化。除非绝对必要,否则不要使用回归任务,如果能够将这类任务转化为分类任务,那是再好不过的。
Gradient Checks
梯度检查实际就是将analytic gradient 和 numerical gradient 进行比价,用numerical gradient检查 analytic gradient是否计算正确。这个过程很关键,而且容易出错,这里介绍一点处理的技巧。
1、Use teh centered formula
2、Use relative error for the comparison
比较两种梯度时,比较绝对误差是没有任何意义的,实践中是比较相对误差。
通常情况下这个error>1e-2说明梯度有问题,需要检查;1e-2>error>1e-4,勉强能够接受;1e-4>error,在一般情况下算是比较好的;如果error<1e-7,那么肯定是正确的了。但是这个误差与网络层数也有关系,网络越深,很明显这个误差会越大,比如对于一个10层的网络,这个error在1e-2的量级也许都是可以接受的,但是对于只有一层的网络这显然太高。所以还要根据实际情况尽心考虑。
3、Use double precision
进行梯度检查时应该使用double,用float精度不够。
4、Stick around active range of floating point
在运算过程中将中间数据浮点数控制在有效范围,太小的数据可能会造成一些数值问题(1e-10甚至更小的数据是没有意义的)。遇到这样的中间结果时,需要暂时进行一个缩放将数值扩大到一个适当的范围,比如到1e0量级。
5、Kinks in the objective
对于某些激活函数,比如ReLU,会出现梯度不连续的问题,在执行梯度检查的时候需要格外注意,否则可能造成意外的结果。
6、Use only few datapoints
解决上述问题的一个方法就是使用更少的数据点,这样既可以有效避免上述问题,还可以使得梯度检查更加快速,效率更高。
7、Be careful with the step size h
h并不是越小越好,如果h太小的话反而可能会造成一些数值问题。
8、Gradcheck during a “characteristic” mode of operation
9、Don’t let the regularization overwhelm the data
10、Remember to turn off dropout/augmentations
11、Check only few dimensions
sanity checks
在开始进行训练之前,需要先对网络进行一些简单的健壮性检查。
Look for correct loss at chance performance
当进行参数初始化时,我们需要保证初始的loss达到我们的预期。所以需要我们单独检查loss(这时要忽略正则化项)。如果loss和我们的预期不符,这时需要检查是否初始化存在问题等。
#
增加正则化项的权重会增加loss,也可以一次为标准进行检查。
Overfitting a tiny subset of data
正式开始训练之前,可以用较小的数据集进行训练,看看能不能使得loss为0(注意这时不要考虑正则化项)。如果不能使得loss为0,那么这个网络应该是存在问题的。当然即便能够使得loss为0,也不代表网络是正确的,不排除有其他bug的情况。
监视学习过程
在训练神经网络时应该监视多种有用的数量。这些图是进入训练过程的窗口,应该被用来获得有关不同超级参数设置的直观性,以及如何改变它们以提高学习效率。下面的图的x轴总是以epoch为单位,其测量在期望的训练期间每个示例已经看到多少次(例如,一个时期意味着每个示例已经被看到一次),优选的是epoch而不是迭代次数,因为迭代次数取决于批量大小的任意设置。
loss function
我们训练的目的就是使得loss最小化,所以loss是最需要跟踪的一个参数。如下图所示:损失曲线通常会出现摆动,这和batch size有关,如果batch size是1,那么这个摆动会很厉害,如果batch size是整个数据集,那么将不会出现摆动。
Train/Val accuracy
训练分类器时跟踪的第二个重要数量是验证/训练准确性。这让我们更直观了解到过拟合现象。过拟合简单来说就是在训练集上表现好,但是在测试集上表现差的现象。从下图可以直观看出。
Ratio of weights:updates
最后一个需要关心的参数是更新幅度与整个值的幅度的比例。这个幅度最好在1e-3左右,如果这个值太小,说明学习率设置太低,反之则太高。我们需要对此作出调整。代码如下所示:
Activation/Gradient distributions per layer
初始化不正确可能会减慢甚至完全停止学习过程。幸运的是,这个问题可以比较容易地诊断出来。 一种方法是绘制网络的所有层的激活/梯度直方图。直观地看,看到任何奇怪的分布并不是一个好兆头。 如果是tanh神经元,我们希望在[-1,1]的全范围内看到神经元激活的分布,而不是看到所有神经元输出零,或者所有神经元在-1或1完全饱和。
First-layer Visulization
处理图像问题时,绘制第一层的特征还是很有用的,可以以此为根据判断参数好坏。
参数更新
当通过反向传播计算出analytic gradient后,我们可以以此进行参数更新。具体的更新方法下面将详细介绍。
Vanilla updata
最简单常用的参数更新方式,直接沿着负梯度的方向更新:
Momentum update
动态更新是另一种在深度网络上几乎总是具有更好的融合速率的方法。其实现如下:
|
|
Nesterov Momentum
Nesterov动量是一个稍微不同的动态更新的版本,最近越来越受欢迎。对凸函数有较强的理论收敛保证,实际上也比标准动量略好。其通常实现如下:
|
|
Adagrad
|
|
RMSprop
|
|
Adam
|
|
实践技巧
推荐的参数更新方式是SGD+Nesterov Momentum或者Adam。
超参数优化
训练神经网络之前需要许多超参数的设置。最常见的超参数有:
初始学习率,learning rate decay schedule(such as decay constant),正则化的一些参数(比如L2的系数,dropout的比例)。
在选择超参数时需要注意以下一些一些事情:
1、Prefer one validation fold to cross-validation:虽然常说需要用交叉验证的方式确定超参数,但是实际上只需要一个验证集就足够了。
2、Hyperparameter ranges:常常在log的尺度上搜索超参数,例如 learning_rate=10**uniform(-6,1)。但是对于一些特殊参数,比如dropout的比例,不需要也不能这样,通常的做法是:dropout=uniform(0,1)。
3、Prefer random search to grid search:实践证明,随机搜索的效率会比网格搜索的效率高。
4、Careful with best values on border:当我们确定的超参数在我们搜索的范围边界上时,我们要特别注意,有可能这个超参数并不是最好的,我们这时需要在这个边界上扩大搜索范围再进行超参数搜索。
5、Stage your search from coarse to fine:一种比较好的实践方式是先在一个大范围内搜索超参数,只需要一个epoch,可以排除一些明显不合适的超参数,这样缩小范围后再进行超参数的确定。
6、Bayesian Hyperparameter Optimization
评估
模型组合(Model Ensembles)
通过一些模型的组合可能对网络效果有额外的一点提高。下面是一些模型组合的方法。
1、Same model,different initializaitons:先用交叉验证的方法确定超参数,然后用不同的初始化参数进行训练。
2、Top models discovered during cross-validation:用交叉验证的方法确定最好的几个参数。
3、Different checkpoints of a single model
4、Running average of parameters during training
参考
cs231n lecture6 slides
cs231n lecture7 slides
cs231n neural network notes 1
cs231n neural network notes 2
cs231n neural network notes 3