Week 5 正则化 & 初始化 & 标准化 & 对拍Debug
1 摘要
这一周主要是深度神经网络的很多配置设定问题,包括原因,解决思路和方法。
深度神经网络感觉确实只能从宏观调控,可能也是运算量过大,这使得随机化的一些算法居然很有用,比如dropout,因为大量导致产生了整体调整效果。
主要问题如下:
-
调参思路
-
正则化,解决过拟合问题,重点在于dropout算法
-
初始化,breaks symmetry效应,Vanishing / Exploding gradients
-
标准化
-
Gradient Checking,学会用这个debug
2 基本概念
训练集/测试集/dev 集合
训练集和测试集非常好理解,主要是测试集和验证集的区别。验证集主要用于调整超参数。关键在于,如果只有一个测试集,那么我们调整之后,容易对它过拟合,所以需要验证集来做调节,最终由测试集测试模型的泛化能力。
Bias/Variance
偏差和方差,一个是低拟合的到时候产生的误差,另一个是过拟合的时候产生的误差。通常,这是我们调参的风向标。
3 调参整体思路
对于偏差——低拟合,解决方式是Bigger network,train longer,nn architecture的优化。
对于方差——过拟合,解决方式是More data,Regulation,nn architecture的优化。
更大的网络,以及正则化,分别对应高偏差、高方差,而且这两个一般不会产生副作用,所以很多时候不需要考虑它们之间的权衡,只需要用好这两个主要方法就可以。
除了这两个方式,其他方法都很容易会在提高其一的同时降低另一个的表现,但好在更大的网络通常没有坏处,regulation对于偏差也影响不大,所以很多时候只需看着使用其一即可。因此,神经网路反倒比较少谈二者取舍。
后面再细谈过拟合的处理问题——基本所有因素都能导致过拟合,所以过拟合通常是比较大的问题。
4 初始化
不好的初始化会产生一些问题,这在浅层神经网路的时候就做过同样的问题,这里整理以下。
Q1:全零初始化为什么不行?全1会怎么样?
其实这是一个问题,重点在于权重相同的话,每一层的神经元上的值将会一模一样——输入w,a完全相同。那么所有节点计算的都是同一个特征,每个单元都是线性函数,那么组合出来的网络也是线性函数,这就是一个逻辑回归,神经网路结构没有意义。应该通过随机初始化来breaks symmetry。
Q2:随机初始化权重太大为什么不行?
可以想象,当特征数量非常大的时候,这种情况下,权重如果也非常大,那么z值就会很大,每一层叠加下来,将会产生vanishing/exploding gradients现象——梯度下降,本身就是非常慢的。
而且权重并不需要很大,只要大于1或者远小于1,那么就会出问题。
解决方法是,He和Xavier 初始化,其实原理很简单,只要所有权重加起来大致等于1就能很大程度避免这个问题,换句话说就是等于1/n。当然,其实只要建立这种和n的关系就是基本的思想。现在一般用得多的是He和Xavier 初始化——n[L-1]分之1的开方。
5 标准化
一般来讲是这样的。
x
−
μ
s
\frac{x-\mu}{s}
sx−μ
其中μ是均值,s可以是范围,可以是标准差等等。其实标准化目的是让x落在基本相同的范围内,所以并不需要那么精确。让特征处于相同的范围,这会使得梯度下降很快,是一种优化的方法。
6 Regulation
正则化的目的是为了处理过拟合问题,因为在数量极多,特征极多的情况下,很容易产生过拟合问题,尤其是计算机视觉领域。
思想:正则化其实基于一个很简单的思想,weight权重越低,模型就会越简单。可以形象地理解为生成的曲线越简单。
6.1 L2正则化
就是之前逻辑回归使用的正则化,之前也细谈过,在J中引入θ之后,使得J和θ之间自动权衡——θ太小误差就会变大,θ太大,J值就会变大。所以就会取一个比较合适的值,缓解过拟合。
从梯度下降来看,这个方法也叫weight decay,每次都会递减λ/m的偏导值。
这个方法引入了λ这个超参数,是很好用的方法之一。但缺点是,因为自动权衡,梯度下降会来回震荡,影响效率。
6.2 Dropout随机关闭神经元
这是一个有趣的方法,那么简单粗暴,却很有效果。而且非常有意思。
方法
以某个概率p随机关闭某一层的一些神经元(设置为0),给每一层设定一个概率,每次运行总是随机关闭掉一些。
原理
为什么这个有用?
理解这个问题,可以从这个角度。加入你把神经网络看成了是有主动向正确结果拟合的意识(其实就是梯度下降 + 损失函数),那么对于每个神经元来说,它便不会将权重集中在某个神经元上——因为它会被随机取消。所以神经元最好的选择是,分散投资,减少投资。
再者,inverse dropout的策略,为了使得z尽可能不变,弥补失去(1-p)的部分,会将A/p,大概使得整体尽可能不变。这因为着A变大,Z不变,而w就变小了。所以整体上,w会变得比较小。这就达成和L2正则化一样的目的。
很有意思的解释吧。其实我觉得,会出现这样的宏观结果,大概就是因为神经网路运算量大,可以视为随机分布,所以一层的每个单元都可以视为等概率消失,这使得,梯度下降并不会集中在某些权重上。
小结
dropout是一个很简单的策略,很奇怪,非常有用,特别是cv中,几乎成了默认选项。以下是实验结果,你可以看到,很有趣,在训练集上很低,在泛化测试集上表现得却特别好。
总的来说,它从全局上压制了w的取值。
On the train set:
Accuracy: 0.928909952607
On the test set:
Accuracy: 0.95
这个方法有些缺点,一是每层都有一个不那么重要的超参数,消失概率,再者,J变得不确定,很难从J下手调参。但这个方法很快,比正则化要快,因为它压制了w取值,但模型梯度下降却不会来回震荡,有点类似于无正则化的梯度下降。
6.3 其他方法
增加数据
这里指一种类型,比如对输入图片进行图像变化之后再做数据输入,比较简单,但会增加计算时间。
Early Stop
这个方法也很有意思,本来我们在训练集上训练完之后,会拿去Dev集上跑,图像一般如下。随着在训练集表现越来越好,在测试集上表现就会越来越差。
很自然的我们可以想到,在变化点停止迭代。这个方法不会引入超参数,但是缺点是:原本机器学习有个优点,我们只需关注J,而现在我们要权衡J和variance。
model | train accuracy | test accuracy |
---|---|---|
3-layer NN without regularization | 95% | 91.5% |
3-layer NN with L2-regularization | 94% | 93% |
3-layer NN with dropout | 93% | 95% |
7 Gradient Checking
神经网络模型太过复杂,所以debug也可想而知非常麻烦。这个主要是建立梯度的对拍程序,目的的检查向后传播或者向前传播是不是有问题。
7.1 原理
微积分的理论,直接设定epsilon,代入求数值化的偏导数,验证向后传播的计算是否正确。
∂
J
∂
θ
=
lim
ε
→
0
J
(
θ
+
ε
)
−
J
(
θ
−
ε
)
2
ε
\frac{\partial J}{\partial \theta}=\lim _{\varepsilon \rightarrow 0} \frac{J(\theta+\varepsilon)-J(\theta-\varepsilon)}{2 \varepsilon}
∂θ∂J=ε→0lim2εJ(θ+ε)−J(θ−ε)
验证的方法如下,结果只要小于2e-7,就认为其基本相同。这明显是一个经验的阈值。
d
i
f
f
e
r
e
n
c
e
=
∥
g
r
a
d
−
gradapprox
∥
2
∥
grad
∥
2
+
∥
gradapprox
∥
2
difference=\frac{ \| g r a d-\text {gradapprox} \|_{2}}{\|\operatorname{grad}\|_{2}+\| \text {gradapprox} \|_{2}}
difference=∥grad∥2+∥gradapprox∥2∥grad−gradapprox∥2
7.2 Debug
这个实在太有用了,所以记一下代码。keys中保存的用于定位bug的位置。
def dictionary_to_vector(parameters):
"""
Roll all our parameters dictionary into a single vector satisfying our specific required shape.
"""
keys = []
count = 0
for key in ["W1", "b1", "W2", "b2", "W3", "b3"]:
# flatten parameter
new_vector = np.reshape(parameters[key], (-1,1))
keys = keys + [key]*new_vector.shape[0]
if count == 0:
theta = new_vector
else:
theta = np.concatenate((theta, new_vector), axis=0)
count = count + 1
return theta, keys
8 神经网络算法效率思考
这算是进一步思考,我是一直很奇怪,为什么数值化的计算效率会很慢,另外就是神经网络为什么那么快。
当成向量思考会简单很多——即只看一组数据。
for i=1:iter:
L层向前传播() -----------------> n*n1*n2*n3...nL -> O(K n)
计算损失() -----------------> nL = O(1)
L层向后传播() -----------------> 这类似于向前传播的逆运算->O(K n)
跨层次传播看成reLU,视为线性,那么就是A1· W1 · W2…,W1是个矩阵,nL x nL-1,矩阵乘法效率为O(N^3),因为W矩阵维度都是常量,所以最终效率为O(iter · K · n)。
由于iter迭代次数相对于特征数量n,数据量m是常数,所以m组数据下,最终效率为O(K n^2)。n的平方,且都为矩阵运算。常数K可能会很大。
数值化计算慢的原因,关键在于
没组数据:
每次迭代:
每个参数(特征n):
向前传播计算损失(O(n))
所以结果是n的三次方。