2022秋招算法岗面经题:训练模型时loss除以10和学习率除以10真的等价吗(SGD等价,Adam不等价)
问题描述:训练深度学习模型时loss除以10和学习率除以10等价吗?
先说结论
这个问题的答案与优化器有关
- 使用Adam、Adagrad、RMSprop等带有二阶动量 v t v_t vt的优化器训练时,当我们将loss除以10,对训练几乎没有影响。
- 使用SGD、Momentum SGD等,将loss除以10,会对训练有影响,并且这个影响和学习率除以10是等价的。
具体分析
Adam
当使用Adam优化器时,若将loss扩大
s
s
s倍,则梯度
g
t
g_t
gt扩大
s
s
s倍,由于
m
t
m_t
mt是
g
t
g_t
gt的累加,
v
t
v_t
vt是
g
t
2
g_t^2
gt2的累加,且
m
^
t
\hat m_t
m^t和
m
t
m_t
mt是线性关系,
v
^
t
\hat v_t
v^t是
v
t
v_t
vt的线性关系,所以Adam的更新公式变成如下形式:
θ
t
=
θ
t
−
1
−
α
s
∗
m
^
t
s
2
∗
v
^
t
+
ϵ
=
θ
t
−
1
−
α
m
^
t
v
^
t
+
ϵ
/
s
≈
θ
t
−
1
−
α
m
^
t
v
^
t
+
ϵ
\theta_t=\theta_{t-1}-\alpha \frac{s * \hat{m}_t}{\sqrt{s^2 * \hat{v}_t}+\epsilon}=\theta_{t-1}-\alpha \frac{\hat{m}_t}{\sqrt{\hat{v}_t}+\epsilon / s}\approx \theta_{t-1}-\alpha \frac{\hat{m}_t}{\sqrt{\hat{v}_t}+\epsilon}
θt=θt−1−αs2∗v^t+ϵs∗m^t=θt−1−αv^t+ϵ/sm^t≈θt−1−αv^t+ϵm^t
当
ϵ
/
s
\epsilon/s
ϵ/s相对于
v
^
t
\sqrt{\hat{v}_t}
v^t是一个很小的量时,上面的约等于就相当于等号了。
现在我们可以尝试回答下面的问题了
问题:“在使用adam的情况下,如果人为把loss 扩大10倍,对adam来讲,会有什么影响?可以通过调整学习率来缓解这个影响吗?”
- 对于Adam而言,如果人为把loss扩大10倍,对参数梯度更新没有什么影响;
- Adam学习率乘上10000,对于参数更新影响巨大。
你如果不相信这个结果,那我们可以写个代码来验证下:
import torch
import torch.nn as nn
torch.manual_seed(20)
class FCModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Sequential(
nn.Linear(6, 6),
nn.ReLU(),
nn.Linear(6, 1),
nn.Sigmoid())
def forward(self, x):
return self.linear(x ** 2).exp()
# reload: ensure same initialization
fc = FCModel()
torch.save(fc, 'fc.pth.tar')
fc1 = torch.load('fc.pth.tar')
fc2 = torch.load('fc.pth.tar')
# optimizer1 = torch.optim.SGD(fc1.parameters(), lr=0.01 * 10000)
# optimizer2 = torch.optim.SGD(fc2.parameters(), lr=0.01)
optimizer1 = torch.optim.Adam(fc1.parameters(), lr=0.01)
optimizer2 = torch.optim.Adam(fc2.parameters(), lr=0.01)
# optimizer2 = torch.optim.Adam(fc2.parameters(), lr=0.01 * 10000)
for i in range(20):
input1 = torch.rand(1, 6)
label1 = torch.rand(1,)
output1 = fc1(input1)
loss1 = label1 - output1
optimizer1.zero_grad()
loss1.backward()
optimizer1.step()
input2 = input1.clone()
label2 = label1.clone()
output2 = fc2(input2)
loss2 = (label2 - output2) * 10000
optimizer2.zero_grad()
loss2.backward()
optimizer2.step()
print("fc1: {}".format(i), fc1.linear[0].weight)
print("fc2: {}".format(i), fc2.linear[0].weight)
print()
思考题:多任务学习为什么可以 a*loss1 + b*loss2
来平衡不同任务?
说到这里我有个疑问:既然对于Adam优化器,我loss除以10对训练情况几乎没有影响,那为什么在多任务学习中常用a*loss1 + b*loss2
这种方式来平衡不同任务的学习呢?
后来我想了想,a*loss1
确实可以让梯度g1扩大a倍,b*loss2
也确实可以让梯度g2扩大b倍,这没有错,但是在Adam里面把这两个梯度加在一起进行平方的,当a和b不相同时,就没有办法提取出相同的倍数,然后分子分母消掉了,因此a*loss1 + b*loss2
这种方式确实可以权衡不同任务训练时的重要性。
具体分析如下:
我们继续写个代码来验证下结论:
import torch
import torch.nn as nn
torch.manual_seed(20)
class FCModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Sequential(
nn.Linear(6, 6),
nn.ReLU(),
nn.Linear(6, 1),
nn.Sigmoid())
def forward(self, x):
return self.linear(x ** 2).exp()
# reload: ensure same initialization
fc = FCModel()
torch.save(fc, 'fc.pth.tar')
fc1 = torch.load('fc.pth.tar')
fc2 = torch.load('fc.pth.tar')
# optimizer1 = torch.optim.SGD(fc1.parameters(), lr=0.01 * 10000)
# optimizer2 = torch.optim.SGD(fc2.parameters(), lr=0.01)
optimizer1 = torch.optim.Adam(fc1.parameters(), lr=0.01)
optimizer2 = torch.optim.Adam(fc2.parameters(), lr=0.01)
# optimizer2 = torch.optim.Adam(fc2.parameters(), lr=0.01 * 10000)
for i in range(20):
input1 = torch.rand(1, 6)
label1 = torch.rand(1, )
output1 = fc1(input1)
loss11 = label1 - output1
loss12 = label1 + output1
loss13 = loss11 + loss12
optimizer1.zero_grad()
loss13.backward()
optimizer1.step()
input2 = input1.clone()
label2 = label1.clone()
output2 = fc2(input2)
loss21 = label2 - output2
loss22 = label2 + output2
loss23 = loss21 + 100 * loss22
optimizer2.zero_grad()
loss23.backward()
optimizer2.step()
print("fc1: {}".format(i), fc1.linear[0].weight)
print("fc2: {}".format(i), fc2.linear[0].weight)
print()
可以看到两者的参数更新情况是不一样的
相关资料
- 知乎:https://www.zhihu.com/question/320377013
- 面试题:2022年互联网秋招算法岗,被虐经历