深度学习调参技巧 调参trick

|公|众|号|包包算法笔记|

背景

事情的起因其实这样,实验室老同学的论文要冲分,问我有没有啥在NN上,基本都有用的刷点方法,最好是就是短小精悍,代码量不大,不需要怎么调参。

一般通用的trick都被写进论文和代码库里了,

像优秀的优化器,学习率调度方法,数据增强,dropout,初始化,BN,LN,确实是调参大师的宝贵经验,大家平常用的也很多。

除了这些,天底下还有这样的好事?

确实有一些这样的方法的,他们通用,简单。根据我的经验,在大多数的数据上都有效。

一、对抗训练

第一个,对抗训练。

对抗训练就是在输入的层次增加扰动,根据扰动产生的样本,来做一次反向传播。

以FGM为例,在NLP上,扰动作用于embedding层。

给个即插即用代码片段吧,引用了知乎id:Nicolas的代码,写的不错,带着看原理很容易就明白了。

import torch
class FGM():
    def __init__(self, model):
        self.model = model
        self.backup = {}

    def attack(self, epsilon=1., emb_name='emb.'):
        # emb_name这个参数要换成你模型中embedding的参数名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name:
                self.backup[name] = param.data.clone()
                norm = torch.norm(param.grad)
                if norm != 0 and not torch.isnan(norm):
                    r_at = epsilon * param.grad / norm
                    param.data.add_(r_at)

    def restore(self, emb_name='emb.'):
        # emb_name这个参数要换成你模型中embedding的参数名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name: 
                assert name in self.backup
                param.data = self.backup[name]
        self.backup = {}

具体FGM的实现

# 初始化
fgm = FGM(model)
for batch_input, batch_label in data:
    # 正常训练
    loss = model(batch_input, batch_label)
    loss.backward() # 反向传播,得到正常的grad
    # 对抗训练
    fgm.attack() # 在embedding上添加对抗扰动
    loss_adv = model(batch_input, batch_label)
    loss_adv.backward() # 反向传播,并在正常的grad基础上,累加对抗训练的梯度
    fgm.restore() # 恢复embedding参数
    # 梯度下降,更新参数
    optimizer.step()
    model.zero_grad()

二、EMA

第二个,EMA(指数滑动平均)

移动平均,保存历史的一份参数,在一定训练阶段后,拿历史的参数给目前学习的参数做一次平滑。这个东西,我之前在earhian的祖传代码里看到的。他喜欢这东西+衰减学习率。确实每次都有用。

代码引用博客:https://fyubang.com/2019/06/01/ema/

# 初始化
ema = EMA(model, 0.999)ema.register()# 训练过程中,更新完参数后,同步update shadow weightsdef train():    optimizer.step()    ema.update()# eval前,apply shadow weights;eval之后,恢复原来模型的参数def evaluate():    ema.apply_shadow()    # evaluate    ema.restore()

具体EMA实现,即插即用:

class EMA():
    def __init__(self, model, decay):
        self.model = model
        self.decay = decay
        self.shadow = {}
        self.backup = {}

    def register(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                self.shadow[name] = param.data.clone()

    def update(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.shadow
                new_average = (1.0 - self.decay) * param.data + self.decay * self.shadow[name]
                self.shadow[name] = new_average.clone()

    def apply_shadow(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.shadow
                self.backup[name] = param.data
                param.data = self.shadow[name]

    def restore(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.backup
                param.data = self.backup[name]
        self.backup = {}

# 初始化
ema = EMA(model, 0.999)
ema.register()

# 训练过程中,更新完参数后,同步update shadow weights
def train():
    optimizer.step()
    ema.update()

# eval前,apply shadow weights;eval之后,恢复原来模型的参数
def evaluate():
    ema.apply_shadow()
    # evaluate
    ema.restore()

三、TTA

第三个,TTA。

这个一句话说明白,测试时候构造靠谱的数据增强,简单一点的数据增强方式比较好,然后把预测结果加起来算个平均。

这个实现实在是比较简单,就不贴代码了。

四、伪标签

第四个,伪标签学习。

这个也一句话说明白,就是用训练的模型,把测试数据,或者没有标签的数据,推断一遍。构成伪标签,然后拿回去训练。注意不要leak。

下面那个老图,比较形象。

image.gif

五、特定样本处理

第五个,特定样本处理。

说这个通用勉强一点,但确实在这类数据上基本都有效。

就是小样本,长尾样本,或者模型不太有把握的样本。把分类过程为根据特征检索的过程。

用向量表征去查找最近邻样本。

这块,有个ICLR2020的文章写的比较好,facebook的老哥把几种典型的方法整理了一下,具体可以参考:

https://arxiv.org/abs/1910.09217

你可能感兴趣的:(深度学习调参技巧 调参trick)