0. 写在前面:关于正则化
众所周知,我们训练一个模型,最想要的是期望风险最小化,而现实中期望风险不可得,所以转而在经验风险最小化与结构风险最小化之间寻得一个平衡,使得我们的模型既能够充分学习训练数据中的规则又能在未知数据中获得一个较好的泛化性能。
这其中,期望风险可以近似表示为我们的模型在测试数据上的损失,经验风险可近似表示为我们的模型在训练数据上的损失,而结构风险则是我们模型的复杂度。
如果单单使经验风险最小化,优化算法就会使模型过度学习,也即发生过拟合。因此,通常会对模型的训练过程进行一定的约束,以免其过度放飞自我,而这种约束即是正则化。
正则化方法有很多,如:L1/L2正则化、权重衰减、早停、DropOut、BN、标签平滑、各种数据增强等。每种方法都能在一定范围内达到对模型进行约束的目的。而我们今天的主角是一个用起来非常简单的方法,简单到只需要一行代码即可,它就是2020年ICML的一篇论文——Do We Need Zero Training Loss Afer Achieving Zero Training Error?。
1. 论文介绍
首先论文题目就很有意思,以一个问句的形式给出:训练误差为0后,还需要使训练损失达到0吗?显然是不需要的。
当我们的模型拟合能力很大时,也即模型很大时,如果我们继续降低训练损失,模型会过度学习训练数据中的特征,很容易把一些噪声或者无用信息当做特征学习进来,从而泛化能力变得很差。
论文摘要大意如下:
过参数化的深度神经网络有能力通过达到零训练误差来记住训练数据。而记住数据之后,会使得训练损失逼近零,从而模型变得过度自信,却在测试集上表现很差。已有的正则化方法不能直接定位到避免零化训练损失这一点上,它们很难找到损失函数的一个平衡点,结果就导致训练损失不是过大就是过小。本文提出了一种比较直接的解决方案,叫做“flooding”的方法,能够有意地防止训练损失的进一步减少。这种方法可以让loss先像往常一样梯度下降到“flooding level”(一个超参,是训练损失的底线)附近, 但当训练损失低于这个值时 ,就进行梯度上升。该方法只需一行代码即可实现,且能够与任何随机优化器以及正则化方法配合使用。使用了这种叫做“flooding”的正则化方法之后,模型会继续进行随机漫步,但训练损失会保持在一个固定值也就是“flooding level”附近,我们希望模型参数最终会漂移到一个使得损失函数较为平坦的区域,从而达到更好的泛化性能。实验也表明了这种正则化方法能够提高性能,同时也能得到测试损失的双下降曲线【注1】。
注1:所谓双下降曲线,就是指loss随着训练的过程先下降再上升然后再下降的一种曲线。
文章中的图1简明地表达了本文主体思想:
(a)是没有加入正则化约束的training loss 和test loss的变化趋势示意图;(b)是加入了flooding正则化的loss变化趋势示意图,可以看出test loss具有双下降曲线性质;而(c)和(d)则是在cifar-10数据集上,模型训练时loss的实际表现,分别对应着(a)和(b)。
而flooding方法的使用也很简单,公式为:
其中,J代表原始的目标函数,b即为“flooding level”,也就是那个关键超参,设定训练损失下线的值。
在训练代码中使用也就是多了一句话:
outputs = model(inputs)
loss = criterion(outputs, labels)
flood = (loss-b).abs()+b # This is it!
optimizer.zerograd()
flood.backward()
optimizer.step()
2. 实战结果
既然用起来如此简单,就没有不试一试的理由了。
于是,我先用一个朴素的训练过程跑了几十个epoch得到训练损失不加约束的下降而测试损失先下降后升高的过拟合现象,然后加入flooding方法对损失函数进行约束,得到了测试损失的双下降曲线:
左图即为训练过程放飞自我的loss,图2为加了flooding之后的loss,结果很明显,该方法还是有一定效果的。
而我的训练代码改动也是极其的小,只是多了一句话:
loss = (loss - 0.4).abs() + 0.4
至于为什么把b设为0.4,我是通过观察上面左图test loss什么时候开始上升的。当然这个值肯定不是最优,我只是用最简单的方法找出一个差不多的值试了一下。原文中是通过设定一个范围,然后循环训练找出最合适的值的。
Te fooding level is chosen from b ∈ {0,0.01,0.02,...,0.40} .
这种暴力破解的方法我就暂且不用了,太浪费时间,后面有空再整。