查看原文
其他

一文梳理基于梯度的黑盒迁移对抗攻击研究进展

鬼谷子 PaperWeekly 2022-10-18



©PaperWeekly 原创 · 作者 | 鬼谷子




引言

黑盒迁移攻击是对抗攻击中非常热门的一个研究方向,基于动量梯度的方法又是黑盒迁移攻击的一个主流方向。当前大部分研究主要通过在数据样本的尺寸,分布,规模,时序等方面来丰富梯度的多样性,使得生成的对抗样本在迁移到其它的模型攻击时,能够有更高的攻击成功率。

本文会介绍最近几年有代表性的黑盒迁移攻击的论文,这些论文的方法经常会被当成论文比较的 baseline。我对论文中涉及到一些数学结论进行补充证明,大部分论文中给出的源码是 tensorflow 的,我又根据论文的算法流程图用 pytorch 对论文的核心方法重新编程了一下,代码实例在文末所示。




注意力攻击(AoA)


2.1 论文简介

在该论文中作者提出一种注意力攻击(AoA),注意显著图是深度学习模型共享的语义属性。作者发现当交叉熵损失被注意力损失取代时,AoA 方法生成对抗样本的可迁移性会显着提高。除此之外由于 AoA 方法只改变了损失函数,它可以很容易地与其它对抗样本可迁移性增强技术相结合,从而实现更好的 SOTA 性能。作者应用 AoA 方法从 ImageNet 验证集中生成 50000 个对抗样本并攻击成功许多神经网络模型,并将数据集命名为 DAmageNet,该数据及是第一个通用对抗数据集。



论文链接:
https://arxiv.org/abs/2001.06325

数据集链接:

http://www.pami.sjtu.edu.cn/Show/56/122

2.2 论文方法

表示输入 和指定类 的注意力热力图。
抑制损失函数 其目的是抑制正确类别 的注意力热力图的大小。当正确类的网络注意力降低时,其他类的注意力会增加并最终超过正确的类,从而导致模型寻求获取有关其它类别的信息而不是正确类别的信息,从而做出不正确的预测,具体的损失函数如下所示:

其中 表示 范数。
分散损失函数 当注意力从原始感兴趣区域分散时,模型可能会失去预测能力。在这种情况下,不需要网络关注任何不正确类别的信息,而是引导它关注图像的不相关区域,具体的损失可以表示为以下形式:



其中通过进行自归一化以消除注意力大小的影响。

边界损失函数 其目的是减小 和 (即第二大概率的热力图)之间的距离。如果第二类的注意力大小超过正确类的注意力大小,网络将更加关注关于错误预测的信息,具体形式如下所示:



不同模型的注意力热力图值差异很大,因此自归一化可以提高对抗样本的可迁移性。除此之外还可以考虑 之间的比率,从而得到以下对数边界损失:



经实验可知,对数边界损失是迁移攻击效果最好的,因此该损失函数被选为目标函数的中的一项。

此外,注意力攻击可以很容易地与交叉熵损失函数  相结合,则有 AoA 损失函数如下所示:



其中 是注意力攻击和交叉熵之间的权衡系数。
通过最小化损失函数 来生成对抗样本,令 ,则具体的更新过程如下所示:



其中梯度 进行 范数正则化,其中 表示的是图像像素数量。为了使得对抗扰动不可见,作者通过与原始干净样本的距离来限制对抗攻击的强度。AoA 也可以直接运用到对抗样本的可迁移攻击中,通过输入修改,AoA 的可迁移性能得到进一步提高。AoA 算法的算法流程图如下所示:



当为有目标攻击时,且攻击目标类别为 ,则 AoA 损失函数如下所示:





线性反向传播(LinBP)


3.1 论文简介
在该论文中,作者重新审视了 Goodfellow 等人之前提出了一个深度学习模型线性假设,在此基础上作者提出一种增强对抗样本可迁移性的方法线性反向传播 (LinBP),这是一种使用梯度的现成攻击以更线性的方式执行反向传播的方法,该方法在神经网络中正常计算前向传播过程,但在计算反向传播损失时,就好像在前向传播中没有遇到一些非线性激活函数一样。


论文链接:
https://arxiv.org/abs/2012.03528

代码链接:

https://github.com/qizhangli/linbp-attack

3.2 论文方法
Goodfellow 等人曾提出一个假设,即对抗样本的可迁移性其主要原因在于深度学习模型其内在的近似线性特征,类似于在同一数据集上训练的线性模型。如下图所示,每个方格表示 CIFAR 数据集的一个样本,每个方格里的颜色中白色表示该样本经过模型分类为正确的类别,其它的颜色表示模型分类出错为其它类别。可以发现样本沿着某个方向扰动,分类边界呈现线性特征。



尽管该假设在小数据集模型中的实验里得到了验证,但在大数据集中的大型网络上几乎没有经验证据可以验证它,更不用说在实践中使用该假设。所以,在该论文中,作者首先需要验证大数据集中的大型网络上对抗样本的可迁移性根植于模型的近似线性特征。

给定一个源模型 ,将输入实例分类输出 类。在该论文中作者比较基于迁移攻击在模型 和更线性的模型 (或更非线性的模型 )上的成功率。为简单起见,考虑由一系列权重矩阵 参数化的源模型,其中 ,其输出可以写为:



其中 是非线性激活函数,一般情况下激活函数会选择 ReLU 激活函数。由于模型 的非线性仅来自于 函数,作者通过简单地删除其中一些 函数来获得所需的 ,从而得到一个与模型 共享相同数量的参数和核心架构的模型。
与使用泰勒展开并在局部获得线性化的某些工作不同,论文作者的方法(称为线性替换,LinS)会导致全局近似。



证明: 是分类器 的由第 层到第 层的子网络,激活函数为 激活函数,即:



由矩阵的微分定理可知:



求微分可知:



求微分:
将以上公式进行整理可得:



进而可推知:


当除去激活函数时,此时则有 ,进而则有:



论文中采用的是行向量,以上证明是用列向量,所以结论得证。

实验是在 CIFAR-10 数据集上使用 VGG-19 网络和批量归一化得到源模型 进行的。作者移除最后两个 VGG 块中的所有非线性单元以产生初始模型 ,即表示为 。它可以写成两个子网的组合,即 ,所以可知自网络 是纯线性的。
由于这种简单的“线性化”会导致网络在预测干净样本时的准确性下降,因此作者尝试微调 LinS 模型 。作者评估由模型 在第 轮后生成对抗样本的可迁移性。实验结果如下图所示,这表明 LinS 方法确实可以提高对抗样本的可迁移性,而且在短期微调的情况下,它大大提高了网络的预测精度。
当迭代轮数 总是有助于生成比模型 更多的可迁移对抗样本,在 上由 I-FGSM 生成的对抗样本也实现了不错的可迁移性。当迭代轮数 时,由于过度拟合,进一步的训练会导致可迁移性降低。还能够发现当模型没有 ReLU 激活函数时,这两个模型都产生了更多可迁移的对抗样本,因此该假设得到了部分验证。



以上实验已经证实,通过直接去除 ReLU 层可以获得提高的对抗样本的可转移性,但是删除越多的 ReLU 层并不总是意味着更好的性能,因为直接修改架构不可避免地会降低预测精度。如何在线性和准确性之间寻求合理的权衡是一个论文作者接下来要解决的问题。在该论文中作者提出了线性反向传播 LinBP 方法,其具体的计算公式如下所示:



其中 是由 的第 层到第 层组成的子网络,则有:



LinBP 方法不需要微调,因为它前向计算并做出预测,就像训练有素的源模型 一样。如下表可知,与或没有微调的 LinS 模型相比,LinBP 的表现良好,实现了在线性和精度之间进行更合理的权衡。由于(部分)没有 ReLU,它们都显示出比基线更高的计算效率。



对于残差块 ,标准反向传播计算导数为 ,而“线性化”计算导数为 ,其中 是一个对角矩阵。作者将梯度进行归一化即在反向传播期间计算 ,其中 ,标量 是由梯度自动确定。



方差调整动量攻击(VMI-FGSM) 


4.1 论文简介 


在该论文中,作者提出了一种梯度方差调整的对抗攻击的方法,其目的是增强基于迭代梯度的攻击方法生成对抗样本的可迁移性。在每次迭代进行梯度计算时,不再直接使用当前梯度进行动量累积,而是进一步考虑上一次迭代的梯度方差来调整当前梯度,从而稳定更新方向,避免不良局部最优。



论文链接:
https://arxiv.org/abs/2103.15571 

代码链接:

https://github.com/JHL-HUST/VT 

4.2 论文方法

给定目标参数为 的分类器  和初始干净对抗样本 ,其中 维, 表示所有的样本。对抗攻击的形式函数如下所示:



对于白盒攻击,可以把对抗攻击看作是一个优化问题,在 的邻域中搜索一个样本,从而最大化目标分类器 的损失函数



定义1:(梯度方差):给定具有参数 和损失函数 的分类器 ,任意图像 和邻域的上限 ,梯度方差可以定义为:



来表示 ,由于输入空间的连续性,不能直接计算 。
因此,通过在 的邻域采样 个样本来近似其值,计算 具体形式如下所示:



其中 表示 维均匀分布。得到梯度方差后,可以用第 次迭代的梯度方差 调整第 次迭代的 的梯度,以稳定更新方向。论文具体的算法流程图如下所示:





梯度加速和尺度不变对抗攻击


5.1 论文简介

在该论文中,作者从将对抗样本生成视为优化过程的角度,提出了两种新的方法来提高对抗样本的可迁移性,即 Nesterov 迭代快速梯度符号方法(NI-FGSM)和尺度不变攻击方法(SIM)。

NI-FGSM 旨在将 Nesterov 加速梯度适应于迭代攻击中,从而有效地预见并提高对抗样本的可迁移性。SIM 方法是基于对深度学习模型的尺度不变特性,利用它来优化输入图像尺度副本上的对抗扰动,以避免对白盒模型的过拟合被攻击并产生更多可转移的对抗样本。

NI-FGSM 和 SIM 可以自然地集成以构建强大的基于梯度的攻击,从而针对防御模型生成更多可转移的对抗样本。



论文链接:
https://arxiv.org/abs/1908.06281

代码链接:

https://github.com/JHL-HUST/SI-NI-FGSM

5.2 论文方法

NAG 是在标准梯度下降法中引入一些轻微的改变,它可以加快训练过程并显着提高收敛性。NAG 可以看作是一种改进的动量方法,其可以表示为:

典型的基于梯度的迭代攻击在每次迭代时贪婪地扰乱梯度符号方向的图像,通常陷入较差的局部最大值,并且比单步攻击表现出弱的可迁移性。但有研究表明在攻击中采用动量可以稳定其更新方向,这有助于摆脱陷入不良的局部最大值并提高可迁移性。

与动量相比,除了稳定更新方向之外,NAG 的预期更新对先前累积的梯度进行了修正, NAG 的这种前瞻性特性可以帮助更轻松、更快地摆脱不良的局部最大值,从而提高可迁移性。在该论文中作者将 NAG 集成到基于迭代梯度的攻击中,以利用 NAG 的前瞻性属性并构建强大的对抗性攻击,作者将其称为 NI-FGSM。

具体来说,在每次迭代中计算梯度之前,会在先前累积梯度的方向上进行一次跳跃。以 开始,NI-FGSM 的更新过程可以形式化为如下所示:



其中 表示迭代 处的累积梯度, 表示 的衰减因子。

除了为对抗攻击考虑更好的优化算法外,作者还通过模型增强来提高对抗样本的可迁移性。作者介绍了保损变换和模型增强的正式定义如下所示:

定义2(保损变换):给定一个输入 及其对应的真实标签 和一个分类器 和交叉熵损失 ,如果存在输入变换 对于任意 满足 ,则此时 是一个保损变换。
定义3(模型增强):给定一个输入 及其对应的真实标签 和一个分类器 和交叉熵损失 ,如果存在保损变换 ,使得 ,则此时新的模型为原始模型的模型增强。
在该论文中,作者通过模型增强从原始模型中推导出一组模型,这是一种通过保损变换获得多个模型的简单方法。为了获得保损变换,作者发现深度神经网络除了平移不变性外,还可能具有尺度不变性。具体来说,同一模型上的原始图像和缩放图像的损失值相似。因此,尺度变换可以作为一种模型增强的方法。由上述分析,作者提出了一种尺度不变攻击方法(SIM),它优化了输入图像尺度副本上的对抗扰动:
其中 表示输入图像 的比例副本,比例因子为 表示比例副本的数量。使用 SIM 攻击,作者可以通过模型增强有效地实现对多个模型的集成攻击,而不是训练一组模型进行攻击。更重要的是,它可以帮助避免对白盒模型的“过拟合”被攻击并生成更具可转移性的对抗样本。
对于生成对抗样本的梯度处理,NI-FGSM 引入了更好的优化算法来稳定和纠正每次迭代的更新方向。对于生成对抗样本的集成攻击,SIM 引入了模型增强以从单个模型中派生多个模型进行攻击。因此,NI-FGSM 和 SIM 可以自然地结合起来构建更强的攻击,在论文将其称为 SI-NI-FGSM。SI-NI-FGSM 攻击算法的流程图如下所示:





代码实例
以下为本文介绍的相关论文算法的 pytorch 的代码实现,为方便调用将每个算法都定义成一个函数。尤其需要注意的是,在注意力攻击 AoA 算法时,会涉及到 pytorch 对损失函数的二次求导操作,并不能简单的用 backward() 函数进行实现。


import numpy as np
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.nn.functional as F
import torchvision
from torchvision import transforms
import matplotlib.pyplot as plt
from torch.autograd.gradcheck import zero_gradients
import os


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784300)
        self.fc2 = nn.Linear(300100)
        self.fc3 = nn.Linear(10010)
    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


def SI_attack(model, images, labels, alpha, mu, m, T):
    x_adv = images.detach()
    g_t = torch.zeros_like(images)
    loss_fn = nn.CrossEntropyLoss()
    epsilon = alpha / T
    for t in range(T):
        g = torch.zeros_like(x_adv)
        x_nes = x_adv + alpha * mu * g_t
        for i in range(m):
            x_temp = (x_nes / (12**i)).detach()
            x_temp.requires_grad = True
            outputs_temp = model(x_temp)
            loss_temp = loss_fn(outputs_temp, labels)
            loss_temp.backward()
            g += x_temp.grad.detach()
        g = g / m
        g_t = mu * g_t + g / torch.unsqueeze(torch.norm(g, p=1, dim=1), 1)
        x_adv = torch.clamp(x_adv + alpha * torch.sign(g_t), 0 ,1).detach()
    return x_adv



def VMI_attack(model, images, lables, iteration, mu, number, epsilon, alpha):
    g = torch.zeros_like(images)
    v = torch.zeros_like(images)
    x_adv = images.detach()
    loss_fn = nn.CrossEntropyLoss()
    for i in range(iteration):
        x_adv.requires_grad = True
        outputs = model(x_adv)
        loss = loss_fn(outputs, lables)
        loss.backward()
        g_prime = x_adv.grad.detach()

        g = mu * g + (g_prime + v) / torch.unsqueeze(torch.norm(g_prime + v, p=1 , dim=1),1)
        grad_temp = torch.zeros_like(x_adv)
        for k in range(number):
            x_temp = x_adv.detach() + (torch.randn(x_adv.shape)-0.5) * 2 * epsilon
            x_temp.requires_grad = True
            output_temp = model(x_temp)
            loss_temp  = loss_fn(output_temp, lables)
            loss_temp.backward()
            grad_temp += x_temp.detach()
            print('grad_temp:', grad_temp.shape)
        v = grad_temp / number  - g_prime
        x_adv = x_adv + alpha * torch.sign(g)
        x_adv = x_adv.detach()
        print('x_adv:', x_adv.shape)
    return x_adv


def AOA_attack(model, input_x, labels, alpha, target):
    delta = torch.zeros_like(input_x)
    input_x.requires_grad = True
    # Compute CrossEntropyLoss
    outputs = model(input_x)

    if target == 'untarget':
        loss1 = nn.CrossEntropyLoss()(outputs, labels)
        grad1 = torch.autograd.grad(outputs[0][labels], input_x, retain_graph = True, create_graph=True# source map
        one_hot_labels = torch.eye(len(outputs[0]))[labels]
        sec_labels = torch.argmax((1-one_hot_labels)*outputs)
        grad2 = torch.autograd.grad(outputs[0][sec_labels], input_x, retain_graph = True, create_graph=True# second map
        # Compute Log Loss
        loss2 = torch.log(torch.norm(grad1[0],p=1)) - torch.log(torch.norm(grad2[0], p=1))
        # AOA loss
        loss = loss1 - alpha * loss2
        delta = torch.autograd.grad(loss, input_x, retain_graph = True)
    else:
        loss1 = nn.CrossEntropyLoss()(outputs, labels)
        grad1 = torch.autograd.grad(outputs[0][labels], input_x, retain_graph = True, create_graph=True# source map
        one_hot_labels = torch.eye(len(outputs[0]))[labels]
        sec_labels = torch.argmax((1-one_hot_labels)*outputs)
        grad2 = torch.autograd.grad(outputs[0][sec_labels], input_x, retain_graph = True, create_graph=True# second map
        # Compute Log Loss
        loss2 = torch.log(torch.norm(grad2[0],p=1)) - torch.log(torch.norm(grad1[0], p=1))
        # AOA loss
        loss = loss1 + alpha * loss2
        delta = torch.autograd.grad(loss, input_x, retain_graph = True)
    return torch.clamp(delta[0], 0 , 1)



if __name__ == "__main__":
    # Define data format

    mnist_transform = transforms.Compose([transforms.ToTensor(), transforms.Lambda(lambda x : x.resize_(28*28))])
    testdata = torchvision.datasets.MNIST(root="./mnist", train=False, download=True, transform=mnist_transform)
    testloader = torch.utils.data.DataLoader(testdata, batch_size=8, shuffle=True, num_workers=0)

    # Load model parameters
    # net = torch.load('mnist_net_all.pkl')
    net = Net()
    alpha = 1
    target = 'target'



    for step, (batch_x, batch_y) in enumerate(testloader):
        for idx in range(batch_x.shape[0]):
            image = batch_x[idx].view(1,-1)
            label = torch.unsqueeze(batch_y[idx],0)
            adversarial_examples = image + AOA_attack(net, image, label, alpha, target)
            print(adversarial_examples)

更多阅读



#投 稿 通 道#

 让你的文字被更多人看到 



如何才能让更多的优质内容以更短路径到达读者群体,缩短读者寻找优质内容的成本呢?答案就是:你不认识的人。


总有一些你不认识的人,知道你想知道的东西。PaperWeekly 或许可以成为一座桥梁,促使不同背景、不同方向的学者和学术灵感相互碰撞,迸发出更多的可能性。 


PaperWeekly 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是最新论文解读,也可以是学术热点剖析科研心得竞赛经验讲解等。我们的目的只有一个,让知识真正流动起来。


📝 稿件基本要求:

• 文章确系个人原创作品,未曾在公开渠道发表,如为其他平台已发表或待发表的文章,请明确标注 

• 稿件建议以 markdown 格式撰写,文中配图以附件形式发送,要求图片清晰,无版权问题

• PaperWeekly 尊重原作者署名权,并将为每篇被采纳的原创首发稿件,提供业内具有竞争力稿酬,具体依据文章阅读量和文章质量阶梯制结算


📬 投稿通道:

• 投稿邮箱:hr@paperweekly.site 

• 来稿请备注即时联系方式(微信),以便我们在稿件选用的第一时间联系作者

• 您也可以直接添加小编微信(pwbot02)快速投稿,备注:姓名-投稿


△长按添加PaperWeekly小编




🔍


现在,在「知乎」也能找到我们了

进入知乎首页搜索「PaperWeekly」

点击「关注」订阅我们的专栏吧


·

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存