diff --git a/attacks/deepfool.py b/attacks/deepfool.py index 5b922c0..25defb3 100644 --- a/attacks/deepfool.py +++ b/attacks/deepfool.py @@ -48,10 +48,11 @@ def _f(xi): delta = tf.map_fn(_f, x, dtype=(tf.float32), back_prop=False, name='deepfool') - xadv = tf.clip_by_value(x + delta*(1+eta), clip_min, clip_max) - if noise: - return xadv, delta + return delta + + xadv = tf.stop_gradient(x + delta*(1+eta)) + xadv = tf.clip_by_value(xadv, clip_min, clip_max) return xadv diff --git a/example/deepfool_mnist.py b/example/deepfool_mnist.py index 7d70250..070c2ef 100644 --- a/example/deepfool_mnist.py +++ b/example/deepfool_mnist.py @@ -210,9 +210,9 @@ def predict(sess, env, X_data, batch_size=128): def make_deepfool(sess, env, X_data, epochs=1, eps=0.01, batch_size=128): """ - Generate FGSM by running env.xadv. + Generate DeepFool by running env.xadv. """ - print('\nMaking adversarials via FGSM') + print('\nMaking adversarials via DeepFool') n_sample = X_data.shape[0] n_batch = int((n_sample + batch_size - 1) / batch_size) diff --git a/example/deepfool_mnist2.py b/example/deepfool_mnist2.py index dcfc11a..549b8f8 100644 --- a/example/deepfool_mnist2.py +++ b/example/deepfool_mnist2.py @@ -228,9 +228,9 @@ def predict(sess, env, X_data, batch_size=128): def make_deepfool(sess, env, X_data, epochs=1, batch_size=128): """ - Generate FGSM by running env.xadv. + Generate DeepFool by running env.xadv. """ - print('\nMaking adversarials via FGSM') + print('\nMaking adversarials via DeepFool') n_sample = X_data.shape[0] n_batch = int((n_sample + batch_size - 1) / batch_size) diff --git a/example/deepfool_mnist2_batch.py b/example/deepfool_mnist2_batch.py index 3276faf..d6a92c4 100644 --- a/example/deepfool_mnist2_batch.py +++ b/example/deepfool_mnist2_batch.py @@ -228,9 +228,9 @@ def predict(sess, env, X_data, batch_size=128): def make_deepfool(sess, env, X_data, epochs=1, batch_size=128): """ - Generate FGSM by running env.xadv. + Generate DeepFool by running env.xadv. """ - print('\nMaking adversarials via FGSM') + print('\nMaking adversarials via DeepFool') n_sample = X_data.shape[0] n_batch = int((n_sample + batch_size - 1) / batch_size) diff --git a/example/deepfool_mnist_batch.py b/example/deepfool_mnist_batch.py index b33cda3..1039fc2 100644 --- a/example/deepfool_mnist_batch.py +++ b/example/deepfool_mnist_batch.py @@ -210,9 +210,9 @@ def predict(sess, env, X_data, batch_size=128): def make_deepfool(sess, env, X_data, epochs=1, eps=0.01, batch_size=128): """ - Generate FGSM by running env.xadv. + Generate DeepFool by running env.xadv. """ - print('\nMaking adversarials via FGSM') + print('\nMaking adversarials via DeepFool') n_sample = X_data.shape[0] n_batch = int((n_sample + batch_size - 1) / batch_size) diff --git a/example/deepfool_noise.py b/example/deepfool_noise.py new file mode 100644 index 0000000..77db916 --- /dev/null +++ b/example/deepfool_noise.py @@ -0,0 +1,285 @@ +""" +Use DeepFool to craft adversarials on MNIST. +""" +import os + +import numpy as np + +import matplotlib +matplotlib.use('Agg') # noqa: E402 +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec + +import tensorflow as tf + +from attacks import deepfool + + +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + + +img_size = 28 +img_chan = 1 +n_classes = 10 + + +print('\nLoading MNIST') + +mnist = tf.keras.datasets.mnist +(X_train, y_train), (X_test, y_test) = mnist.load_data() +X_train = np.reshape(X_train, [-1, img_size, img_size, img_chan]) +X_train = X_train.astype(np.float32) / 255 +X_test = np.reshape(X_test, [-1, img_size, img_size, img_chan]) +X_test = X_test.astype(np.float32) / 255 + +to_categorical = tf.keras.utils.to_categorical +y_train = to_categorical(y_train) +y_test = to_categorical(y_test) + +print('\nSpliting data') + +ind = np.random.permutation(X_train.shape[0]) +X_train, y_train = X_train[ind], y_train[ind] + +VALIDATION_SPLIT = 0.1 +n = int(X_train.shape[0] * (1-VALIDATION_SPLIT)) +X_valid = X_train[n:] +X_train = X_train[:n] +y_valid = y_train[n:] +y_train = y_train[:n] + +print('\nConstruction graph') + + +def model(x, logits=False, training=False): + with tf.variable_scope('conv0'): + z = tf.layers.conv2d(x, filters=32, kernel_size=[3, 3], + padding='same', activation=tf.nn.relu) + z = tf.layers.max_pooling2d(z, pool_size=[2, 2], strides=2) + + with tf.variable_scope('conv1'): + z = tf.layers.conv2d(z, filters=64, kernel_size=[3, 3], + padding='same', activation=tf.nn.relu) + z = tf.layers.max_pooling2d(z, pool_size=[2, 2], strides=2) + + with tf.variable_scope('flatten'): + shape = z.get_shape().as_list() + z = tf.reshape(z, [-1, np.prod(shape[1:])]) + + with tf.variable_scope('mlp'): + z = tf.layers.dense(z, units=128, activation=tf.nn.relu) + z = tf.layers.dropout(z, rate=0.25, training=training) + + logits_ = tf.layers.dense(z, units=10, name='logits') + y = tf.nn.softmax(logits_, name='ybar') + + if logits: + return y, logits_ + return y + + +class Dummy: + pass + + +env = Dummy() + + +with tf.variable_scope('model'): + env.x = tf.placeholder(tf.float32, (None, img_size, img_size, img_chan), + name='x') + env.y = tf.placeholder(tf.float32, (None, n_classes), name='y') + env.training = tf.placeholder_with_default(False, (), name='mode') + + env.ybar, logits = model(env.x, logits=True, training=env.training) + + with tf.variable_scope('acc'): + count = tf.equal(tf.argmax(env.y, axis=1), tf.argmax(env.ybar, axis=1)) + env.acc = tf.reduce_mean(tf.cast(count, tf.float32), name='acc') + + with tf.variable_scope('loss'): + xent = tf.nn.softmax_cross_entropy_with_logits(labels=env.y, + logits=logits) + env.loss = tf.reduce_mean(xent, name='loss') + + with tf.variable_scope('train_op'): + optimizer = tf.train.AdamOptimizer() + env.train_op = optimizer.minimize(env.loss) + + env.saver = tf.train.Saver() + +with tf.variable_scope('model', reuse=True): + env.adv_epochs = tf.placeholder(tf.int32, (), name='adv_epochs') + env.noise = deepfool(model, env.x, epochs=env.adv_epochs, batch=True, + noise=True) + +print('\nInitializing graph') + +sess = tf.InteractiveSession() +sess.run(tf.global_variables_initializer()) +sess.run(tf.local_variables_initializer()) + + +def evaluate(sess, env, X_data, y_data, batch_size=128): + """ + Evaluate TF model by running env.loss and env.acc. + """ + print('\nEvaluating') + + n_sample = X_data.shape[0] + n_batch = int((n_sample+batch_size-1) / batch_size) + loss, acc = 0, 0 + + for batch in range(n_batch): + print(' batch {0}/{1}'.format(batch + 1, n_batch), end='\r') + start = batch * batch_size + end = min(n_sample, start + batch_size) + cnt = end - start + batch_loss, batch_acc = sess.run( + [env.loss, env.acc], + feed_dict={env.x: X_data[start:end], + env.y: y_data[start:end]}) + loss += batch_loss * cnt + acc += batch_acc * cnt + loss /= n_sample + acc /= n_sample + + print(' loss: {0:.4f} acc: {1:.4f}'.format(loss, acc)) + return loss, acc + + +def train(sess, env, X_data, y_data, X_valid=None, y_valid=None, epochs=1, + load=False, shuffle=True, batch_size=128, name='model'): + """ + Train a TF model by running env.train_op. + """ + if load: + if not hasattr(env, 'saver'): + return print('\nError: cannot find saver op') + print('\nLoading saved model') + return env.saver.restore(sess, 'model/{}'.format(name)) + + print('\nTrain model') + n_sample = X_data.shape[0] + n_batch = int((n_sample+batch_size-1) / batch_size) + for epoch in range(epochs): + print('\nEpoch {0}/{1}'.format(epoch + 1, epochs)) + + if shuffle: + print('\nShuffling data') + ind = np.arange(n_sample) + np.random.shuffle(ind) + X_data = X_data[ind] + y_data = y_data[ind] + + for batch in range(n_batch): + print(' batch {0}/{1}'.format(batch + 1, n_batch), end='\r') + start = batch * batch_size + end = min(n_sample, start + batch_size) + sess.run(env.train_op, feed_dict={env.x: X_data[start:end], + env.y: y_data[start:end], + env.training: True}) + if X_valid is not None: + evaluate(sess, env, X_valid, y_valid) + + if hasattr(env, 'saver'): + print('\n Saving model') + os.makedirs('model', exist_ok=True) + env.saver.save(sess, 'model/{}'.format(name)) + + +def predict(sess, env, X_data, batch_size=128): + """ + Do inference by running env.ybar. + """ + print('\nPredicting') + n_classes = env.ybar.get_shape().as_list()[1] + + n_sample = X_data.shape[0] + n_batch = int((n_sample+batch_size-1) / batch_size) + yval = np.empty((n_sample, n_classes)) + + for batch in range(n_batch): + print(' batch {0}/{1}'.format(batch + 1, n_batch), end='\r') + start = batch * batch_size + end = min(n_sample, start + batch_size) + y_batch = sess.run(env.ybar, feed_dict={env.x: X_data[start:end]}) + yval[start:end] = y_batch + print() + return yval + + +def make_deepfool(sess, env, X_data, epochs=1, eps=0.01, batch_size=128): + """ + Generate DeepFool by running env.xadv. + """ + print('\nMaking adversarials via DeepFool') + + n_sample = X_data.shape[0] + n_batch = int((n_sample + batch_size - 1) / batch_size) + X_noise = np.empty_like(X_data) + + for batch in range(n_batch): + print(' batch {0}/{1}'.format(batch + 1, n_batch), end='\r') + start = batch * batch_size + end = min(n_sample, start + batch_size) + noise = sess.run(env.noise, feed_dict={env.x: X_data[start:end], + env.adv_epochs: epochs}) + X_noise[start:end] = noise + print() + + return X_noise + + +print('\nTraining') + +train(sess, env, X_train, y_train, X_valid, y_valid, load=False, epochs=5, + name='mnist') + +print('\nEvaluating on clean data') + +evaluate(sess, env, X_test, y_test) + +print('\nGenerating adversarial data') + +X_noise = make_deepfool(sess, env, X_test, epochs=3) +X_adv = np.clip(X_test + 1.02*X_noise, 0, 1) +print(np.min(X_noise), np.max(X_noise)) + +print('\nEvaluating on adversarial data') + +evaluate(sess, env, X_adv, y_test) + +print('\nRandomly sample adversarial data from each category') + +y1 = predict(sess, env, X_test) +y2 = predict(sess, env, X_adv) + +z0 = np.argmax(y_test, axis=1) +z1 = np.argmax(y1, axis=1) +z2 = np.argmax(y2, axis=1) + +print('\nPlotting results') +fig = plt.figure(figsize=(10, 2.2)) +gs = gridspec.GridSpec(2, 10, wspace=0.05, hspace=0.05) + +for i in range(10): + print('Target {0}'.format(i)) + ind, = np.where(np.all([z0 == i, z1 == i, z2 != i], axis=0)) + ind = np.random.choice(ind) + xcur = [X_test[ind], X_adv[ind]] + ycur = y2[ind] + zcur = z2[ind] + + for j in range(2): + img = np.squeeze(xcur[j]) + ax = fig.add_subplot(gs[j, i]) + ax.imshow(img, cmap='gray', interpolation='none') + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_xlabel('{0} ({1:.2f})'.format(zcur, ycur[zcur]), fontsize=12) + +print('\nSaving figure') +gs.tight_layout(fig) +os.makedirs('img', exist_ok=True) +plt.savefig('img/deepfool_mnist.png')