Projected Gradient Descent(PGD)对抗训练

本文介绍了Projected Gradient Descent (PGD)对抗训练的原理和过程,相较于FGM,PGD通过多次小步迭代寻找更优的对抗样本。在非线性模型中,PGD能获得更好的攻击效果。给出了PGD的伪代码和PyTorch实现,并解释了其在对抗训练中的应用,用于增强模型的鲁棒性。

PGD对抗训练什么是:

之前说到了FGM对抗训练,FGM 解决的思路其实就是梯度上升,那么 FGM 简单粗暴的“一步到位”,是不是有可能并不能走到约束内的最优点呢?当然是有可能的。于是,一个很 intuitive 的改进诞生了:Projected Gradient Descent(PGD)的方法,简单的说,就是“小步走,多走几步”,如果走出了扰动半径为 ϵ 的空间,就映射回“球面”上,以保证扰动不要过大.


伪代码:

pgd整个对抗训练的过程如下,伪代码如下:

1.计算x的前向loss、反向传播得到梯度并备份;
2.对于每步t:

 a.根据embedding矩阵的梯度计算出r,并加到当前embedding上,相当于x+r(超出范围则投影回epsilon内);
if t 不是最后一步,则进行b步骤:
将模型梯度归0,根据a的x+r计算前后向并得到梯度,继续a步骤;
if t 是最后一步,则进行c步骤:
恢复(1)的梯度,根据a的x+r计算前后向得到梯度并将梯度累加到(1)的梯度上,跳出循环;

3.将embedding恢复为(1)时的值;

4.根据2 中 c 的梯度对参数进行更新。


PGD代码实现(基于pytorch):

class PGD:
    def __init__(self, model, eps=1., alpha=0.3):
        self.model = (
            model.module if hasattr(model, "module") else model
        )
        self.eps = eps
        self.alpha = alpha
        self.emb_backup = {}
        self.grad_backup = {}

    def attack(self, 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 = self.alpha * param.grad / norm
                    param.data.add_(r_at)
                    param.data = self.project(name, param.data)

    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):
        r = param_data - self.emb_backup[param_name]
        if torch.norm(r) > self.eps:
            r = self.eps * 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]

PGD训练代码实现(基于pytorch):

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()

总结:

 一般来说,PGD的攻击效果比FGSM要好,首先,如果目标模型是一个线性模型,那么用FGSM就可以了,因为此时loss对输入的导数是固定的,换言之,使得loss下降的方向是明确的,即使你多次迭代,扰动的方向也不会改变。而对于一个非线性模型,仅仅做一次迭代,方向是不一定完全正确的,这也是为什么FGSM的效果一般的原因了。
 

<think>我们正在讨论PGDProjectedGradientDescent)攻击函数。PGD是一种基于梯度的对抗攻击方法,它通过迭代的方式生成对抗样本,旨在使模型对输入产生错误的预测,同时限制扰动的幅度(通常以$L_\infty$或$L_2$范数为约束)。下面我将详细解释PGD的工作原理和实现机制。###PGD攻击函数的工作机制PGD攻击的核心思想是通过多次迭代的梯度上升来最大化模型的损失函数(例如交叉熵损失),从而生成对抗样本。在每一步迭代中,PGD会计算当前对抗样本的梯度,并沿着梯度方向更新样本(因为我们的目标是最大化损失,所以是梯度上升而非梯度下降)。然后,PGD会将更新后的样本投影到预先设定的扰动范围内(例如$\epsilon$-球内),以确保扰动不会超过允许的幅度。具体步骤如下:1.**初始化**:从原始样本$x$开始,初始化对抗样本$x_{adv}$。通常,初始对抗样本可以是原始样本加上一个随机的小扰动(但保持在扰动约束范围内),或者直接使用原始样本(即从$x$开始迭代)。2.**迭代更新**:对于每一次迭代($t=1,2,\dots,T$):-**计算梯度**:计算损失函数$J(\theta,x_{adv},y_{true})$关于$x_{adv}$的梯度,记为$\nabla_{x}J$。其中,$y_{true}$是样本的真实标签(对于无目标攻击)或目标标签(对于有目标攻击)。-**更新对抗样本**:沿着梯度的方向更新对抗样本(因为我们要最大化损失):$$x_{adv}\leftarrowx_{adv}+\alpha\cdot\text{sign}(\nabla_{x}J)$$这里,$\alpha$是步长(学习率),$\text{sign}$函数用于获取梯度的符号(在$L_\infty$约束下常用,类似于FGSM)。对于$L_2$约束,我们通常直接使用梯度方向(而不取符号)。-**投影操作**:将更新后的对抗样本投影回扰动约束范围内。对于$L_\infty$约束,即要求对抗样本满足$\|x_{adv}-x\|_\infty\leq\epsilon$,投影操作可以简单地通过裁剪实现:$$x_{adv}\leftarrow\text{clip}(x_{adv},x-\epsilon,x+\epsilon)$$同时,我们还需要确保对抗样本在合法的输入空间内(例如,对于图像,像素值通常在[0,1]或[0,255]之间)。3.**返回对抗样本**:经过$T$次迭代后,返回最终的对抗样本$x_{adv}$。###有目标攻击与无目标攻击-**无目标攻击**:目标是使模型对对抗样本的预测与真实标签不同。此时,损失函数通常选择交叉熵损失$J(\theta,x_{adv},y_{true})$,并通过梯度上升来最大化该损失。-**有目标攻击**:目标是使模型将对抗样本预测为一个指定的目标类别$y_{target}$($y_{target}\neqy_{true}$)。此时,损失函数可以设计为最小化对抗样本在目标类别上的损失(例如,我们希望目标类别的概率最大),即最小化$J(\theta,x_{adv},y_{target})$。但更常见的做法是最大化目标类别的概率,这等价于最小化负的交叉熵损失:$$J(\theta,x_{adv},y_{target})=-\text{CrossEntropy}(f(x_{adv}),y_{target})$$然后通过梯度上升来最大化这个损失(即使得目标类别的概率增加)。###投影操作的具体实现投影操作是PGD算法的关键步骤,它确保每次更新后的对抗样本都满足扰动约束。根据不同的范数约束,投影操作不同:-**$L_\infty$约束**:投影操作等价于将每个像素的扰动裁剪到$[-\epsilon,\epsilon]$范围内。在PyTorch中,可以使用`torch.clamp`函数:```pythondelta=torch.clamp(x_adv-x,-epsilon,epsilon)x_adv=x+delta```或者直接裁剪对抗样本:```pythonx_adv=torch.clamp(x_adv,x-epsilon,x+epsilon)```-**$L_2$约束**:投影操作需要将扰动向量投影到半径为$\epsilon$的球内。如果更新后的扰动$\delta$的$L_2$范数大于$\epsilon$,则将其缩放至$\epsilon$:```pythondelta=x_adv-xnorm=torch.norm(delta)ifnorm>epsilon:delta=delta*epsilon/normx_adv=x+delta```###PGD与FGSM的关系PGD可以看作是FGSM(FastGradientSignMethod)的迭代版本。FGSM只执行一步梯度上升:$$x_{adv}=x+\epsilon\cdot\text{sign}(\nabla_{x}J)$$而PGD则执行多步迭代,每一步都进行梯度上升和投影。因此,PGD通常比FGSM更强大,生成的对抗样本攻击成功率更高。###代码实现示例(PyTorch)以下是一个使用PyTorch实现的有目标PGD攻击的代码示例:```pythonimporttorchdefpgd_attack(model,x,y_target,epsilon,alpha,num_iter):"""参数:model:目标模型x:原始输入样本y_target:目标类别epsilon:扰动上限(L∞范数)alpha:步长num_iter:迭代次数返回:x_adv:对抗样本"""#初始化对抗样本x_adv=x.clone().detach().requires_grad_(True)foriinrange(num_iter):#前向传播计算损失output=model(x_adv)loss=torch.nn.functional.cross_entropy(output,y_target)#梯度清零ifx_adv.gradisnotNone:x_adv.grad.zero_()#反向传播计算梯度loss.backward()grad=x_adv.grad.data#更新对抗样本:梯度上升(有目标攻击,我们最小化目标类的损失,但这里使用梯度上升最大化损失函数)#注意:有目标攻击时,我们通常最小化目标类的损失,但PGD中我们使用梯度上升来最大化损失(因为我们取了负号)#但通常我们会直接最小化目标类的损失,所以这里使用梯度下降。为了统一,我们定义损失为负的交叉熵,然后梯度上升。#更常见的做法:我们定义损失为模型对目标类别的负对数概率,然后最小化这个损失(即梯度下降)。但PGD通常用梯度上升最大化损失。#这里我们采用有目标攻击的标准形式:我们希望模型在目标类别上的概率高,所以最小化交叉熵损失(等同于最大化概率)。#但PGD攻击中,我们使用梯度上升来最大化损失(即让损失变大)是不对的。有目标攻击时,我们希望损失变小(即模型在目标类别上的概率变高)。#因此,我们实际上需要梯度下降来最小化损失。然而,在对抗攻击中,有目标攻击的PGD通常使用以下方式:#损失=-交叉熵(输出,y_target)然后最大化这个损失(即梯度上升)#这样做的效果是让目标类别的概率增加。#所以,我们这里使用负的交叉熵损失,然后梯度上升。#我们重新定义损失:损失=-交叉熵(输出,y_target)#则梯度上升会使得损失增加,即交叉熵减少(目标类别概率增加)#但上面的损失已经计算为交叉熵,所以我们可以取负号#或者直接使用梯度下降(即减去梯度)来最小化交叉熵损失(这样更直接)#这里我们采用梯度下降的方式(最小化交叉熵损失):x_adv=x_adv-alpha*grad.sign()#使用符号函数,L∞攻击#投影:确保扰动在ε范围内delta=torch.clamp(x_adv-x,-epsilon,epsilon)x_adv=x+delta#确保对抗样本在合法范围内(例如,图像像素在0到1之间)x_adv=torch.clamp(x_adv,0,1)returnx_adv.detach()```注意:上面的代码示例是有目标攻击(希望模型将对抗样本分类为目标类别`y_target`)。对于无目标攻击,只需将损失函数改为原始样本的标签`y_true`的交叉熵,然后最大化该损失(即使用梯度上升:`x_adv=x_adv+alpha*grad.sign()`)。###总结PGD攻击通过多次迭代的梯度上升和投影操作,生成在约束范围内的对抗样本,从而有效欺骗深度学习模型。其优势在于攻击成功率高,是评估模型鲁棒性的标准方法之一。PGD也是对抗训练中常用的攻击方法,用于生成训练过程中的对抗样本[^3]。###相关问题1.PGD攻击中如何选择迭代次数和步长参数以达到最佳攻击效果?2.针对不同的范数约束(如$L_2$和$L_\infty$),PGD的实现有何不同?3.如何利用PGD进行对抗训练以提高模型的鲁棒性?4.PGD与C&W(Carlini&Wagner)攻击在原理和效果上有何区别?[^2]</think>### PGD攻击函数的工作原理与实现机制 PGDProjected Gradient Descent)是一种基于梯度的迭代对抗攻击方法,其核心目标是通过微小扰动使模型产生错误分类,同时确保扰动在指定范数约束范围内(常用$L_\infty$约束)。以下是其工作机制的详细解析: #### 数学原理 PGD可形式化为约束优化问题: $$\min_{\delta} \mathcal{L}(f(x+\delta), y_{\text{target}}) \quad \text{s.t.} \quad \|\delta\|_\infty \leq \epsilon$$ 其中: - $x$:原始输入样本 - $\delta$:对抗扰动 - $f$:目标模型 - $y_{\text{target}}$:目标类别(有目标攻击)或真实标签(无目标攻击) - $\epsilon$:扰动上限 #### 工作流程 1. **初始化**: - 从原始样本$x$开始:$x^{(0)} = x + \mathcal{U}(-\epsilon, \epsilon)$ - 或零初始化:$x^{(0)} = x$ 2. **迭代更新**($t=1,2,\dots,T$): a. **梯度计算**: $$\nabla_t = \nabla_x \mathcal{L}(f(x^{(t)}), y_{\text{target}})$$ b. **梯度上升更新**: $$x^{(t+1)} = x^{(t)} + \alpha \cdot \text{sign}(\nabla_t) \quad (\text{针对} L_\infty)$$ 或 $$x^{(t+1)} = x^{(t)} + \alpha \cdot \frac{\nabla_t}{\|\nabla_t\|_2} \quad (\text{针对} L_2)$$ c. **投影操作**: $$x^{(t+1)} = \text{clip}(x^{(t+1)}, x - \epsilon, x + \epsilon)$$ $$x^{(t+1)} = \text{clip}(x^{(t+1)}, \text{输入值域})$$ 3. **输出**: - 最终对抗样本:$x_{\text{adv}} = x^{(T)}$ #### 关键机制解析 1. **梯度符号函数**: - 使用$\text{sign}(\nabla)$而非$\nabla$本身,确保在$L_\infty$约束下最大化扰动效率 - 数学性质:$\text{sign}$函数保持梯度方向但统一幅值,符合$L_\infty$球面约束[^3] 2. **投影操作**: - 核心功能:将迭代点映射回可行域 - 实现方式: ```python # PyTorch实现 delta = torch.clamp(x_adv - x_orig, -epsilon, epsilon) x_adv = x_orig + delta ``` - 数学等价:$x_{\text{adv}} = \Pi_{x+\mathcal{B}_\infty(\epsilon)}(x + \eta \nabla)$ 3. **损失函数设计**: - **无目标攻击**: $$\mathcal{L} = -\text{CE}(f(x_{\text{adv}}), y_{\text{true}})$$ 最大化真实类别的损失 - **有目标攻击**: $$\mathcal{L} = \text{CE}(f(x_{\text{adv}}), y_{\text{target}})$$ 最小化目标类别的损失 #### 与FGSM的对比 | 特性 | FGSM | PGD | |--------------|--------------------------|------------------------------| | 迭代次数 | 单步 | 多步(通常50-300次) | | 攻击强度 | 较弱 | 强(SOTA基准方法) | | 收敛性 | 非优化解 | 局部最优解 | | 投影操作 | 一次裁剪 | 每步迭代后投影 | | 鲁棒性评估 | 初步测试 | 正式基准 | > PGD本质是FGSM的迭代推广,通过多次投影梯度下降寻找最优扰动[^3] #### 核心优势 1. **强攻击性**:多步优化能找到更强的对抗样本 2. **可控扰动**:严格满足$\|\delta\|_\infty \leq \epsilon$约束 3. **模型不可知**:仅需模型梯度信息 4. **灵活性**:支持$L_2/L_\infty$等多种范数约束 ### PyTorch实现代码 ```python import torch def pgd_attack(model, x, y_true, epsilon=8/255, alpha=2/255, iters=100, targeted=False, y_target=None): """ PGD攻击实现(L∞约束) 参数: model: 目标模型 x: 原始输入(0-1归一化) y_true: 真实标签 epsilon: 扰动上限 alpha: 步长 iters: 迭代次数 targeted: 是否定向攻击 y_target: 目标类别(定向攻击时需指定) """ device = x.device x_adv = x.clone().detach().requires_grad_(True) # 损失函数选择 criterion = torch.nn.CrossEntropyLoss() for i in range(iters): output = model(x_adv) # 计算损失 if targeted: loss = criterion(output, y_target) # 最小化目标类损失 else: loss = -criterion(output, y_true) # 最大化真实类损失 # 梯度计算与更新 grad = torch.autograd.grad(loss, x_adv)[0] with torch.no_grad(): x_adv += alpha * torch.sign(grad) # L∞更新 # 投影操作 delta = torch.clamp(x_adv - x, -epsilon, epsilon) x_adv = torch.clamp(x + delta, 0, 1).detach_() x_adv.requires_grad_(True) return x_adv.detach() ``` ### 测试示例 ```python # 加载预训练ResNet18 model = torch.hub.load('pytorch/vision', 'resnet18', pretrained=True).eval() # 输入示例 x = torch.rand(1, 3, 224, 224) # 归一化图像 y_true = torch.tensor([285]) # 真实类别(暹罗猫) # 无目标攻击 adv_x = pgd_attack(model, x, y_true, epsilon=8/255, alpha=1/255, iters=100) # 有目标攻击(定向到类924:鳄梨酱) targeted_adv_x = pgd_attack(model, x, y_true, targeted=True, y_target=torch.tensor([924])) ``` ### 性能分析 1. **攻击成功率**: - 在ImageNet上,$\epsilon=8/255$时PGD可达>95%攻击成功率 - 迭代次数与成功率正相关,但收益递减(100步后提升有限) 2. **扰动可视化**: - $L_\infty$约束下,最大扰动$\max|\delta|=8/255≈0.031$,人眼不可辨 - 扰动模式呈现高频噪声特征 3. **防御意义**: - PGD生成的对抗样本广泛用于对抗训练 - 模型在PGD对抗训练后鲁棒性显著提升[^3] ### 相关问题 1. PGD投影操作在$L_2$范数约束下如何实现? 2. 如何通过自适应步长策略加速PGD收敛? 3. PGD与C&W攻击在优化目标和约束处理上有何本质区别?[^2] 4. 为什么PGD被认为是评估模型鲁棒性的"黄金标准"? > PGD通过迭代投影机制实现了约束空间内的最优扰动搜索,其数学形式可视为带约束的梯度上升算法[^3]。
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值