FGSMとは? 敵対的サンプルのノイズを強くしても、誤分類が増え続けなかった理由
約13分で読めます
Last updated on

FGSMとは? 敵対的サンプルのノイズを強くしても、誤分類が増え続けなかった理由


「ごくわずかな、人間の目にはほとんど気づけないノイズを画像に足すだけで、パンダを99%の自信でテナガザルだと誤分類させられる」——adversarial examples(敵対的サンプル)の話は、AIの安全性を語るときによく引き合いに出される。有名な例だとGoodfellowらのFGSM(Fast Gradient Sign Method)論文のパンダの画像がそれで、見た目はほぼ変わらないのに、モデルの確信度だけが完全にひっくり返る。

これを自分の手で確かめたことは一度もなかった。ただし今回やるのは、大きな画像分類モデルへの攻撃ではない。2次元の小さなtoyデータセットに、自分でスクラッチ実装したMLPを学習させ、そのMLPだけをFGSMで殴ってみる、という小規模でクリーンな再現実験だ。実運用の画像認識モデルがどうか、という主張はしていない。それでも「決定境界のすぐ近くにいる点は、本当にごくわずかな一押しで判定が反転するのか」という核心の部分は、この小さな実験でも十分に検証できる。

実験に使ったコードの全文はGitHubに置いています。

先に結果を書くと、この分類器への1ステップのFGSMでは、ノイズを強くしても誤分類の割合は増え続けなかった。 ただし、攻撃で反転しなかった点が「あらゆる攻撃に強い」と分かったわけではない。この記事の見どころは、その2つを分けて、入力が境界を横切る様子を追えるところにある。

two-moonsに97パラメータのMLPを学習させる

データはdropoutの記事でも使ったtwo-moons(ノイズ0.15、標準化してstd=1.0)。1000点を700(学習)/300(テスト)に分割し、2→24(tanh)→1(sigmoid)のMLPを、順伝播・逆伝播・Adamまで全部numpyで自前実装して4000エポック学習した。

初見の方のために、そもそも何をやらせているのかを先に書いておく。two-moonsは、2つの三日月(クレセント)型の点群が互い違いに噛み合うように並んだ2次元の人工データセットで、各点には「どちらの三日月から生成されたか」のラベル(class 0/class 1)が付いている。モデルの仕事は、座標(x1, x2)だけを見てこのラベルを当てる、ごく普通の2クラス分類だ——下の図で言えば、青い点群と赤い点群のあいだに境界線を引くこと。そして今回の攻撃側の「成功」は、もともと正しく分類されていた点の座標をほんの少しだけ動かして、この判定を誤りに変える(以降「反転」と呼ぶ)ことと定義する。

def forward(params, X):
    W1, b1, W2, b2 = params
    z1 = X @ W1 + b1
    h1 = np.tanh(z1)
    z2 = h1 @ W2 + b2
    out = sigmoid(z2).ravel()
    return out, (X, z1, h1, z2)

パラメータ数はたったの97個。それでもテスト精度は0.9833まで上がった。

two-moonsデータセットの散布図に、学習済みMLPの決定境界を赤青の塗り分けで重ねた図。青が背景の左上の三日月、赤が右下の三日月にほぼ沿って複雑に曲がりくねった境界線が引かれている。中央付近には境界が小さく波打つ箇所や、赤領域の中に浮かぶ小さな青い島が見える。タイトルには隠れ24ユニット・パラメータ97個・テスト精度0.9833と明記されている

わずか97パラメータでも、境界は単純な直線ではなく、データの重なり具合に合わせて細かく波打っている。この「波打ち具合」が、あとでFGSMの挙動に効いてくる。

FGSM: 勾配の向きは「重み」ではなく「入力」について取る

通常の学習では、損失を重みについて微分して重みを更新する。FGSMはこれの逆で、損失を入力について微分し、その符号方向に入力そのものを動かす。

def grad_wrt_input(params, cache, y_true, out):
    """FGSMの核心: 重みではなく「入力X」についての dL/dX を計算する"""
    W1, b1, W2, b2 = params
    X, z1, h1, z2 = cache
    d_z2 = (out - y_true).reshape(-1, 1)   # 点ごと、平均しない
    d_h1 = d_z2 @ W2.T
    d_z1 = d_h1 * (1 - np.tanh(z1) ** 2)
    d_X = d_z1 @ W1.T                       # (n, 2) dL/dX
    return d_X

これで各点ごとに ∂L/∂x\partial L / \partial x が求まる。あとは

xadv=x+ε⋅sign(∂L∂x)x_{adv} = x + \varepsilon \cdot \mathrm{sign}\left(\frac{\partial L}{\partial x}\right)

の式どおり、符号だけを使って各点を摂動させる。FGSMは、各座標の変化量をε以下に制限する(L∞L_\infty制約)もとで、損失の一次近似を最大にする動かし方だ。勾配の大きさに比例して進む方法とは違い、各成分の符号を使う。Goodfellowらの原論文で提案された方法である。

たとえば入力が(0.2,−0.4)(0.2,-0.4)、損失の勾配が(3,−0.5)(3,-0.5)、ε=0.1なら、加えるのは(0.1,−0.1)(0.1,-0.1)で、移動後は(0.3,−0.5)(0.3,-0.5)。2次元で両成分が非ゼロなら、移動距離(L2L_2)は2ε\sqrt{2}\varepsilonになる。ε=0.1を「平面上で0.1だけ動く」と読むと、尺度を取り違える。

テスト300点のうちもともと正しく分類されていた295点を攻撃対象にし、εを0から2.5まで0.01刻みで細かくスイープして、「もともと正解していた点のうち何%が誤分類に反転したか」(反転率)を実測した。

結果1: 反転率はε=0.26(stdの26%)で10%、ε=0.41で25%——ただし45%前後で頭打ち

epsilon(横軸、標準化後データのstd=1.0が基準)に対する反転率(縦軸、%)の実測カーブ。0から緩やかに立ち上がり、ε=0.26で10%、ε=0.41で25%のラインを超えたあと、ε=0.9付近で一度42%台まで上がり、そこからε=1.6にかけて36%台まで下がる谷がある。その後再び上昇してε=2.0付近で最高の48%に達し、終点のε=2.5では45%前後に落ち着いている。ε=2.5に達してもグラフは50%のラインを一度も超えていない

正直、予想していたのは「εを上げ続ければいずれ反転率はほぼ100%に近づく」という素直な右肩上がりのカーブだった。実際は違った。

  • ε=0.05: 反転率 1.02%
  • ε=0.10: 反転率 2.03%
  • ε=0.20: 反転率 6.10%
  • ε=0.26: 反転率が初めて10%を超える
  • ε=0.30: 反転率 12.88%
  • ε=0.41: 反転率が初めて25%を超える
  • その先は単調ですらない。ε=0.9付近で42.4%まで上がったあと、ε=1.6にかけて36.3%まで一度下がり、ε=2.03でスイープ全体の最高値48.1%をつける
  • ε=2.5(データの標準偏差2.5個分、もはや”微小”とはとても言えない大きさ)の終点では反転率45.08%。スイープ全体を通して、反転率は一度も50%に届かなかった

295点中111点(37.6%)は、調べたε=0〜2.5の0.01刻みの範囲では一度も反転しなかった。 2.5より先や、刻みの間、別の方向は、この結果だけでは分からない。

今回のFGSMは元の点で求めた符号方向を固定して進む。反転率が42%台から36%台へ下がった区間では、いったん誤分類された点の一部が正しいクラスの領域へ戻ったことになる。局所的な一次近似で選んだ方向が、遠くまで最も有効とは限らないのだ。

攻撃の成功率と、モデルの頑健さは違う

εが大きくなれば、攻撃者が選べる「各座標の変化がε以内」の領域は広がる。したがって、その領域内の最も悪いケースを探し切れれば、攻撃成功率は下がらない。小さいεで見つけた成功例を、そのまま使えるからだ。

一方、FGSMは各εで1つの候補を作るだけ。その候補が失敗することと、領域内に成功例が存在しないことは別になる。今回下がったのは、決まった方法で作った候補の成功率であって、最悪ケースへの弱さではない。

結果2: それでも境界のすぐ隣の点は、ε=0.01というごく小さな摂動で反転する

反転した184点について、調べた刻みの中で初めて反転したεを求めた。最も小さいものは0.010、中央値0.490、最大2.490だった。これは固定したFGSM方向で、離散的に探索した値だ。あらゆる方向を探した「境界までの最短距離」ではない。

  • ε≦0.01で反転する点: 295点中1点(0.3%)
  • ε≦0.05で反転する点: 295点中3点(1.0%)
  • ε≦0.10で反転する点: 295点中6点(2.0%)

小さな摂動で反転する点も、今回の方向では反転しない点もあった。効き方には、境界までの距離だけでなく、攻撃方向と境界の形も関わる。反転しなかった点について「境界から遠い」とは断定できない。

εを10%反転率に達する0.26に固定し、実際にどの点がどう動いたのかを個別に見てみる。

ε=0.26でのFGSM摂動の散布図。背景は決定境界と灰色の正解テスト点。白抜き丸(摂動前)から色付きのX印(摂動後)へ矢印が伸びている10点が強調されており、赤い矢印(6点)は境界をまたいで反転した点、青い矢印(4点)は境界の同じ側にとどまって反転しなかった点。タイトルには295点中32点が反転(10.8%)と明記されている

矢印は斜め45度を向いている。勾配の各成分が非ゼロならsignは±1なので、2次元の移動方向は4つの対角方向になる(ゼロ成分があれば例外)。今回の矢印の長さはε2≈0.37\varepsilon\sqrt{2} \approx 0.37で共通だ。同じ長さでも、その方向に境界があるか、途中で何回またぐかによって結果が変わる。

εを連続的に上げていくアニメーション

εを0から1.0まで連続的に上げながら、正解していた295点全体がどう動いていくかをGIFにした。

εを0から1.0まで連続的に上げていきながら、正解していた295点の散布図が決定境界の上で少しずつ位置をずらしていくアニメーション。序盤(ε<0.2)はほとんど点の位置が変わらないが、εが上がるにつれて境界に近い点から順に色の境界線を越えて反対側の色の領域に入っていく。タイトルには現在のεと反転率が表示され、序盤はほぼ0%、後半になるにつれて反転率の数字が上がっていく

序盤はほとんど何も起きていないように見えるのに、εが上がるにつれて境界に近い点から順番に色を変えていく様子が見える。全員が一斉に動くのに、実際に「反対側に落ちる」のは境界のすぐそばにいた点だけ、というのがこのアニメーションで一番伝わってほしいところだ。

手を動かして意外だったこと

一番意外だったのは、ε=0〜2.5の探索で反転率が単調に増えず、最大でも48.1%だったことだ。攻撃の許容範囲を広げることと、良い攻撃候補を見つけることを、自分が同じものとして考えていたと気づいた。

もう1つは、境界のすぐそばの点がε=0.01という本当にわずかな摂動で反転していたこと。集計された反転率のカーブだけを見ていたら、この「ごく一部の点が信じられないほど脆い」という事実には気づけなかった。全体の反転率が10%を超えるにはε=0.26まで必要だったのに、最も脆い1点はその26分の1のεで既に落ちていた。平均的な頑健さと、最悪ケースの脆さは、まったく違う指標だということを実測して痛感した。

再現するときに分けて記録したいこと

記録するものなぜ必要か
入力の尺度とε標準化後の0.1と、画像の画素値の0.1は違う
攻撃前の正解数今回の反転率の分母は300点ではなく295点
各εでの成功率増減や、いったん戻る挙動を見つける
ε以下で一度でも成功した点の割合過去の成功例を含めた結果を区別する
探した方向・刻み・範囲未探索の領域にまで結論を広げない

さらに頑健性を調べるなら、勾配を更新しながら複数回進む攻撃や、異なる初期点からの探索との比較が必要だ。この実験ではそこまでは測っていない。通常のテスト精度と攻撃への強さを、別の評価項目として扱うところが出発点になる。

まとめ

  • two-moonsデータ(1000点、標準化)に2→24(tanh)→1(sigmoid)、パラメータ97個のMLPを学習させ、テスト精度0.9833を達成した
  • FGSM(損失の入力に対する勾配の符号方向に xadv=x+ε⋅sign(∂L/∂x)x_{adv} = x + \varepsilon \cdot \mathrm{sign}(\partial L/\partial x) で摂動)を、正しく分類されていた295点に対して実施した
  • 反転率(誤分類に変わった割合)はε=0.26で10%、ε=0.41で25%に達したが、ε=2.5(標準偏差2.5個分)まで上げても45%前後で頭打ちになり(最高でもε=2.03の48.1%で、50%には一度も届かなかった)、295点中111点(37.6%)は一度も反転しなかった
  • 調べた中ではε=0.010で反転する点もあった。ただし、この値は最短の攻撃距離を保証しない
  • FGSMの失敗は、他の攻撃も失敗する証拠にはならない。集計値と個々の点、攻撃の性能とモデルの頑健さを分けて読む必要がある

境界そのものがどう作られるかは、陰陽データでモデルの表現力を比べた記事で可視化している。評価方法の落とし穴を続けて読むなら、乱数から精度100%が出たデータリークの実験へ。