元学习—对抗式元学习(ADML)
在之前的文章中,我们介绍了MAML模型,有兴趣的读者可以参考元学习—模型不可知元学习(MAML)。在下面的文章中,我们将介绍MAML模型在对抗式学习中的应用,即MAML模型的一个变体ADML模型。
1 FGSM方法
在对抗式的学习中,需要同时使用到真实样本和对抗样本。对于对抗样本的生成,有很多种方法,我们下面来看其中的一种方法,即基于梯度的攻击算法(FGSM)。
一般情况下,我们会计算模型参数的梯度值来更新模型参数,以求得使得模型的Loss最小,在FGSM中,为了获取对抗样本,我们计算输入数据的关于Loss的梯度结果。在实际的计算中,我们只计算一次梯度下降步骤,以此来保证计算的有效性。计算完之后,我们利用符号函数来进一步计算结果。首先,符号函数的定义如下:
s
i
g
n
(
x
)
=
{
1
x
>
0
0
x
=
0
−
1
x
<
0
sign(x)=\left\{ \begin{aligned} 1 & & x>0 \\ 0 && x=0\\ -1& & x<0 \\ \end{aligned} \right.
sign(x)=⎩⎪⎨⎪⎧10−1x>0x=0x<0
最终,在输入样本为x的基础之上,我们可以生成其对抗样本
X
a
d
v
X_{adv}
Xadv,即:
X
a
d
v
=
x
+
ε
s
i
g
n
(
▽
x
J
(
x
,
y
t
r
u
e
)
)
X_{adv}=x+εsign(▽_xJ(x,y_{true}))
Xadv=x+εsign(▽xJ(x,ytrue))
其中
J
(
)
J()
J()表示当前样本x的损失。以一个 图像的样本为例,我们可以得到如下图所示的对抗样本:
2 ADML算法
现在,我们已经可以通过FGSM算法来获取对抗样本了,下一步则是利用ADML算法进行学习。在ADML中,我们将使用真实数据和对抗数据来训练元学习的模型。这种对抗式的学习方式有助于我们寻找出更加健壮的参数 θ θ θ,通过在内层和外层循环中(这个部分如果有疑问,请参考我开头提到的博客。)来使用真实数据和对抗数据来计算损失,更新参数。ADML利用了真实样本和对抗样本来获取了一个更好的,更具健壮性的模型初始化参数。该参数可以被应用到其他的任务之上。
简单的回顾一下MAML算法,首先,我们需要一个任务集合T,并且任务集合中的任务以概率 p ( T ) p(T) p(T)分布。进一步,我们按照概率分布来采样任务 T i T_i Ti,同时,对于每一个任务,我们对于其训练集和测试集各采样k个样本点。同样的方式,在ADML算法,我们还需要为对抗样本的训练集和测试集各采样K个样本点。即: D c l e a n i t r a i n , D a d v i t r a i n , D c l e a n i t e s t , D a d v i t e s t D_{clean_i}^{train},D_{adv_i}^{train},D_{clean_i}^{test},D_{adv_i}^{test} Dcleanitrain,Dadvitrain,Dcleanitest,Dadvitest。
现在,我们计算训练集的Loss,并且通过梯度下降算法进行最小化损失,并且寻找最优参数
θ
′
θ'
θ′。因为我们拥有真实和对抗数据,我们可以同时为两个数据集计算出最优参数
θ
c
l
e
a
n
i
′
θ_{clean_i}'
θcleani′和
θ
a
d
v
i
′
θ_{adv_i}'
θadvi′,具体定义的形式如下:
θ
c
l
e
a
n
i
′
=
θ
−
α
1
▽
θ
L
T
i
(
f
θ
,
D
c
l
e
a
n
i
t
r
a
i
n
)
θ'_{clean_i}=θ-α_1▽_θL_{T_i}(f_θ,D_{clean_i}^{train})
θcleani′=θ−α1▽θLTi(fθ,Dcleanitrain)
θ
a
d
v
i
′
=
θ
−
α
1
▽
θ
L
T
i
(
f
θ
,
D
a
d
v
i
t
r
a
i
n
)
θ'_{adv_i}=θ-α_1▽_θL_{T_i}(f_θ,D_{adv_i}^{train})
θadvi′=θ−α1▽θLTi(fθ,Dadvitrain)
现在,我们进行元学习的训练阶段,通过最小化测试集的损失以及优化的参数
θ
i
′
θ_i'
θi′来寻找最优的模型初始化参数
θ
θ
θ。具体的,根据之前计算出来的
θ
c
l
e
a
n
i
′
θ_{clean_i}'
θcleani′和
θ
a
d
v
i
′
θ_{adv_i}'
θadvi′,我们可以计算出两组最优的模型初始化参数,即:
θ
=
θ
−
β
1
▽
θ
∑
T
i
−
p
(
T
)
L
T
i
(
f
θ
c
l
e
a
n
i
′
,
D
a
d
v
i
t
e
s
t
)
θ=θ-β_1▽_θ∑_{T_i~-p(T)}L_{T_i}(f_{θ_{clean_i}'},D_{adv_i}^{test})
θ=θ−β1▽θTi −p(T)∑LTi(fθcleani′,Dadvitest)
θ
=
θ
−
β
2
▽
θ
∑
T
i
−
p
(
T
)
L
T
i
(
f
θ
a
d
v
i
′
)
,
D
c
l
e
a
n
i
t
e
s
t
θ=θ-β_2▽_θ∑_{T_i~-p(T)}L_{T_i}(f_{θ_{adv_i}'}),D_{clean_i}^{test}
θ=θ−β2▽θTi −p(T)∑LTi(fθadvi′),Dcleanitest
最终,我们使用真实数据与对抗样本对参数进行优化。下面,我们给出ADML算法的算法描述:
下面,我们来简单的分析一下,ADML算法中内存循环与外层循环:
- 在内层循环的过程中,我们基于训练样本 D a d v i D_{adv_i} Dadvi和 D c l e a n i D_{clean_i} Dcleani,使用梯度下降算法,计算新的模型参数,即 θ a d v i ′ θ_{adv_i}' θadvi′与 θ c l e a n i ′ θ_{clean_i}' θcleani′。
- 在外层循环,即元学习的过程中,我们使用上一步计算出来的参数
θ
a
d
v
i
′
θ_{adv_i}'
θadvi′与
θ
c
l
e
a
n
i
′
θ_{clean_i}'
θcleani′来优化损失函数
L
i
(
f
θ
a
d
v
i
′
)
L_i(f_{θ_{adv_i}'})
Li(fθadvi′)和
L
i
(
f
θ
c
l
e
a
n
i
′
)
L_i(f_{θ_{clean_i}'})
Li(fθcleani′),以此来更新θ。具体的公式定义如下:
从上述公式中不难返现,我们在元学习更新的过程中,使用的是任务i中真实样本组成的测试集来对应的任务i中由对抗样本产生的参数 θ a d v i ′ θ_{adv_i}' θadvi′,以此来就散损失,同时使用任务i中的对抗样本组成的测试集来对应由真实样本产生的参数 θ c l e a n i ′ θ_{clean_i}' θcleani′。这是一个交叉操作的过程,最终对参数θ进行更新。我们使用下图描述一下这个过程:
最终,我们来分析一下这种对于参数θ更新过程的设计:
对于每一个任务 T i T_i Ti,在内层的梯度更新中,ADML首先通过对样本来对参数 θ a d v i θ_{adv_i} θadvi来进行更新,使其能够适应于对抗样本的特征。同时对于真实样本也采用了相同的操作。最后能够获取到两个参数 θ a d v i ′ θ_{adv_i}' θadvi′与 θ c l e a n i ′ θ_{clean_i}' θcleani′,即上图的粉色和紫色的点。然后进入外层的元更新阶段,在这一个阶段,ADML算法使用上面获取的最优参数来更新初始参数θ。并且希望参数θ能够到达最优的位置,即 θ i ∗ θ_i^* θi∗,其处于两个样本空间的交叉的位置,同时能够适应真实样本与对抗样本,最终能够提高全局的效果。上图中,我们给出的是通过一个任务进行更新的流程。当使用任务集合中的所有任务时,θ可以被优化到所有任务对应的所有样本空间中(包括每一个任务的真实样本空间与对抗样本空间)的交集中,以此来完成对于所有任务,所有样本的支持。
2 ADML算法的实现(基于Pytorch)
#encoding=utf-8
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch.optim as optim
#生成数据
def sample_point(k):
x = np.random.rand(k,50)
y = np.random.choice([0,1],size=k,p=[.5,.5]).reshape([-1,1])
x = torch.from_numpy(x)
x = x.float()
y = torch.from_numpy(y)
y = y.float()
return x,y
class FGSM(nn.Module):
def __init__(self,input_dim,hidden_dim):
super(FGSM, self).__init__()
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.linear = nn.Linear(input_dim,hidden_dim)
def forward(self,x):
#x = Variable(x,requires_grad=True)
return self.linear(x).reshape(-1,1)
class ADML(nn.Module):
def __init__(self,input_dim,hidden_dim):
super(ADML, self).__init__()
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.W = nn.Parameter(torch.zeros(size=[input_dim,hidden_dim]))
def forward(self,x):
y_predict = torch.matmul(x,self.W).reshape(-1,1)
return y_predict
fgsmModel = FGSM(50,1)
admlModel = ADML(50,1)
optimerf = optim.Adam(fgsmModel.parameters(),lr=0.01,weight_decay=1e-5)
optimera = optim.Adam(admlModel.parameters(),lr=0.01,weight_decay=1e-5)
loss_functionf = nn.MSELoss()
loss_functiona = nn.MSELoss()
#定义一些相关的超参数
epoches = 100
tasks = 10
betac = 0.0001
betaa = 0.0001
theta_martix_clean = torch.zeros(size=[10,50,1])
theta_martix_clean = theta_martix_clean.float()
theta_martix_adv = torch.zeros(size=[10,50,1])
theta_martix_adv = theta_martix_adv.float()
ori_theta = torch.rand(size=[50,1])
ori_theta = ori_theta.float()
meta_gradient_adv = torch.zeros_like(ori_theta)
meta_gradient_clean = torch.zeros_like(ori_theta)
epsilon = 0.001
def train(epoch):
global ori_theta,meta_gradient, meta_gradient_adv, meta_gradient_clean
loss_sum_clean = 0.0
loss_sum_adv = 0.0
for i in range(tasks):
'''
对每一个任务进行迭代
'''
#首先,生成原始样本,调用FGSM生成对抗样本
x_train,y_train = sample_point(10)
x_train = Variable(x_train,requires_grad=True)
optimerf.zero_grad()
y_fgsm_predict = fgsmModel(x_train)
loss_fgsm_pre = loss_functionf(y_train,y_fgsm_predict)
loss_fgsm_pre.backward()
optimerf.step()
x_adv = x_train + epsilon * torch.sign(x_train.grad.detach_())
# 调用真实样本和对抗样本来训练ADML模型,注意这里对于每一个任务中的真实样本和对抗样本需要分别训练,顺序不影响最终结果
#先训练真实数据
admlModel.W.data = ori_theta.data
optimera.zero_grad()
y_predict = admlModel(x_train)
loss_adml_clean = loss_functiona(y_train,y_predict)
loss_sum_clean = loss_sum_clean + loss_adml_clean.item()
loss_adml_clean.backward()
optimera.step()
#保存参数结果
theta_martix_clean[i,:] = admlModel.W
#然后,训练对抗样本集合
admlModel.W.data = ori_theta.data
optimera.zero_grad()
y_predict = admlModel(x_adv)
loss_adml_adv = loss_functiona(y_train,y_predict)
loss_sum_adv = loss_sum_adv + loss_adml_adv.item()
loss_adml_adv.backward()
optimera.step()
theta_martix_adv[i,:] = admlModel.W
for i in range(tasks):
'''
下面开始测试过程:同理,我们需要测试用的真实样本集合与对抗样本集合
'''
#首先,生成真实样本和对抗样本
x_test, y_test = sample_point(10)
x_test = Variable(x_test, requires_grad=True)
optimerf.zero_grad()
y_fgsm_predict = fgsmModel(x_test)
loss_fgsm_pre = loss_functionf(y_test, y_fgsm_predict)
loss_fgsm_pre.backward()
optimerf.step()
x_adv_test = x_test + epsilon * torch.sign(x_test.grad.detach_())
# 进一步,我们需要使用真实样本的参数来计算对抗样本组成的测试集
# 同时,我们使用对抗样本生成的参数来计算真实样本组成的测试集
# 首先,我们用真实集的参数来计算对抗样本
admlModel.W.data = theta_martix_clean[i]
optimera.zero_grad()
y_adv_predict_test = admlModel(x_adv_test)
loss_adml_adv_test = loss_functiona(y_test,y_adv_predict_test)
loss_adml_adv_test.backward()
optimera.step()
meta_gradient_adv = meta_gradient_adv + admlModel.W
#然后,我们使用对抗集参数来计算真实演变
admlModel.W.data = theta_martix_adv[i]
optimera.zero_grad()
y_predict_test = admlModel(x_test)
loss_adml_test = loss_functiona(y_test, y_predict_test)
loss_adml_test.backward()
optimera.step()
meta_gradient_clean = meta_gradient_clean + admlModel.W
#最后,我们来更新原始的参数
ori_theta = ori_theta - betac * meta_gradient_clean
ori_theta = ori_theta - betaa * meta_gradient_adv
print("the Epoch is {:04d}".format(epoch),"the loss clean is {:.4f}".format(loss_sum_clean),"the loss adv is {:.4f}".format(loss_sum_adv))
if __name__ == "__main__":
for epoch in range(epoches):
train(epoch)