如果說卷積神經網路是昔日影帝的話,那麼產生對抗已然成為深度學習研究領域中一顆新晉的耀眼新星,它將徹底地改變我們認知世界的方式。對抗學習訓練為指導人工智慧完成複雜任務提供了一個全新的思路,產生對抗圖片能夠非常輕鬆的愚弄之前訓練好的分類器,因此如何利用產生對抗圖片提高系統的魯棒性是一個很有研究的熱點問題。
神經網路合成的對抗樣本很容易讓人大吃一驚,這是因為對輸入進行小巧精心製作的擾動就可能導致神經網路以任意選擇的方式對輸入進行錯誤地分類。鑒於對抗樣本轉移到物質世界,可以使其變得非常強大,因此這是一個值得關注的安全問題。比如說Face Service,若一張對抗映像也被識別為真人的話,就會出現一些安全隱患及之後帶來的巨大損失。對產生對抗映像感興趣的讀者可以關注一下最近的Kaggle挑戰賽NIPS。
在這篇文章中,將手把手帶領讀者利用TensorFlow實現一個簡單的演算法來合成對抗樣本,之後使用這種技術建立一個魯棒的對抗性例子。
import tensorflow as tfimport tensorflow.contrib.slim as slimimport tensorflow.contrib.slim.nets as netstf.logging.set_verbosity(tf.logging.ERROR)sess = tf.InteractiveSession()
首先,設定輸入映像。使用tf.Variable而不是使用tf.placeholder,這是因為要確保它是可訓練的。當我們需要時,仍然可以輸入它。
image = tf.Variable(tf.zeros((299, 299, 3)))
接下來,載入Inception v3模型。
def inception(image, reuse): preprocessed = tf.multiply(tf.subtract(tf.expand_dims(image, 0), 0.5), 2.0) arg_scope = nets.inception.inception_v3_arg_scope(weight_decay=0.0) with slim.arg_scope(arg_scope): logits, _ = nets.inception.inception_v3( preprocessed, 1001, is_training=False, reuse=reuse) logits = logits[:,1:] # ignore background class probs = tf.nn.softmax(logits) # probabilities return logits, probslogits, probs = inception(image, reuse=False)
接下來,載入預訓練的權重。這個Inception v3的top-5的準確率為93.9%。
import tempfilefrom urllib.request import urlretrieveimport tarfileimport osdata_dir = tempfile.mkdtemp()inception_tarball, _ = urlretrieve( 'http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz')tarfile.open(inception_tarball, 'r:gz').extractall(data_dir)restore_vars = [ var for var in tf.global_variables() if var.name.startswith('InceptionV3/')]saver = tf.train.Saver(restore_vars)saver.restore(sess, os.path.join(data_dir, 'inception_v3.ckpt'))
接下來,編寫一些代碼來顯示映像,並對它進行分類及顯示分類結果。
import jsonimport matplotlib.pyplot as pltimagenet_json, _ = urlretrieve( 'http://www.anishathalye.com/media/2017/07/25/imagenet.json')with open(imagenet_json) as f: imagenet_labels = json.load(f)def classify(img, correct_class=None, target_class=None): fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 8)) fig.sca(ax1) p = sess.run(probs, feed_dict={image: img})[0] ax1.imshow(img) fig.sca(ax1) topk = list(p.argsort()[-10:][::-1]) topprobs = p[topk] barlist = ax2.bar(range(10), topprobs) if target_class in topk: barlist[topk.index(target_class)].set_color('r') if correct_class in topk: barlist[topk.index(correct_class)].set_color('g') plt.sca(ax2) plt.ylim([0, 1.1]) plt.xticks(range(10), [imagenet_labels[i][:15] for i in topk], rotation='vertical') fig.subplots_adjust(bottom=0.2) plt.show()
樣本映像
載入樣本映像,並確保它已被正確分類。
import PILimport numpy as npimg_path, _ = urlretrieve('http://www.anishathalye.com/media/2017/07/25/cat.jpg')img_class = 281img = PIL.Image.open(img_path)big_dim = max(img.width, img.height)wide = img.width > img.heightnew_w = 299 if not wide else int(img.width * 299 / img.height)new_h = 299 if wide else int(img.height * 299 / img.width)img = img.resize((new_w, new_h)).crop((0, 0, 299, 299))img = (np.asarray(img) / 255.0).astype(np.float32)classify(img, correct_class=img_class)
對抗樣本
給定一個映像X,神經網路輸出標籤上的機率分布為P(y|X)。當手工製作對抗輸入時,我們想要找到一個X’,使得logP(y’|X’)被最大化為目標標籤y’,即輸入將被錯誤分類為目標類。通過約束一些ℓ∞半徑為ε的箱,要求‖X- X’‖∞≤ε,我們可以確保X’與原始X看起來不太一樣。
在這個架構中,對抗樣本是解決一個約束最佳化的問題,可以使用反向傳播和投影梯度下降來解決,基本上也是用與訓練網路本身相同的技術。演算法很簡單:
首先將對抗樣本初始化為X’←X。然後,重複以下過程直到收斂:
1. X'←X^+α⋅∇logP(y'|X')2. X'←clip(X',X - ε,X+ε)
初始化
首先從最簡單的部分開始:編寫一個TensorFlow op進行相應的初始化。
x = tf.placeholder(tf.float32, (299, 299, 3))x_hat = image # our trainable adversarial inputassign_op = tf.assign(x_hat, x)
梯度下降步驟
接下來,編寫梯度下降步驟以最大化目標類的對數機率(或最小化交叉熵)。
learning_rate = tf.placeholder(tf.float32, ())y_hat = tf.placeholder(tf.int32, ())labels = tf.one_hot(y_hat, 1000)loss = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=[labels])optim_step = tf.train.GradientDescentOptimizer( learning_rate).minimize(loss, var_list=[x_hat])
投影步驟
最後,編寫投影步驟,使得對抗樣本在視覺上與原始映像相似。另外,將其限定為[0,1]範圍內保持有效映像。
epsilon = tf.placeholder(tf.float32, ())below = x - epsilonabove = x + epsilonprojected = tf.clip_by_value(tf.clip_by_value(x_hat, below, above), 0, 1)with tf.control_dependencies([projected]): project_step = tf.assign(x_hat, projected)
執行
最後,準備合成一個對抗樣本。我們任意選擇“鱷梨醬”(imagenet class 924)作為我們的目標類。
demo_epsilon = 2.0/255.0 # a really small perturbationdemo_lr = 1e-1demo_steps = 100demo_target = 924 # "guacamole"# initialization stepsess.run(assign_op, feed_dict={x: img})# projected gradient descentfor i in range(demo_steps): # gradient descent step _, loss_value = sess.run( [optim_step, loss], feed_dict={learning_rate: demo_lr, y_hat: demo_target}) # project step sess.run(project_step, feed_dict={x: img, epsilon: demo_epsilon}) if (i+1) % 10 == 0: print('step %d, loss=%g' % (i+1, loss_value))adv = x_hat.eval() # retrieve the adversarial examplestep 10, loss=4.18923step 20, loss=0.580237step 30, loss=0.0322334step 40, loss=0.0209522step 50, loss=0.0159688step 60, loss=0.0134457step 70, loss=0.0117799step 80, loss=0.0105757step 90, loss=0.00962179step 100, loss=0.00886694
這種對抗映像與原始映像在視覺上無法區分,沒有可見的人為加工。但是它會以很高的機率分類為“鱷梨醬”。
classify(adv, correct_class=img_class, target_class=demo_target)
魯棒的對抗樣本
現在來看一個更進階的例子。遵循我們的方法來合成穩健的對抗樣本,以找到對貓映像的單一擾動,這在某些選擇的變換分布下同時對抗,可以選擇任何可微分變換的分布;在這篇文章中,我們將合成一個單一的對抗輸入,設定θ∈[- π/4,π/4],這對旋轉是魯棒的。
在繼續下面的工作之前,檢查一下之前的例子是否能對抗旋轉,比如說設定角度為θ=π/8。
ex_angle = np.pi/8angle = tf.placeholder(tf.float32, ())rotated_image = tf.contrib.image.rotate(image, angle)rotated_example = rotated_image.eval(feed_dict={image: adv, angle: ex_angle})classify(rotated_example, correct_class=img_class, target_class=demo_target)
看起來我們之前產生的對抗樣本不是旋轉不變的。
那麼,如何使得一個對抗樣本對變換的分布是魯棒的呢。給定一些變換分布T,我們可以最大化Et~TlogP(y’|t(X’)),約束條件為‖X- X’‖∞≤ε。可以通過投影梯度下降法來解決這個最佳化問題,注意到∇Et~TlogP(y’|t(X’))與Et~T∇logP(y’|t(X’))相等,並在每個梯度下降步驟中來逼近樣本。
可以使用一個技巧讓TensorFlow為我們做到這一點,而不是通過手動實現梯度採樣得到:我們可以類比基於採樣的梯度下降,作為隨機分類器的集合中的梯度下降,隨機分類器從分布中隨機抽取並在分類之前變換輸入。
num_samples = 10average_loss = 0for i in range(num_samples): rotated = tf.contrib.image.rotate( image, tf.random_uniform((), minval=-np.pi/4, maxval=np.pi/4)) rotated_logits, _ = inception(rotated, reuse=True) average_loss += tf.nn.softmax_cross_entropy_with_logits( logits=rotated_logits, labels=labels) / num_samples
我們可以重複使用assign_op和project_step,但為了這個新目標,必須寫一個新的optim_step。
optim_step = tf.train.GradientDescentOptimizer( learning_rate).minimize(average_loss, var_list=[x_hat])
最後,我們準備運行PGD來產生對抗輸入。和前面的例子一樣,選擇“鱷梨醬”作為我們的目標類。
demo_epsilon = 8.0/255.0 # still a pretty small perturbationdemo_lr = 2e-1demo_steps = 300demo_target = 924 # "guacamole"# initialization stepsess.run(assign_op, feed_dict={x: img})# projected gradient descentfor i in range(demo_steps): # gradient descent step _, loss_value = sess.run( [optim_step, average_loss], feed_dict={learning_rate: demo_lr, y_hat: demo_target}) # project step sess.run(project_step, feed_dict={x: img, epsilon: demo_epsilon}) if (i+1) % 50 == 0: print('step %d, loss=%g' % (i+1, loss_value))adv_robust = x_hat.eval() # retrieve the adversarial examplestep 50, loss=0.0804289step 100, loss=0.0270499step 150, loss=0.00771527step 200, loss=0.00350717step 250, loss=0.00656128step 300, loss=0.00226182
這種對抗映像被高度信任地歸類為“鱷梨醬”,即使是旋轉的情況下。
rotated_example = rotated_image.eval(feed_dict={image: adv_robust, angle: ex_angle})classify(rotated_example, correct_class=img_class, target_class=demo_target)
下面來看一下在整個角度範圍內產生的魯棒對抗樣本的旋轉不變性,看P(y’|x’)在θ∈[- π/4,π/4]。
thetas = np.linspace(-np.pi/4, np.pi/4, 301)p_naive = []p_robust = []for theta in thetas: rotated = rotated_image.eval(feed_dict={image: adv_robust, angle: theta}) p_robust.append(probs.eval(feed_dict={image: rotated})[0][demo_target]) rotated = rotated_image.eval(feed_dict={image: adv, angle: theta}) p_naive.append(probs.eval(feed_dict={image: rotated})[0][demo_target])robust_line, = plt.plot(thetas, p_robust, color='b', linewidth=2, label='robust')naive_line, = plt.plot(thetas, p_naive, color='r', linewidth=2, label='naive')plt.ylim([0, 1.05])plt.xlabel('rotation angle')plt.ylabel('target class probability')plt.legend(handles=[robust_line, naive_line], loc='lower right')plt.show()
從圖中藍色曲線可以看到,產生的對抗樣本是超級有效。
原文連結:
http://www.anishathalye.com/2017/07/25/synthesizing-adversarial-examples/?spm=5176.100239.blogcont149583.28.NUZKV8