Reference : https://tutorials.pytorch.kr/beginner/fgsm_tutorial.html
https://jaketae.github.io/study/fgsm/
FGSM(Fast Gradient Sign Attack)에 대해 알아보자.
위협 모델
적대적 공격은 종류가 여러가지 있는데, 크게 두가지로 나뉜다.
화이트박스 공격은 공격자가 모델에 대해 아키텍처, 입력, 출력 가중치를 포함한 모든 것을 알고 있고 접근할 수 있다고 가정한다.
블랙박스 공격은 공격자가 모델의 입력과 출력에 대해서만 접근 가능하고 모델의 가중치와 아키텍처에 관한 내용은 모른다고 가정한다.
공격자의 목표는 여러 유형중
오분류의 목표는 공격자가 출력으로 나온 분류 결과가 잘못 되도록 하나 새로운 분류 결과가 어떤 것이 나오는지 신경 쓰지 않는다.
소스/타겟 오분류는 공격자가 원래 특정 소스 클래스의 이미지를 다른 특정 대상 클래스로 분류하도록 변경하려고 한다.
FGSM 공격은 오분류를 목표로 하는 화이트박스 공격이다.
FGSM(Fast Gradient Sign Attack)
이 공격은 학습 방식, 변화도(gradients)를 활용해 신경망을 공격한다.
아이디어는 다음과 같다.
역전파 변화도를 기반으로 가중치를 조정하여 손실을 최소화 하는것이 아니라 공격이 동일한 역전파 변화도를 기반으로 손실을 최대화 하는 방향으로 조정하여 데이터를 조정한다.
다시 말해 공격은 입력 데이터에서 계산된 손실 변화도를 사용하고 입력 데이터를 조정하여 손실이 최대가 되게 한다.
그림으로부터, 는 원본 입력 이미지가 "판다"로 옳게 분류
는 를 위한 정답 label
는 모델의 파라미터, 는 네트워크의 학습을 위해서 사용되는 손실을 나타낸다.
공격은 계산을 위해 입력 데이터에 변화도를 역전파한다.
그러고 나서, 변화도는 손실 값이 최대화 되는 방향으로 (예를 들면, 작은 스텝(step) 만큼 (그림에서는 혹은 0.007) 입력 데이터에 적용됩니다. 결과로 나오는 작은 변화된 이미지 () 는 타겟 네트워크에 의해 '긴팔원숭이'로 오분류 되나 여전히 육안으로는 분명히 '판다'입니다.
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
# NOTE: 아래는 MNIST 데이터셋을 내려받을 때 "User-agent" 관련한 제한을 푸는 코드입니다.
# 더 자세한 내용은 https://github.com/pytorch/vision/issues/3497 을 참고해주세요.
from six.moves import urllib
opener = urllib.request.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
urllib.request.install_opener(opener)
입력
epsilons - 실행에 사용할 엡실론의 리스트입니다. 엡실론 0의 값은 원래 테스트 셋의 모델 성능을 나타내므로 목록에 유지하는 것이 중요합니다. 또한 직관적으로 엡실론이 클수록 작은 변화가 더 눈에 띄지만 모델 정확도를 저하 시키는 측면에서 더 효과가 있습니다. 여기서 데이터의 범위는 0-1 이기 때문에 엡실론의 값은 1을 초과할 수 없습니다.
pretrained_model - pytorch/examples/mnist
를 통해 미리 학습된 MNIST 모델의 경로. 튜토리얼을 간편하게 하려면 여기 에서 미리 학습된 모델을 다운로드하세요.
use_cuda - CUDA 를 사용할지 말지 정하는 이진 플래그. 본 튜토리얼에서는 CPU 시간이 오래 걸리지 않으므로 CUDA를 지원하는 GPU 의 여부는 중요하지 않습니다.
epsilons = [0, .05, .1, .15, .2, .25, .3]
pretrained_model = "data/lenet_mnist_model.pth"
use_cuda=True
FGSM 공격
이제 원래 입력을 교란시켜 적대적인 예를 만드는 함수를 정의 할 수 있습니다. fgsm_attack 함수는 입력 파라미터로 3가지를 가집니다. 첫번째는 원본 이미지 ( ), 두번째는 엡실론 으로 픽셀 단위의 작은 변화를 주는 값입니다 ( ). 마지막은 data_grad 로 입력 영상 ( 에 대한 변화도 손실 값입니다. 아래 식에 따른 작은 변화가 적용된 이미지를 생성합니다.
Perturbation
하나의 간단한 linear classifier를 가정해봅시다.
여기서 w는 가중치 행렬이고, 우리는 x에 아주 작은 perturbation 를 더해주려고 합니다.
그러면, logits은 다음과 같습니다.
이 의미는, 아주 작은 가 주어졌을 때, 실제로 classifier의 logits에 주어지는 perturbation의 효과는 라는 의미이다.
이러한 아이디어가 기저에 있으면서 FGSM은 이 사람의 눈으로 구별할 수 없지만, 치명적인 변화를 불러 일으키는 를 찾는것이다.
예시를 들어서 설명해보면 다음과 같습니다.
어떤 RGB 이미지를 입력으로 받는 이미지 분류기를 생각해보자. 일반적으로 RGB 이미지들은 0부터 255의 integer 픽셀 값들을 가진다. 이러한 값들은 일반적으로 255로 나누는 전처리 과정을 거친다. 따라서 데이터의 정교함이 8bit를 가지는 bottleneck으로 제한된다. 이는 perturbation이 1/255보다 작을때, 분류기의 아웃풋이 다른 예측을 할 것이라고 기대하지 않는다. 다른말로 이, 위 bottleneck(1/255)보다 낮은 경우 model의 어떤 변화도 일으킬 수 없다.
적대적 예제는 의 값을 최대화 하여 모델에 혼동을 주어 잘못 예측하도록 만든다. 당연하게도 에는 제약조건이 붙는다. 만약에 엄청나게 큰 perturbation을 입력 이미지에 준다면, 눈으로 확인할 수 있을 정도의 변화를 주기때문에, 이러한 경우를 방지하기 위해 다음과 같은 제약을 둔다.
# FGSM 공격 코드
def fgsm_attack(image, epsilon, data_grad):
# data_grad 의 요소별 부호 값을 얻어옵니다
sign_data_grad = data_grad.sign()
# 입력 이미지의 각 픽셀에 sign_data_grad 를 적용해 작은 변화가 적용된 이미지를 생성합니다
perturbed_image = image + epsilon*sign_data_grad
# 값 범위를 [0,1]로 유지하기 위해 자르기(clipping)를 추가합니다
perturbed_image = torch.clamp(perturbed_image, 0, 1)
# 작은 변화가 적용된 이미지를 리턴합니다
return perturbed_image
TEST
def test( model, device, test_loader, epsilon ):
# 정확도 카운터
correct = 0
adv_examples = []
# 테스트 셋의 모든 예제에 대해 루프를 돕니다
for data, target in test_loader:
# 디바이스(CPU or GPU) 에 데이터와 라벨 값을 보냅니다
data, target = data.to(device), target.to(device)
# 텐서의 속성 중 requires_grad 를 설정합니다. 공격에서 중요한 부분입니다
data.requires_grad = True
# 데이터를 모델에 통과시킵니다
output = model(data)
init_pred = output.max(1, keepdim=True)[1] # 로그 확률의 최대값을 가지는 인덱스를 얻습니다
# 만약 초기 예측이 틀리면, 공격하지 않도록 하고 계속 진행합니다
if init_pred.item() != target.item():
continue
# 손실을 계산합니다
loss = F.nll_loss(output, target)
# 모델의 변화도들을 전부 0으로 설정합니다
model.zero_grad()
# 후방 전달을 통해 모델의 변화도를 계산합니다
loss.backward()
# 변화도 값을 모읍니다
data_grad = data.grad.data
# FGSM 공격을 호출합니다
perturbed_data = fgsm_attack(data, epsilon, data_grad)
# 작은 변화가 적용된 이미지에 대해 재분류합니다
output = model(perturbed_data)
# 올바른지 확인합니다
final_pred = output.max(1, keepdim=True)[1] # 로그 확률의 최대값을 가지는 인덱스를 얻습니다
if final_pred.item() == target.item():
correct += 1
# 0 엡실론 예제에 대해서 저장합니다
if (epsilon == 0) and (len(adv_examples) < 5):
adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
else:
# 추후 시각화를 위하 다른 예제들을 저장합니다
if len(adv_examples) < 5:
adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
# 해당 엡실론에서의 최종 정확도를 계산합니다
final_acc = correct/float(len(test_loader))
print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon, correct, len(test_loader), final_acc))
# 정확도와 적대적 예제를 리턴합니다
return final_acc, adv_examples