連載三:PyCon2018|用對抗樣本攻擊PNASNet模型(附原始碼)

程式碼醫生發表於2019-03-04

第八屆中國Python開發者大會PyConChina2018,由PyChina.org發起,由來自CPyUG/TopGeek等社群的30位組織者,近150位志願者在北京、上海、深圳、杭州、成都等城市舉辦。致力於推動各類Python相關的技術在網際網路、企業應用等領域的研發和應用。
程式碼醫生工作室有幸接受邀請,參加了這次會議的北京站專場。在會上主要分享了《人工智慧實戰案例分享-影像處理與數值分析》。
會上分享的一些案例主要是來源於《python帶我起飛——入門、進階、商業實戰》一書與《深度學習之TensorFlow:入門、原理與進階實戰》一書。另外,還擴充了若干其它案例。在本文作為補充,將會上分享的其它案例以詳細的圖文方式補充進來,並提供原始碼。共分為4期連載。

  1. 用slim呼叫PNASNet模型

  2. 用slim微調PNASNet模型

  3. 用對抗樣本攻擊PNASNet模型

  4. 惡意域名檢測例項

連載三:PyCon2018|用對抗樣本攻擊PNASNet模型(附原始碼)

用FGSM生成樣本來攻擊PNASNet網路,令其將狗識別成盤子

本例通過使用FGSM的方式,來生成攻擊樣本。對PNASNet發起攻擊,使其輸出的識別結果,產生錯誤。

案例描述

先用一張哈士奇狗的照片,輸入帶有預編譯模型PNASNet網路。觀察其返回結果。

通過梯度下降演算法訓練一個模擬樣本。待模擬樣本生成完成之後,輸入PNASNet令其輸出識別結果為盤子。

該案例所實現的FGSM原理很簡單:將輸入的圖片當作訓練引數。通過優化器不斷的迭代,來將圖片逐漸改造成輸入模型認為使盤子的樣子。下面就來演示其實現過程。

程式碼實現:建立PNASNet模型

在程式碼“3-1 使用AI模型來識別影像.py”中,介紹了一個使用PNASNet網路的預編譯模型,進行圖片識別的例子。本案例就在該程式碼基礎上實現。首先“3-1 使用AI模型來識別影像.py”程式碼檔案的整個工作環境複製一份。並編寫pnasnetfun函式,實現模型的建立。完整的程式碼如下:

線上性迴歸模型中新增指定節點到檢查點檔案

 1 import sys                                         #初始化環境變數
 2 nets_path = r`slim`
 3 if nets_path not in sys.path:
 4    sys.path.insert(0,nets_path)
 5 else:
 6    print(`already add slim`)
 7
 8 import tensorflow as tf                           #引入標頭檔案
 9 from nets.nasnet import pnasnet
10 import numpy as np
11 from tensorflow.python.keras.preprocessing import image
12
13 import matplotlib as mpl
14 import matplotlib.pyplot as plt
15 mpl.rcParams[`font.sans-serif`]=[`SimHei`]#用來正常顯示中文標籤  
16 mpl.rcParams[`font.family`] = `STSong`
17 mpl.rcParams[`font.size`] = 15
18
19 slim = tf.contrib.slim
20 arg_scope = tf.contrib.framework.arg_scope
21
22 tf.reset_default_graph()                       
23 image_size = pnasnet.build_pnasnet_large.default_image_size       #獲得圖片輸入尺寸
24 LANG = `ch`                                                        #使用中文標籤
25
26 if LANG==`ch`:
27    def getone(onestr):
28        return onestr.replace(`,`,` `).replace(`
`,``)
29
30    with open(`中文標籤.csv`,`r+`) as f:                             #開啟檔案               
31        labelnames =list( map(getone,list(f))  )
32        print(len(labelnames),type(labelnames),labelnames[:5])             #顯示輸出中文標籤    
33 else: 
34    from datasets import imagenet
35    labelnames = imagenet.create_readable_names_for_imagenet_labels()     #獲得資料集標籤
36    print(len(labelnames),labelnames[:5])                                       #顯示輸出標籤
37
38 def pnasnetfun(input_imgs,reuse ):
39    preprocessed = tf.subtract(tf.multiply(tf.expand_dims(input_imgs, 0), 2.0), 1.0) 
40    arg_scope = pnasnet.pnasnet_large_arg_scope()                         #獲得模型名稱空間
41
42    with slim.arg_scope(arg_scope):                                        #建立PNASNet模型
43        with slim.arg_scope([slim.conv2d,
44                             slim.batch_norm, slim.fully_connected,
45                             slim.separable_conv2d],reuse=reuse):
46
47            logits, end_points = pnasnet.build_pnasnet_large(preprocessed,num_classes = 1001, is_training=False)   
48            prob = end_points[`Predictions`]  
49    return logits, prob複製程式碼

程式碼13~17行在《深度學習之TensorFlow:入門、原理與進階實戰》9.7.4節也用過,是支援中文顯示的作用。程式碼43行的作用,是支援PNASNet模型的變數複用功能。

注意:

在建立模型時,需要將其設為不可訓練(程式碼47行)。這樣才能保證,在後面12.2.3節通過訓練生成攻擊樣本時,PNASNet模型不會有變化。

程式碼實現:搭建輸入層並載入圖片,復現PNASNet預測效果

在本案例中,沒有使用佔位符來建立輸入層,而是使用了張量。使用tf.Variable定義的張量與佔位符的效果一樣,同樣可以支援注入機制。這麼做的目的是為了在後面製造攻擊樣本時使用。接著是載入圖片、載入預編譯模型、將圖片注入到預編譯模型中的操作。並最終視覺化輸出預測結果。完整的程式碼如下:

程式碼12-1 線上性迴歸模型中新增指定節點到檢查點檔案(續)

50 input_imgs = tf.Variable(tf.zeros((image_size, image_size, 3)))                        
51 logits, probs = pnasnetfun(input_imgs,reuse=False)    
52 checkpoint_file = r`pnasnet-5_large_2017_12_13model.ckpt`   #定義模型路徑
53 variables_to_restore = slim.get_variables_to_restore()
54 init_fn = slim.assign_from_checkpoint_fn(checkpoint_file, variables_to_restore,ignore_missing_vars=True)
55
56 sess = tf.InteractiveSession()         #建立會話
57 init_fn(sess)                             #載入模型
58
59 img_path = `./dog.jpg`                #載入圖片
60 img = image.load_img(img_path, target_size=(image_size, image_size))
61 img = (np.asarray(img) / 255.0).astype(np.float32)
62
63 def showresult(img,p):                #視覺化模型輸出結果
64    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 8))
65    fig.sca(ax1)
66
67    ax1.axis(`off`)
68    ax1.imshow(img)
69    fig.sca(ax1)
70
71    top10 = list((-p).argsort()[:10])
72    lab= [labelnames[i][:15] for i in top10]
73    topprobs = p[top10]
74    print(list(zip(top10,lab,topprobs)))
75
76    barlist = ax2.bar(range(10), topprobs)
77
78    barlist[0].set_color(`g`)
79    plt.sca(ax2)
80    plt.ylim([0, 1.1])
81    plt.xticks(range(10), lab, rotation=`vertical`)
82    fig.subplots_adjust(bottom=0.2)
83    plt.show()
84
85 p = sess.run(probs, feed_dict={input_imgs: img})[0]
86 showresult(img,p)
複製程式碼

程式碼執行後,如下結果:

[(249, `愛斯基摩犬 哈士奇 `, 0.35189062), (251, `哈士奇 `, 0.34352344), (250, `雪橇犬 阿拉斯加愛斯基摩狗 `, 0.007250515), (271, `白狼 北極狼 `, 0.0034629034), (175, `挪威獵犬 `, 0.0028237076), (538, `狗拉雪橇 `, 0.0025286602), (270, `灰狼 `, 0.0022800271), (274, `澳洲野狗 澳大利亞野犬 `, 0.0018357899), (254, `巴辛吉狗 `, 0.0015468642), (280, `北極狐狸 白狐狸 `, 0.0009330675)]

並得到視覺化圖片,如圖12-1:

連載三:PyCon2018|用對抗樣本攻擊PNASNet模型(附原始碼)

圖12-1 PNASNet模型輸出

圖12-1中,左側為輸入的圖片,右側為其預測的結果。可以看到模型能夠成功的預測出該圖片內容是一隻哈士奇狗。

程式碼實現:定義圖片變化範圍,並通過人工校驗的方式調整引數

在製作樣本時,不能讓圖片的變化太大。還要讓圖片在人眼看上去能夠接收才行。這裡需要手動設定閾值,限制圖片的變化範圍。然後將生成的圖片顯示出來,由人眼判斷圖片是否正常可用。確保沒有失真。完整的程式碼如下:

程式碼12-1 線上性迴歸模型中新增指定節點到檢查點檔案(續)

 1 def floatarr_to_img(floatarr):                                    #將浮點型轉化為圖片
 2    floatarr=np.asarray(floatarr*255)
 3    floatarr[floatarr>255]=255
 4    floatarr[floatarr<0]=0
 5    return floatarr.astype(np.uint8)
 6
 7 x = tf.placeholder(tf.float32, (image_size, image_size, 3))     #定義佔位符
 8 assign_op = tf.assign(input_imgs, x)                              #為input_imgs賦值 
 9 sess.run( assign_op, feed_dict={x: img})
10
11 below = input_imgs - 2.0/255.0                                  #定義圖片的變化範圍
12 above = input_imgs + 2.0/255.0
13
14 belowv,abovev = sess.run( [below,above])                        #生成閾值圖片
15
16 plt.imshow(floatarr_to_img(belowv))                                #顯示圖片,用於人眼驗證
17 plt.show()
18 plt.imshow(floatarr_to_img(abovev))
19 plt.show()複製程式碼

在程式碼第8行,為input_imgs賦值之後,才可以使用。因為到目前為止input_imgs還沒有初始化,必須呼叫input_imgs.initializer或tf.assign為其賦值。這部分知識與《深度學習之TensorFlow:入門、原理與進階實戰》一書的3.3.8節相關。

這裡設定的閾值是:每個畫素上下變化最大不超過2(見程式碼11、12行)。經過程式碼執行後,輸出的圖片如圖12-2、圖12-3。

連載三:PyCon2018|用對抗樣本攻擊PNASNet模型(附原始碼)

如圖12-2、12-3所示,該圖片完全在人類接收範圍。一眼看去,就是一隻哈士奇狗。

程式碼實現:用梯度下降方式生成攻擊樣本

與正常的網路模型訓練目標不同,這裡的訓練目標不是模型中的權重,而是輸入模型的張量。在12.2.1節中,生成模型的同時,已經將模型固定好(設為不可訓練)。這樣在模型訓練的過程中,反向梯度傳播的最終作用值就是輸入的張量input_imgs了。

訓練步驟與正常的網路模型一致,設定一個其它類別的標籤。建立loss節點,令每次優化時,模型的輸出都接近於設定的標籤類別。完整的程式碼如下:

程式碼12-1 線上性迴歸模型中新增指定節點到檢查點檔案(續)

20 label_target = 880                                        #設定一個其它類別的目標標籤
21 label =tf.constant(label_target)
22 #定義loss節點
23 loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=[label])
24 learning_rate = 1e-1                                    #定義學習率
25 optim_step = tf.train.GradientDescentOptimizer(            #定義優化器
26    learning_rate).minimize(loss, var_list=[input_imgs])
27
28 #將調整後的圖片,按照指定閾值截斷
29 projected = tf.clip_by_value(tf.clip_by_value(input_imgs, below, above), 0, 1)
30 with tf.control_dependencies([projected]):
31    project_step = tf.assign(input_imgs, projected)
32
33 demo_steps = 400                                        #定義迭代次數
34 for i in range(demo_steps):                                #開始訓練
35    _, loss_value = sess.run([optim_step, loss])
36    sess.run(project_step)
37
38    if (i+1) % 10 == 0:                                    #輸出訓練狀態
39        print(`step %d, loss=%g` % (i+1, loss_value))
40        if loss_value<0.02:                                #達到標準後,提前結束
41            break
複製程式碼

程式碼執行後,輸出如下結果:

step 10, loss=5.29815

step 20, loss=0.00202808

程式碼顯示,僅迭代20次。模型即可達到了標準。在實際執行中,由於選用的圖片,和指定的其它類別不同。迭代次數有可能不同。

程式碼實現:用生成的樣本攻擊模型

接下來就是最有意思的環節了。用訓練好的攻擊樣本來攻擊模型。實現起來非常簡單,直接將input_imgs最為輸入,執行模型。具體程式碼如下:

程式碼12-1 線上性迴歸模型中新增指定節點到檢查點檔案(續)

42 adv = input_imgs.eval()                         #獲取圖片
43 p = sess.run(probs)[0]                            #得到模型結果
44 showresult(floatarr_to_img(adv),p)                #視覺化模型結果
45 plt.imsave(`dog880.jpg`,floatarr_to_img(adv))    #儲存模型複製程式碼

程式碼執行後,如下結果:

[(880, `傘 `, 0.9981244), (930, `雪糕 冰棍 冰棒 `, 0.00011489283), (630, `脣膏 口紅 `, 8.979097e-05), (553, `女用長圍巾 `, 4.4915465e-05), (615, `和服 `, 3.441378e-05), (729, `塑料袋 `, 3.353129e-05), (569, `裘皮大衣 `, 3.0552055e-05), (904, `假髮 `, 2.2152075e-05), (898, `洗衣機 自動洗衣機 `, 2.1341652e-05), (950, `草莓 `, 2.0412743e-05)]

並得到視覺化圖片,如圖12-4:

連載三:PyCon2018|用對抗樣本攻擊PNASNet模型(附原始碼)

圖12-4 模型識別攻擊樣本的結果

從輸出結果和圖12-4的顯示可以看出。模型已經把哈士奇當作了傘。

【程式碼獲取】:關注公眾號:xiangyuejiqiren  公眾號回覆“pycon3

如果覺得本文有用

可以分享給更多小夥伴

連載三:PyCon2018|用對抗樣本攻擊PNASNet模型(附原始碼)

相關文章