赞
踩
之前说到了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 的梯度对参数进行更新。
- 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 = 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的效果一般的原因了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。