FGSM代码实践
淺談FGSM
FGSM是什么?從機(jī)器學(xué)習(xí)到深度學(xué)習(xí),第一次聽這個(gè)名詞,后來查閱資料了解到是一種圖像的攻擊方法。本來有一個(gè)模型可以識別出你的圖片內(nèi)容,你把一張小狗的圖片喂給模型,模型告訴你是狗,把貓喂給模型,模型告訴你是貓。當(dāng)你給這張小狗的圖片添加上噪聲之后(肉眼無法識別有沒有加噪聲),再次喂給模型,模型告訴你是這是其他東西,從而達(dá)到了欺騙模型的目的。
FGSM原理
一種基于梯度生成對抗樣本的算法,屬于對抗攻擊中的無目標(biāo)攻擊(即不要求對抗樣本經(jīng)過model預(yù)測指定的類別,只要與原樣本預(yù)測的不一樣即可)
? ? ? ? ? ? ?圖1? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖二(噪聲)? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖三(對抗樣本)
把圖一喂給模型,模型告訴你57.7%的概率是熊貓,別看概率小,機(jī)器總會輸出概率值最大的那個(gè)結(jié)果。隨后把圖二加入圖一中,生成一張新的圖三,把圖三喂給模型,模型告訴你99.3%是金絲猴,但肉眼看依然是熊貓,這就是FGSM攻擊方法
如果要看具體公式什么的跳轉(zhuǎn)這個(gè)鏈接,這些符號對我很不友好,但代碼能看懂就行https://blog.csdn.net/qq_35414569
FGSM代碼實(shí)戰(zhàn)
?1、pytorch導(dǎo)入各種包,預(yù)先設(shè)置一個(gè)擾動值e,再把設(shè)備選擇好,有錢GPU,沒錢CPU,此處實(shí)現(xiàn)的是無目標(biāo)攻擊。
from __future__ import print_function import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torchvision import datasets, transforms import numpy as np import matplotlib.pyplot as plt from torchvision import models import cv2 from torch.autograd import Variable e=0.5#擾動值 #獲取計(jì)算設(shè)備 默認(rèn)是CPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu")2、圖像的加載以及預(yù)處理,喂給模型的圖片還是挺講究的,要考慮圖片通道數(shù)(RGB),尺寸,圖片類型,不然模型不吃。
image_path="data/goldfish.jpg" orig = cv2.imread(image_path)[..., ::-1] orig = cv2.resize(orig, (224, 224)) img = orig.copy().astype(np.float32) #使用Imagenet的均值和標(biāo)準(zhǔn)差是一種常見的做法。它們是根據(jù)數(shù)百萬張圖像計(jì)算得出的。如果要在自己的數(shù)據(jù)集上從頭開始訓(xùn)練,則可以計(jì)算新的均值和標(biāo)準(zhǔn)差,這是一種經(jīng)驗(yàn)值 mean = [0.485, 0.456, 0.406] std = [0.229, 0.224, 0.225] #歸一化,為什么進(jìn)行歸一化,因?yàn)橛?xùn)練效果好 img /= 255.0 img = (img - mean) / std #把HWC的圖片轉(zhuǎn)為CHW的圖片 img = img.transpose(2, 0, 1)img=np.expand_dims(img, axis=0)img = Variable(torch.from_numpy(img).to(device).float()) print(img.shape)3、定義模型,優(yōu)化器、損失等等參數(shù)
#使用預(yù)測模式 主要影響droupout和BN層的行為 model = models.alexnet(pretrained=True).to(device).eval() #取真實(shí)標(biāo)簽 label=np.argmax(model(img).data.cpu().numpy())#這里為什么要加cup()?因?yàn)閚p無法直接轉(zhuǎn)為cuda使用,要先轉(zhuǎn)cpu print("label={}".format(label)) # 圖像數(shù)據(jù)梯度可以獲取 img.requires_grad = True# 設(shè)置為不保存梯度值 自然也無法修改 for param in model.parameters():param.requires_grad = Falseoptimizer = torch.optim.Adam([img])#優(yōu)化器 loss_func = torch.nn.CrossEntropyLoss()#交叉熵計(jì)算損失epochs = 10#訓(xùn)練輪次 target = 31#原始圖片的標(biāo)簽 target = Variable(torch.Tensor([float(target)]).to(device).long())#轉(zhuǎn)換數(shù)據(jù)類型 print(target)4、定義fgsm攻擊函數(shù)
def fgsm_attack(image, epsilon, data_grad):# 使用sign(符號)函數(shù),將對x求了偏導(dǎo)的梯度進(jìn)行符號化sign_data_grad = data_grad.sign()# 通過epsilon生成對抗樣本perturbed_image = image + epsilon*sign_data_grad#噪聲越來越大,機(jī)器越來越難以識別,但人眼可以看出差別# 做一個(gè)剪裁的工作,將torch.clamp內(nèi)部大于1的數(shù)值變?yōu)?,小于0的數(shù)值等于0,防止image越界perturbed_image = torch.clamp(perturbed_image, 0, 1)# 返回對抗樣本return perturbed_image5、簡單進(jìn)行訓(xùn)練就達(dá)到了目的,原本標(biāo)簽為31,最后預(yù)測的結(jié)果不為31,收斂于610,loss也收斂,這里是純數(shù)據(jù)對比,沒有可視化,可視化代碼還在碼
for epoch in range(epochs):# forward + backwardoutput = model(img)loss = loss_func(output, target)label = np.argmax(output.data.cpu().numpy())print("epoch={} loss={} label={}".format(epoch, loss, label))# 梯度清零optimizer.zero_grad()# 反向傳遞 計(jì)算梯度loss.backward()img.data = fgsm_attack(img,e,img.data)總結(jié)
代碼可直接在pycharm里運(yùn)行,前提是有pytorch運(yùn)行環(huán)境,如有問題可一起探討交流,哪里有錯誤也請指出,虛心接收,AI小白一枚。
2021年10月9日續(xù)可視化代碼
更新一下可視化代碼,可直接運(yùn)行,報(bào)錯找我,此處實(shí)現(xiàn)的是定向攻擊,注釋都標(biāo)的挺清楚了
import matplotlib.pyplot as plt import torch import torchvision from torchvision import datasets, transforms from torch.autograd import Variable import torch.utils.data.dataloader as Data import torch.nn as nn from torchvision import models import numpy as np import cv2 from PIL import Image#對比展現(xiàn)原始圖片和對抗樣本圖片 def show_images_diff(original_img,original_label,adversarial_img,adversarial_label):import matplotlib.pyplot as pltplt.figure()#歸一化if original_img.any() > 1.0:original_img=original_img/255.0if adversarial_img.any() > 1.0:adversarial_img=adversarial_img/255.0plt.subplot(131)plt.title('Original')plt.imshow(original_img)plt.axis('off')plt.subplot(132)plt.title('Adversarial')plt.imshow(adversarial_img)plt.axis('off')plt.subplot(133)plt.title('Adversarial-Original')difference = adversarial_img - original_img#(-1,1) -> (0,1)difference=difference / abs(difference).max()/2.0+0.5plt.imshow(difference,cmap=plt.cm.gray)plt.axis('off')plt.tight_layout()plt.show() #獲取計(jì)算設(shè)備 默認(rèn)是CPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu")#圖像加載以及預(yù)處理 image_path="data/goldfish.jpg" orig = cv2.imread(image_path)[..., ::-1] orig = cv2.resize(orig, (224, 224)) img = orig.copy().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()) print(img.shape)#使用預(yù)測模式 主要影響droupout和BN層的行為 model = models.alexnet(pretrained=True).to(device).eval() #取真實(shí)標(biāo)簽 label=np.argmax(model(img).data.cpu().numpy())#這里為什么要加cup()?因?yàn)閚p無法直接轉(zhuǎn)為cuda使用,要先轉(zhuǎn)cpu print("label={}".format(label))# 圖像數(shù)據(jù)梯度可以獲取 img.requires_grad = True# 設(shè)置為不保存梯度值 自然也無法修改 for param in model.parameters():param.requires_grad = Falseoptimizer = torch.optim.Adam([img])#優(yōu)化器 loss_func = torch.nn.CrossEntropyLoss()#交叉熵計(jì)算損失epochs = 1#訓(xùn)練輪次,只需一次,多了就變成I-FGSM了 e = 0.001#擾動值target = 254#此處是一個(gè)定向攻擊 target = Variable(torch.Tensor([float(target)]).to(device).long())#轉(zhuǎn)換數(shù)據(jù)類型for epoch in range(epochs):# forward + backwardoutput = model(img)loss = loss_func(output, target)label = np.argmax(output.data.cpu().numpy())print("epoch={} loss={} label={}".format(epoch, loss, label))# 如果定向攻擊成功if label == target:print("成功")break# 梯度清零optimizer.zero_grad()# 反向傳遞 計(jì)算梯度loss.backward()img.data = img.data - e * torch.sign(img.grad.data)#FGSM最重要的公式 print(model(img).argmax()) adv=img.data.cpu().numpy()[0] adv = adv.transpose(1, 2, 0) adv = (adv * std) + mean adv = adv * 255.0 adv = np.clip(adv, 0, 255).astype(np.uint8)#np數(shù)據(jù)類型是0-255,轉(zhuǎn)PIL之后進(jìn)行存儲 #對抗樣本的保存 # print(adv.shape) # img = Image.fromarray(adv) # img.show() # plt.imshow(img) # plt.show() # img.save('one.jpg')#圖片存儲為什么不用指定路徑,存儲結(jié)果在遠(yuǎn)程服務(wù)器 show_images_diff(orig,388,adv,target.data.cpu().numpy()[0])運(yùn)行結(jié)果如下:
?原始標(biāo)簽為31,定向攻擊目標(biāo)為254
?這里其實(shí)我自己有個(gè)問題沒有搞定,生成的對抗樣本保存時(shí)為什么不用指定路徑,我無法直接保存到pycharm中,我是保存在遠(yuǎn)程服務(wù)器上,但我從服務(wù)器上并不能打開這張圖片,是黑的,等于保存失敗......
總結(jié)
- 上一篇: TMDB电影数据分析报告
- 下一篇: c语言队列的作用,C语言队列