学习率的选择与切换:
- 初期Adam,后期SGD,切换方法参考:Improving Generalization Performance by Switching from Adam to SGD
- 首先来看第1个问题,切换之后用什么样的学习率。
Adam的下降方向是
n t A d a m = ( α / V t ) ∗ m t n^{Adam}_t = (\alpha/\sqrt{V_t})*m_t ntAdam=(α/Vt)∗mt
而SGD的下降方向是.
n t S G D = α S G D ⋅ g t n^{SGD}_t = \alpha^{SGD}\cdot g_t ntSGD=αSGD⋅gt
, n t S G D n^{SGD}_t ntSGD必定可以分解为 n t A d a m n^{Adam}_t ntAdam 所在方向及其正交方向上的两个方向之和,那么其在 n t A d a m n^{Adam}_t ntAdam 方向上的投影就意味着SGD在Adam算法决定的下降方向上前进的距离,而在 n t A d a m n^{Adam}_t ntAdam 的正交方向上的投影是 SGD 在自己选择的修正方向上前进的距离。
图片来自原文,这里p为Adam下降方向,g为梯度方向,r为SGD的学习率。如果SGD要走完Adam未走完的路,那就首先要接过Adam的大旗——沿着
n
t
A
d
a
m
n^{Adam}_t
ntAdam 方向走一步,而后在沿着其正交方向走相应的一步。这样我们就知道该如何确定SGD的步长(学习率)了——SGD在Adam下降方向上的正交投影,应该正好等于Adam的下降方向(含步长)。也即:
p
r
o
j
n
t
S
G
D
=
n
t
A
d
a
m
proj_{n^{SGD}_t} =n^{Adam}_t
projntSGD=ntAdam
解这个方程,我们就可以得到接续进行SGD的学习率:
α
t
S
G
D
=
(
(
n
t
A
d
a
m
)
T
n
t
A
d
a
m
)
/
(
(
n
t
A
d
a
m
)
T
g
t
)
\alpha_t^{SGD}=((n_t^{Adam})^Tn_t^{Adam})/((n_t^{Adam})^Tg_t)
αtSGD=((ntAdam)TntAdam)/((ntAdam)Tgt)为了减少噪声影响,作者使用移动平均值来修正对学习率的估计:
λ
t
S
G
D
=
β
2
⋅
λ
t
−
1
S
G
D
+
(
1
−
β
2
)
α
t
S
G
D
\lambda_t^{SGD}=\beta_2\cdot\lambda_{t-1}^{SGD}+(1-\beta_2)\alpha_t^{SGD}
λtSGD=β2⋅λt−1SGD+(1−β2)αtSGD
λ
~
t
S
G
D
=
λ
t
S
G
D
/
(
1
−
β
2
t
)
\tilde{\lambda}^{SGD}_t =\lambda_t^{SGD}/(1-\beta_2^t)
λ~tSGD=λtSGD/(1−β2t)这里直接复用了Adam的
β
2
\beta_2
β2参数。然后来看第一个问题,何时进行算法的切换。作者的回答也很简单,那就是当 SGD的相应学习率的移动平均值基本不变的时候,即:
∣
λ
~
t
S
G
D
−
α
t
S
G
D
∣
<
ϵ
|\tilde{\lambda}^{SGD}_t-\alpha_t^{SGD}|<\epsilon
∣λ~tSGD−αtSGD∣<ϵ
每次迭代玩都计算一下SGD接班人的相应学习率,如果发现基本稳定了,那就SGD以
λ
~
t
S
G
D
\tilde{\lambda}^{SGD}_t
λ~tSGD 为学习率接班前进。
使用方法
pip install pytorch-swats
import swats
optimizer = swats.SWATS(model.parameters())
data_loader = torch.utils.data.DataLoader(...)
for epoch in range(10):
for inputs, targets in data_loader:
# deleting the stored grad values
optimizer.zero_grad()
outputs = model(inputs)
loss = loss_fn(outputs, targets)
loss.backward()
# performing parameter update
optimizer.step()
其他小技巧
- 先用小数据集进行实验。
- 数据集一定要充分的打散(shuffle)。 这样在使用自适应学习率算法的时候,可以避免某些特征集中出现,而导致的有时学习过度、有时学习不足,使得下降方向出现偏差的问题。
- 在语义分割任务时可使用lovasa-loss
lovasa-loss - 如果上述学习率效果差,可考虑换为warm restart 学习率:
Cosine Annealing w. Snapshot Ensemble
CYCLE=8000
LR_INIT=0.
1LR_MIN=0.001
scheduler = lambda x: ((LR_INIT-LR_MIN)/2)*(np.cos(PI*(np.mod(x-1,CYCLE)/(CYCLE)))+1)+LR_MIN