@article{moosavidezfooli2017universal,
title={Universal Adversarial Perturbations},
author={Moosavidezfooli, Seyedmohsen and Fawzi, Alhussein and Fawzi, Omar and Frossard, Pascal},
pages={86--94},
year={2017}}
概
深度學習的脆弱以及周知了, 但是到底脆弱到何種程度, 本文作者發現, 可以找到一種對抗攝動, 令其作用在不同的數據上, 結果大部分都會被攻破(即被網絡誤判). 甚至, 這種對抗攝動可以跨越網絡結構的障礙, 即在這個網絡結構上的一般性的對抗攝動, 也能有效地攻擊別的網絡.
主要內容
一般地對抗樣本, 是針對特定的網絡\(\hat{k}\), 特定的樣本\(x_i \in \mathbb{R}^d\), 期望找到攝動\(v_i \in \mathbb{R}^d\), 使得
而本文的通用攝動(universal perturbations)是希望找到一個\(v, \|v\|_p \le \xi\), 且
其中\(\mu\)為數據的分布.
算法
構造這樣的\(v\)的算法如下:
其中
為向\(p\)范數球的投影.
實驗部分
實驗1
實驗1, 在訓練數據集(ILSVRC 2012)\(X\)上(攝動是在此數據上計算得到的), 以及在驗證數據集上, 攻擊不同的網絡.
實驗2
實驗2測試這種通用攝動的網絡結構的轉移性, 即在一個網絡上尋找攝動, 在其它模型上計算此攝動的攻擊成功率. 可見, 這種通用攝動的可遷移性是很強的.
實驗3
實驗3, 研究了樣本個數對攻擊成功率的一個影響, 可以發現, 即便我們用來生成攝動的樣本個數只有500(少於類別個數1000)都能有不錯的成功率.
代碼
代碼因為還有用到了別的模塊, 這放在這里看看, 論文有自己的代碼.
import torch
import logging
from configs.adversarial.universal_attack_cfg import cfg
sub_logger = logging.getLogger("__main__.__submodule__")
class AttackUni:
def __init__(self, net, device,
attack=cfg.attack, epsilon=cfg.epsilon, attack_cfg=cfg.attack_cfg,
max_iterations=cfg.max_iterations, fooling_rate=cfg.fooling_rate,
boxmin=0., boxmax=1.):
""" the attack to construct universal perturbation
:param net: the model
:param device: may use gpu to train
:param attack: default: PGDAttack
:param epsilon: the epsilon to constraint the perturbation
:param attack_cfg: the attack's config
:param max_iterations: max_iterations for stopping early
:param fooling_rate: the fooling rate we want
:param boxmin: default: 0
:param boxmax: default: 1
"""
attack_cfg['net'] = net
attack_cfg['device'] = device
self.net = net
self.device = device
self.epsilon = epsilon
self.attack = attack(**attack_cfg)
self.max_iterations = max_iterations
self.fooling_rate = fooling_rate
self.boxmin = boxmin
self.boxmax = boxmax
def initialize_perturbation(self):
self.perturbation = torch.tensor(0., device=self.device)
def update_perturbation(self, perturbation):
self.perturbation += perturbation
self.perturbation = self.clip(self.perturbation).to(self.device)
def clip(self, x):
return torch.clamp(x, -self.epsilon, self.epsilon)
def compare(self, x, label):
x_adv = x + self.perturbation
out = self.net(x_adv)
pre = out.argmax(dim=1)
return (pre == label).sum()
def attack_one(self, img):
result = self.attack.attack_batch(img+self.perturbation)
perturbation = result['perturbations'][0]
self.update_perturbation(perturbation)
def attack_batch(self, dataloader):
total = len(dataloader)
self.initialize_perturbation()
for epoch in range(self.max_iterations):
count = 0
for img, label in dataloader:
img = img.to(self.device)
label = img.to(self.device)
if self.compare(img, label):
self.attack_one(img)
else:
count += 1
if count / total > self.fooling_rate:
break
sub_logger.info("[epoch: {0:<3d}] 'fooling_rate': {1:<.6f}".format(
epoch, count / total
))
return self.perturbation