AutoAttackの論文解説・実装
AutoAttackとは
AutoAttackとは,Reliable Evaluation of Adversarial Robustness with an Ensemble of Diverse Parameter-free Attacks [Croce+ ICML2020]において提案されたadversarial attackの手法の一つで,現在(2022/07/15)のstate of the artです.本記事ではこのAutoAttackについての解説・実装を行います.(ちなみに公式実装があります.)
この論文の貢献
この論文の貢献としては以下の三つが挙げられます.
- ハイパーパラメータが不要なadversarial attackの手法を提案したこと.
- Adversarial examplesにロバストな既存のほとんどのモデルに対し,論文内で報告された値より低いrobust accuracy(adversarial examplesに対する精度)を達成したこと.
- それにより,adversarial attackに対するモデルの頑健性を評価する基本的な指標となったこと.
従来のadversarial attack(PGDやFGSMなど)はモデルや画像によって異なるハイパーパラメータを設定する必要があり,その設定によってrobust accuracyも変わってくるためモデルの頑健性に対しての公平な比較ができていませんでした.AutoAttackはそのハイパーパラメータが不要であるため,モデル間で公平な頑健性の比較ができ,さらに既存の攻撃手法よりも攻撃成功率が高いという利点があります.
以下,一つ目のハイパーパラメータ不要なAttackの手法について解説し,二つ目のrobust accuracyについて実際にコードを実装して比較実験してみます.
Auto-PGD
AutoAttackは四種類のadversarial attackのアンサンブル攻撃となっています.四つのAdversarial Attackの手法のうち,二つは既存の攻撃手法,二つはAuto-PGDというハイパーパラメータが不要なPGDを用います.ここではPGDとAuto-PGDについて解説していきます.
PGDは画像に少しずつノイズを加えて,制約()に収まるように画像を更新していくadversarial examplesの生成手法です.回目の画像から次の画像を生成するPGDの更新式は以下のようになります.
ここでは領域内に投影する関数,はステップサイズです.に勾配を足して,それが元画像から離れすぎないようにによって戻す,といったイメージです.ステップサイズは攻撃対象の画像,モデルによって調整する必要があるハイパーパラメータです.
Auto-PGDはこれにmomentumを足したような感じです.
ここでです.は画像の更新の差分なので,この部分がmomentumの役割となっています.
momentum以外にPGDと違うところはステップサイズの自動変更と使用する損失関数です.
ステップサイズ
さて,PGDとは違うステップサイズの自動変更ですが,チェックポイント(次で解説します)のiterationにおいて次の二つの条件のうち,どちらかを満たした時に更新されます.
ここでで固定します. この条件の意味ですが,一つ目は前回のチェックポイントからどのくらいの更新が成功したかということです.目的関数の値が良くなっている割合が多いということは,ステップサイズは適切であるということを意味します.よって,その場合はステップサイズを更新しなくても良いということになります.
二つ目は前回のチェックポイントと今回での値が変わらず,さらに目的関数の一番高かった値が変わらないとき,という条件です.つまりの値が更新されていないのにも関わらず目的関数の最大値が変わらないという,泥沼の状況(同じところをぐるぐる回っているなど)にハマるのを防いでいるということです.
Python(PyTorch)での実装例
# j回目の更新エポックにおいて,チェックポイントのリストw, 各エポックの目的関数fの値とその時のfの最大値からetaの値を更新するか判定する関数 def condition1(j): count = 0 for i in range(w[j-1], w[j]): if f_list[i+1] > f_list[i]: count += 1 if count < rho*(w[j] - w[j-1]): return True return False def condition2(j): if etas[w[j-1]] == etas[w[j]] and f_maxlist[w[j-1]] == f_maxlist[w[j]]: return True return False
チェックポイント
Auto-PGDではadversarial examples生成のiterationにおいて,チェックポイントのエポックをいくつか設け,そのチェックポイントでステップサイズを小さくするかどうか決定します.チェックポイントはで計算します.ここで,],として更新式は以下を使います.
Python(PyTorch)での実装例
# ステップサイズを更新するエポックを計算する関数 def checkpoints(N_iter=100): w = [] p = [0, 0.22] while True: next_p = p[-1] + max(p[-1] - p[-2] - 0.03, 0.06) if next_p > 1: break p.append(next_p) for pj in p: w.append(math.ceil(pj * N_iter)) return w
チェックポイントで更新されると...
チェックポイントでステップサイズが更新された場合,そこからの探索はを用います.
これは,今までに一番よかった(目的関数の値が大きかった)画像をもとに,そこからステップサイズを小さくして探索する,ということです.
PGDの損失関数の欠点
よく使われる損失関数のCross-entropyは,入力に対しラベルがのとき,
と表せます.これを微分すると以下のようになります.
もしかつ全てのについてならばとなり,勾配消失してしまいます.さらに,として損失関数を倍したものを使用すると,の増加に伴い容易に勾配消失が起こり,CEを使うのは攻撃として適切でないことがわかります.
使用する損失関数
CRに変わる損失関数として,Auto-PGDではDifference of Logits Ratio(DLR) lossという損失関数を使います.
ここでは出力のロジットのうち,番目に高いものを指します.つまりです.この損失関数を最大化することは,をに分類しないようにすることと同義です.さらにに分類されなくなると,のロジットを最小化するようにされています.分母にもロジットの差を入れることで,CrossEntropyで問題となる勾配消失を解決しています.
これをターゲットクラスに誘導するときは
という損失関数を使います.DLRと分母が違うのは,の時に分子と分母で打ち消して損失が定数になるのを防ぐためです.
Python(PyTorch)での実装例
# モデルの出力(logits)と正解ラベル(label)を受け取ってDLR Lossを返す関数 def DLRLoss(logits, label): length = logits.shape[0] z_y = logits[range(length), label] z_max = torch.zeros(length) z_pi1 = logits.data.max(1)[0] z_pi3 = torch.topk(logits, 3, dim=1)[0][:,2] for i in range(length): if logits[i].data.max(0)[1] == label[i]: z_max[i] = torch.topk(logits[i], 2)[0][1] else: z_max[i] = torch.max(logits[i]) loss = -(z_y - z_max)/(z_pi1 - z_pi3) return torch.sum(loss)/loss.shape[0] # モデルの出力(logits)と正解ラベル(label),ターゲットのクラス(targets)を受け取ってTargeted DLR Lossを返す関数 def TargetedDLRLoss(logits, label, targets): eps = 1e-12 length = logits.shape[0] z_y = logits[range(length), label] z_t = logits[range(length), targets] topk_z = torch.topk(logits, 4, dim=1)[0] z_pi1 = topk_z[:,0] z_pi3 = topk_z[:,2] z_pi4 = topk_z[:,3] loss = -(z_y - z_t)/(z_pi1 - (z_pi3 + z_pi4)/2 + eps) return torch.sum(loss)/loss.shape[0]
AutoAttack
これでPGDのパラメータが不要なAuto-PGD(APGD)と,DLR lossという損失関数が新たに使えるようになりました.AutoAttackでは,このAPGDと(Targeted)DLR loss,さらに既存のFABとSquare attackのアンサンブルで攻撃します.具体的には以下の四つの攻撃を使います.
- :CrossEntropyを使ったrandom restartのないAPGD
- :Targeted DLR lossを使ったAPGD
- :targetを絞ったFAB
- :5000回のクエリのSquare Attack
white-boxの攻撃は100回のiterationを用います.各画像について,これらの攻撃の内最も攻撃が成功したものを採用します.論文によると,このAutoAttackのkey pointは攻撃の多様性にあるようです.APGDが最大の損失を目指す攻撃である一方,FABは加える摂動が最小になるようにしたり,勾配を利用しないblack-boxのSquare Attackがあったりとそれぞれ攻撃の特性が異なっています.
Python(PyTorch)での実装例
以上をまとめると,AutoAttackに使われるAPGD_CEのコードは以下のようになります.
class APGD_CE(): def __init__(self, model, eps=0.03, rho=0.75, N_iter=100, alpha=0.75, device="cuda"): self.model = model self.eps = eps self.rho = rho self.N_iter = N_iter self.alpha = alpha self.loss = nn.CrossEntropyLoss() self.device = device self.w = [] # iterationごとに目的関数の値のリスト self.f_list = [] # iterationごとのetaのリスト self.etas = [] # iterationごとに目的関数の最大値のリスト self.f_maxlist = [] self.p = [0, 0.22] self.checkpoints() def condition1(self, j): count = 0 for i in range(self.w[j-1],self.w[j]): if self.f_list[i+1] > self.f_list[i]: count += 1 if count < self.rho*(self.w[j] - self.w[j-1]): return True return False def condition2(self, j): if self.etas[self.w[j-1]] == self.etas[self.w[j]] and self.f_maxlist[self.w[j-1]] == self.f_maxlist[self.w[j]]: return True return False def checkpoints(self): w = [] while True: next_p = self.p[-1] + max(self.p[-1] - self.p[-2] - 0.03, 0.06) if next_p > 1: break self.p.append(next_p) for pj in self.p: w.append(math.ceil(pj * self.N_iter)) self.w = w def perturb(self, x, y): x_orig = x.clone().detach() x_0 = x.clone().detach() x_0.requires_grad = True # 目的関数fの初期値 self.model.zero_grad() logit_0 = self.model(x_0) f_0 = self.loss(logit_0, y) f_0.backward() # 目的関数fの次の値 eta = 2*self.eps x_1pre = x_0 + eta*x_0.grad.sign() noize_x1 = torch.clamp(x_1pre - x_orig, min=-self.eps, max=self.eps) x_1 = torch.clamp(x_orig + noize_x1, min=0.0, max=1.0).clone().detach() x_1.requires_grad = True self.model.zero_grad() logit_1 = self.model(x_1) f_1 = self.loss(logit_1, y) f_1.backward() f_max = max(f_0, f_1) x_max = (x_orig if f_max == f_0 else x_1) eta_0 = 2*self.eps self.f_list = [f_0, f_1] self.f_maxlist = [f_0, f_max] self.etas = [eta_0, eta_0] f_xk = f_1 x_k = x_1 x_km = x_orig wj = 1 for k in range(1, self.N_iter): # calculate z z_pre = x_k + self.etas[-1]*x_k.grad.sign() noise_z = torch.clamp(z_pre - x_orig, min=-self.eps, max=self.eps) z = torch.clamp(x_orig + noise_z, min=0.0, max=1.0) # calculate x_kp := x^(k+1) x_pre = x_k + self.alpha*(z - x_k) + (1 - self.alpha)*(x_k - x_km) noize_x = torch.clamp(x_pre - x_orig, min=-self.eps, max=self.eps) x_kp = torch.clamp(x_orig + noize_x, min=0.0, max=1.0).clone().detach() x_kp.requires_grad = True # update x,f max logit = self.model(x_kp) f_xkp = self.loss(logit, y) f_xkp.backward() if f_xkp > f_max: x_max = x_kp f_max = f_xkp # update eta if k in self.w: if self.condition1(wj) or self.condition2(wj): eta = eta/2 x_kp = x_max wj += 1 # update x, x_km x_km = x_k x_k = x_kp # append eta, f, f_max self.etas.append(eta) self.f_list.append(f_xkp) self.f_maxlist.append(f_max) return x_max
実験
以上のAutoAttackを実装して実験し,既存研究のモデルを評価してみます.
環境
結果
データセットはCIFAR10とMNISTを用い,手元で実験した値とAutoAttackの論文内で報告されていた値(括弧内の値)を示します.論文内ではadversarial trainingなどをしてロバストになっている先行研究のモデルを使用しているので,それに倣って同じモデルを使用しています.大体報告通りの結果になっていますね.論文の値との微妙な違いはseed値やバッチサイズによるものだと考えられます.
CIFAR10
論文 | Clean | APGD | APGD_T | AA |
---|---|---|---|---|
Carmon | 89.69 (89.69) | 61.84 (61.74) | 59.62 (59.54) | 59.60 (59.53) |
Ding | 84.36 (84.36) | 50.25 (50.12) | 41.79 (41.74) | 41.57 (41.44) |
MNIST
論文 | Clean | APGD | APGD_T | AA |
---|---|---|---|---|
Ding | 98.95 (98.95) | 95.31 (94.58) | 95.47 (94.62) | 91.39 (91.40) |
まとめ
この論文をまとめると,従来の
- モデルごとにハイパーパラメータの調整が必要なAdversarial Attack
- 勾配消失などの問題があるCross-Entropy Loss
- モデルの頑健性の一般的な指標がない
という問題を解決し,これらは
- Auto-PGDというmomentum付きPGDというハイパーパラメータ不要な手法
- 特別に設計したDLR Lossという損失関数
によって実現している.
参考文献
- Reliable evaluation of adversarial robustness with an ensemble of diverse parameter-free attacks [Croce+ ICML2020]
- Towards Deep Learning Models Resistant to Adversarial Attacks [Madry+ ICLR2018]
- Minimally distorted adversarial examples with a fast adaptive boundary attack [Croce+ ICML2020]
- Square attack: a query-efficient black-box adversarial attack via random search [Andriushchenko+ ECCV2020]
- Unlabeled Data Improves Adversarial Robustness [Carmon+ NeurIPS2019]
- MMA Training: Direct Input Space Margin Maximization through Adversarial Training [Ding+ ICLR2020]