7.10 白盒攻击Faster RCNN
7.10 白盒攻击faster rcnn
以faster rcnn模型为例,我们介绍如何基于tensorflow平台对目标检测模型进行白盒攻击,让目前分类的结果产生错误,相应的示例代码位于:
https://github.com/duoergun0729/adversarial_examples/blob/master/code/10-case4-faster-rcnn.ipynb
如图7-53所示,tensorflow实现的faster rcnn,整个计算图的输入是image_tensor,输出结果为检测出的region proposals即detection_boxes,物体分类得分即detection_scores,物体分类的标签值即detection_classes。
faster rcnn是先检测出region proposals,然后再检测出物体分类,整个物体检测阶段还是分为了两个阶段。我们的目标是让物体分类结果产生错误,那么可以在第二阶段进行攻击。如图7-54所示,整个计算图的输入是image_tensor,输出的为:
? rpn_nms_bboxes_和rpn_nms_indices_,表示第一阶段计算图检测出的region proposal。
? patched_input_,表示经过第一阶段计算图处理后的图像数据。
如图7-55所示,计算图的输入tensorarraygatherv3:0表示经过第一阶段处理过的图像数据,reshape_7:0和reshape_8:0表示第一阶段检测出的region proposal。计算图的输出分别为:
? second_stage_cls_scores,表示目标分类的得分。
? second_stage_loc_bboxes,表示经过第二阶段回归调整过的region proposal。
图7-53 faster rcnn的计算图
图7-54 faster rcnn的计算图(第一阶段)
图7-55 faster rcnn的计算图(第二阶段)
假设我们需要欺骗faster rcnn,让它把人识别为马。我们仍然基于迭代优化的思想,首先需要定义好哪些量是可以被优化器调整的,哪些量是输入量,损失函数是什么。由于我们是在第二阶段进行攻击,所以把原始图像作为输入,经过第一阶段计算图的处理,得到新的处理过的图像数据。我们定义扰动量为可以调整的变量,扰动量和处理过的图像数据组成了新的数据,并作为输入进入第二阶段的计算图。与攻击图像分类模型一样,这里的损失函数是计算分类结果和我们的攻击目标标签值之间的交叉熵,并使用优化器根据损失函数不断迭代调整扰动量的值,直到满足最大迭代次数或者损失值足够小,下面我们将介绍代码是如何实现的。图7-56为白盒攻击faster rcnn的流程图。
加载测试图片,并转换成固定大小,见图7-57。
image_path="../picture/objectdetect/adv-case2.jpg"
image = image.open(image_path).convert("rgb")
image=image.resize((320,200))
image_np = load_image_into_numpy_array(image.copy())
如图7-58所示,正常情况下,faster rcnn可以识别图7-57中的人,并使用region proposal标记出人的范围。其中人的分类标签值为1,马的分类标签值为19。
定义第二阶段的输入/输出,定义的输入为self.image_input_和self.patch_placeholder_,其中self.image_input_输入的是第一阶段处理后的图像数据。定义可以被优化器迭代训练调整的参数self.patch_,可以理解为叠加到self.image_input_上的扰动,且形状完全一样。self.patch_placeholder_用于给变量赋值使用。
self.image_input_ = tf.placeholder(tf.float32, shape=self.patch_shape, name='image_input_placeholder')
self.patch_ = tf.get_variable('patch', self.patch_shape, dtype=tf.float32,
initializer=tf.zeros_initializer)
self.patch_placeholder_ = tf.placeholder(dtype=tf.float32, shape=self.patch_shape, name='patch_placeholder')
self.assign_patch_ = tf.assign(self.patch_, self.patch_placeholder_)
图7-56 白盒攻击faster rcnn的流程图
为了训练方便,把self.patch_定义为[–1,1]或者[0,1],因此需要把self.image_input_归一化处理。本例中把两者都假设为[0,1],叠加后的图像数据为self.patched_input_。因为第二阶段的计算图的输入必须在[0,255],需要使用tf.fake_quant_with_min_max_vars进行截断处理。tf.fake_quant_with_min_max_vars函数除了可以进行截断,还可以计算梯度。在使用优化器调整参数时,要求计算图中经过的全部tensor都可以计算梯度。
图7-57 目标检测使用的测试图片
图7-58 faster rcnn针对测试图片标记检测结果
patched_input_ = self.patch_+self.image_input_/255.0
self.patched_input_ = tf.fake_quant_with_min_max_vars(patched_input_*255.0,
min=0, max=255)
定义第二阶段计算图的输入,其中与region proposal相关的代码如下:
# 定义输入,便于在tf中给预测框输入数据
self.rpn_nms_bboxes_placeholder_ = tf.placeholder(tf.float32, shape=(none, 4),
name='rpn_nms_bboxes')
self.rpn_nms_indices_placeholder_ = tf.placeholder(tf.int32, shape=(none),
name='rpn_nms_indices')
加载模型文件成计算图,并且将输入tensor进行映射替换,其中self.patched_input_为第二阶段计算图的输入。
def create_graph(dirname):
with tf.gfile.fastgfile(dirname, 'rb') as f:
graph_def = self.sess.graph_def
graph_def.parsefromstring(f.read())
_ = tf.import_graph_def(graph_def, name='adv_model',
input_map={
'preprocessor/map/tensorarraystack/tensorarraygatherv3:0':
self.patched_input_,
'reshape_7:0':self.rpn_nms_bboxes_placeholder_,
'reshape_8:0':self.rpn_nms_indices_placeholder_}
)
create_graph(path_to_frozen_graph)
本质上我们攻击的还是图像分类模型,因此在定义损失函数时,还是计算图像预测值和定向攻击的标签值之间的交叉熵。
# 计算第二阶段的分类损失函数
self.second_stage_cls_scores_ =
self.graph.get_tensor_by_name('adv_model/secondstagepostprocessor/convert_
scores:0')
second_stage_cls_logits_ =
self.graph.get_tensor_by_name('adv_model/secondstagepostprocessor/scale_
logits:0')
self.second_stage_cls_labels_ = tf.placeholder(tf.float32,
shape=second_stage_cls_logits_.shape, name='second_stage_cls_labels')
其中self.second_stage_cls_labels_表示我们期望的标签值,second_stage_cls_logits_表示目标分类的结果,损失函数是计算两者之间的交叉熵,如图7-59所示。
图7-59 损失函数计算方法
定义了损失函数,就可以进一步定义优化器以及需要迭代训练调整的参数self.patch_。由于tf.train.adamoptimizer在训练过程中也需要使用变量,因此需要调用全局的初始化函数tf.global_variables_initializer。
# 加权后的损失函数的总和
self.loss_ = self.second_stage_cls_loss_
self.train_op_ = tf.train.adamoptimizer(0.01).minimize(self.loss_,
var_list=[self.patch_])
# 初始化参数,adam的参数也需要这样初始化,gradientdescent可以省略这一步
self.sess.run(tf.global_variables_initializer())
使用第一阶段的计算图,输入原始图像数据得到region proposal相关的self.rpn_nms_bboxes_和self.rpn_nms_indices_。
def inference_rpn(self, images):
feed_dict = { self.image_input_: images }
tensors = [self.rpn_nms_bboxes_,
self.rpn_nms_indices_ ]
rpn_nms_bboxes, rpn_nms_indices = self.sess.run(tensors, feed_dict)
return rpn_nms_bboxes, rpn_nms_indices
定义迭代优化的过程,输入是region proposal相关的self.rpn_nms_bboxes_和self.rpn_nms_indices_,原始图像和定向攻击的目标标签second_stage_cls_labels。
rpn_nms_bboxes, rpn_nms_indices = self.inference_rpn(images)
feed_dict = {
self.image_input_: images,
self.second_stage_cls_labels_: second_stage_cls_labels,
self.rpn_nms_bboxes_placeholder_: rpn_nms_bboxes,
self.rpn_nms_indices_placeholder_: rpn_nms_indices, }
tensors = [ self.train_op_,self.loss_]
train_op, loss= self.sess.run(tensors, feed_dict)
定义目标标签,相对于一般的图像分类问题,目标检测模型的目标标签定义会复杂一些。scores代表是模型预测的region proposal的得分,目前faster rcnn会返回300个region proposal,每个region proposal会得到在91个物体分类上的得分。我们希望识别为人的region proposal最终识别为马,也就是要把原来识别为标签id为1的识别为19,那么需要先遍历scores,找到识别为人的得分最高的region proposal,把该region proposal其他分类的得分设置为0,马的得分设置为1。
target_class = 19
from_class = 1
def create_target_labels(scores, from_class, to_class):
target_labels = np.zeros_like(scores)
classes = np.argmax(scores[:, :, 1:], axis=2)+1
for i, _ in enumerate(classes):
for j, cls in enumerate(classes[i]):
cls = to_class
target_labels[i, j, cls] = 1
return target_labels
迭代优化,最大迭代次数为300,通过优化器在反向传递中不断调整输入图像,最终得到图7-60,把人识别为马。细心的读者会发现,背景上有比较明显的扰动,事实上扰动默认会在整个图片上发生,从背景到需要攻击的物体。
#开始迭代求解
epochs=300
for epoch in range(epochs):
loss = adv_model_instance.train_step(
np.expand_dims(image_np.copy(),0),target_labels)
#显示中间结果
if (epoch % 50) == 0:
print("epoch={} loss={}".format(epoch,loss))
adv_model_instance.inference_draw(np.expand_dims(image_np.copy(),0))
图7-60 faster rcnn把图片中的人识别为马
以faster rcnn模型为例,我们介绍如何基于tensorflow平台对目标检测模型进行白盒攻击,让目前分类的结果产生错误,相应的示例代码位于:
https://github.com/duoergun0729/adversarial_examples/blob/master/code/10-case4-faster-rcnn.ipynb
如图7-53所示,tensorflow实现的faster rcnn,整个计算图的输入是image_tensor,输出结果为检测出的region proposals即detection_boxes,物体分类得分即detection_scores,物体分类的标签值即detection_classes。
faster rcnn是先检测出region proposals,然后再检测出物体分类,整个物体检测阶段还是分为了两个阶段。我们的目标是让物体分类结果产生错误,那么可以在第二阶段进行攻击。如图7-54所示,整个计算图的输入是image_tensor,输出的为:
? rpn_nms_bboxes_和rpn_nms_indices_,表示第一阶段计算图检测出的region proposal。
? patched_input_,表示经过第一阶段计算图处理后的图像数据。
如图7-55所示,计算图的输入tensorarraygatherv3:0表示经过第一阶段处理过的图像数据,reshape_7:0和reshape_8:0表示第一阶段检测出的region proposal。计算图的输出分别为:
? second_stage_cls_scores,表示目标分类的得分。
? second_stage_loc_bboxes,表示经过第二阶段回归调整过的region proposal。
图7-53 faster rcnn的计算图
图7-54 faster rcnn的计算图(第一阶段)
图7-55 faster rcnn的计算图(第二阶段)
假设我们需要欺骗faster rcnn,让它把人识别为马。我们仍然基于迭代优化的思想,首先需要定义好哪些量是可以被优化器调整的,哪些量是输入量,损失函数是什么。由于我们是在第二阶段进行攻击,所以把原始图像作为输入,经过第一阶段计算图的处理,得到新的处理过的图像数据。我们定义扰动量为可以调整的变量,扰动量和处理过的图像数据组成了新的数据,并作为输入进入第二阶段的计算图。与攻击图像分类模型一样,这里的损失函数是计算分类结果和我们的攻击目标标签值之间的交叉熵,并使用优化器根据损失函数不断迭代调整扰动量的值,直到满足最大迭代次数或者损失值足够小,下面我们将介绍代码是如何实现的。图7-56为白盒攻击faster rcnn的流程图。
加载测试图片,并转换成固定大小,见图7-57。
image_path="../picture/objectdetect/adv-case2.jpg"
image = image.open(image_path).convert("rgb")
image=image.resize((320,200))
image_np = load_image_into_numpy_array(image.copy())
如图7-58所示,正常情况下,faster rcnn可以识别图7-57中的人,并使用region proposal标记出人的范围。其中人的分类标签值为1,马的分类标签值为19。
定义第二阶段的输入/输出,定义的输入为self.image_input_和self.patch_placeholder_,其中self.image_input_输入的是第一阶段处理后的图像数据。定义可以被优化器迭代训练调整的参数self.patch_,可以理解为叠加到self.image_input_上的扰动,且形状完全一样。self.patch_placeholder_用于给变量赋值使用。
self.image_input_ = tf.placeholder(tf.float32, shape=self.patch_shape, name='image_input_placeholder')
self.patch_ = tf.get_variable('patch', self.patch_shape, dtype=tf.float32,
initializer=tf.zeros_initializer)
self.patch_placeholder_ = tf.placeholder(dtype=tf.float32, shape=self.patch_shape, name='patch_placeholder')
self.assign_patch_ = tf.assign(self.patch_, self.patch_placeholder_)
图7-56 白盒攻击faster rcnn的流程图
为了训练方便,把self.patch_定义为[–1,1]或者[0,1],因此需要把self.image_input_归一化处理。本例中把两者都假设为[0,1],叠加后的图像数据为self.patched_input_。因为第二阶段的计算图的输入必须在[0,255],需要使用tf.fake_quant_with_min_max_vars进行截断处理。tf.fake_quant_with_min_max_vars函数除了可以进行截断,还可以计算梯度。在使用优化器调整参数时,要求计算图中经过的全部tensor都可以计算梯度。
图7-57 目标检测使用的测试图片
图7-58 faster rcnn针对测试图片标记检测结果
patched_input_ = self.patch_+self.image_input_/255.0
self.patched_input_ = tf.fake_quant_with_min_max_vars(patched_input_*255.0,
min=0, max=255)
定义第二阶段计算图的输入,其中与region proposal相关的代码如下:
# 定义输入,便于在tf中给预测框输入数据
self.rpn_nms_bboxes_placeholder_ = tf.placeholder(tf.float32, shape=(none, 4),
name='rpn_nms_bboxes')
self.rpn_nms_indices_placeholder_ = tf.placeholder(tf.int32, shape=(none),
name='rpn_nms_indices')
加载模型文件成计算图,并且将输入tensor进行映射替换,其中self.patched_input_为第二阶段计算图的输入。
def create_graph(dirname):
with tf.gfile.fastgfile(dirname, 'rb') as f:
graph_def = self.sess.graph_def
graph_def.parsefromstring(f.read())
_ = tf.import_graph_def(graph_def, name='adv_model',
input_map={
'preprocessor/map/tensorarraystack/tensorarraygatherv3:0':
self.patched_input_,
'reshape_7:0':self.rpn_nms_bboxes_placeholder_,
'reshape_8:0':self.rpn_nms_indices_placeholder_}
)
create_graph(path_to_frozen_graph)
本质上我们攻击的还是图像分类模型,因此在定义损失函数时,还是计算图像预测值和定向攻击的标签值之间的交叉熵。
# 计算第二阶段的分类损失函数
self.second_stage_cls_scores_ =
self.graph.get_tensor_by_name('adv_model/secondstagepostprocessor/convert_
scores:0')
second_stage_cls_logits_ =
self.graph.get_tensor_by_name('adv_model/secondstagepostprocessor/scale_
logits:0')
self.second_stage_cls_labels_ = tf.placeholder(tf.float32,
shape=second_stage_cls_logits_.shape, name='second_stage_cls_labels')
其中self.second_stage_cls_labels_表示我们期望的标签值,second_stage_cls_logits_表示目标分类的结果,损失函数是计算两者之间的交叉熵,如图7-59所示。
图7-59 损失函数计算方法
定义了损失函数,就可以进一步定义优化器以及需要迭代训练调整的参数self.patch_。由于tf.train.adamoptimizer在训练过程中也需要使用变量,因此需要调用全局的初始化函数tf.global_variables_initializer。
# 加权后的损失函数的总和
self.loss_ = self.second_stage_cls_loss_
self.train_op_ = tf.train.adamoptimizer(0.01).minimize(self.loss_,
var_list=[self.patch_])
# 初始化参数,adam的参数也需要这样初始化,gradientdescent可以省略这一步
self.sess.run(tf.global_variables_initializer())
使用第一阶段的计算图,输入原始图像数据得到region proposal相关的self.rpn_nms_bboxes_和self.rpn_nms_indices_。
def inference_rpn(self, images):
feed_dict = { self.image_input_: images }
tensors = [self.rpn_nms_bboxes_,
self.rpn_nms_indices_ ]
rpn_nms_bboxes, rpn_nms_indices = self.sess.run(tensors, feed_dict)
return rpn_nms_bboxes, rpn_nms_indices
定义迭代优化的过程,输入是region proposal相关的self.rpn_nms_bboxes_和self.rpn_nms_indices_,原始图像和定向攻击的目标标签second_stage_cls_labels。
rpn_nms_bboxes, rpn_nms_indices = self.inference_rpn(images)
feed_dict = {
self.image_input_: images,
self.second_stage_cls_labels_: second_stage_cls_labels,
self.rpn_nms_bboxes_placeholder_: rpn_nms_bboxes,
self.rpn_nms_indices_placeholder_: rpn_nms_indices, }
tensors = [ self.train_op_,self.loss_]
train_op, loss= self.sess.run(tensors, feed_dict)
定义目标标签,相对于一般的图像分类问题,目标检测模型的目标标签定义会复杂一些。scores代表是模型预测的region proposal的得分,目前faster rcnn会返回300个region proposal,每个region proposal会得到在91个物体分类上的得分。我们希望识别为人的region proposal最终识别为马,也就是要把原来识别为标签id为1的识别为19,那么需要先遍历scores,找到识别为人的得分最高的region proposal,把该region proposal其他分类的得分设置为0,马的得分设置为1。
target_class = 19
from_class = 1
def create_target_labels(scores, from_class, to_class):
target_labels = np.zeros_like(scores)
classes = np.argmax(scores[:, :, 1:], axis=2)+1
for i, _ in enumerate(classes):
for j, cls in enumerate(classes[i]):
cls = to_class
target_labels[i, j, cls] = 1
return target_labels
迭代优化,最大迭代次数为300,通过优化器在反向传递中不断调整输入图像,最终得到图7-60,把人识别为马。细心的读者会发现,背景上有比较明显的扰动,事实上扰动默认会在整个图片上发生,从背景到需要攻击的物体。
#开始迭代求解
epochs=300
for epoch in range(epochs):
loss = adv_model_instance.train_step(
np.expand_dims(image_np.copy(),0),target_labels)
#显示中间结果
if (epoch % 50) == 0:
print("epoch={} loss={}".format(epoch,loss))
adv_model_instance.inference_draw(np.expand_dims(image_np.copy(),0))
图7-60 faster rcnn把图片中的人识别为马