对抗训练NLP


对抗训练是一种引入噪声的训练方式,可以对参数进行正则化,提升模型鲁棒性和泛化能力。

也就是在输入的层次增加扰动,根据扰动产生的样本,来做一次反向传播。
对抗样本一般需要具有两个特点

  • 相对于原始输入,所添加的扰动是微小的;
  • 能使模型犯错。

用一句话形容对抗训练的思路,就是在输入上进行梯度上升(增大loss)(使得输入尽可能跟原来不一样),在参数上进行梯度下降(减小loss)(使得模型尽可能能够正确识别)。由于输入会进行embedding lookup,所以实际的做法是在embedding table上进行梯度上升

接下来介绍不同的方法,后续方法优化的主要方向有两点得到更优的扰动 & 提升训练速度


一、FGSM (Fast Gradient Sign Method): ICLR2015

FGSM的全称是Fast Gradient Sign Method(快速梯度下降法)
https://www.cnblogs.com/tangweijqxx/p/10615950.html
是由Goodfellow提出对抗训练时的方法,假设对于输入的梯度为:
在这里插入图片描述

那扰动肯定是沿着梯度的方向往损失函数的极大值走:
在这里插入图片描述
FGSM作者的想法很简单,就是将扰动( r a d v r_adv radv)加到参数中,这样就能使得损失值越来越大,模型将输入图像错分类成正确类别以外的其他任何一个类别都算攻击成功(即加入扰动,使模型分类错误)。其论文是通过对损失函数做一个梯度,然后对梯度取sign,其中sign是个分段函数,>0 为1,小于0为-1,等于0为0。缺点: 但是存在一个问题,就是会改变梯度的方向。

二、FGM (Fast Gradient Method): ICLR2017

FSGM是每个方向上都走相同的一步,Goodfellow后续提出的FGM则是根据具体的梯度进行scale,得到更好的对抗样本:

在这里插入图片描述

伪代码:

对于每个x:
  1.计算x的前向loss、反向传播得到梯度
  2.根据embedding矩阵的梯度计算出r,并加到当前embedding上,相当于x+r
  3.计算x+r的前向loss,反向传播得到对抗的梯度,累加到(1)的梯度上
  4.将embedding恢复为(1)时的值
  5.根据(3)的梯度对参数进行更新

# 初始化
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()

FGM实现代码

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 = {}

三、PGD (Projected Gradient Descent): ICLR2018

FGM直接通过epsilon参数一下子算出了对抗扰动,这样得到的可能不是最优的。因此PGD进行了改进,多迭代几次,慢慢找到最优的扰动。
FGM简单粗暴的“一步到位”,可能走不到约束内的最优点。PGD则是“小步走,多走几步”,如果走出了扰动半径为epsilon的空间,就映射回“球面”上,以保证扰动不要过大
在这里插入图片描述

在这里插入图片描述

伪代码:

对于每个x:
  1.计算x的前向loss、反向传播得到梯度并备份
  对于每步t:
    2.根据embedding矩阵的梯度计算出r,并加到当前embedding上,相当于x+r(超出范围则投影回epsilon内)
    3.t不是最后一步: 将梯度归0,根据1的x+r计算前后向并得到梯度
    4.t是最后一步: 恢复(1)的梯度,计算最后的x+r并将梯度累加到(1)5.将embedding恢复为(1)时的值
  6.根据(4)的梯度对参数进行更新

可以看到,在循环中r是逐渐累加的,要注意的是最后更新参数只使用最后一个x+r算出来的梯度

四、FreeAT (Free Adversarial Training): NIPS2019

从FGSM到PGD,主要是优化对抗扰动的计算,虽然取得了更好的效果,但计算量也一步步增加。对于每个样本,FGSM和FGM都只用计算两次,一次是计算x的前后向,一次是计算x+r的前后向。而PGD则计算了K+1次,消耗了更多的计算资源。因此FreeAT被提了出来,在PGD的基础上进行训练速度的优化。

FreeAT的思想是在对每个样本x连续重复m次训练,计算r时复用上一步的梯度,为了保证速度,整体epoch会除以m。r的更新公式为:
在这里插入图片描述
伪代码:

初始化r=0
对于epoch=1...N/m:
  对于每个x:
    对于每步m:
      1.利用上一步的r,计算x+r的前后向,得到梯度
      2.根据梯度更新参数
      3.根据梯度更新r
      

缺点: FreeLB指出,FreeAT的问题在于每次的r对于当前的参数都是次优的(无法最大化loss),因为当前r是由r(t-1)和theta(t-1)计算出来的,是对于theta(t-1)的最优。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值