对抗训练:FGM与PGD方法介绍

对抗训练:FGM与PGD方法介绍

为什么会有对抗训练

当我们对一个事物进行建模或描述时,即使这种描述可能存在偏差,它仍然是对该事物本质的一种映射或表达。对于一张猫的图片来说,我们知道它是猫,但当我们对图片进行裁剪、缩放、遮掩和模糊后(对于模型而言,常见的数据增强(如裁剪、模糊等)是有意识地改变输入,但它们通常不会欺骗模型。而对抗样本 则是通过在输入中加入微小且人类难以察觉的扰动 ,以最大化模型的预测错误,从而检验模型的鲁棒性。这里只是为了方便理解故使用这些),我们可能不太能认出这是猫,但他实际上仍是猫。为了让模型能在这种情况下认出他是猫,所以我们认为构建这种人类难以察觉但会导致模型错误预测的微小扰动样本加入到模型的训练中,能使模型具备辨别的能力,而这种生成困难表示的方式就是对样本进行数据扰动,而在深度学习中,由于深度模型具有极高的拟合能力,它们很容易在训练数据上表现良好,但在面对稍有偏差的输入时却可能表现极差。而训练模型的数据往往本身就是有噪声且不充分的,所以我们需要引入对抗训练,让模型能优化到那些他原本可能薄弱的实例。

数据扰动的几种方式

1. FGM

在FGM中,数据扰动让输入或嵌入在最大化损失的方向上沿梯度单位方向扰动一次(通常要对梯度进行归一化以控制扰动幅度,常见的是使用L2归一,即将梯度除以它的L2范数),使得模型损失最大程度增长,让模型更能学到知识,至于这里的梯度是如何来的,如果你看过使用FGM的代码,你会发现在一个batch中调用了两次model和loss,第一次正是用来计算梯度帮助第二次生成对抗样本的
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> x a d v = x + ε ⋅ ∇ x L ( x , y ) ∥ ∇ x L ( x , y ) ∥ x_{adv} = x + ε \cdot \frac{\nabla_x L(x, y)}{\|\nabla_x L(x, y)\|} </math>xadv=x+ε⋅∥∇xL(x,y)∥∇xL(x,y)

  • ε 扰动强度
  • ∇_x L(x, y) 损失函数L的梯度
ini 复制代码
 class FGM:
     def __init__(self, model):
         self.model = model
         self.backup = {}
 ​
     def attack(self, epsilon=1.0, emb_name='embed_tokens'): #例子是nlp文本生成的,其中embedding这里会给出每个token的向量,所以对他的参数进行扰动就能实现对样例的扰动
         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:
                     r_at = epsilon * param.grad / norm
                     param.data.add_(r_at)
 ​
     def restore(self, emb_name='embed_tokens'):
         for name, param in self.model.named_parameters():
             if param.requires_grad and emb_name in name and name in self.backup:
                 param.data = self.backup[name]
         self.backup = {}
 ​
 '''
 在batch中
 '''
 ​
 output=model(**batch)
 loss=output.loss
 loss.backward()
 fgm.attack() #进行数据扰动
 output2=model(**batch)
 loss2=output2.loss
 loss2.backward()
 fgm.restore() #恢复扰动数据,因为有些时候的扰动实际上是通过作作用于模型参数做的,如nlp,因为他的原始输入是文字等,无法扰动。所以攻击完后要复原,趁着参数还没更新

2. PGD

与 FGM 只执行一次线性扰动不同,PGD 会在一个局部邻域内多步迭代地进行扰动,每一步都在当前对抗样本基础上进一步最大化损失,并通过投影确保扰动不会超过指定的上限,从而更有效地逼近最坏情况。

第一个样本
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> x 0 a d v = x + δ 0 , 其中 δ 0 ∼ U ( − ϵ , ϵ ) x_{0}^{adv} = x + δ_0,\quad \text{其中 } δ_0 \sim \mathcal{U}(-\epsilon, \epsilon) </math>x0adv=x+δ0,其中 δ0∼U(−ϵ,ϵ)

后续样本
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> x t + 1 = x t + α ∗ s i g n ( ∇ x L ( x t , y ) ) x t + 1 = c l i p ( x t + 1 , x − ε , x + ε ) x_{t+1} = x_t + α * sign(∇x L(x_t, y))\\ x{t+1} = clip(x_{t+1}, x-ε, x+ε) </math>xt+1=xt+α∗sign(∇xL(xt,y))xt+1=clip(xt+1,x−ε,x+ε)

clip 对扰动进行裁剪防止过大或过小

python 复制代码
 class PGD:
     def __init__(self, model, emb_name='model.encoder.embed_tokens', epsilon=1.0, alpha=0.3, K=3):
         self.model = model
         self.emb_name = emb_name  # 目标embedding名称,因为例子是nlp文本生成的,其中embedding这里会给出每个token的向量,所以对他的参数进行扰动就能实现对样例的扰动
         self.epsilon = epsilon    # 扰动半径
         self.alpha = alpha        # 每步步长
         self.K = K                # 攻击步数
         self.emb_backup = {}      # 原始embedding备份
         self.grad_backup = {}
 ​
     def attack(self, is_first_attack=False):
         for name, param in self.model.named_parameters():
             if param.requires_grad and self.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 = self.alpha * param.grad / norm
                     param.data.add_(r_at)
                     param.data = self.project(name, param.data)
 ​
     def restore(self):
         for name, param in self.model.named_parameters():
             if param.requires_grad and self.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):
         r = param_data - self.emb_backup[param_name]
         if torch.norm(r) > self.epsilon:
             r = self.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 and param.grad is not None:
                 self.grad_backup[name] = param.grad.clone()
 ​
     def restore_grad(self):
         for name, param in self.model.named_parameters():
             if param.requires_grad and param.grad is not None:
                 param.grad = self.grad_backup[name]
 ​
 ​
 '''
 在batch中
 '''
 outputs = model(input_ids=batch['input_ids'], attention_mask=batch['attention_mask'], labels=batch['labels'])
 loss = outputs.loss
 epoch_loss += loss.item()
 loss.backward()
 ​
 pgd.backup_grad() #保留原梯度,用来后续还原
 for t in range(pgd.K):
     pgd.attack(is_first_attack=(t==0)) #数据扰动
     if t != pgd.K - 1:
         model.zero_grad() #清除中间梯度防止污染累计梯度
     else:
         pgd.restore_grad() #最后一次攻击后恢复梯度
     outputs_adv = model(input_ids=batch['input_ids'], attention_mask=batch['attention_mask'], labels=batch['labels'])
     adv_loss = outputs_adv.loss
     adv_loss.backward() #求梯度用于优化
 pgd.restore() #恢复扰动数据

PGD多次生成对抗样本并优化的优势

  1. 攻击能力更强
  2. 搜索更充分
  3. 克服局部最优解

缺点显而易见,太耗时间了,所以对于PGD而言,对抗次数是一个重要的超参数,需要平衡性能与效果

为什么对抗训练有效

  • 对抗训练不仅仅是鲁棒性提升,它本质上是一种正则化方法,可以减少过拟合;
  • 它也迫使模型在局部邻域保持一致预测(类似一致性正则项);
  • 可以提高模型对分布外样本(out-of-distribution)更稳定。

因此,对抗训练不仅是一种提升鲁棒性的技术手段,更是一种提升泛化能力、缓解过拟合的正则化策略,尤其在面对现实世界中非理想输入时,它表现出明显优势。

相关推荐
我不是小upper1 小时前
anaconda、conda、pip、pytorch、torch、tensorflow到底是什么?它们之间有何联系与区别?
人工智能·pytorch·深度学习·conda·tensorflow·pip
z樾1 小时前
Sum-rate计算
开发语言·python·深度学习
zzywxc7872 小时前
在处理大数据列表渲染时,React 虚拟列表是提升性能的关键技术,但在实际实现中常遇到渲染抖动和滚动定位偏移等问题。
前端·javascript·人工智能·深度学习·react.js·重构·ecmascript
美狐美颜sdk2 小时前
直播平台中的美白滤镜实现:美颜SDK的核心架构与性能优化指南
人工智能·深度学习·计算机视觉·美颜sdk·第三方美颜sdk·视频美颜sdk·美颜api
老鱼说AI11 小时前
循环神经网络RNN原理精讲,详细举例!
人工智能·rnn·深度学习·神经网络·自然语言处理·语音识别
爱分享的飘哥12 小时前
第三十篇:AI的“思考引擎”:神经网络、损失与优化器的核心机制【总结前面2】
人工智能·深度学习·神经网络·优化器·损失函数·mlp·训练循环
阿男官官13 小时前
[Token]ALGM: 基于自适应局部-全局token合并的简单视觉Transformer用于高效语义分割, CVPR2024
人工智能·深度学习·transformer·语义分割
李元豪14 小时前
nl2sql grpo强化学习训练,加大数据量和轮数后,准确率没提升,反而下降了,如何调整
人工智能·深度学习·机器学习
热心不起来的市民小周14 小时前
基于 BiLSTM+自注意力机制(改进双塔神经网络) 的短文本语义匹配
人工智能·深度学习·神经网络
麦兜*15 小时前
大模型时代,Transformer 架构中的核心注意力机制算法详解与优化实践
jvm·后端·深度学习·算法·spring·spring cloud·transformer