该论文是关于黑盒攻击可迁移性的文章。在当前大量的研究中,许多方法直接攻击代理模型并获得的可迁移性的对抗样本来欺骗目标模型,但由于代理模型和目标模型之间的不匹配,使得它们的攻击效果受到局限。在该论文中,作者从一个新颖的角度解决了这个问题,通过训练一个元代理模型(MSM),以便对这个模型的攻击可以更容易地迁移到到其它模型中去。该方法的目标函数在数学上被表述为一个双层优化问题,为了能够让训练过程梯度有效,作者提出了新的梯度更新过程,并在理论上给出了证明。实验结果表明,通过攻击元代理模型,可以获得更强的可迁移性的对抗样本来欺骗包括对抗训练的黑盒模型,攻击成功率远高于现有方法。根据论文提出的算法框架,我用pytorch进行了简易的代码实现,感兴趣的人可以更改代码中的数据集部分尝试跑一下。
论文链接: https://arxiv.org/abs/2109.01983
FGSM攻击是基于梯度的单步攻击方式,通过利用的梯度上升法使得分类损失函数增大,从而降低目标分类器的分类置信度。具体公式如下所示:
x
a
d
v
=
C
l
i
p
(
x
+
ϵ
⋅
s
i
g
n
(
∇
x
L
(
f
(
x
)
,
y
)
)
)
x_{adv}=\mathrm{Clip}(x+\epsilon \cdot \mathrm{sign}(\nabla_{x}L(f(x),y)))
xadv=Clip(x+ϵ⋅sign(∇xL(f(x),y)))其中
x
x
x是干净样本,
x
a
d
v
x_{adv}
xadv为对抗样本,
y
y
y是对应的分类标签,
ϵ
\epsilon
ϵ是攻击步长,且有
ϵ
<
L
∞
\epsilon < L_\infty
ϵ<L∞。
f
f
f是被攻击的目标分类器,
C
l
i
p
\mathrm{Clip}
Clip为截断函数,
L
L
L为交叉熵损失函数。
PGD攻击是一种扩展FGSM攻击的多步攻击方式,其具体公式如下所示:
x
a
d
v
k
=
C
l
i
p
(
x
a
d
v
k
−
1
+
ϵ
T
⋅
s
i
g
n
(
∇
x
a
d
v
k
−
1
L
(
f
(
x
a
d
v
k
−
1
)
,
y
)
)
)
x^k_{adv}=\mathrm{Clip}(x^{k-1}_{adv}+\frac{\epsilon}{T}\cdot \mathrm{sign}(\nabla_{x^{k-1}_{adv}}L(f(x^{k-1}_{adv}),y)))
xadvk=Clip(xadvk−1+Tϵ⋅sign(∇xadvk−1L(f(xadvk−1),y)))
x
a
d
v
k
x^{k}_{adv}
xadvk为第
k
k
k步生成的对抗样本,并且
x
a
d
v
0
x_{adv}^0
xadv0表示是初始的干净样本,攻击的迭代的次数为
T
T
T。
对于黑盒攻击,目标模型的内部参数信息对攻击者是隐藏的并且不允许进行查询访问。攻击者只可以访问目标模型使用的数据集,以及与目标模型共享数据集的单个或一组源模型。现有的可迁移对抗攻击方法对这些源模型进行各种攻击,并希望获得可以欺骗未知目标模型的具有可迁移性的对抗样本。在该论文中作者提出了一种新的元学习迁移攻击框架来训练元代理模型,其目标是攻击元代理模型可以产生比直接攻击原始模型更强大的可迁移对抗样本。
令
A
\mathcal{A}
A为对抗攻击算法,
M
θ
\mathcal{M}_\theta
Mθ为具有参数为
θ
\theta
θ的元代理模型,给一个样本
x
x
x,攻击
M
θ
\mathcal{M}_\theta
Mθ的对抗样本可以表示为
A
(
M
θ
,
x
,
y
)
=
x
a
d
v
=
C
l
i
p
(
x
+
ϵ
⋅
s
i
g
n
(
∇
x
L
(
M
θ
(
x
)
,
y
)
)
)
\mathcal{A}(\mathcal{M}_\theta,x,y)=x_{adv}=\mathrm{Clip}(x+\epsilon\cdot \mathrm{sign}(\nabla_x L(\mathcal{M}_\theta(x),y)))
A(Mθ,x,y)=xadv=Clip(x+ϵ⋅sign(∇xL(Mθ(x),y)))因为在攻击时,只能获取到一组源模型的
F
1
,
⋯
,
F
N
\mathcal{F}_1,\cdots,\mathcal{F}_N
F1,⋯,FN,所以需要在源模型中评估对抗样本
A
(
M
θ
,
x
,
y
)
\mathcal{A}(\mathcal{M}_\theta,x,y)
A(Mθ,x,y)的可迁移性,并通过最大化这
N
N
N个源模型的参数来优化元代理模型,具体的形式如下所示
arg
max
θ
E
(
x
,
y
)
∼
D
[
∑
i
=
1
N
L
(
F
i
(
A
(
M
θ
,
x
,
y
)
)
,
y
)
]
\arg\max\limits_{\theta}\mathbb{E}_{(x,y)\sim D}\left[\sum\limits_{i=1}^N L(\mathcal{F}_i(\mathcal{A}(\mathcal{M}_\theta,x,y)),y)\right]
argθmaxE(x,y)∼D[i=1∑NL(Fi(A(Mθ,x,y)),y)]其中
D
D
D为训练数据的分布。该目标的结构和训练过程如图下图所示,可以将其视为元学习或双层优化方法。 在内层优化中,对抗样本是由元代理模型上的白盒攻击通常是梯度上升的方法生成的,而在外层优化中,作者将对抗样本送到源模型中以计算鲁棒损失。
以计算
∇
θ
L
(
F
1
(
x
+
ϵ
c
⋅
g
e
n
s
0
)
,
y
)
\nabla_\theta L(\mathcal{F}_1(x+\epsilon_c\cdot g^0_{ens}),y)
∇θL(F1(x+ϵc⋅gens0),y)为例,由链式法则可知,
x
x
x与
θ
\theta
θ是相互独立的,进而则有
∂
L
(
F
1
(
x
+
ϵ
c
⋅
g
e
n
s
0
)
,
y
)
∂
g
e
n
s
0
⋅
g
e
n
s
0
∂
θ
\frac{\partial L(\mathcal{F}_1(x+\epsilon_c \cdot g^0_{ens}),y)}{\partial g^0_{ens}}\cdot \frac{g^0_{ens}}{\partial \theta}
∂gens0∂L(F1(x+ϵc⋅gens0),y)⋅∂θgens0将以上公式可以进一步扩展为
∇
θ
g
e
n
s
0
=
∇
θ
g
1
0
+
γ
1
⋅
∇
θ
g
t
0
+
γ
2
⋅
∇
θ
g
s
0
\nabla_\theta g^0_{ens}=\nabla_\theta g_1^0 +\gamma_1\cdot \nabla_{\theta}g_t^0+\gamma_2 \cdot \nabla_\theta g_s^0
∇θgens0=∇θg10+γ1⋅∇θgt0+γ2⋅∇θgs0又因为
g
s
0
g_s^0
gs0等于
s
i
g
n
(
g
0
)
\mathrm{sign}(g^0)
sign(g0),符号函数引入了离散的操作,则
g
s
0
g_s^0
gs0的梯度为
0
0
0。所以可以进一步得到
∇
θ
g
e
n
s
0
=
∇
θ
g
1
0
+
γ
⋅
g
t
0
=
∇
θ
(
∇
x
L
(
M
θ
(
x
)
,
y
)
s
u
m
(
a
b
s
(
∇
x
L
(
M
θ
(
x
)
,
y
)
)
)
)
+
γ
1
⋅
∇
θ
(
a
r
c
t
a
n
(
∇
x
L
(
M
θ
(
x
)
,
y
)
m
e
a
n
(
a
b
s
(
∇
x
L
(
M
θ
(
x
)
,
y
)
)
)
)
)
∇θg0ens=∇θg01+γ⋅g0t=∇θ(∇xL(Mθ(x),y)sum(abs(∇xL(Mθ(x),y))))+γ1⋅∇θ(arctan(∇xL(Mθ(x),y)mean(abs(∇xL(Mθ(x),y)))))
下表为在Cifar-10数据集上8个目标网络的迁移攻击成功率,其中元代理模型是使用这8个源模型进行训练得到的。如下表的定量结果可知,论文中提出的方法MTA-PGD的性能比之前所有的方法都要好得多,对抗攻击的迁移成功率显著提高。
下图为探究在Cifar-10数据集上,对抗攻击算法中不同的迭代次数对于迁移攻击成功率的关系。从左半图可知,整体来看最佳的迭代攻击次数为
T
t
=
7
T_t=7
Tt=7;从右半图可知,随着迭代次数
T
v
T_v
Tv的增加,迁移攻击成功率也显著增加。
如下图所示,为不同的攻击方法生成的定性的可视化结果图。可以直观地发现,论文中提出的方法生成的对抗扰动更具有针对性,并且在视觉上生成的对抗样本与原干净样本更加相似。
论文中具体的pytorch的简易代码实现如下所示,主要对论文算法流程图中关键涉及的步骤都进行了实现,数据集和模型的部分可以自行进行替换。
iport torch
import torch.nn as nn
import torch.utils.data as Data
import numpy as np
import os
import torch.nn.functional as F
from copy import deepcopy
def generate_dataset(sample_num, class_num, X_shape):
Label_list = []
Sample_list = []
for i in range(sample_num):
y = np.random.randint(0, class_num)
Label_list.append(y)
Sample_list.append(np.random.normal(y, 0.2, X_shape))
return torch.tensor(Sample_list).to(torch.float32), torch.tensor(Label_list).to(torch.int64)
class Normal_Dataset(Data.Dataset):
def __init__(self, Numpy_Dataset):
super(Normal_Dataset, self).__init__()
self.data_tensor = Numpy_Dataset[0]
self.target_tensor = Numpy_Dataset[1]
def __getitem__(self, index):
return self.data_tensor[index], self.target_tensor[index]
def __len__(self):
return self.data_tensor.size(0)
class Classifer(nn.Module):
def __init__(self):
super(Classifer, self).__init__()
self.conv1 = nn.Conv2d(in_channels = 3, out_channels = 10, kernel_size = 9) # 10, 36x36
self.conv2 = nn.Conv2d(in_channels = 10, out_channels = 20, kernel_size = 17 ) # 20, 20x20
self.fc1 = nn.Linear(20*20*20, 512)
self.fc2 = nn.Linear(512, 7)
def forward(self, x):
in_size = x.size(0)
out = self.conv1(x)
out = F.relu(self.conv2(out))
out = out.view(in_size, -1)
out = F.relu(self.fc1(out))
out = self.fc2(out)
out = F.softmax(out, dim=1)
return out
class MTA_training(object):
def __init__(self, sml, dataloader, bs, msm, epsilon, iteration, gamma_1, gamma_2):
self.source_model_list = source_model_list
self.dataloader = dataloader
self.batch_size = batch_size
self.meta_surrogate_model = meta_surrogate_model
self.epsilon = epsilon
self.iteration = iteration
self.dataloader = dataloader
self.gamma1 = gamma1
self.gamma2 = gamma2
def single_attack(self, x, y, meta_surrogate_model):
delta = torch.zeros_like(x)
delta.requires_grad = True
outputs = meta_surrogate_model(x + delta)
loss = nn.CrossEntropyLoss()(outputs, y)
loss.backward()
grad = delta.grad.detach()
## The equation (4) of the paper
g_1 = grad/torch.sum(torch.abs(grad))
g_t = 2/np.pi * torch.atan(grad/torch.mean(torch.abs(grad)))
g_s = torch.sign(grad)
g_ens = g_1 + self.gamma1 * g_t + self.gamma2 * g_s
## The equation (5) of the paper
x_adv = torch.clamp(x + self.epsilon/ self.iteration * g_ens, 0 ,1)
return x_adv
def training(self, epoch):
loss_fn = nn.CrossEntropyLoss()
optim = torch.optim.SGD(meta_surrogate_model.parameters(), lr=0.001)
for X, Y in self.dataloader:
theta_old_list = []
for parameter in meta_surrogate_model.parameters():
theta_old_list.append(deepcopy(parameter.data))
X_adv = X
for k in range(self.iteration):
X_adv = self.single_attack(X_adv, Y, self.meta_surrogate_model)
loss = 0
for source_model in self.source_model_list:
outputs = source_model(X_adv)
loss += loss_fn(outputs , Y)
optim.zero_grad()
loss.backward()
optim.step()
for parameter_old, parameter in zip(theta_old_list, meta_surrogate_model.parameters()):
parameter.data = 2 * parameter.data - parameter_old.data
if __name__ == '__main__':
batch_size = 2
epsilon = 0.03
iteration = 10
epoch = 1
gamma1 = 0.01
gamma2 = 0.01
numpy_dataset = generate_dataset(10, 7, (3, 44, 44))
dataset = Normal_Dataset(numpy_dataset)
dataloader = Data.DataLoader(
dataset = dataset,
batch_size = batch_size,
num_workers = 0,)
source_model_list = []
source_model1 = Classifer()
source_model2 = Classifer()
source_model_list.append(source_model1)
source_model_list.append(source_model2)
meta_surrogate_model = Classifer()
meta_training = MTA_training(source_model_list, dataloader, batch_size, meta_surrogate_model, epsilon, iteration, gamma1, gamma2)
meta_training.training(10)