炼丹之道 | NLP中的对抗训练

每天给你送来NLP技术干货!


作者 | 王嘉宁@华师数据学院  

整理 | NewBeeNLP  

https://blog.csdn.net/qq_36426650/article/details/122807916

对抗训练本质是为了提高模型的鲁棒性,一般情况下在传统训练的基础上,添加了对抗训练是可以进一步提升效果的,在比赛打榜、调参时是非常重要的一个trick。对抗训练在CV领域内非常常用,那么在NLP领域如何使用呢?本文简单总结几种常用的对抗训练方法。

对抗训练旨在对原始输入样本 上施加扰动 ,得到对抗样本后用其进行训练:

公式理解:

  • 最大化扰动:挑选一个能使得模型产生更大损失(梯度较大)的扰动量,作为攻击;

  • 最小化损失:根据最大的扰动量,添加到输入样本后,朝着最小化含有扰动的损失(梯度下降)方向更新参数;

这个被构造出来的“对抗样本”并不能具体对应到某个单词,因此,反过来在推理阶段是没有办法通过修改原始输入得到这样的对抗样本。

对抗训练有两个作用,一是 提高模型对恶意攻击的鲁棒性 ,二是 提高模型的泛化能力

在CV任务,根据经验性的结论,对抗训练往往会使得模型在非对抗样本上的表现变差,然而神奇的是,在NLP任务中,模型的泛化能力反而变强了。

常用的几种对抗训练方法有FGSM、FGM、PGD、FreeAT、YOPO、FreeLB、SMART。本文暂时只介绍博主常用的3个方法,分别是 FGM PGD FreeLB 。具体实现时,不同的对抗方法会有差异,但是 从训练速度和代码编辑难易程度的角度考虑,推荐使用FGM和迭代次数较少的PGD

一、FGM算法

  • 首先计算输入样本 (通常为word embedding)的损失函数以及在 处的梯度:;

  • 计算在输入样本的扰动量:,其中 为超参数,默认取1.0;

  • 得到对抗样本:;

  • 根据得到的对抗样本,再次喂入模型中,计算损失,并累积梯度;

  • 恢复原始的word embedding,接着下一个batch。

FGM的代码量很少,只需要自行实现简单的类即可:

import torch
class FGM():
    def __init__(self, model):
        self.model = model
        self.backup = {} # 用于保存模型扰动前的参数

    def attack(
        self, 
        epsilon=1., 
        emb_name='word_embeddings' # emb_name表示模型中embedding的参数名
    ):
        '''
        生成扰动和对抗样本
        '''
        for name, param in self.model.named_parameters(): # 遍历模型的所有参数 
            if param.requires_grad and emb_name in name: # 只取word embedding层的参数
                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='word_embeddings' # emb_name表示模型中embedding的参数名
    ):
        '''
        恢复添加扰动的参数
        '''
        for name, param in self.model.named_parameters(): # 遍历模型的所有参数
            if param.requires_grad and emb_name in name:  # 只取word embedding层的参数
                assert name in self.backup
                param.data = self.backup[name] # 重新加载保存的参数值
        self.backup = {}

在训练时,只需要额外添加5行代码:

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

二、PGD算法

Project Gradient Descent(PGD)是一种迭代攻击算法,相比于普通的FGM 仅做一次迭代,PGD是做多次迭代,每次走一小步,每次迭代都会将扰动投射到规定范围内。形式化描述为:

其中 为扰动约束空间(一个半径为 的球体),原始的输入样本对应的初识点为球心,避免扰动超过球面。迭代多次后,保证扰动在一定范围内,如下图所示:
d3e3e89f36d6a21e1d77f791d673a3b4.png

代码实现如下所示:

import torch
class PGD():
    def __init__(self, model):
        self.model = model
        self.emb_backup = {}
        self.grad_backup = {}

    def attack(self, epsilon=1., alpha=0.3, emb_name='word_embeddings', is_first_attack=False):
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name:
                if is_first_attack:
                    self.emb_backup[name] = param.data.clone()
                norm = torch.norm(param.grad)
                if norm != 0 and not torch.isnan(norm):
                    r_at = alpha * param.grad / norm
                    param.data.add_(r_at)
                    param.data = self.project(name, param.data, epsilon)

    def restore(self, emb_name='word_embeddings'):
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name: 
                assert name in self.emb_backup
                param.data = self.emb_backup[name]
        self.emb_backup = {}

    def project(self, param_name, param_data, epsilon):
        r = param_data - self.emb_backup[param_name]
        if torch.norm(r) > epsilon:
            r = epsilon * r / torch.norm(r)
        return self.emb_backup[param_name] + r

    def backup_grad(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                self.grad_backup[name] = param.grad.clone()

    def restore_grad(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                param.grad = self.grad_backup[name]
pgd = PGD(model)
K = 3
for batch_input, batch_label in data:
    # 正常训练
    loss = model(batch_input, batch_label)
    loss.backward() # 反向传播,得到正常的grad
    pgd.backup_grad()
    # 累积多次对抗训练——每次生成对抗样本后,进行一次对抗训练,并不断累积梯度
    for t in range(K):
        pgd.attack(is_first_attack=(t==0)) # 在embedding上添加对抗扰动, first attack时备份param.data
        if t != K-1:
            model.zero_grad()
        else:
            pgd.restore_grad()
        loss_adv = model(batch_input, batch_label)
        loss_adv.backward() # 反向传播,并在正常的grad基础上,累加对抗训练的梯度
    pgd.restore() # 恢复embedding参数
    # 梯度下降,更新参数
    optimizer.step()
    model.zero_grad()

三、FreeLB算法

FreeLB针对PGD的多次迭代训练的问题进行了改进:

  • PGD是迭代 次扰动后取最后一次扰动的梯度更新参数,FreeLB是取 次迭代中的平均梯度(将 次迭代转换为类似一个虚拟的batch)。

  • 对抗训练和dropout不能同时使用;

具体的算法流程为:
329db83fb52c3f7ad7f84606f42e6fd9.png

很明显找到FreeLB与PGD的区别在于累积的方式:

  • FreeLB:通过对 K K K 次梯度的平均累积作为扰动更新
    69207ea21f2d6855c92e399f0bffc0f8.png

  • PGD:只取最后一次的梯度进行更新
    d054835b1dd1056c4f7c97fef89fac2d.png

实现流程如下图所示:
6924f3f9e98ea87a9131869288761406.png

其他对抗训练方法,以及更为详细的理论讲解,可参考文末参考文献。


📝论文解读投稿,让你的文章被更多不同背景、不同方向的人看到,不被石沉大海,或许还能增加不少引用的呦~ 投稿加下面微信备注“投稿”即可。

最近文章

COLING'22 | SelfMix:针对带噪数据集的半监督学习方法

ACMMM 2022 | 首个针对跨语言跨模态检索的噪声鲁棒研究工作

ACM MM 2022 Oral  | PRVR: 新的文本到视频跨模态检索子任务


投稿或交流学习,备注:昵称-学校(公司)-方向,进入DL&NLP交流群。

方向有很多:机器学习、深度学习,python,情感分析、意见挖掘、句法分析、机器翻译、人机对话、知识图谱、语音识别等。

ddba8758590369402346fd457f5794b0.png

记得备注~

本文参考资料

[1]

一文搞懂NLP中的对抗训练FGSM/FGM/PGD/FreeAT/YOPO/FreeLB/SMART: https://zhuanlan.zhihu.com/p/103593948

[2]

NLP --- >对抗学习:从FGM, PGD到FreeLB: https://blog.csdn.net/chencas/article/details/103551852/

[3]

【炼丹技巧】功守道:NLP中的对抗训练 + PyTorch实现: https://zhuanlan.zhihu.com/p/91269728

[4]

对抗学习总结:FGSM->FGM->PGD->FreeAT, YOPO ->FreeLb->SMART->LookAhead->VAT: https://blog.csdn.net/weixin_36378508/article/details/116131036

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
训练语言模型技术是自然语言处理(NLP)领域的一个重要研究方向,它能够处理含有自然语言的非结构化文本数据,并从提取出有用的信息。预训练语言模型技术已经在许多领域得到应用,包括文本分类、语言翻译、情感分析、问答系统等。 预训练语言模型技术基于深度学习算法,其最流行的是基于Transformer架构的模型,例如BERT,GPT等。预训练语言模型由两个阶段构成:预训练和微调。在第一阶段预训练过程,模型从大量的未标记文本数据提取出通用的语言表示,也就是模型了解了整个语言的基础知识。在第二阶段微调过程,模型被用于特定的任务,以便优化它们的性能。 预训练语言模型技术有以下优点:首先,它可以在无标记的文本数据上进行训练,因此可以利用大量存在的未标记数据来提高模型性能;其次,预训练使得模型可以更快地适应新任务,因为它已经掌握了基础知识;最后,它可以在多种NLP任务通用,因为它们都需要一种通用的语言表示。 与其他机器学习技术相比,预训练语言模型技术存在一些局限性,例如需要大量的计算资源、需要大量的训练时间等。此外,由于语言数据的多样性,预训练语言模型可能会出现典型性偏差问题,即在不同领域或文化,语言使用会有所不同,因此模型预测可能有误。 总的来说,预训练语言模型技术是NLP领域非常有前景的技术,在未来的研究和应用将继续得到广泛的应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值