8.1.1 图像旋转对鲁棒性的影响
8.1.1 图像旋转对鲁棒性的影响
下面我们结合实际的例子来介绍旋转对鲁棒性的影响,对应的代码路径为:
https://github.com/duoergun0729/adversarial_examples/blob/master/code/8-
case1.ipynb
#图像加载以及预处理
image_path="../picture/left.jpg"
img = cv2.imread(image_path)[..., ::-1]
img = cv2.resize(img, (rows,cols))
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),90,1)
img1 = cv2.warpaffine(img,matrix,(cols,rows))
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),180,1)
img2 = cv2.warpaffine(img,matrix,(cols,rows))
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),270,1)
img3 = cv2.warpaffine(img,matrix,(cols,rows))
如图8-1所示,直观的感觉,当图像发生旋转时,大多数情况下不会影响人对物体分类的结果。除了少数的角度/方向敏感的情况,如图8-2所示,原始图像为向左,图像分别逆时针旋转90度、180度、270度后,人对该图像会分别识别为向下、向右和向上。
图8-1 图像发生旋转不影响分类
图8-2 图像发生旋转影响分类
我们以经典的熊猫图像来验证我们的想法。首先我们定义一个工具函数infer_img,使用的预测模型是pytorch框架下基于imagenet2012数据集训练的alexnet模型。infer_img的输入为图像数据img以及标签t。t默认为0,表示返回预测结果中概率最大的分类标签的概率,当t不为0时,返回指定的分类标签的概率。需要指出的是,pytorch框架下的imagenet2012数据集,图像数据的标准化比较特殊,需要使用特定的均值mean和标准差std:
def infer_img(img,t=0):
#标准化
img=img.astype(np.float32)
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
img /= 255.0
img = (img - mean) / std
img = img.transpose(2, 0, 1)
img=np.expand_dims(img, axis=0)
img = variable(torch.from_numpy(img).to(device).float())
#使用预测模式主要影响dropout和bn层的行为
model = models.alexnet(pretrained=true).to(device).eval()
output=f.softmax(model(img),dim=1)
label=np.argmax(output.data.cpu().numpy())
pro=output.data.cpu().numpy()[0][label]
#当t不为0时返回指定类别的概率
if t != 0:
pro=output.data.cpu().numpy()[0][t]
return pro
我们对原始的熊猫图片进行预测。
print(infer_img(orig))
print(infer_img(orig,t=288))
print(infer_img(orig,t=388))
预测结果为,分类为标签388(熊猫对应的分类标签)的概率为92.71%,分类为标签288的概率非常低。
0.9270878
3.1756701e-06
0.9270878
下面我们把原始图像逐渐旋转至180度,每次旋转的角度增加10度,并记录下旋转角度和预测为熊猫对应标签的概率。旋转的方法使用了4.2.1节的仿射变化。
#验证原始图片的旋转不变性
rotate_range = range(0,180,10)
original_pro = []
for i in rotate_range:
#构造仿射变换矩阵
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),i,1)
#进行仿射变化
rotate_img = cv2.warpaffine(orig.copy(),matrix,(cols,rows))
#获得预测为熊猫的概率值
pro=infer_img(rotate_img.copy(),388)
print("rotate={} pro[388]={}".format(i,pro))
original_pro += [pro]
#绘图,横轴代表旋转的角度,纵轴代表预测为熊猫对应标签的概率
fig, ax = plt.subplots()
ax.plot(np.array(rotate_range), np.array(original_pro), 'b--',
label='probability of class 388')
legend = ax.legend(loc='upper center', shadow=true, fontsize='large')
legend.get_frame().set_facecolor('#ffffff')
plt.xlabel('rotate range')
plt.ylabel('probability')
plt.show()
如图8-3所示,横轴代表旋转的角度,纵轴代表预测为熊猫对应标签的概率。我们假设分类概率大于50%表示不影响分类结果,可见当旋转角度小于60度或者在90度和110度之间时不影响分类结果。如果以更加严格的要求,认为分类概率大于80%表示不影响分类结果,那么可以认为旋转30度以内不影响分类结果。通过本例可以看出,图像分类模型对于图像旋转具有一定的鲁棒性,当旋转角度较小时对分类结果影响不大。事实上在进行模型训练时,为了增加训练数据量,会人为地把训练数据随机旋转一定的角度,这一过程也叫作数据增强。大量的实验证明,图像预测阶段旋转的角度如果在数据增强的范围内,不会影响分类结果。
图8-3 图像分类模型对图像旋转的鲁棒性示例
我们针对熊猫图像,使用fgsm算法生成定向攻击的对抗样本,定向攻击的目标标签为288,并且概率大于一定的阈值才认为攻击成功。
epochs=100
e=0.007
target=288
target=variable(torch.tensor([float(target)]).to(device).long())
for epoch in range(epochs):
# forward + backward
output = f.softmax(model(img),dim=1)
loss = loss_func(output, target)
label=np.argmax(output.data.cpu().numpy())
pro=output.data.cpu().numpy()[0][label]
print("epoch={} loss={} label={} pro={}".format(epoch,loss,label,pro))
#如果定向攻击成功并且概率大于阈值
if (label == target) and ( pro > 0.80):
print("")
break
#梯度清零
optimizer.zero_grad()
#反向传递,计算梯度
loss.backward()
img.data=img.data-e*torch.sign(img.grad.data)
为了后继图像旋转处理方便,需要把归一化的对抗样本还原成正常图片格式。对抗样本的形状为[3,224,224],需要转换为[224,224,3]:
#把对抗样本转换成正常图片格式
adv=img.data.cpu().numpy()[0]
print(adv.shape)
adv = adv.transpose(1, 2, 0)
adv = (adv * std) + mean
adv = adv * 255.0
adv = np.clip(adv, 0, 255).astype(np.uint8)
print(adv.shape)
下面我们把对抗样本逐渐旋转至180度,每次旋转的角度增加10度,并记录下旋转角度和预测为熊猫和定向攻击目标对应标签的概率。
#验证旋转对于对抗样本的影响
rotate_range = range(0,180,10)
adv_288_pro = []
adv_388_pro = []
for i in rotate_range:
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),i,1)
rotate_img = cv2.warpaffine(adv.copy(),matrix,(cols,rows))
#记录熊猫的概率
pro_388=infer_img(rotate_img.copy(),388)
#记录定向攻击目标的概率
pro_288=infer_img(rotate_img.copy(),288)
print("rotate={} pro[388]={} pro[288]={}".format(i,pro_388,pro_288))
adv_288_pro += [pro_288]
adv_388_pro += [pro_388]
fig, ax = plt.subplots()
ax.plot(np.array(rotate_range), np.array(adv_288_pro), 'b--',
label='probability of class 288')
ax.plot(np.array(rotate_range), np.array(adv_388_pro), 'r',
label='probability of class 388')
legend = ax.legend(loc='upper center', shadow=true, fontsize='large')
legend.get_frame().set_facecolor('#ffffff')
plt.xlabel('rotate range')
plt.ylabel('probability')
plt.show()
如图8-4所示,横轴代表旋转的角度,纵轴代表预测概率,实线代表预测为熊猫的概率,虚线代表预测为定向攻击目标的概率。我们假设分类概率大于50%的预测结果可信,那么可以认为当旋转角度小于5度时,该定向攻击的对抗样本有效,当角度大于10度时,定向攻击失效。如果以更加严格的要求,认为分类概率大于80%的结果可信,那么可以认为当旋转角度在30度到60度之间时,原有模型可以有效抵御定向攻击样本。
图8-4 对抗样本对图像旋转的鲁棒性示例
那么是否存在一种方式,把图像旋转一定角度后,既不影响对原始图像的分类,又可以抵御对抗样本的影响呢?下面我们把对抗样本和原始图片逐渐旋转至180度,每次旋转的角度增加10度,并记录下旋转角度和预测为熊猫的概率。
#综合分析验证旋转对于对抗样本和正常图片分类的影响
rotate_range = range(0,180,10)
original_pro = []
adv_pro = []
for i in rotate_range:
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),i,1)
rotate_adv_img = cv2.warpaffine(adv.copy(),matrix,(cols,rows))
pro_388=infer_img(rotate_adv_img.copy(),388)
adv_pro+= [pro_388]
rotate_img = cv2.warpaffine(orig.copy(),matrix,(cols,rows))
pro=infer_img(rotate_img.copy(),388)
original_pro += [pro]
print("rotate={} adv_pro[388]={}
original_pro[388]={}".format(i,pro_388,pro))
fig, ax = plt.subplots()
ax.plot(np.array(rotate_range), np.array(adv_pro), 'b--',
label='probability of adversarial')
ax.plot(np.array(rotate_range), np.array(original_pro), 'r',
label='probability of original')
legend = ax.legend(loc='upper center', shadow=true, fontsize='large')
legend.get_frame().set_facecolor('#ffffff')
plt.xlabel('rotate range')
plt.ylabel('probability')
plt.show()
如图8-5所示,横轴代表旋转的角度,纵轴代表预测概率,实线代表原始图像预测为熊猫的概率,虚线代表对抗样本预测为熊猫的概率。我们假设分类概率大于80%的结果可信,那么可以认为当旋转角度在30度到60度之间时,原有模型可以有效抵御定向攻击样本,同时不影响正常图片分类。
图8-5 图像旋转对原始图像和对抗样本的鲁棒性示例
下面我们结合实际的例子来介绍旋转对鲁棒性的影响,对应的代码路径为:
https://github.com/duoergun0729/adversarial_examples/blob/master/code/8-
case1.ipynb
#图像加载以及预处理
image_path="../picture/left.jpg"
img = cv2.imread(image_path)[..., ::-1]
img = cv2.resize(img, (rows,cols))
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),90,1)
img1 = cv2.warpaffine(img,matrix,(cols,rows))
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),180,1)
img2 = cv2.warpaffine(img,matrix,(cols,rows))
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),270,1)
img3 = cv2.warpaffine(img,matrix,(cols,rows))
如图8-1所示,直观的感觉,当图像发生旋转时,大多数情况下不会影响人对物体分类的结果。除了少数的角度/方向敏感的情况,如图8-2所示,原始图像为向左,图像分别逆时针旋转90度、180度、270度后,人对该图像会分别识别为向下、向右和向上。
图8-1 图像发生旋转不影响分类
图8-2 图像发生旋转影响分类
我们以经典的熊猫图像来验证我们的想法。首先我们定义一个工具函数infer_img,使用的预测模型是pytorch框架下基于imagenet2012数据集训练的alexnet模型。infer_img的输入为图像数据img以及标签t。t默认为0,表示返回预测结果中概率最大的分类标签的概率,当t不为0时,返回指定的分类标签的概率。需要指出的是,pytorch框架下的imagenet2012数据集,图像数据的标准化比较特殊,需要使用特定的均值mean和标准差std:
def infer_img(img,t=0):
#标准化
img=img.astype(np.float32)
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
img /= 255.0
img = (img - mean) / std
img = img.transpose(2, 0, 1)
img=np.expand_dims(img, axis=0)
img = variable(torch.from_numpy(img).to(device).float())
#使用预测模式主要影响dropout和bn层的行为
model = models.alexnet(pretrained=true).to(device).eval()
output=f.softmax(model(img),dim=1)
label=np.argmax(output.data.cpu().numpy())
pro=output.data.cpu().numpy()[0][label]
#当t不为0时返回指定类别的概率
if t != 0:
pro=output.data.cpu().numpy()[0][t]
return pro
我们对原始的熊猫图片进行预测。
print(infer_img(orig))
print(infer_img(orig,t=288))
print(infer_img(orig,t=388))
预测结果为,分类为标签388(熊猫对应的分类标签)的概率为92.71%,分类为标签288的概率非常低。
0.9270878
3.1756701e-06
0.9270878
下面我们把原始图像逐渐旋转至180度,每次旋转的角度增加10度,并记录下旋转角度和预测为熊猫对应标签的概率。旋转的方法使用了4.2.1节的仿射变化。
#验证原始图片的旋转不变性
rotate_range = range(0,180,10)
original_pro = []
for i in rotate_range:
#构造仿射变换矩阵
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),i,1)
#进行仿射变化
rotate_img = cv2.warpaffine(orig.copy(),matrix,(cols,rows))
#获得预测为熊猫的概率值
pro=infer_img(rotate_img.copy(),388)
print("rotate={} pro[388]={}".format(i,pro))
original_pro += [pro]
#绘图,横轴代表旋转的角度,纵轴代表预测为熊猫对应标签的概率
fig, ax = plt.subplots()
ax.plot(np.array(rotate_range), np.array(original_pro), 'b--',
label='probability of class 388')
legend = ax.legend(loc='upper center', shadow=true, fontsize='large')
legend.get_frame().set_facecolor('#ffffff')
plt.xlabel('rotate range')
plt.ylabel('probability')
plt.show()
如图8-3所示,横轴代表旋转的角度,纵轴代表预测为熊猫对应标签的概率。我们假设分类概率大于50%表示不影响分类结果,可见当旋转角度小于60度或者在90度和110度之间时不影响分类结果。如果以更加严格的要求,认为分类概率大于80%表示不影响分类结果,那么可以认为旋转30度以内不影响分类结果。通过本例可以看出,图像分类模型对于图像旋转具有一定的鲁棒性,当旋转角度较小时对分类结果影响不大。事实上在进行模型训练时,为了增加训练数据量,会人为地把训练数据随机旋转一定的角度,这一过程也叫作数据增强。大量的实验证明,图像预测阶段旋转的角度如果在数据增强的范围内,不会影响分类结果。
图8-3 图像分类模型对图像旋转的鲁棒性示例
我们针对熊猫图像,使用fgsm算法生成定向攻击的对抗样本,定向攻击的目标标签为288,并且概率大于一定的阈值才认为攻击成功。
epochs=100
e=0.007
target=288
target=variable(torch.tensor([float(target)]).to(device).long())
for epoch in range(epochs):
# forward + backward
output = f.softmax(model(img),dim=1)
loss = loss_func(output, target)
label=np.argmax(output.data.cpu().numpy())
pro=output.data.cpu().numpy()[0][label]
print("epoch={} loss={} label={} pro={}".format(epoch,loss,label,pro))
#如果定向攻击成功并且概率大于阈值
if (label == target) and ( pro > 0.80):
print("")
break
#梯度清零
optimizer.zero_grad()
#反向传递,计算梯度
loss.backward()
img.data=img.data-e*torch.sign(img.grad.data)
为了后继图像旋转处理方便,需要把归一化的对抗样本还原成正常图片格式。对抗样本的形状为[3,224,224],需要转换为[224,224,3]:
#把对抗样本转换成正常图片格式
adv=img.data.cpu().numpy()[0]
print(adv.shape)
adv = adv.transpose(1, 2, 0)
adv = (adv * std) + mean
adv = adv * 255.0
adv = np.clip(adv, 0, 255).astype(np.uint8)
print(adv.shape)
下面我们把对抗样本逐渐旋转至180度,每次旋转的角度增加10度,并记录下旋转角度和预测为熊猫和定向攻击目标对应标签的概率。
#验证旋转对于对抗样本的影响
rotate_range = range(0,180,10)
adv_288_pro = []
adv_388_pro = []
for i in rotate_range:
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),i,1)
rotate_img = cv2.warpaffine(adv.copy(),matrix,(cols,rows))
#记录熊猫的概率
pro_388=infer_img(rotate_img.copy(),388)
#记录定向攻击目标的概率
pro_288=infer_img(rotate_img.copy(),288)
print("rotate={} pro[388]={} pro[288]={}".format(i,pro_388,pro_288))
adv_288_pro += [pro_288]
adv_388_pro += [pro_388]
fig, ax = plt.subplots()
ax.plot(np.array(rotate_range), np.array(adv_288_pro), 'b--',
label='probability of class 288')
ax.plot(np.array(rotate_range), np.array(adv_388_pro), 'r',
label='probability of class 388')
legend = ax.legend(loc='upper center', shadow=true, fontsize='large')
legend.get_frame().set_facecolor('#ffffff')
plt.xlabel('rotate range')
plt.ylabel('probability')
plt.show()
如图8-4所示,横轴代表旋转的角度,纵轴代表预测概率,实线代表预测为熊猫的概率,虚线代表预测为定向攻击目标的概率。我们假设分类概率大于50%的预测结果可信,那么可以认为当旋转角度小于5度时,该定向攻击的对抗样本有效,当角度大于10度时,定向攻击失效。如果以更加严格的要求,认为分类概率大于80%的结果可信,那么可以认为当旋转角度在30度到60度之间时,原有模型可以有效抵御定向攻击样本。
图8-4 对抗样本对图像旋转的鲁棒性示例
那么是否存在一种方式,把图像旋转一定角度后,既不影响对原始图像的分类,又可以抵御对抗样本的影响呢?下面我们把对抗样本和原始图片逐渐旋转至180度,每次旋转的角度增加10度,并记录下旋转角度和预测为熊猫的概率。
#综合分析验证旋转对于对抗样本和正常图片分类的影响
rotate_range = range(0,180,10)
original_pro = []
adv_pro = []
for i in rotate_range:
matrix = cv2.getrotationmatrix2d((cols/2,rows/2),i,1)
rotate_adv_img = cv2.warpaffine(adv.copy(),matrix,(cols,rows))
pro_388=infer_img(rotate_adv_img.copy(),388)
adv_pro+= [pro_388]
rotate_img = cv2.warpaffine(orig.copy(),matrix,(cols,rows))
pro=infer_img(rotate_img.copy(),388)
original_pro += [pro]
print("rotate={} adv_pro[388]={}
original_pro[388]={}".format(i,pro_388,pro))
fig, ax = plt.subplots()
ax.plot(np.array(rotate_range), np.array(adv_pro), 'b--',
label='probability of adversarial')
ax.plot(np.array(rotate_range), np.array(original_pro), 'r',
label='probability of original')
legend = ax.legend(loc='upper center', shadow=true, fontsize='large')
legend.get_frame().set_facecolor('#ffffff')
plt.xlabel('rotate range')
plt.ylabel('probability')
plt.show()
如图8-5所示,横轴代表旋转的角度,纵轴代表预测概率,实线代表原始图像预测为熊猫的概率,虚线代表对抗样本预测为熊猫的概率。我们假设分类概率大于80%的结果可信,那么可以认为当旋转角度在30度到60度之间时,原有模型可以有效抵御定向攻击样本,同时不影响正常图片分类。
图8-5 图像旋转对原始图像和对抗样本的鲁棒性示例