Training Neural Network
首先在复习前边知识的部分,提出Data Preprocessing之后的数据对于weights的改变更有鲁棒性,也更容易进行优化。在超参数调试时,通常可以先选择两三个超参数进行调试(也可以选择优先级高的超参数进行调试),若一起调试,在指数级的超参数空间上很难遍历。
Optimization
while True:
weights_grad = evaluate_gradient(loss_fun, data, weights)
weights += -step_size * weights_grad
使用SGD时出现的问题
提出了一个概念condition number:Hession矩阵中最大值与最小值的比值,如果condition number很大,那么在优化过程中会出现一个方向上优化很快,另一个方向优化很慢,更新的方向会出现Zigzag的路径。
优化过程中出现局部最优点和鞍点,这两种情况均会使gradient变为0,参数更新停止,在高维空间中,鞍点的出现更为频繁。
SGD的梯度来自于mini batches,所以更新路线会很曲折(由于存在噪声)。
解决方案
SGD+ Momentum
initialize
  
v
t
=
0
\text{initialize}\;v_t = 0
initializevt=0
v
t
+
1
=
ρ
v
t
+
▽
f
(
x
t
)
v_{t+1} = \rho v_t + \triangledown f(x_t)
vt+1=ρvt+▽f(xt)
x
t
+
1
=
x
t
−
α
v
t
+
1
x_{t+1} = x_t - \alpha v_{t+1}
xt+1=xt−αvt+1
该方法的直观理解是在更新过程引入速度(velocity)的概念,
ρ
\rho
ρ 理解为在速度更新的基础上引入了摩擦力(friction)。
在更新时引入了之前的速度,那么变化快的方向的值会相互抵消,减少了向敏感处前进的数量,相反在不那么敏感的方向上更新速度会加快;在更新过程中其实对noisy做了一个平均,更新过程中可以向更smooth的方向移动。
Nesterov Momentum
v
t
+
1
=
ρ
v
t
−
α
▽
f
(
x
~
t
)
v_{t+1} = \rho v_t - \alpha \triangledown f(\tilde{x}_t)
vt+1=ρvt−α▽f(x~t)
x
~
t
+
1
=
x
~
t
+
v
t
+
1
+
ρ
(
v
t
+
1
−
v
t
)
\tilde{x}_{t+1} = \tilde{x}_t +v_{t+1} + \rho(v_{t+1} - v_t)
x~t+1=x~t+vt+1+ρ(vt+1−vt)
dx = compute_gradient(x)
old_v = v
v = rho * v - learning_rate * dx
x += -rho * old_v + (1 + rho) * v
RMSprop
AdaGrad
grad_squared = 0
while True:
dx = compute_gradient(x)
grad_squared += dx * dx
x -= learning_rate * dx / (np.sqrt(grad_sqared) + 1e-7)
该方法主要是为了处理condition number很大的情况,这样处理之后,对于 slow direction,值会在一定程度上变大;对于quick direction,值会在一定程度上变小。这种方法缺点也很明显,当迭代次数增大时,grad_suqred的值会变的很大,更新权重会越来越小,而且该方法对于局部最优点和鞍点依然无法解决。
对该方法进行改进就提出了RMSProp
grad_squared = 0
while True:
dx = compute_gradient(x)
grad_squared = decay_rate * grad_squared + (1 - decay_rate) * dx * dx # 削弱了平方项的权重
x -= learning_rate * dx / (np.sqrt(grad_sqared) + 1e-7)
Adam
first_moment = 0
second_moment = 0
for t in range(1, num_iterations):
dx = compute_gradient(x)
first_moment = beta1 * first_moment + (1-beta1) * dx# Momentum
second_moment = beta2 * second_moment + (1 - beta2) * dx * dx# RMSProp
first_unbias = first_moment / (1 - beta1 ** t)# Bias correction
second_unbias = second_moment / (1 - beta2 ** t)# Bias correction
x -= learning_rate * first_unbias / (np.sqrt(second_unbias ) + 1e-7) #RMSProp
通常Adam的超参设置为beta1 = 0.9 beta2 = 0.999 learning_rate = 1e-3或5e-4
Learning rate的更新策略
exponential decay:
α
=
α
0
e
−
k
t
\alpha = \alpha_0 e^{-kt}
α=α0e−kt
1/t decay:
α
=
α
0
/
(
1
+
k
t
)
\alpha = \alpha_0/(1+kt)
α=α0/(1+kt)
learning rate的更新策略:使用不同的learning rate绘制loss的下降曲线,再根据曲线,判断使用 learning rate decay的位置。
Second-Order Optimization
根据泰勒展开式:
J
(
θ
)
≈
J
(
θ
0
)
+
(
θ
−
θ
0
)
T
▽
θ
J
(
θ
0
)
+
1
2
(
θ
−
θ
0
)
T
H
(
θ
−
θ
0
)
J(\theta) \approx J(\theta_0) + (\theta - \theta_0)^T\triangledown_{\theta}J(\theta_0) + \frac{1}{2} (\theta -\theta_0)^TH(\theta -\theta_0)
J(θ)≈J(θ0)+(θ−θ0)T▽θJ(θ0)+21(θ−θ0)TH(θ−θ0)
求解之后可得
θ
∗
=
θ
0
−
H
−
1
▽
θ
J
(
θ
0
)
\theta ^* = \theta_0 - H^{-1}\triangledown_{\theta} J(\theta_0)
θ∗=θ0−H−1▽θJ(θ0)
好处:
不需要learning rate或者其他的超参数
缺点:
Hession矩阵的存储需要
O
(
N
2
)
O(N^2)
O(N2),转换需要
O
(
N
3
)
O(N^3)
O(N3)
改善方法:
- BFGS:不是直接对Hession矩阵取逆,而是对Hession矩阵的你矩阵进行估计,复杂度为 O ( N 2 ) O(N^2) O(N2)。
- L-BFGS: 不需要对Hession矩阵进行存储,该方法对于将输入全部输入(full-batch)的情况很有效,但是没有很好的办法应用到mini-batch情况下。
在实际情况中,我们通常使用Adam,如果可以将数据集全部输入也可以使用L-BFGS
对于训练集的误差与测试集的误差差距很大的情况,我们可以采取几种方法来提高学习的准确率:
3. 我们可以训练多个网络,通过对网络的结果取平均来提高准确率,通常可以提高2%的准确率。
4. 在一次训练过程中截取某一时刻的训练结果,在测试阶段使用这些不同时刻训练结果进行输出,最后将输出整合。在训练时我们可以使用小的学习速率和大的学习速率交叉使用的方法,会使snapshot的结果表现性能更好。
5. 在更新参数时对结果进行平滑衰减,即
while True:
data_batch = dataset.sample_data_batch()
loss = network.forward(data_batch)
dx = network.backward()
x += -learning_rate * dx
x_test = 0.995 * x_test + 0.005 * x
Drop out
通常使用在全连接层后边,如果用在卷积层后边,那么随机选择一些channel上的卷积核参数归0。
Dropout有作用的原因:
- 强迫网络减小对某些特定参数的依赖,避免特征之间的相互适应
- dropout可以理解为是多个网络模型的整合(每次训练一种状态的链接可以认为是一个模型)
在测试阶段,我们将神经元激活之后的值乘以dropout的概率作为当前神经元的输出,这样做是为了使测试时的输出为训练时输出的期望。
H1 = np.maximum(0, np.dot(W1, X) + b1) * p
H2 = np.maximum(0, np.dot(W2, H1) + b2) * p
out = np.doy(W3, H2) + b3
更一般的情况在训练时我们将选择出的神经元的激活值增大
def train_step(X):
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = (np.random.rand(*H1.shape) < p) /p
H1 *= U1
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = (np.random.rand(*H2.shape) < p) /p
H2 *= U2
out = np.doy(W3, H2) + b3
def predict(X):
H1 = np.maximum(0, np.dot(W1, X) + b1) * p
H2 = np.maximum(0, np.dot(W2, H1) + b2) * p
out = np.doy(W3, H2) + b3
Regularization的一般形式:在训练时加入一种随机值,在测试时取随机值的平均。一种例子为Batch normalization,训练时选择随机的batch,测试时则选择固定的参数取normalization。
Data Augmentation
Regularization 的一些方法
- L2 Regularization: R ( W ) = ∑ k ∑ l W k , l 2 R(W) = \sum_k\sum_lW^2_{k,l} R(W)=∑k∑lWk,l2
- L1 Regularization: R ( W ) = ∑ k ∑ l ∣ W k , l ∣ R(W) = \sum_k\sum_l|W_{k,l}| R(W)=∑k∑l∣Wk,l∣
- Elastic net(L1 + L2): R ( W ) = ∑ k ∑ l β W k , l 2 + ∣ W k , l ∣ R(W) = \sum_k\sum_l \beta W^2_{k,l} + |W_{k,l}| R(W)=∑k∑lβWk,l2+∣Wk,l∣
- Drop out
- Batch Normalization
- Data Augmentation
- Drop connection
- Fractional Max Pooling
随机取 2 \sqrt{2} 2附近的值,在测试时取一些固定pooling的结果平均或者测试很多样本取平均 - 随机深度,跳过其中的某一些层
Transfer Learning
很有效的方法
将非常大训练集的训练的网络参数 转换到自己的网络中
TensorFlow:https://github.com/tensorflow/models
Pytorch:https://github.com/pytorch/vision
最近有一篇论证预训练有效性的论文:https://mp.weixin.qq.com/s/qBK_-xO2BH7U8abl8I7dOQ