连载三:PyCon2018|用对抗样本攻击PNASNet模型(附源码)

第八届中国Python开发者大会PyConChina2018,由PyChina.org发起,由来自CPyUG/TopGeek等社区的30位组织者,近150位志愿者在北京、上海、深圳、杭州、成都等城市举办。致力于推动各类Python相关的技术在互联网、企业应用等领域的研发和应用。
代码医生工作室有幸接受邀请,参加了这次会议的北京站专场。在会上主要分享了《人工智能实战案例分享-图像处理与数值分析》。
会上分享的一些案例主要是来源于《python带我起飞——入门、进阶、商业实战》一书与《深度学习之TensorFlow:入门、原理与进阶实战》一书。另外,还扩充了若干其它案例。在本文作为补充,将会上分享的其它案例以详细的图文方式补充进来,并提供源码。共分为4期连载。
  1. 用slim调用PNASNet模型

  2. 用slim微调PNASNet模型

  3. 用对抗样本攻击PNASNet模型

  4. 恶意域名检测实例

用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:

图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。

如图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:

图12-4 模型识别攻击样本的结果

从输出结果和图12-4的显示可以看出。模型已经把哈士奇当作了伞。


【代码获取】:关注公众号:xiangyuejiqiren  公众号回复“pycon3

如果觉得本文有用

可以分享给更多小伙伴


猜你喜欢

转载自juejin.im/post/5bdfce6151882516eb55e295
今日推荐