元学习—对抗式元学习(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)
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
):
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
):
'''
对每一个任务进行迭代
'''
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_
())
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
)