連載三: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('\n','')
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_13\model.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模型(附原始碼)


相關文章