深度学习 作业6

import os
#讀取 label.csv
import pandas as pd
#讀取圖片
from PIL import Image
import numpy as np

import torch
#Loss function
import torch.nn.functional as F
#讀取資料
import torchvision.datasets as datasets
from torch.utils.data import Dataset, DataLoader
#載入預訓練的模型
import torchvision.models as models
#將資料轉換成符合預訓練模型的形式
import torchvision.transforms as transforms
#顯示圖片
import matplotlib.pyplot as plt

device = torch.device(“cuda”)

#實作一個繼承 torch.utils.data.Dataset 的 Class 來讀取圖片
class Adverdataset(Dataset):
def init(self, root, label, transforms):
# 圖片所在的資料夾
self.root = root
# 由 main function 傳入的 label
self.label = torch.from_numpy(label).long()
# 由 Attacker 傳入的 transforms 將輸入的圖片轉換成符合預訓練模型的形式
self.transforms = transforms
# 圖片檔案名稱的 list
self.fnames = []

    for i in range(200):
        self.fnames.append("{:03d}".format(i))

def __getitem__(self, idx):
    # 利用路徑讀取圖片
    img = Image.open(os.path.join(self.root, self.fnames[idx] + '.png'))
    # 將輸入的圖片轉換成符合預訓練模型的形式
    img = self.transforms(img)
    # 圖片相對應的 label
    label = self.label[idx]
    return img, label

def __len__(self):
    # 由於已知這次的資料總共有 200 張圖片 所以回傳 200
    return 200

載入模型並執行 FGSM 攻擊
class Attacker:
def init(self, img_dir, label):
# 讀入預訓練模型 vgg16
self.model = models.vgg16(pretrained = True)
self.model.cuda()
self.model.eval()
self.mean = [0.485, 0.456, 0.406]
self.std = [0.229, 0.224, 0.225]
# 把圖片 normalize 到 0~1 之間 mean 0 variance 1
# 在作业三已经提及了,注释过了该方法的具体内容
self.normalize = transforms.Normalize(self.mean, self.std, inplace=False)
transform = transforms.Compose([
transforms.Resize((224, 224), interpolation=3),
transforms.ToTensor(),
self.normalize
])
# 利用 Adverdataset 這個 class 讀取資料,将标签值和训练值给dataset
self.dataset = Adverdataset(’./data/images’, label, transform)
# __init__中的几个重要的输入:1、dataset,这个就是PyTorch已有的数据读取接口
#(比如torchvision.datasets.ImageFolder)或者自定义的数据接口的输出,
# 该输出要么是torch.utils.data.Dataset类的对象,要么是继承自torch.utils.data.Dataset类的自定义类的对象。
# 2、batch_size,根据具体情况设置即可。3、shuffle(打乱数据),一般在训练数据中会采用

   self.loader = torch.utils.data.DataLoader(
            self.dataset,
            batch_size = 1,
            shuffle = False)

# FGSM 攻擊
def fgsm_attack(self, image, epsilon, data_grad):#epsilon是后面自己设置数,体现噪声的强度
    # 找出 gradient 的方向
    sign_data_grad = data_grad.sign()
    # 將圖片加上 gradient 方向乘上 epsilon 的 noise
    perturbed_image = image + epsilon * sign_data_grad#生成有噪音的图片
    # 將圖片超過 1 或是小於 0 的部分 clip 掉
    # perturbed_image = torch.clamp(perturbed_image, 0, 1)
    return perturbed_image

def attack(self, epsilon):
    # 存下一些成功攻擊後的圖片 以便之後顯示
    adv_examples = []
    wrong, fail, success = 0, 0, 0
    for (data, target) in self.loader:
        data, target = data.to(device), target.to(device)#把数据转化为cpu,或者GPU的数据类型
        data_raw = data;
        data.requires_grad = True#自动计算梯度
        # 將圖片丟入 model 進行測試 得出相對應的 class
        output = self.model(data)
        init_pred = output.max(1, keepdim=True)[1]

        # 如果 class 錯誤 就不進行攻擊
        if init_pred.item() != target.item():
            wrong += 1
            continue
        
        # 如果 class 正確 就開始計算 gradient 進行 FGSM 攻擊
        loss = F.nll_loss(output, target)
        self.model.zero_grad()#梯度置零,防止叠加
        loss.backward()
        data_grad = data.grad.data
        perturbed_data = self.fgsm_attack(data, epsilon, data_grad)

        # 再將加入 noise 的圖片丟入 model 進行測試 得出相對應的 class        
        output = self.model(perturbed_data)
        final_pred = output.max(1, keepdim=True)[1]
      
        if final_pred.item() == target.item():
            # 辨識結果還是正確 攻擊失敗
            fail += 1
        else:
            # 辨識結果失敗 攻擊成功
            success += 1
            # 將攻擊成功的圖片存入
            if len(adv_examples) < 5:
              adv_ex = perturbed_data * torch.tensor(self.std, device = device).view(3, 1, 1) + torch.tensor(self.mean, device = device).view(3, 1, 1)
              adv_ex = adv_ex.squeeze().detach().cpu().numpy() 
               #squeeze 数据的维度进行压缩,去掉维数为1的的维度,比如是一行或者一列这种,一个一行三列(1,3)的数去掉第一个维数为一的维度之后就变成(3)行
              data_raw = data_raw * torch.tensor(self.std, device = device).view(3, 1, 1) + torch.tensor(self.mean, device = device).view(3, 1, 1)
              data_raw = data_raw.squeeze().detach().cpu().numpy()
              adv_examples.append( (init_pred.item(), final_pred.item(), data_raw , adv_ex) )        
    final_acc = (fail / (wrong + success + fail))
    
    print("Epsilon: {}\tTest Accuracy = {} / {} = {}\n".format(epsilon, fail, len(self.loader), final_acc))
    return adv_examples, final_acc

執行攻擊 並顯示攻擊成功率
if name == ‘main’:
# 讀入圖片相對應的 label
df = pd.read_csv("./data/labels.csv")
df = df.loc[:, ‘TrueLabel’].to_numpy()
label_name = pd.read_csv("./data/categories.csv")
label_name = label_name.loc[:, ‘CategoryName’].to_numpy()
# new 一個 Attacker class
attacker = Attacker(’./data/images’, df)
# 要嘗試的 epsilon
epsilons = [0.1, 0.01]

accuracies, examples = [], []

# 進行攻擊 並存起正確率和攻擊成功的圖片
for eps in epsilons:
    ex, acc = attacker.attack(eps)
    accuracies.append(acc)
    examples.append(ex)

Epsilon: 0.1 Test Accuracy = 6 / 200 = 0.03

Epsilon: 0.01 Test Accuracy = 54 / 200 = 0.27
生成攻击后的图片
cnt = 0
plt.figure(figsize=(30, 30))
for i in range(len(epsilons)):#遍历之前eps里的元素,idx值为0,1
for j in range(len(examples[i])):#遍历样本值,
cnt += 1
#subplot(nrows, ncols, plot_number)
#或者写成subplot(nrows ncols plot_number)也行(中间不用逗号,前提是只能是三位数)
#这个函数用来表示把figure分成nrows*ncols的子图表示,
#nrows:子图的行数
#ncols:子图的列数
#plot_number 索引值,表示把图画在第plot_number个位置(从左下角到右上角)
plt.subplot(len(epsilons),len(examples[0]) * 2,cnt)
plt.xticks([], [])#坐标轴变名用法
plt.yticks([], [])
if j == 0:
plt.ylabel(“Eps: {}”.format(epsilons[i]), fontsize=14)
orig,adv,orig_img, ex = examples[i][j]
# plt.title("{} -> {}".format(orig, adv))
plt.title(“original: {}”.format(label_name[orig].split(’,’)[0]))
orig_img = np.transpose(orig_img, (1, 2, 0))
plt.imshow(orig_img)
cnt += 1
plt.subplot(len(epsilons),len(examples[0]) * 2,cnt)
plt.title(“adversarial: {}”.format(label_name[adv].split(’,’)[0]))
ex = np.transpose(ex, (1, 2, 0))
plt.imshow(ex)
plt.tight_layout()
plt.show()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值