我们真的需要把训练集的损失降到零吗?

在训练模型的时候,我们需要将损失函数一直训练到0吗?显然不用。一般来说,我们是用训练集来训练模型,但希望的是验证机的损失越小越好,而正常来说训练集的损失降到一定值后,验证集的损失就会开始上升,因此没必要把训练集的损失降低到0

既然如此,在已经达到了某个阈值之后,我们可不可以做点别的事情来提升模型性能呢?ICML2020的论文《Do We Need Zero Training Loss After Achieving Zero Training Error?》回答了这个问题,不过实际上它并没有很好的描述"为什么",而只是提出了"怎么做"

思路描述

论文提供的解决方案非常简单,假设原来的损失函数是

L

(

θ

)

mathcal{L}(theta)

L(θ),现在改为

L

~

(

θ

)

tilde{mathcal{L}}(theta)

L~(θ)

L

~

(

θ

)

=

L

(

θ

)

b

+

b

(1)

tilde{mathcal{L}}(theta)=|mathcal{L}(theta)-b|+btag{1}

L~(θ)=L(θ)b+b(1)
其中

b

b

b是预先设定的阈值。当

L

(

θ

)

>

b

mathcal{L}(theta)>b

L(θ)>b

L

~

(

θ

)

=

L

(

θ

)

tilde{mathcal{L}}(theta)=mathcal{L}(theta)

L~(θ)=L(θ),这时就是执行普通的梯度下降;而

L

(

θ

)

<

b

mathcal{L}(theta)<b

L(θ)<b

L

~

(

θ

)

=

2

b

L

(

θ

)

tilde{mathcal{L}}(theta)=2b-mathcal{L}(theta)

L~(θ)=2bL(θ),注意到损失函数变号了,所以这时候是梯度上升。因此,总的来说就是以

b

b

b为阈值,低于阈值时反而希望损失函数变大。论文把这个改动称为**“Flooding”**

这样做有什么效果呢?论文显示,在某些任务中,训练集的损失函数经过这样处理后,验证集的损失能出现"二次下降(Double Descent)",如下图


左图:不加Flooding的训练示意图;右图:加了Flooding的训练示意图

简单来说,就是最终的验证集效果可能更好一些,原论文的实验结果如下:


Flooding的实验结果:第一行W表示是否使用weight decay,第二行E表示是否使用early stop,第三行的F表示是否使用Flooding

个人分析

如何解释这个方法呢?可以想像,当损失函数达到

b

b

b之后,训练流程大概就是在交替执行梯度下降和梯度上升。直观想的话,感觉一步上升一步下降,似乎刚好抵消了。事实真的如此吗?我们来算一下看看。假设先下降一步后上升一步,学习率为

ε

varepsilon

ε,那么:

θ

n

=

θ

n

1

ε

g

(

θ

n

1

)

θ

n

+

1

=

θ

n

+

ε

g

(

θ

n

)

(2)

begin{aligned}&theta_n = theta_{n-1} - varepsilon g(theta_{n-1})\ &theta_{n+1} = theta_n + varepsilon g(theta_n) end{aligned}tag{2}

θn=θn1εg(θn1)θn+1=θn+εg(θn)(2)
其中

g

(

θ

)

=

θ

L

(

θ

)

g(theta)=nabla_{theta}mathcal{L}(theta)

g(θ)=θL(θ),现在我们有

θ

n

+

1

=

θ

n

1

ε

g

(

θ

n

1

)

+

ε

g

(

θ

n

1

ε

g

(

θ

n

1

)

)

θ

n

1

ε

g

(

θ

n

1

)

+

ε

(

g

(

θ

n

1

)

ε

θ

g

(

θ

n

1

)

g

(

θ

n

1

)

)

=

θ

n

1

ε

2

2

θ

g

(

θ

n

1

)

2

(3)

begin{aligned}theta_{n+1} =&, theta_{n-1} - varepsilon g(theta_{n-1}) + varepsilon gbig(theta_{n-1} - varepsilon g(theta_{n-1})big)\ approx&,theta_{n-1} - varepsilon g(theta_{n-1}) + varepsilon big(g(theta_{n-1}) - varepsilon nabla_{theta} g(theta_{n-1}) g(theta_{n-1})big)\ =&,theta_{n-1} - frac{varepsilon^2}{2}nabla_{theta}Vert g(theta_{n-1})Vert^2 end{aligned}tag{3}

θn+1==θn1εg(θn1)+εg(θn1εg(θn1))θn1εg(θn1)+ε(g(θn1)εθg(θn1)g(θn1))θn12ε2θg(θn1)2(3)

近似那一步实际上是使用了泰勒展开,我们将

θ

n

1

theta_{n-1}

θn1看作

x

x

x

ε

g

(

θ

n

1

)

varepsilon g(theta_{n-1})

εg(θn1)看作

Δ

x

Delta x

Δx,由于

g

(

x

Δ

x

)

g

(

x

)

Δ

x

=

x

g

(

x

)

frac{g(x - Delta x) - g(x)}{-Delta x} = nabla_x g(x)

Δxg(xΔx)g(x)=xg(x)
所以

g

(

x

Δ

x

)

=

g

(

x

)

Δ

x

x

g

(

x

)

g(x - Delta x) = g(x) - Delta x nabla_x g(x)

g(xΔx)=g(x)Δxxg(x)

最终的结果就是相当于学习率为

ε

2

2

frac{varepsilon^2}{2}

2ε2、损失函数为梯度惩罚

g

(

θ

)

2

=

θ

L

(

θ

)

2

Vert g(theta)Vert^2 = Vert nabla_{theta} mathcal{L}(theta)Vert^2

g(θ)2=θL(θ)2的梯度下降。更妙的是,改为"先上升再下降",其表达式依然是一样的(这不禁让我想起"先涨价10%再降价10%“和"先降价10%再涨价10%的故事”)。因此,平均而言,Flooding对损失函数的改动,相当于在保证了损失函数足够小之后去最小化

x

L

(

θ

)

2

Vert nabla_x mathcal{L}(theta)Vert^2

xL(θ)2,也就是推动参数往更平稳的区域走,这通常能提高泛化性(更好地抵抗扰动),因此一定程度上就能解释Flooding有作用的原因了

本质上来讲,这跟往参数里边加入随机扰动、对抗训练等也没什么差别,只不过这里是保证了损失足够小后再加扰动

继续脑洞

想要使用Flooding非常简单,只需要在原有代码基础上增加一行即可

logits = model(x)
loss = criterion(logits, y)
loss = (loss - b).abs() + b # This is it!
optimizer.zero_grad()
loss.backward()
optimizer.step()

有心是用这个方法的读者可能会纠结于

b

b

b的选择,原论文说

b

b

b的选择是一个暴力迭代的过程,需要多次尝试

The flood level is chosen from

b

{

0

,

0.01

,

0.02

,

.

.

.

,

0.50

}

bin {0, 0.01,0.02,...,0.50}

b{0,0.01,0.02,...,0.50}

不过笔者倒是有另外一个脑洞:

b

b

b无非就是决定什么时候开始交替训练罢了,那如果我们从一开始就用不同的学习率进行交替训练呢?也就是自始自终都执行

θ

n

=

θ

n

1

ε

1

g

(

θ

n

1

)

θ

n

+

1

=

θ

n

+

ε

2

g

(

θ

n

)

(4)

begin{aligned}&theta_n = theta_{n-1} - varepsilon_1 g(theta_{n-1})\ &theta_{n+1} = theta_n + varepsilon_2 g(theta_n) end{aligned}tag{4}

θn=θn1ε1g(θn1)θn+1=θn+ε2g(θn)(4)
其中

ε

1

>

ε

2

varepsilon_1 > varepsilon_2

ε1>ε2,这样我们就把

b

b

b去掉了(引入了

ε

1

,

ε

2

varepsilon_1, varepsilon_2

ε1,ε2的选择,天下没有免费的午餐)。重复上述近似展开,我们就得到

θ

n

+

1

=

θ

n

1

ε

1

g

(

θ

n

1

)

+

ε

2

g

(

θ

n

1

ε

1

g

(

θ

n

1

)

)

θ

n

1

ε

1

g

(

θ

n

1

)

+

ε

2

(

g

(

θ

n

1

)

ε

1

θ

g

(

θ

n

1

)

g

(

θ

n

1

)

)

=

θ

n

1

(

ε

1

ε

2

)

g

(

θ

n

1

)

ε

1

ε

2

2

θ

g

(

θ

n

1

)

2

=

θ

n

1

(

ε

1

ε

2

)

θ

[

L

(

θ

n

1

)

+

ε

1

ε

2

2

(

ε

1

ε

2

)

θ

L

(

θ

n

1

)

2

]

(5)

begin{aligned} theta_{n+1} =& , theta_{n-1} - varepsilon_1g(theta_{n-1})+varepsilon_2g(theta_{n-1} - varepsilon_1g(theta_{n-1}))\ approx&, theta_{n-1} - varepsilon_1g(theta_{n-1}) + varepsilon_2(g(theta_{n-1}) - varepsilon_1nabla_theta g(theta_{n-1})g(theta_{n-1}))\ =&, theta_{n-1} - (varepsilon_1 - varepsilon_2) g(theta_{n-1}) - frac{varepsilon_1varepsilon_2}{2}nabla_{theta}Vert g(theta_{n-1})Vert^2\ =&,theta_{n-1} - (varepsilon_1 - varepsilon_2)nabla_{theta}left[mathcal{L}(theta_{n-1}) + frac{varepsilon_1varepsilon_2}{2(varepsilon_1 - varepsilon_2)}Vert nabla_{theta}mathcal{L}(theta_{n-1})Vert^2right] end{aligned}tag{5}

θn+1===θn1ε1g(θn1)+ε2g(θn1ε1g(θn1))θn1ε1g(θn1)+ε2(g(θn1)ε1θg(θn1)g(θn1))θn1(ε1ε2)g(θn1)2ε1ε2θg(θn1)2θn1(ε1ε2)θ[L(θn1)+2(ε1ε2)ε1ε2θL(θn1)2](5)
这就相当于自始自终都在用学习率

ε

1

ε

2

varepsilon_1-varepsilon_2

ε1ε2来优化损失函数

L

(

θ

)

+

ε

1

ε

2

2

(

ε

1

ε

2

)

θ

L

(

θ

)

2

mathcal{L}(theta) + frac{varepsilon_1varepsilon_2}{2(varepsilon_1 - varepsilon_2)}Vertnabla_{theta}mathcal{L}(theta)Vert^2

L(θ)+2(ε1ε2)ε1ε2θL(θ)2了,也就是说一开始就把梯度惩罚给加了进去,这样能提升模型的泛化性能吗?《Backstitch: Counteracting Finite-sample Bias via Negative Steps》里边指出这种做法在语音识别上是有效的,请读者自行测试甄别

效果检验

我随便在网上找了个竞赛,然后利用别人提供的以BERT为baseline的代码,对Flooding的效果进行了测试,下图分别是没有做Flooding和参数

b

=

0.7

b=0.7

b=0.7的Flooding损失值变化图,值得一提的是,没有做Flooding的验证集最低损失值为0.814198,而做了Flooding的验证集最低损失值为0.809810

根据知乎文章一行代码发一篇ICML?底下用户Curry评论所言:“通常来说

b

b

b值需要设置成比’Validation Error开始上升’的值更小,1/2处甚至更小,结果更优”,所以我仔细观察了下没有加Flooding模型损失值变化图,大概在loss为0.75到1.0左右的时候开始出现过拟合现象,因此我又分别设置了

b

=

0.4

b=0.4

b=0.4

b

=

0.5

b=0.5

b=0.5,做了两次Flooding实验,结果如下图

值得一提的是,

b

=

0.4

b=0.4

b=0.4

b

=

0.5

b=0.5

b=0.5时,验证集上的损失值最低仅为0.809958和0.796819,而且很明显验证集损失的整体上升趋势更加缓慢。接下来我做了一个实验,主要是验证"继续脑洞"部分以不同的学习率一开始就交替着做梯度下降和梯度上升的效果,其中,梯度下降的学习率我设为

1

e

5

1e-5

1e5,梯度上升的学习率为

1

e

6

1e-6

1e6,结果如下图,验证集的损失最低仅有0.783370

References

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>