前置 设置PGD的扰动积累步数为K步
计算在正常embedding下的loss和grad 即先后进行forward、backward 在此时 将模型所有grad进行备份
K步的for循环: # 反向传播(计算grad)是为了计算当前embedding权重下的扰动r。同时为了不干扰后序扰动r的计算 还要将每次算出的grad清零
a. 对抗攻击 如果是首步 先保存一下未经attack的grad。然后按照PGD公式以及当前embedding层的grad计算扰动 然后将扰动累加到embedding权重上
b. if-else分支
i. 非第K-1步时 模型当前梯度清零
ii. 到了第K-1步时 恢复到step-1时备份的梯度 因为梯度在数次backward中已被修改
c. 使用目前的模型参数 包括被attack后的embedding权重 以及batch_input 做前后向传播 得到loss、更新grad
恢复embedding层2.a时保存的embedding的权重 注意恢复的是权重 而非梯度
optimizer.step() 梯度下降更新模型参数。这里使用的就是累加了K次扰动后计算所得的grad
class PGD(): def __init__(self, model, emb_name, epsilon 1., alpha 0.3): # emb_name这个参数要换成你模型中embedding的参数名 self.model model self.emb_name emb_name self.epsilon epsilon self.alpha alpha self.emb_backup {} self.grad_backup {} def attack(self, is_first_attack False): for name, param in self.model.named_parameters(): if param.requires_grad and self.emb_name in name: if is_first_attack: self.emb_backup[name] param.data.clone() norm torch.norm(param.grad) if norm ! 0: r_at self.alpha * param.grad / norm param.data.add_(r_at) param.data self.project(name, param.data, self.epsilon) def restore(self): for name, param in self.model.named_parameters(): if param.requires_grad and self.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, epsilon): r param_data - self.emb_backup[param_name] if torch.norm(r) epsilon: r epsilon * 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)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()
FreeAT (Free Adversarial Training): NIPS2019
从FGSM到PGD 主要是优化对抗扰动的计算 虽然取得了更好的效果 但计算量也一步步增加。对于每个样本 FGSM和FGM都只用计算两次 一次是计算x的前后向 一次是计算x r的前后向。而PGD则计算了K 1次 消耗了更多的计算资源。因此FreeAT被提了出来 在PGD的基础上进行训练速度的优化。