从渐层模型到深层模型,抛开模型框架、结构选择问题,仅在模型训练上就会出现如下问题:
- 梯度消失、梯度爆炸
- 数据量不够
- 训练速度极其之慢
- 参数过多,导致过拟合严重
损失函数
Logistic Sigmoid激活函数作为每层激活函数的问题:
- 导数易饱和,当输入过大或者过小时,导数都向0饱和,导致层数增加后,开始层的参数变化非常微小
- 平均值为0.5,而不是0
中间层通常使用的损失函数为ReLU,虽然突出先计算快速,但存在一个问题:容易出现网络中神经单元死亡的现象,因为输入小于0时,导数为0,反向传播时传递的误差导数也是0。
此后在损失函数上有诸多发展:LeakyReLU, RReLU(Randomized leaky ReLU), PReLU(Parametric leaky ReLU), ELU, SELU(Scaled ELU)
相较于ReLU,ELU的主要缺点就是计算慢,但它收敛率会更快一些,另外采用ELU,网络在进行数据测试时,计算也会稍微慢一些
ELU是在2015年提出,SELU是在2017年提出的一个改进,SELU可以使网络自正则化,训练时会输出均值为1,标准差为1的数据,效果相较于其它网络会好很多,但是需要满足一定条件:
输入特征需要标准化
隐藏层权重必须采用LeCun Initialization
网络架构必须是序列型,在非序列性网络(如RNN)中使用的话不能确保自正则化,也不会一定优于其它激活函数的训练效果
原论文只证明了所有隐藏层都是密集层(Dense)时,能够自正则化,其它类型的网络不确定
隐藏层激活函数选择建议:SELU>ELU>leaky ReLU>ReLU>tanh>logistic,如果网络架构导致SELU无法自正则化,ELU会优于SELU,如果比较在意运行延迟,可以选择leaky ReLU,如果不想调整另外的超参数,可以使用Keras默认的(比如leaky ReLU的
为0.3)。
而如果时间和计算资源稍微充裕,可以交叉验证其它的几个激活函数效果:RReLU(如果网络过拟合)、PReLUde(如果训练样本集非常大),而速度第一位的话,ReLU是最佳选择(许多库对ReLU有特别的硬件优化)
数据集
数据集划分
在进行深度学习训练时,数据集通常需要作出划分,如果数据集不包含测试集,通常的划分为训练集、验证集,比例为:(80%, 20%), (75%, 25%)。而数据集中带测试集的话,通常划分为训练集、验证集合测试集,比例为:(60%, 20%, 20%)
上述是对于一般情况下的数据集,由于验证集、测试集只是为了能够检验不同模型的实际效果,在大数据时代,对于数据集较为庞大,如100万,测试集和验证集可能只有几万,1%,2%也有可能。
在训练时,很重要的一点是:训练集、验证集和测试集需要具有相同的概率分布,通常做法是事先将整个数据集进行随机重排,然后再进行分割。
数据集结果反馈
训练结束并经过测试后,首先观察在验证集上的表现,如果在验证集上的误差较大,说明网络欠拟合(高误差),需要调整模型、参数、训练时间等,如果在验证集上误差较小但是在测试集上误差较大,说明网络过拟合(高方差),需要更多数据、正则化等措施。
隐藏层网络参数初始化
- Xavier
- LeCun
- He
另一个问题:网络参数初始化,Xavier initialization(或Glorot Initialization),如下(其中指该网络连接层的神经元数目,
指连接的输入层,
指连接的输出层,
指两者的平均):
%5C%5C%20%0Ar%20%3D%20%5Csqrt%7B%5Cfrac%7B3%7D%7Bfan%7Bavg%7D%7D%7D%2C%20%5C%20%5C%20%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83U(-r%2C%20r)%0A#card=math&code=%5Csigma%5E2%20%3D%20%5Cfrac%7B1%7D%7Bfan%7Bavg%7D%7D%2C%20%5C%20%5C%20%E6%AD%A3%E6%80%81%E5%88%86%E5%B8%83N%280%2C%20%5Csigma%5E2%29%5C%5C%20%0Ar%20%3D%20%5Csqrt%7B%5Cfrac%7B3%7D%7Bfan_%7Bavg%7D%7D%7D%2C%20%5C%20%5C%20%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83U%28-r%2C%20r%29%0A)
如果将上面的换成
,那么就是LeCun Initialization,另外还有He Initialization,每种初始方式又都分为正态分布和均匀分布。通常来说Xavier Initialization用于tanh、logistic、softmax,He Initialization用于ReLU和变量,LeCun Initialization用于SELU。
实际上随机正态初始化、Xavier、He的区别在于:Xavier在随机正态初始化之上乘上了,而He是在乘上了
注:不同初始化方法导致不同的训练结果,随机参数初始化是为了打破网络间的对称性,如果每层网络参数都设为相同,训练时会导致每层参数更新时基本都是相同的;初始化参数不应该设得过大,期望值最好设为0。
在Keras中,参数初始化默认为Xavier Initialization的均匀分布,可通过kernel_initializer
进行更换,使用VarianceScaling
更换
keras.layers.Dense(10, activation='relu', kernel_initialize='he_normal')
he_avg_init = keras.initializers.VarianceScaling(scale=2, mode='fan_avg')
梯度
梯度计算
- 微分公式
- 数值近似
梯度检查
通过数值近似,也可以来检查反向梯度计算是否有误,通常检查标准是:
%20%3D%20L(%5Ctheta_1%2C%5Ctheta_2%2C%20%5Ctheta_3%2C%5Ccdots%2C%5Ctheta_n)%0A#card=math&code=J%28%5Ctheta%29%20%3D%20L%28%5Ctheta_1%2C%5Ctheta_2%2C%20%5Ctheta_3%2C%5Ccdots%2C%5Ctheta_n%29%0A)
其中为需要进行更新的参数,
为其合成的一个向量,通过数值近似可以分别得到所有
的结果,即
,然后计算下式:
如果其值接近,可认为反向梯度计算结果正确
注意:
- 只在debug中使用,不要在训练中使用
- 如果检查有问题,仔细看看计算步骤,找出bug
- 不要忘记正则化,如果使用了
- 不要同Dropout一同使用
- 在随机初始化的时候运行提督检查,然后在训练一段时间之后再检查
数据正规化
正规化
- 通过scikit-learn的
StandardScaler
BatchNormalization层
同时使用HE Initialization和ELU可以训练开始时时显著减少梯度爆炸/消失的风险,但是不能训练中这些问题不会产生。对于这个问题,Batch Normalization(BN,于2015年提出)能解决这个问题。
并且在初始层进行BN处理后,训练数据可以不用正规化(StandandScaler
)
具体操作(Keras):
- 在层前或层后加入一个
BatchNormalization()
,通常用在层前的更多
实际操作:
对于每层激活函数的输入(对应上面的层前加入BN层)或输出(对应上面的层后加入BN层),如果是,有:
%5E2%20%5C%5C%5C%5C%0AZ%7Bnorm%7D%20%3D%20%5Cfrac%7BZ-%5Cmu%7D%7B%5Csqrt%7B%5Csigma%5E2%20%2B%20%5Cepsilon%7D%7D%20%5C%5C%5C%5C%0A%5Ctilde%20Z%20%3D%20%5Cgamma%20Z%7Bnorm%7D%20%2B%20%5Cbeta%0A#card=math&code=%5Cmu%20%3D%20%5Cfrac%7B1%7D%7Bm%7D%20%5Csum%20Z%20%5C%5C%5C%5C%0A%5Csigma%5E2%20%3D%20%5Cfrac%7B1%7D%7Bm%7D%20%5Csum%20%28Z%20-%20%5Cmu%29%5E2%20%5C%5C%5C%5C%0AZ%7Bnorm%7D%20%3D%20%5Cfrac%7BZ-%5Cmu%7D%7B%5Csqrt%7B%5Csigma%5E2%20%2B%20%5Cepsilon%7D%7D%20%5C%5C%5C%5C%0A%5Ctilde%20Z%20%3D%20%5Cgamma%20Z%7Bnorm%7D%20%2B%20%5Cbeta%0A)
因为并不是每个分布都是在0,1的正态分布,通过可以进行分布的调整
梯度值控制
为防止梯度爆炸,有一个思路是将梯度值控制在一定范围之内。对于Keras,有两种超参数设置:
optimizer = keras.optimizers.SGD(clipvalue=1.0)
optimizer = keras.optimizers.SGD(clipnorm=1.0)
上述代码都是讲梯度值控制在绝对值为1.0之内,区别是clipvalue
(按值)只调整超过1的梯度值,将超过1的都设为1;而clipnorm
(按模)按照同比例将所有梯度进行放缩,使得最大的梯度值在范围之内。
优化器
除了常规的梯度下降优化,常用的还有:momentum optimization(Vanilla momentum optimization), Nesterov Accelerated Gradient(Nesterov momentum optimization), AdaGrad, RMSProp, Adam and Nadam optimization.
- momentum optimization[1964]
保存了上一次迭代的部分梯度更新值,计算公式比较直白,如下式,右半部分为正常的梯度更新值(学习率乘以梯度值,加上负号,由于沿着负梯度方向),左半部分为改进,加入了对上次梯度更新值,加上一定的保存率,也可以认为是动量受到的阻力。
由于是对SGD的调整,在Keras中只需在SGD上加入一个超参数即可,超参数的值对应于上述值。
optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9)
%0A#card=math&code=mt%20%3D%20%5Cbeta%20m%7Bt-1%7D%20-%20%5Ceta%20%5Cnabla_%5Ctheta%20J%28%5Ctheta%29%0A)
- Nesterov Accelerated Gradient[1983]
对上面一种优化器的改进,一个小变种,从公式就可以很明显的看出差异和改进。
解释:由于动量向量朝向正确的更新方向,可以将其引入到损失函数之中,可以使得计算值更朝向最优点靠近。optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9, nesterov=True)
%0A#card=math&code=mt%20%3D%20%5Cbeta%20m%7Bt-1%0A%7D%20-%20%5Ceta%20%5Cnabla%5Ctheta%20J%28%5Ctheta%2B%5Cbeta%20m%7Bt-1%7D%29%0A)
- AdaGrad[2011]
全称Adaptive Gradient,如其名,两步操作,使得梯度能够更快向极值点靠近,在一般情况下会比梯度下降要好,但是容易过早收敛、难以脱离局部极值点,一般不在深度神经网络中使用。%20%5Cotimes%20%5Cnabla%5Ctheta%20J(%5Ctheta)%5C%5C%0A%5Ctheta%20%5Cgets%20%5Ctheta%20-%20%5Ceta%20%5Cnabla%5Ctheta%20J(%5Ctheta)%5Coslash%20%5Csqrt%7Bs%2B%5Cepsilon%7D%0A#card=math&code=s%20%5Cgets%20s%20%2B%20%5Cnabla%20%5Ctheta%20J%28%5Ctheta%29%20%5Cotimes%20%5Cnabla%5Ctheta%20J%28%5Ctheta%29%5C%5C%0A%5Ctheta%20%5Cgets%20%5Ctheta%20-%20%5Ceta%20%5Cnabla_%5Ctheta%20J%28%5Ctheta%29%5Coslash%20%5Csqrt%7Bs%2B%5Cepsilon%7D%0A)
- RMSProp[2012]
通过只累计最近迭代的梯度值(引入一个衰减率,基本可取固定值0.9),解决AdaGrad会快速收敛到局部极值的缺点(多极值下,基本不会收敛到全局最优),实测效果基本总是好于AdaGrad,即使在简单的问题上,实际中经常被使用。
optimizer = keras.optimizers.RMSprop(lr=0.001, rho=0.9)
%5Cnabla%5Ctheta%20J(%5Ctheta)%5Cotimes%20J(%5Ctheta)%5C%5C%0A%5Ctheta%20%5Cgets%20%5Ctheta%20-%20%5Ceta%20%5Cnabla%5Ctheta%20J(%5Ctheta)%20%5Coslash%20%5Csqrt%7Bs%2B%20%5Cepsilon%20%7D%0A#card=math&code=s%20%5Cgets%20%5Cbeta%20s%20%2B%20%281%20-%20%5Cbeta%29%5Cnabla%5Ctheta%20J%28%5Ctheta%29%5Cotimes%20J%28%5Ctheta%29%5C%5C%0A%5Ctheta%20%5Cgets%20%5Ctheta%20-%20%5Ceta%20%5Cnabla%5Ctheta%20J%28%5Ctheta%29%20%5Coslash%20%5Csqrt%7Bs%2B%20%5Cepsilon%20%7D%0A)
- Adam[2015]
全称Adaptive Moment Estimation,结合Momentum optimization和RMSProp的思想,这两种分别是调节学习率和修正梯度,故可以采取综合的手段将两者结合。
衰减率、
,通常取值为0.9和0.999,而
默认即可,默认为1e-7。
下面5个式子中1、2、5接近Momentum optimization和RMSProp,而3、4为细节处理,加快、
在刚开始训练时的值,因为初始时为0,刚开始会偏小。
optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)
%5Cnabla%5Ctheta%20J(%5Ctheta)%5C%5C%0As%20%5Cgets%20%5Cbeta_2%20s%20%2B%20(1-%5Cbeta_2)%5Cnabla%5Ctheta%20J(%5Ctheta)%20%5Cotimes%20J(%5Ctheta)%5C%5C%0A%5Cwidehat%7Bm%7D%20%5Cgets%20%5Cfrac%7Bm%7D%7B1-%5Cbeta1%5Et%7D%20%5C%5C%0A%5Cwidehat%7Bs%7D%20%5Cgets%20%5Cfrac%7Bs%7D%7B1-%5Cbeta_2%5Et%7D%20%5C%5C%0A%5Ctheta%20%5Cgets%20%5Ctheta%20%2B%20%5Ceta%20%5Cwidehat%7Bm%7D%5Coslash%20%5Csqrt%7B%5Cwidehat%7Bs%7D%20%2B%20%5Cepsilon%7D%0A#card=math&code=m%20%5Cgets%20%5Cbeta_1%20m%20-%20%281-%5Cbeta_1%29%5Cnabla%5Ctheta%20J%28%5Ctheta%29%5C%5C%0As%20%5Cgets%20%5Cbeta2%20s%20%2B%20%281-%5Cbeta_2%29%5Cnabla%5Ctheta%20J%28%5Ctheta%29%20%5Cotimes%20J%28%5Ctheta%29%5C%5C%0A%5Cwidehat%7Bm%7D%20%5Cgets%20%5Cfrac%7Bm%7D%7B1-%5Cbeta_1%5Et%7D%20%5C%5C%0A%5Cwidehat%7Bs%7D%20%5Cgets%20%5Cfrac%7Bs%7D%7B1-%5Cbeta_2%5Et%7D%20%5C%5C%0A%5Ctheta%20%5Cgets%20%5Ctheta%20%2B%20%5Ceta%20%5Cwidehat%7Bm%7D%5Coslash%20%5Csqrt%7B%5Cwidehat%7Bs%7D%20%2B%20%5Cepsilon%7D%0A)
- Nadam[2016]
加Nesterov Momentum加速梯度的策略加入Adam中,收敛速度会略快一些。
正则化手段
L1正则化
L2正则化
L1、L2混合正则化
Dropout[2012]
每轮训练时,按一定比例对网络中的神经元进行随机丢弃,即每次针对网络的一个子图进行训练,防止每次针对固定网络训练而导致的过拟合。
有三点注意的:1. 只是在训练时进行随机丢弃(或者屏蔽),训练结束后使用时采用所有的网络神经元 2. 训练中的随机概率值固定,如50%,每次训练都随机屏蔽掉一半的神经元(每层)来训练网络权重 3. 训练结束后,得将所得所有权重进行一定程度的放大,如50%的丢弃率,则最后把所有的权重扩大至2倍,使得在使用所有网络神经元之后,输出值与训练时的值保持一致。
另外,对于自正则化网络,如损失函数为SELU的网络,应该使用AlphaDropoutkeras.layers.Dropout(rate=0.5) # 只在训练时生效,训练结束后不再操作
- Monte Carlo Dropout[2016]
采用蒙特卡洛采样,可以不用重新训练或重新建立新模型,就可以提升经过Dropout处理生成网络的表现。
具体操作:对于训练所得的模型,预测结果时,在Dropout层发挥作用的情况下(那么每次预测结果都不一样),用模型对数据重复预测(例如100次),然后取平均值,使得结果更加准确。不过引入了一个超参数——采样的样本数,其数值选取属于一个权衡过程,值小计算快,值大准确度高
可通过子类化Dropout实现MCDropout层 ```python y_probas = np.stack([model(X_test_scaled, training=True) for sample in range(100)]) y_probas = y_probas.mean(axis=0)
MCDropout
class MCDropout(keras.layers.Dropout): def call(self, inputs): return super().call(inputs, training=True)
-
Max-Norm
<br />另一种正则化手段,对于每个神经元的输入连接,限制其权重值,使得,其中是引入的超参数,降低可以增大正则化程度
```python
keras.layers.Dense(100, activation='elu', kernel_initializer='he_normal', kernel_constraint=keras.constraints.max_norm(1.))
- 其它:数据增强、过早终止、正交化(orthogonalization)
提升训练速度的方法
- 选择好的连接层初始化策略
- 选择好的激活函数
- 使用批量正规化
- 使用迁移学习(通过现有预训练网络,或者通过辅助任务、非监督学习进行构建预训练网络)
- 更换优化器