Pytorch | 利用BIM/I-FGSM針對CIFAR10上的ResNet分類器進行對抗攻擊
- CIFAR數據集
- BIM介紹
- 基本原理
- 算法流程
- BIM代碼實現
- BIM算法實現
- 攻擊效果
- 代碼匯總
- bim.py
- train.py
- advtest.py
之前已經針對CIFAR10訓練了多種分類器:
Pytorch | 從零構建AlexNet對CIFAR10進行分類
Pytorch | 從零構建Vgg對CIFAR10進行分類
Pytorch | 從零構建GoogleNet對CIFAR10進行分類
Pytorch | 從零構建ResNet對CIFAR10進行分類
Pytorch | 從零構建MobileNet對CIFAR10進行分類
Pytorch | 從零構建EfficientNet對CIFAR10進行分類
Pytorch | 從零構建ParNet對CIFAR10進行分類
本篇文章我們使用Pytorch實現BIM/I-FGSM對CIFAR10上的ResNet分類器進行攻擊.
CIFAR數據集
CIFAR-10數據集是由加拿大高級研究所(CIFAR)收集整理的用于圖像識別研究的常用數據集,基本信息如下:
- 數據規模:該數據集包含60,000張彩色圖像,分為10個不同的類別,每個類別有6,000張圖像。通常將其中50,000張作為訓練集,用于模型的訓練;10,000張作為測試集,用于評估模型的性能。
- 圖像尺寸:所有圖像的尺寸均為32×32像素,這相對較小的尺寸使得模型在處理該數據集時能夠相對快速地進行訓練和推理,但也增加了圖像分類的難度。
- 類別內容:涵蓋了飛機(plane)、汽車(car)、鳥(bird)、貓(cat)、鹿(deer)、狗(dog)、青蛙(frog)、馬(horse)、船(ship)、卡車(truck)這10個不同的類別,這些類別都是現實世界中常見的物體,具有一定的代表性。
下面是一些示例樣本:
BIM介紹
BIM(Basic Iterative Method)算法,也稱為迭代快速梯度符號法(Iterative Fast Gradient Sign Method,I-FGSM),是一種基于梯度的對抗攻擊算法,以下是對它的詳細介紹:
基本原理
- 利用模型梯度:與FGSM(Fast Gradient Sign Method)算法類似,BMI算法也是利用目標模型對輸入數據的梯度信息來生成對抗樣本。通過在原始輸入樣本上添加一個微小的擾動,使得模型對擾動后的樣本產生錯誤的分類結果。
- 迭代更新擾動:不同于FGSM只進行一次梯度計算和擾動添加,BMI算法通過多次迭代來逐步調整擾動,每次迭代都根據當前模型對擾動后樣本的梯度來更新擾動,使得擾動更具針對性和有效性,從而增加攻擊的成功率。
算法流程
- 初始化:首先獲取原始的輸入圖像(x)和對應的真實標簽 y y y,并設置一些攻擊參數,如擾動量 ? \epsilon ?、步長 α \alpha α 和迭代次數 T T T 等。然后將原始圖像復制一份作為初始的對抗樣本 x a d v = x x^{adv}=x xadv=x。
- 迭代攻擊:在每次迭代 t t t( t = 1 , 2 , ? , T t = 1, 2, \cdots, T t=1,2,?,T)中,將當前的對抗樣本 x a d v x^{adv} xadv 輸入到目標模型 f f f 中,計算模型的輸出 f ( x a d v ) f(x^{adv}) f(xadv) 和損失 J ( x a d v , y ) J(x^{adv}, y) J(xadv,y),其中損失函數通常使用交叉熵損失等。接著計算損失關于對抗樣本的梯度 ? x a d v J ( x a d v , y ) \nabla_{x^{adv}}J(x^{adv}, y) ?xadv?J(xadv,y),并根據梯度的符號來更新對抗樣本: x a d v = x a d v + α ? sign ( ? x a d v J ( x a d v , y ) ) x^{adv}=x^{adv}+\alpha\cdot \text{sign}(\nabla_{x^{adv}}J(x^{adv}, y)) xadv=xadv+α?sign(?xadv?J(xadv,y))。
- 裁剪擾動:為了確保擾動后的樣本與原始樣本在視覺上不會有太大差異,需要對更新后的對抗樣本進行裁剪,使其滿足 x a d v = clip ( x a d v , x ? ? , x + ? ) x^{adv}=\text{clip}(x^{adv}, x-\epsilon, x+\epsilon) xadv=clip(xadv,x??,x+?),即保證擾動后的樣本在原始樣本的 ? \epsilon ? 鄰域內。
- 終止條件判斷:經過(T)次迭代后,得到最終的對抗樣本(x^{adv}),此時將其輸入到目標模型中,若模型對其的預測結果與真實標簽不同,則攻擊成功,否則攻擊失敗。
BIM代碼實現
BIM算法實現
import torch
import torch.nn as nndef BIM(model, criterion, original_images, labels, epsilon, num_iterations=10):"""BIM (Basic Iterative Method)I-FGSM (Iterative Fast Gradient Sign Method)參數:model: 要攻擊的模型criterion: 損失函數original_images: 原始圖像labels: 原始圖像的標簽epsilon: 最大擾動幅度num_iterations: 迭代次數 """# alpha 每次迭代步長alpha = epsilon / num_iterationsperturbed_images = original_images.clone().detach().requires_grad_(True)for _ in range(num_iterations):# 計算損失outputs = model(perturbed_images)loss = criterion(outputs, labels)model.zero_grad()# 計算梯度loss.backward()# 更新對抗樣本perturbation = alpha * perturbed_images.grad.sign()perturbed_images = perturbed_images + perturbationperturbed_images = torch.clamp(perturbed_images, original_images - epsilon, original_images + epsilon)perturbed_images = perturbed_images.detach().requires_grad_(True)return perturbed_images
攻擊效果
代碼匯總
bim.py
import torch
import torch.nn as nndef BIM(model, criterion, original_images, labels, epsilon, num_iterations=10):"""BIM (Basic Iterative Method)I-FGSM (Iterative Fast Gradient Sign Method)參數:model: 要攻擊的模型criterion: 損失函數original_images: 原始圖像labels: 原始圖像的標簽epsilon: 最大擾動幅度num_iterations: 迭代次數 """# alpha 每次迭代步長alpha = epsilon / num_iterationsperturbed_images = original_images.clone().detach().requires_grad_(True)for _ in range(num_iterations):# 計算損失outputs = model(perturbed_images)loss = criterion(outputs, labels)model.zero_grad()# 計算梯度loss.backward()# 更新對抗樣本perturbation = alpha * perturbed_images.grad.sign()perturbed_images = perturbed_images + perturbationperturbed_images = torch.clamp(perturbed_images, original_images - epsilon, original_images + epsilon)perturbed_images = perturbed_images.detach().requires_grad_(True)return perturbed_images
train.py
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from models import ResNet18# 數據預處理
transform_train = transforms.Compose([transforms.RandomCrop(32, padding=4),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])transform_test = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])# 加載Cifar10訓練集和測試集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=False, transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=False, transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=100, shuffle=False, num_workers=2)# 定義設備(GPU或CPU)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 初始化模型
model = ResNet18(num_classes=10)
model.to(device)# 定義損失函數和優化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)if __name__ == "__main__":# 訓練模型for epoch in range(10): # 可以根據實際情況調整訓練輪數running_loss = 0.0for i, data in enumerate(trainloader, 0):inputs, labels = data[0].to(device), data[1].to(device)optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()running_loss += loss.item()if i % 100 == 99:print(f'Epoch {epoch + 1}, Batch {i + 1}: Loss = {running_loss / 100}')running_loss = 0.0torch.save(model.state_dict(), f'weights/epoch_{epoch + 1}.pth')print('Finished Training')
advtest.py
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from models import *
from attacks import *
import ssl
import os
from PIL import Image
import matplotlib.pyplot as pltssl._create_default_https_context = ssl._create_unverified_context# 定義數據預處理操作
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])# 加載CIFAR10測試集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,shuffle=False, num_workers=2)# 定義設備(GPU優先,若可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")model = ResNet18(num_classes=10).to(device)criterion = nn.CrossEntropyLoss()# 加載模型權重
weights_path = "weights/epoch_10.pth"
model.load_state_dict(torch.load(weights_path, map_location=device))if __name__ == "__main__":# 在測試集上進行FGSM攻擊并評估準確率model.eval() # 設置為評估模式correct = 0total = 0epsilon = 16 / 255 # 可以調整擾動強度for data in testloader:original_images, labels = data[0].to(device), data[1].to(device)original_images.requires_grad = Trueattack_name = 'BIM'if attack_name == 'FGSM':perturbed_images = FGSM(model, criterion, original_images, labels, epsilon)elif attack_name == 'BIM':perturbed_images = BIM(model, criterion, original_images, labels, epsilon)perturbed_outputs = model(perturbed_images)_, predicted = torch.max(perturbed_outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()accuracy = 100 * correct / total# Attack Success RateASR = 100 - accuracyprint(f'Load ResNet Model Weight from {weights_path}')print(f'epsilon: {epsilon}')print(f'ASR of {attack_name} : {ASR}%')