├── requirements.txt ├── assets ├── cross_distribution.png ├── adv_paintings_incv3.jpg └── adv_unbound_paintings_incv3.jpg ├── saved_models └── ReadMe.md ├── LICENSE ├── gaussian_smoothing.py ├── generate_and_save_adv.py ├── generators.py ├── eval.py ├── utils.py ├── README.md └── train.py /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | numpy 3 | argparse 4 | pandas 5 | imageio 6 | 7 | -------------------------------------------------------------------------------- /assets/cross_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muzammal-Naseer/CDA/HEAD/assets/cross_distribution.png -------------------------------------------------------------------------------- /assets/adv_paintings_incv3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muzammal-Naseer/CDA/HEAD/assets/adv_paintings_incv3.jpg -------------------------------------------------------------------------------- /assets/adv_unbound_paintings_incv3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muzammal-Naseer/CDA/HEAD/assets/adv_unbound_paintings_incv3.jpg -------------------------------------------------------------------------------- /saved_models/ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Download Pretrained Generators 2 | 3 | https://drive.google.com/open?id=1H_o90xtHYbK7M3bMMm_RtwTtCdDmaPJY 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Muzammal Naseer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gaussian_smoothing.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import math 4 | 5 | 6 | # Courtesy of: https://discuss.pytorch.org/t/is-there-anyway-to-do-gaussian-filtering-for-an-image-2d-3d-in-pytorch/12351/3 7 | def get_gaussian_kernel(kernel_size=3, pad=2, sigma=2, channels=3): 8 | # Create a x, y coordinate grid of shape (kernel_size, kernel_size, 2) 9 | x_coord = torch.arange(kernel_size) 10 | x_grid = x_coord.repeat(kernel_size).view(kernel_size, kernel_size) 11 | y_grid = x_grid.t() 12 | xy_grid = torch.stack([x_grid, y_grid], dim=-1).float() 13 | 14 | mean = (kernel_size - 1) / 2. 15 | variance = sigma ** 2. 16 | 17 | # Calculate the 2-dimensional gaussian kernel which is 18 | # the product of two gaussian distributions for two different 19 | # variables (in this case called x and y) 20 | gaussian_kernel = (1. / (2. * math.pi * variance)) * \ 21 | torch.exp( 22 | -torch.sum((xy_grid - mean) ** 2., dim=-1) / \ 23 | (2 * variance) 24 | ) 25 | 26 | # Make sure sum of values in gaussian kernel equals 1. 27 | gaussian_kernel = gaussian_kernel / torch.sum(gaussian_kernel) 28 | 29 | # Reshape to 2d depthwise convolutional weight 30 | gaussian_kernel = gaussian_kernel.view(1, 1, kernel_size, kernel_size) 31 | gaussian_kernel = gaussian_kernel.repeat(channels, 1, 1, 1) 32 | 33 | gaussian_filter = nn.Conv2d(in_channels=channels, out_channels=channels, 34 | kernel_size=kernel_size, groups=channels, padding=kernel_size-pad, bias=False) 35 | 36 | gaussian_filter.weight.data = gaussian_kernel 37 | gaussian_filter.weight.requires_grad = False 38 | 39 | return gaussian_filter 40 | 41 | -------------------------------------------------------------------------------- /generate_and_save_adv.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | import torchvision 7 | import torch 8 | import torch.nn as nn 9 | import torch.optim as optim 10 | from torch.utils.data import DataLoader 11 | import torchvision.datasets as datasets 12 | import torchvision.transforms as transforms 13 | import torchvision.utils as vutils 14 | import imageio 15 | from generators import * 16 | from gaussian_smoothing import * 17 | from utils import * 18 | 19 | 20 | parser = argparse.ArgumentParser(description='Cross Data Transferability') 21 | parser.add_argument('--train_dir', default='paintings', help='Generator Training Data: paintings, comics, ImageNet') 22 | parser.add_argument('--test_dir', default='IN/', help='Image Directory') 23 | parser.add_argument('--save_dir', default='adv/', help='Image Directory') 24 | parser.add_argument('--batch_size', type=int, default=10, help='Batch size for evaluation') 25 | parser.add_argument('--epochs', type=int, default=9, help='Saving Instance') 26 | parser.add_argument('--eps', type=int, default=16, help='Perturbation Budget') 27 | parser.add_argument('--model_type', type=str, default= 'incv3', help ='Model under attack: vgg16, vgg19, incv3, res152') 28 | parser.add_argument('--attack_type', type=str, default='img', help='Training is either img/noise dependent') 29 | parser.add_argument('--target', type=int, default=-1, help='-1 if untargeted') 30 | parser.add_argument('--gk', action='store_true', help='Apply Gaussian Smoothings to GAN Output') 31 | parser.add_argument('--rl', action='store_true', help='Relativstic or Simple GAN') 32 | args = parser.parse_args() 33 | 34 | eps = args.eps/255 35 | 36 | 37 | # GPU 38 | device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') 39 | 40 | # Load Generator 41 | netG = load_gan(args) 42 | netG.to(device) 43 | netG.eval() 44 | 45 | 46 | # Data 47 | # Input dimensions: Inception takes 3x299x299 48 | if args.model_type in ['vgg16', 'vgg19', 'res152']: 49 | scale_size = 256 50 | img_size = 224 51 | else: 52 | scale_size = 300 53 | img_size = 299 54 | 55 | # Setup-Data 56 | data_transform = transforms.Compose([ 57 | transforms.Resize(scale_size), 58 | transforms.CenterCrop(img_size), 59 | transforms.ToTensor(), 60 | ]) 61 | 62 | test_dir = args.test_dir 63 | test_set = datasets.ImageFolder(test_dir, data_transform) 64 | test_loader = torch.utils.data.DataLoader(test_set,batch_size=args.batch_size, shuffle=False, num_workers=4, pin_memory=True) 65 | 66 | test_size = len(test_set) 67 | print('Test data size:', test_size) 68 | 69 | sourcedir = args.test_dir 70 | targetdir = args.save_dir 71 | all_classes = sorted(os.listdir(args.test_dir)) 72 | 73 | # Setup-Gaussian Kernel 74 | if args.gk: 75 | kernel_size = 3 76 | pad = 2 77 | sigma = 1 78 | kernel = get_gaussian_kernel(kernel_size=kernel_size, pad=pad, sigma=sigma).cuda() 79 | 80 | 81 | counter = 0 82 | current_class = None 83 | current_class_files = None 84 | big_img = [] 85 | for i, (img, label) in enumerate(test_loader): 86 | img = img.to(device) 87 | 88 | adv = netG(img).detach() 89 | if args.gk: 90 | adv = kernel(adv) 91 | adv = torch.min(torch.max(adv, img - eps), img + eps) 92 | adv = torch.clamp(adv, 0.0, 1.0) 93 | 94 | print('L-infity:{}'.format((img-adv).max()*255)) 95 | 96 | 97 | # Courtesy of: https://github.com/rgeirhos/Stylized-ImageNet/blob/master/code/preprocess_imagenet.py 98 | for img_index in range(adv.size()[0]): 99 | source_class = all_classes[label[img_index]] 100 | source_classdir = os.path.join(sourcedir, source_class) 101 | assert os.path.exists(source_classdir) 102 | 103 | target_classdir = os.path.join(targetdir, source_class) 104 | if not os.path.exists(target_classdir): 105 | os.makedirs(target_classdir) 106 | 107 | if source_class != current_class: 108 | # moving on to new class: 109 | # start counter (=index) by 0, update list of files 110 | # for this new class 111 | counter = 0 112 | current_class_files = sorted(os.listdir(source_classdir)) 113 | 114 | current_class = source_class 115 | 116 | target_img_path = os.path.join(target_classdir, 117 | current_class_files[counter]).replace(".JPEG", ".png") 118 | 119 | # if size_error == 1: 120 | # big_img.append(target_img_path) 121 | 122 | adv_to_save = np.transpose(adv[img_index, :, :, :].detach().cpu().numpy(), (1, 2, 0)) * 255 123 | imageio.imwrite(target_img_path, adv_to_save.astype(np.uint8)) 124 | # save_image(tensor=adv[img_index, :, :, :], 125 | # filename=target_img_path) 126 | counter += 1 127 | # 128 | # del(img) 129 | # del(adv) 130 | # del(adv1) 131 | 132 | print('Number of Images Processed:', (i + 1) * args.batch_size) 133 | -------------------------------------------------------------------------------- /generators.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import numpy as np 5 | 6 | 7 | ########################### 8 | # Generator: Resnet 9 | ########################### 10 | 11 | # To control feature map in generator 12 | ngf = 64 13 | 14 | class GeneratorResnet(nn.Module): 15 | def __init__(self, inception=False, data_dim='high'): 16 | ''' 17 | :param inception: if True crop layer will be added to go from 3x300x300 t0 3x299x299. 18 | :param data_dim: for high dimentional dataset (imagenet) 6 resblocks will be add otherwise only 2. 19 | ''' 20 | super(GeneratorResnet, self).__init__() 21 | self.inception = inception 22 | self.data_dim = data_dim 23 | # Input_size = 3, n, n 24 | self.block1 = nn.Sequential( 25 | nn.ReflectionPad2d(3), 26 | nn.Conv2d(3, ngf, kernel_size=7, padding=0, bias=False), 27 | nn.BatchNorm2d(ngf), 28 | nn.ReLU(True) 29 | ) 30 | 31 | # Input size = 3, n, n 32 | self.block2 = nn.Sequential( 33 | nn.Conv2d(ngf, ngf * 2, kernel_size=3, stride=2, padding=1, bias=False), 34 | nn.BatchNorm2d(ngf * 2), 35 | nn.ReLU(True) 36 | ) 37 | 38 | # Input size = 3, n/2, n/2 39 | self.block3 = nn.Sequential( 40 | nn.Conv2d(ngf * 2, ngf * 4, kernel_size=3, stride=2, padding=1, bias=False), 41 | nn.BatchNorm2d(ngf * 4), 42 | nn.ReLU(True) 43 | ) 44 | 45 | # Input size = 3, n/4, n/4 46 | # Residual Blocks: 6 47 | self.resblock1 = ResidualBlock(ngf * 4) 48 | self.resblock2 = ResidualBlock(ngf * 4) 49 | if self.data_dim == 'high': 50 | self.resblock3 = ResidualBlock(ngf * 4) 51 | self.resblock4 = ResidualBlock(ngf * 4) 52 | self.resblock5 = ResidualBlock(ngf * 4) 53 | self.resblock6 = ResidualBlock(ngf * 4) 54 | # self.resblock7 = ResidualBlock(ngf*4) 55 | # self.resblock8 = ResidualBlock(ngf*4) 56 | # self.resblock9 = ResidualBlock(ngf*4) 57 | 58 | # Input size = 3, n/4, n/4 59 | self.upsampl1 = nn.Sequential( 60 | nn.ConvTranspose2d(ngf * 4, ngf * 2, kernel_size=3, stride=2, padding=1, output_padding=1, bias=False), 61 | nn.BatchNorm2d(ngf * 2), 62 | nn.ReLU(True) 63 | ) 64 | 65 | # Input size = 3, n/2, n/2 66 | self.upsampl2 = nn.Sequential( 67 | nn.ConvTranspose2d(ngf * 2, ngf, kernel_size=3, stride=2, padding=1, output_padding=1, bias=False), 68 | nn.BatchNorm2d(ngf), 69 | nn.ReLU(True) 70 | ) 71 | 72 | # Input size = 3, n, n 73 | self.blockf = nn.Sequential( 74 | nn.ReflectionPad2d(3), 75 | nn.Conv2d(ngf, 3, kernel_size=7, padding=0) 76 | ) 77 | 78 | self.crop = nn.ConstantPad2d((0, -1, -1, 0), 0) 79 | 80 | def forward(self, input): 81 | x = self.block1(input) 82 | x = self.block2(x) 83 | x = self.block3(x) 84 | x = self.resblock1(x) 85 | x = self.resblock2(x) 86 | if self.data_dim == 'high': 87 | x = self.resblock3(x) 88 | x = self.resblock4(x) 89 | x = self.resblock5(x) 90 | x = self.resblock6(x) 91 | # x = self.resblock7(x) 92 | # x = self.resblock8(x) 93 | # x = self.resblock9(x) 94 | x = self.upsampl1(x) 95 | x = self.upsampl2(x) 96 | x = self.blockf(x) 97 | if self.inception: 98 | x = self.crop(x) 99 | return (torch.tanh(x) + 1) / 2 # Output range [0 1] 100 | 101 | 102 | class ResidualBlock(nn.Module): 103 | def __init__(self, num_filters): 104 | super(ResidualBlock, self).__init__() 105 | self.block = nn.Sequential( 106 | nn.ReflectionPad2d(1), 107 | nn.Conv2d(in_channels=num_filters, out_channels=num_filters, kernel_size=3, stride=1, padding=0, 108 | bias=False), 109 | nn.BatchNorm2d(num_filters), 110 | nn.ReLU(True), 111 | 112 | nn.Dropout(0.5), 113 | 114 | nn.ReflectionPad2d(1), 115 | nn.Conv2d(in_channels=num_filters, out_channels=num_filters, kernel_size=3, stride=1, padding=0, 116 | bias=False), 117 | nn.BatchNorm2d(num_filters) 118 | ) 119 | 120 | def forward(self, x): 121 | residual = self.block(x) 122 | return x + residual 123 | 124 | 125 | 126 | if __name__ == '__main__': 127 | netG = GeneratorResnet(data_dim='low') 128 | test_sample = torch.rand(1, 3, 32, 32) 129 | print('Generator output:', netG(test_sample).size()) 130 | print('Generator parameters:', sum(p.numel() for p in netG.parameters() if p.requires_grad)) -------------------------------------------------------------------------------- /eval.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | import torchvision 7 | import torch 8 | import torch.nn as nn 9 | import torch.optim as optim 10 | from torch.utils.data import DataLoader 11 | import torchvision.datasets as datasets 12 | import torchvision.transforms as transforms 13 | import torchvision.utils as vutils 14 | 15 | from gaussian_smoothing import * 16 | from utils import * 17 | 18 | parser = argparse.ArgumentParser(description='Cross Data Transferability') 19 | parser.add_argument('--train_dir', default='paintings', help='Generator Training Data: paintings, comics, ') 20 | parser.add_argument('--test_dir', default='../../IN/val', help='ImageNet Validation Data') 21 | parser.add_argument('--is_nips', action='store_true', help='Evaluation on NIPS data') 22 | parser.add_argument('--measure_adv', action='store_true', help='If not triggered then measuring only clean accuracy') 23 | parser.add_argument('--batch_size', type=int, default=20, help='Batch Size') 24 | parser.add_argument('--epochs', type=int, default=9, help='Which Saving Instance to Evaluate') 25 | parser.add_argument('--eps', type=int, default=10, help='Perturbation Budget') 26 | parser.add_argument('--model_type', type=str, default= 'vgg16', help ='Model against GAN is trained: vgg16, vgg19, incv3, res152') 27 | parser.add_argument('--model_t',type=str, default= 'vgg19', help ='Model under attack : vgg16, vgg19, incv3, res152, res50, dense121, sqz' ) 28 | parser.add_argument('--target', type=int, default=-1, help='-1 if untargeted') 29 | parser.add_argument('--attack_type', type=str, default='img', help='Training is either img/noise dependent') 30 | parser.add_argument('--gk', action='store_true', help='Apply Gaussian Smoothings to GAN Output') 31 | parser.add_argument('--rl', action='store_true', help='Relativstic or Simple GAN') 32 | args = parser.parse_args() 33 | print(args) 34 | 35 | # Normalize (0-1) 36 | eps = args.eps/255 37 | 38 | # GPU 39 | device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') 40 | 41 | # Input dimensions: Inception takes 3x299x299 42 | if args.model_type in ['vgg16', 'vgg19', 'res152']: 43 | scale_size = 256 44 | img_size = 224 45 | else: 46 | scale_size = 300 47 | img_size = 299 48 | 49 | 50 | if args.measure_adv: 51 | netG=load_gan(args) 52 | netG.to(device) 53 | netG.eval() 54 | 55 | model_t = load_model(args) 56 | model_t = model_t.to(device) 57 | model_t.eval() 58 | 59 | 60 | 61 | # Setup-Data 62 | data_transform = transforms.Compose([ 63 | transforms.Resize(scale_size), 64 | transforms.CenterCrop(img_size), 65 | transforms.ToTensor(), 66 | ]) 67 | 68 | mean = [0.485, 0.456, 0.406] 69 | std = [0.229, 0.224, 0.225] 70 | def normalize(t): 71 | t[:, 0, :, :] = (t[:, 0, :, :] - mean[0])/std[0] 72 | t[:, 1, :, :] = (t[:, 1, :, :] - mean[1])/std[1] 73 | t[:, 2, :, :] = (t[:, 2, :, :] - mean[2])/std[2] 74 | 75 | return t 76 | 77 | test_dir = args.test_dir 78 | test_set = datasets.ImageFolder(test_dir, data_transform) 79 | test_size = len(test_set) 80 | print('Test data size:', test_size) 81 | 82 | # Fix labels if needed 83 | if args.is_nips: 84 | test_set = fix_labels_nips(test_set, pytorch=True) 85 | else: 86 | test_set = fix_labels(test_set) 87 | 88 | test_loader = torch.utils.data.DataLoader(test_set,batch_size=args.batch_size, shuffle=False, num_workers=4, pin_memory=True) 89 | 90 | 91 | # Setup-Gaussian Kernel 92 | if args.gk: 93 | kernel_size = 3 94 | pad = 2 95 | sigma = 1 96 | kernel = get_gaussian_kernel(kernel_size=kernel_size, pad=pad, sigma=sigma).cuda() 97 | 98 | 99 | # Evaluation 100 | adv_acc = 0 101 | clean_acc = 0 102 | fool_rate = 0 103 | for i, (img, label) in enumerate(test_loader): 104 | img, label = img.to(device), label.to(device) 105 | 106 | clean_out = model_t(normalize(img.clone().detach())) 107 | clean_acc += torch.sum(clean_out.argmax(dim=-1) == label).item() 108 | 109 | 110 | 111 | if args.measure_adv: 112 | # Unrestricted Adversary 113 | adv = netG(img) 114 | 115 | # Apply smoothing 116 | if args.gk: 117 | adv = kernel(adv) 118 | 119 | # Projection 120 | adv = torch.min(torch.max(adv, img - eps), img + eps) 121 | adv = torch.clamp(adv, 0.0, 1.0) 122 | 123 | adv_out = model_t(normalize(adv.clone().detach())) 124 | adv_acc +=torch.sum(adv_out.argmax(dim=-1) == label).item() 125 | 126 | fool_rate += torch.sum(adv_out.argmax(dim=-1) != clean_out.argmax(dim=-1)).item() 127 | 128 | #l_inf = torch.dist(img.view(-1), adv.view(-1), float('inf')) 129 | if i == 0: 130 | vutils.save_image(vutils.make_grid(adv, normalize=True, scale_each=True), 'adv.png') 131 | vutils.save_image(vutils.make_grid(img, normalize=True, scale_each=True), 'org.png') 132 | 133 | if args.measure_adv: 134 | print('At Batch:{}\t l_inf:{}'.format(i, (img - adv).max() * 255)) 135 | else: 136 | print('At Batch:{}'.format(i)) 137 | 138 | print('Clean:{0:.3%}\t Adversarial :{1:.3%}\t Fooling Rate:{2:.3%}'.format(clean_acc/test_size, adv_acc/test_size, fool_rate/test_size)) 139 | 140 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision 3 | from generators import GeneratorResnet 4 | import pandas as pd 5 | 6 | 7 | # Load a particular generator 8 | def load_gan(args): 9 | # Load Generator 10 | if args.model_type == 'incv3': 11 | netG = GeneratorResnet(inception=True) 12 | else: 13 | netG = GeneratorResnet() 14 | 15 | print('Label: {} \t Attack: {} dependent \t Model: {} \t Distribution: {} \t Saving instance: {}'.format(args.target, 16 | args.attack_type, 17 | args.model_type, 18 | args.train_dir, 19 | args.epochs)) 20 | if args.rl: 21 | netG.load_state_dict(torch.load('saved_models/netG_{}_{}_{}_{}_{}_rl.pth'.format(args.target, 22 | args.attack_type, 23 | args.model_type, 24 | args.train_dir, 25 | args.epochs))) 26 | else: 27 | netG.load_state_dict(torch.load('saved_models/netG_{}_{}_{}_{}_{}.pth'.format(args.target, 28 | args.attack_type, 29 | args.model_type, 30 | args.train_dir, 31 | args.epochs))) 32 | 33 | return netG 34 | 35 | 36 | # Load ImageNet model to evaluate 37 | def load_model(args): 38 | # Load Targeted Model 39 | if args.model_t == 'dense201': 40 | model_t = torchvision.models.densenet201(pretrained=True) 41 | elif args.model_t == 'vgg19': 42 | model_t = torchvision.models.vgg19(pretrained=True) 43 | elif args.model_t == 'vgg16': 44 | model_t = torchvision.models.vgg16(pretrained=True) 45 | elif args.model_t == 'incv3': 46 | model_t = torchvision.models.inception_v3(pretrained=True) 47 | elif args.model_t == 'res152': 48 | model_t = torchvision.models.resnet152(pretrained=True) 49 | elif args.model_t == 'res50': 50 | model_t = torchvision.models.resnet50(pretrained=True) 51 | elif args.model_t == 'sqz': 52 | model_t = torchvision.models.squeezenet1_1(pretrained=True) 53 | 54 | return model_t 55 | 56 | ############################################################ 57 | # If you have all 1000 class folders. Then using default loader is ok. 58 | # In case you have few classes (let's 50) or collected random images in a folder 59 | # then we need to fix the labels. 60 | # The code below will fix the labels for you as long as you don't change "orginal imagenet ids". 61 | # for example "ILSVRC2012_val_00019972.JPEG ... " 62 | 63 | def fix_labels(test_set): 64 | val_dict = {} 65 | with open("val.txt") as file: 66 | for line in file: 67 | (key, val) = line.split(' ') 68 | val_dict[key.split('.')[0]] = int(val.strip()) 69 | 70 | new_data_samples = [] 71 | for i, j in enumerate(test_set.samples): 72 | org_label = val_dict[test_set.samples[i][0].split('/')[-1].split('.')[0]] 73 | new_data_samples.append((test_set.samples[i][0], org_label)) 74 | 75 | test_set.samples = new_data_samples 76 | return test_set 77 | ############################################################ 78 | 79 | 80 | ############################################################# 81 | # This will fix labels for NIPS ImageNet 82 | def fix_labels_nips(test_set, pytorch=False): 83 | 84 | ''' 85 | :param pytorch: pytorch models have 1000 labels as compared to tensorflow models with 1001 labels 86 | ''' 87 | 88 | filenames = [i.split('/')[-1] for i, j in test_set.samples] 89 | # Load provided files and get image labels and names 90 | image_classes = pd.read_csv("images.csv") 91 | image_metadata = pd.DataFrame({"ImageId": [f[:-4] for f in filenames]}).merge(image_classes, on="ImageId") 92 | true_classes = image_metadata["TrueLabel"].tolist() 93 | target_classes = image_metadata["TargetClass"].tolist() 94 | 95 | # Populate the dictionary: key(image path), value ([true label, target label]) 96 | val_dict = {} 97 | for f, i in zip(filenames, range(len(filenames))): 98 | val_dict[f] = [true_classes[i], target_classes[i]] 99 | 100 | new_data_samples = [] 101 | for i, j in enumerate(test_set.samples): 102 | org_label = val_dict[test_set.samples[i][0].split('/')[-1]][0] 103 | if pytorch: 104 | new_data_samples.append((test_set.samples[i][0], org_label-1)) 105 | else: 106 | new_data_samples.append((test_set.samples[i][0], org_label)) 107 | 108 | test_set.samples = new_data_samples 109 | 110 | return test_set 111 | 112 | 113 | # Rescale image b/w (-1, +1) 114 | def rescale(image): 115 | return image*2-1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cross-Domain Transferable Perturbations 2 | 3 | 4 | Pytorch Implementation of "Cross-Domain Transferability of Adversarial Perturbations" (NeurIPS 2019) [arXiv link](https://arxiv.org/abs/1905.11736). 5 | 6 | ### Table of Contents 7 | 1) [Highlights](#Highlights) 8 | 2) [Usage](#Usage) 9 | 3) [Pretrained-Generators](#Pretrained-Generators) 10 | 4) [How to Set-Up Data](#Datasets) 11 | 5) [Training/Eval](#Training) 12 | 6) [Create-Adversarial-Dataset](#Create-Adversarial-Dataset) 13 | 7) [Citation](#Citation) 14 | 15 | 16 | ## Highlights 17 | 18 | 1. The transferability of adversarial examples makes real-world attacks possible in black-box settings, 19 | where the attacker is forbidden to access the internal parameters of the model. we propose a framework capable of launching highly transferable attacks that crafts adversarial patterns to mislead networks trained on different domains. The core of our proposed adversarial function is a generative network that is trained using a relativistic supervisory signal that enables domain-invariant perturbation. 20 | 2. We mainly focus on image classfication task but you can use our pretrained adversarial generators to test robustness of your model regardless of the task (Image classification, Segmentation, Object Detection etc.) 21 | 3. You don't need any particular setup (label etc.) to generate adversaries using our method. You can generate adversarial images of any size for any image dataset of your choice (see how to set-up data directory below). 22 | 23 | ![Learning Algo](/assets/cross_distribution.png) 24 | 25 | ## Usage 26 | #### Dependencies 27 | 1. Install [pytorch](https://pytorch.org/). 28 | 2. Install python packages using following command: 29 | ``` 30 | pip install -r requirements.txt 31 | ``` 32 | #### Clone the repository. 33 | ``` 34 | git clone https:https://github.com/Muzammal-Naseer/Cross-domain-perturbations.git 35 | cd Cross-domain-perturbations 36 | ``` 37 | 38 | ## Pretrained-Generators 39 | Download pretrained adversarial generators from [here](https://drive.google.com/open?id=1H_o90xtHYbK7M3bMMm_RtwTtCdDmaPJY) to 'saved_models' folder. 40 | 41 | Adversarial generators are trained against following four models. 42 | * ResNet152 43 | * Inceptionv3 44 | * VGG19 45 | * VGG16 46 | 47 | These models are trained on ImageNet and available in Pytorch. 48 | 49 | ## Datasets 50 | * Training data: 51 | * [ImageNet](http://www.image-net.org/) Training Set. 52 | * [Paintings](https://www.kaggle.com/c/painter-by-numbers) 53 | * [Comics](https://www.kaggle.com/cenkbircanoglu/comic-books-classification) 54 | * [Chexnet](https://stanfordmlgroup.github.io/projects/chexnet/) 55 | * You can try your own data set as well. 56 | 57 | * Evaluations data: 58 | * [ImageNet](http://www.image-net.org/) Validation Set (50k images). 59 | * [Subset](https://github.com/LiYingwei/Regional-Homogeneity/tree/master/data) of ImageNet validation set (5k images). 60 | * [NeurIPS dataset](https://www.kaggle.com/c/nips-2017-non-targeted-adversarial-attack) (1k images). 61 | * You can try your own dataset as well. 62 | 63 | * Directory structure should look like this: 64 | ``` 65 | |Root 66 | |ClassA 67 | img1 68 | img2 69 | ... 70 | |ClassB 71 | img1 72 | img2 73 | ... 74 | ``` 75 | ## Training 76 |

Run the following command 77 | 78 | ``` 79 | python train.py --model_type res152 --train_dir paintings --eps 10 80 | ``` 81 | This will start trainig a generator trained on Paintings (--train_dir) against ResNet152 (--model_type) under perturbation budget 10 (--eps) with relativistic supervisory signal.

82 | ## Evaluations 83 |

Run the following command 84 | 85 | ``` 86 | python eval.py --model_type res152 --train_dir imagenet --test_dir ../IN/val --epochs 0 --model_t vgg19 --eps 10 --measure_adv --rl 87 | ``` 88 | This will load a generator trained on ImageNet (--train_dir) against ResNet152 (--model_type) and evaluate clean and adversarial accuracy of VGG19 (--model_t) under perturbation budget 10 (--eps).

89 | 90 | 91 | ## Create-Adversarial-Dataset 92 |

If you need to save adversaries for visualization or adversarial training, run the following command: 93 | 94 | ``` 95 | python generate_and_save_adv.py --model_type incv3 --train_dir paintings --test_dir 'your_data/' --eps 255 96 | ``` 97 | You should see beautiful images (unbounded adversaries) like this: 98 | ![unbounded adversaries](/assets/adv_unbound_paintings_incv3.jpg) 99 | 100 | ## Citation 101 | If you find our work, this repository and pretrained adversarial generators useful. Please consider giving a star :star: and citation. 102 | ``` 103 | @article{naseer2019cross, 104 | title={Cross-domain transferability of adversarial perturbations}, 105 | author={Naseer, Muhammad Muzammal and Khan, Salman H and Khan, Muhammad Haris and Shahbaz Khan, Fahad and Porikli, Fatih}, 106 | journal={Advances in Neural Information Processing Systems}, 107 | volume={32}, 108 | pages={12905--12915}, 109 | year={2019} 110 | } 111 | ``` 112 | ## Contact 113 | Muzammal Naseer - muzammal.naseer@anu.edu.au 114 |
115 | Suggestions and questions are welcome! 116 | 117 | 118 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | import os 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | import torchvision 8 | import torch 9 | import torch.nn as nn 10 | import torch.optim as optim 11 | from torch.utils.data import DataLoader 12 | import torchvision.datasets as datasets 13 | import torchvision.transforms as transforms 14 | 15 | from utils import * 16 | 17 | parser = argparse.ArgumentParser(description='Cross Data Transferability') 18 | parser.add_argument('--train_dir', default='paintings', help='paintings, comics, imagenet') 19 | parser.add_argument('--batch_size', type=int, default=15, help='Number of trainig samples/batch') 20 | parser.add_argument('--epochs', type=int, default=10, help='Number of training epochs') 21 | parser.add_argument('--lr', type=float, default=0.0002, help='Initial learning rate for adam') 22 | parser.add_argument('--eps', type=int, default=10, help='Perturbation Budget') 23 | parser.add_argument('--model_type', type=str, default='incv3', 24 | help='Model against GAN is trained: vgg16, vgg19, incv3, res152') 25 | parser.add_argument('--attack_type', type=str, default='img', help='Training is either img/noise dependent') 26 | parser.add_argument('--target', type=int, default=-1, help='-1 if untargeted') 27 | args = parser.parse_args() 28 | print(args) 29 | 30 | # Normalize (0-1) 31 | eps = args.eps/255 32 | 33 | # GPU 34 | device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') 35 | 36 | #################### 37 | # Model 38 | #################### 39 | if args.model_type == 'vgg16': 40 | model = torchvision.models.vgg16(pretrained=True) 41 | elif args.model_type == 'vgg19': 42 | model = torchvision.models.vgg19(pretrained=True) 43 | elif args.model_type == 'incv3': 44 | model = torchvision.models.inception_v3(pretrained=True) 45 | elif args.model_type == 'res152': 46 | model = torchvision.models.resnet152(pretrained=True) 47 | model = model.to(device) 48 | model.eval() 49 | 50 | # Input dimensions 51 | if args.model_type in ['vgg16', 'vgg19', 'res152']: 52 | scale_size = 256 53 | img_size = 224 54 | else: 55 | scale_size = 300 56 | img_size = 299 57 | 58 | 59 | # Generator 60 | if args.model_type == 'incv3': 61 | netG = GeneratorResnet(inception=True) 62 | else: 63 | netG = GeneratorResnet() 64 | netG.to(device) 65 | 66 | # Optimizer 67 | optimG = optim.Adam(netG.parameters(), lr=args.lr, betas=(0.5, 0.999)) 68 | 69 | # Data 70 | data_transform = transforms.Compose([ 71 | transforms.Resize(scale_size), 72 | transforms.CenterCrop(img_size), 73 | transforms.ToTensor(), 74 | ]) 75 | 76 | mean = [0.485, 0.456, 0.406] 77 | std = [0.229, 0.224, 0.225] 78 | def normalize(t): 79 | t[:, 0, :, :] = (t[:, 0, :, :] - mean[0])/std[0] 80 | t[:, 1, :, :] = (t[:, 1, :, :] - mean[1])/std[1] 81 | t[:, 2, :, :] = (t[:, 2, :, :] - mean[2])/std[2] 82 | 83 | return t 84 | 85 | train_dir = args.train_dir 86 | train_set = datasets.ImageFolder(train_dir, data_transform) 87 | train_loader = torch.utils.data.DataLoader(train_set, batch_size=args.batch_size, shuffle=True, num_workers=4, pin_memory=True) 88 | train_size = len(train_set) 89 | print('Training data size:', train_size) 90 | 91 | 92 | # Loss 93 | criterion = nn.CrossEntropyLoss() 94 | 95 | 96 | #################### 97 | # Set-up noise if required 98 | #################### 99 | if args.attack_type == 'noise': 100 | noise = np.random.uniform(0, 1, img_size * img_size * 3) 101 | # Save noise 102 | np.save('saved_models/noise_{}_{}_{}_rl'.format(args.target, args.model_type, args.train_dir), noise) 103 | im_noise = np.reshape(noise, (3, img_size, img_size)) 104 | im_noise = im_noise[np.newaxis, :, :, :] 105 | im_noise_tr = np.tile(im_noise, (args.batch_size, 1, 1, 1)) 106 | noise_tensor_tr = torch.from_numpy(im_noise_tr).type(torch.FloatTensor).to(device) 107 | 108 | 109 | # Training 110 | print('Label: {} \t Attack: {} dependent \t Model: {} \t Distribution: {} \t Saving instances: {}'.format(args.target, args.attack_type, args.model_type, args.train_dir, args.epochs)) 111 | for epoch in range(args.epochs): 112 | running_loss = 0 113 | for i, (img, _) in enumerate(train_loader): 114 | img = img.to(device) 115 | 116 | # whatever the model think about the input 117 | label = model(normalize(img.clone().detach())).argmax(dim=-1).detach() 118 | 119 | if args.target == -1: 120 | targte_label = torch.LongTensor(img.size(0)) 121 | targte_label.fill_(args.target) 122 | targte_label = targte_label.to(device) 123 | 124 | netG.train() 125 | optimG.zero_grad() 126 | 127 | if args.attack_type == 'noise': 128 | adv = netG(noise_tensor_tr) 129 | else: 130 | adv = netG(img) 131 | 132 | # Projection 133 | adv = torch.min(torch.max(adv, img - eps), img + eps) 134 | adv = torch.clamp(adv, 0.0, 1.0) 135 | 136 | if args.target == -1: 137 | # Gradient accent (Untargetted Attack) 138 | adv_out = model(normalize(adv)) 139 | img_out = model(normalize(img)) 140 | 141 | loss = -criterion(adv_out-img_out, label) 142 | 143 | else: 144 | # Gradient decent (Targetted Attack) 145 | # loss = criterion(model(normalize(adv)), targte_label) 146 | loss = criterion(model(normalize(adv)), targte_label) + criterion(model(normalize(img)), label) 147 | loss.backward() 148 | optimG.step() 149 | 150 | if i % 10 == 9: 151 | print('Epoch: {0} \t Batch: {1} \t loss: {2:.5f}'.format(epoch, i, running_loss / 100)) 152 | running_loss = 0 153 | running_loss += abs(loss.item()) 154 | 155 | torch.save(netG.state_dict(), 'saved_models/netG_{}_{}_{}_{}_{}_rl.pth'.format(args.target, args.attack_type, args.model_type, args.train_dir, epoch)) 156 | 157 | # Save noise 158 | if args.attack_type == 'noise': 159 | # Save transformed noise 160 | t_noise = netG(torch.from_numpy(im_noise).type(torch.FloatTensor).to(device)) 161 | t_noise_np = np.transpose(t_noise[0].detach().cpu().numpy(), (1,2,0)) 162 | f = plt.figure() 163 | plt.imshow(t_noise_np, interpolation='spline16') 164 | plt.xticks([]) 165 | plt.yticks([]) 166 | #plt.show() 167 | f.savefig('saved_models/noise_transformed_{}_{}_{}_{}_rl'.format(args.target, args.model_type, args.train_dir, epoch) + ".pdf", bbox_inches='tight') 168 | np.save('saved_models/noise_transformed_{}_{}_{}_{}_rl'.format(args.target, args.model_type, args.train_dir, epoch), t_noise_np) 169 | --------------------------------------------------------------------------------