Edited at

ディープラーニングを騙す


はじめに

ディープラーニングを「騙す」ことができるのをご存知だろうか。本記事では、ディープラーニングを「騙す」アルゴリズムのうち、Fast Gradient Sign Method[1]とCarlini-Wagner $L_2$ attack[2]の2つの手法を紹介する。


攻撃対象

3層のCNNで構成されたネットワークで訓練した、MNISTに対する識別器を攻撃対象とする。テスト画像(1万枚)に対する識別精度は98%となった。

実装


train.py

import numpy as  np

import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras.backend as K
from tensorflow import keras
from tensorflow.keras.datasets import mnist

# データをロード
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 前処理
x_train = np.expand_dims(x_train, 3)
x_train = x_train.astype("float32") / 255

x_test = np.expand_dims(x_test, 3)
x_test = x_test.astype("float32") / 255

y_train = keras.utils.to_categorical(y_train)
y_test = keras.utils.to_categorical(y_test)

# モデルの定義
model = keras.models.Sequential()
model.add(keras.layers.Conv2D(32, (2, 2),
activation="relu",
padding="same",
input_shape=(28, 28, 1)))
model.add(keras.layers.Conv2D(128, (2, 2),
activation="relu",
padding="valid"))
model.add(keras.layers.Conv2D(128, (1, 1),
activation="relu",
padding="valid"))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(10, activation="softmax"))

model.compile(loss="categorical_crossentropy",
optimizer="adam",
metrics=["acc"])

# 訓練
es = keras.callbacks.EarlyStopping(monitor="val_acc", patience=1)
history = model.fit(x_train, y_train, epochs=20,
batch_size=64,
callbacks=[es],
validation_split=0.2)




Fast Gradient Sign Method

Fast Gradient Sign Methodは2014年にGoodfellowらによって提案された手法であり、攻撃にかかる学習時間が短いことに特徴がある。ただし、その反面、攻撃の成功率は100%にはならない。具体的には、$J$を損失関数、$X$をインプット、$y_{\rm true}$を真のラベルとして、次の操作を行う。

$$

{\bf\it X}^{\rm adv}={\bf\it X}+\epsilon~{\rm sign}\left(\nabla_{X}J({\bf\it X},y_{\rm true})\right)

$$

Kerasを使うと10行程度で実装できる。上記のMNISTモデルに対して、この攻撃により生成した画像(1万枚)の識別精度は3.8%となった。

実装


FGSM.py

target = K.placeholder()

loss = K.sum(K.square(model.output - target))
grads = K.gradients(loss, model.input)[0]
fn = K.function([model.input, target], [K.sign(grads)])
grad_sign = []
for i in range(20):
part = np.arange(i * 500, (i + 1) * 500)
grad_sign.append(fn([x_test[part], y_test[part]])[0])
grad_sign = np.concatenate(grad_sign)

eps = 0.25
x_adv = np.clip(x_test + eps * grad_sign, 0, 1)





結果の一部を次図に示す。ぼやけてはいるものの、人間が判定するとまず間違わないはずの画像を間違えている。

FGSM_with_txt.png


Carlini-Wagner L2 attack

Carlini-Wagner $L_2$ attackは2016年にCarliniらによって提案された手法であり、それまでに提案されていた攻撃に対する防御方法をかいくぐることができる手法である。この手法は、特定のラベルへと間違わせることができ、高い精度で攻撃を成功させることができる。

$$

\parallel\frac{1}{2}(\tanh(w)+1)-x\parallel^2_2+c\cdot f(\frac{1}{2}(\tanh(w)+1))

$$

を$w$について最小化する。第一項は元画像との差異が小さくなるようにする損失である。ここで、

$$

x'=\frac{1}{2}(\tanh(w)+1)

$$

は攻撃後の画像である。また、

$$

f(x')=\max(\max\{Z(x')_i:i\neq t\}-Z(x') _t,-\kappa)

$$

は変化させたいラベルの確率が最大となるようにする損失である。なお、$Z(x)$はsoftmax関数を適用する前のネットワークの出力である。

Kerasでの実装はうまくいかなかったので、論文著者の実装を用いた。

実装


Carlini-Wagner_L2_attack.py

# ライセンスを追記しました。危ない...

'''
Copyright (c) 2019 ryuoujisinta
Copyright (c) 2016 Nicholas Carlini

LICENSE

Redistribution and use in source and binary forms, with or without |
modification, are permitted provided that the following conditions are met: |

1. Redistributions of source code must retain the above copyright notice, this |
list of conditions and the following disclaimer. |
2. Redistributions in binary form must reproduce the above copyright notice, |
this list of conditions and the following disclaimer in the documentation |
and/or other materials provided with the distribution. |

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
'''

import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow import keras
import numpy as np
import random
from PIL import Image

# 著者の実装
from nn_robust_attacks.l2_attack import CarliniL2

# 著者の実装の一部
def generate_data(data, samples, targeted=True, start=0, inception=False):
"""
Generate the input data to the attack algorithm.

data: the images to attack
samples: number of samples to use
targeted: if true, construct targeted attacks, otherwise untargeted attacks
start: offset into data to use
inception: if targeted and inception, randomly sample 100 targets intead of 1000
"""
inputs = []
targets = []
for i in range(samples):
if targeted:
if inception:
seq = random.sample(range(1, 1001), 10)
else:
seq = range(data.test_labels.shape[1])

for j in seq:
if (j == np.argmax(data.test_labels[start+i])) and (inception == False):
continue
inputs.append(data.test_data[start+i])
targets.append(np.eye(data.test_labels.shape[1])[j])
else:
inputs.append(data.test_data[start+i])
targets.append(data.test_labels[start+i])

inputs = np.array(inputs)
targets = np.array(targets)

return inputs, targets

class MNIST_data:
def __init__(self):
_, (x_test, y_test) = mnist.load_data()
x_test = np.expand_dims(x_test, 3)
self.test_data = x_test.astype("float32") / 255
self.test_labels = keras.utils.to_categorical(y_test)

class MNISTModel:
def __init__(self):
self.num_channels = 1
self.image_size = 28
self.num_labels = 10

model = keras.models.Sequential()
model.add(keras.layers.Conv2D(32, (2, 2),
activation="relu",
padding="same",
input_shape=(28, 28, 1)))
model.add(keras.layers.Conv2D(128, (2, 2),
activation="relu",
padding="valid"))
model.add(keras.layers.Conv2D(128, (1, 1),
activation="relu",
padding="valid"))
model.add(keras.layers.Flatten())
# softmaxは適用しない
model.add(keras.layers.Dense(10))

# 訓練済みモデルのロード
model.load_weights('mnist_cnn.h5')

self.model = model

def predict(self, data):
return self.model(data)

N = 100

with tf.Session() as sess:
data, model = MNIST_data(), MNISTModel()
attack = CarliniL2(sess, model, batch_size=100, max_iterations=1000,
confidence=0, boxmin=0, boxmax=1)

inputs, targets = generate_data(data, samples=N, targeted=True,
start=0, inception=False)

  adv = attack.attack(inputs, targets)



上記のMNISTモデルに対して、この攻撃により生成した画像(900枚)に対する推定結果は、すべて変化させたいラベルとなった。つまりは精度100%である。結果の一部を次図に示す。対角線上が攻撃前の画像であり、それ以外は攻撃後の画像である。FGSMに比べ、元画像との差異が小さいことがわかる。

carlini_l2_with_text.png


最後に

ディープラーニングが画像認識などの分野で素晴らしい成果をあげ、社会に浸透していきているのはいうまでもないだろう。一方、認識結果が間違うと多大な損害を出す恐れがある応用(サイバーセキュリティ、病院での診断など)では、導入に慎重な姿勢が見られる。一因として、複雑さに起因する、このような脆弱性が挙げられる。実際にdeployする際には、このような脆弱性も把握しておきたい。次回は、本記事で取り上げた攻撃手法を検知可能なDkNN[3]について書く予定である。


参考文献

[1] I. J. Goodfellow, J. Shlens and C. Szegedy, arXiv1412.6572

[2] N. Carlini and D. Wagner, arXiv:1608.04644

[3] N. Papernot and P. McDaniel, arXive:1803.04765