自动驾驶模型下的对抗攻击代码解析
本文的代码来源是 :自动驾驶模型下的对抗性攻击和防御【文献阅读】
data.py数据集
导入第三方库的包。这里基本上没什么问题,有什么问题报错就pip什么包就可以。
import numpy as np
import pandas as pd
import torch
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
from skimage import io, transform
from torchvision import transforms, utils
from torch.utils.data import DataLoader, Dataset
import os
import random
from scipy import ndimage
import cv2
# from scipy.misc import imread, imresize
import matplotlib.pyplot as plt
# 目前下面这个包还有点疑问。
import viewer
接下来是创建concatDataset类这个类别主要是创建了源类。我通过代码的主要功能来看是将数据集转换为数组的形式。
class ConcatDataset(Dataset):
def __init__(self, *datasets):
self.datasets = datasets
def __getitem__(self, i):
# 将数据集转换为数组的形式
return tuple(d[i] for d in self.datasets)
def __len__(self):
# 求出数据集的长度
return min(len(d) for d in self.datasets)
接下来是创建AdvDataset类,主要是用来对数据集进行细致的分类。
class AdvDataset(Dataset):
def __init__(self, root_dir, hmb_list, transform=None, type_='train'):
# 有文件目录root_dir,HMB类型的列表(就是不同种类的数据集)hum_list.
self.root_dir = root_dir
self.hmb_list = hmb_list
# 数据集的类型:训练集or测试集?
self.type = type_
# 获取数据集
self.data = self.get_data()
self.transform = transform
def get_data(self):
#数据集转数组?
data_info = []
#训练集的后续步骤
if self.type == 'train':
#在我们有hmb_list列表(数组)的情况下,来获取我们的数据集。
for hmb in self.hmb_list:
#使用pandas读取我们的目录索引表(也就是实验代码中的HMBX.csv文件)
df = pd.read_csv(hmb + '.csv')
#作用是提取到里面第一个文件名,也就是HMB1.csv文件中的1479424215880976321
start_name = int(df['image_name'][0][:-4])
#如果提供的实验目录中有'opt_attack'目录的话
if 'opt_attack' in self.root_dir:
#将每64行的文件名.npy字符串放入到数组name_list里面。也就是[1479424215880976321.npy,...]
name_list = [str(start_name + i*64) + '.npy' for i in range(len(df))]
else:
#否则是每行都要放入。
name_list = [str(start_name + i) + '.npy' for i in range(len(df))]
#同时将文件中的所有image_name索引内容改为name_list数组内容,换句话说就是将1479424215880976321.png转为1479424215880976321.npy
df['image_name'] = name_list
#将整个文件放入到一个大的数组里面。
data_info.append(df)
# 返回一个数据连接(换句话说就是我们可以通过二维索引来精确每一个值)
return pd.concat(data_info)
#测试集的后续步骤(同理)找到相关索引目录,并将文件列表的数据改为.npy的数据。
elif self.type == 'test':
df = pd.read_csv('ch2_final_eval.csv')
start_name = int(df['frame_id'][0])
name_list = [str(start_name + i) + '.npy' for i in range(len(df))]
df['frame_id'] = name_list
# df['frame_id'] = df['frame_id'].apply(str)
return df
def __len__(self):
# return len(self.images)
#返回训练或测试数据集的长度
return len(self.data)
def __getitem__(self, idx):
#设置集合???
sample = {}
# image_name = str(self.images.iloc[idx, 4][7:])
#训练集
if self.type == 'train':
#我的猜测是读取到数据索引表中第一行的索引idx值
image_name = self.data.iloc[idx, 0]
#第二行
steer = self.data.iloc[idx, 1]
#第三行
hmb = self.data.iloc[idx, 2]
# # hmb = self.images.iloc[idx, 1]
#创建从图片路径(self.root_dir\hmb\image_name)
img_address = os.path.join(self.root_dir, hmb, image_name)
# image = io.imread(img_address)
#为打开.npy文件
image = np.load(img_address)
# print(steer)
#创建样本集,图像:和转向角。
sample = {'image': image, 'steer': np.array([steer])}
else:
# 测试集同理
image_name = self.data.iloc[idx, 0]
steer = self.data.iloc[idx, 1]
img_address = os.path.join(self.root_dir, self.hmb_list[0], 'npy', image_name)
image = np.load(img_address)
sample = {'image': image, 'steer': np.array([steer])}
# 这个目前我还不太清楚是干什么的?》???
# 对样本进行变换
if self.transform:
sample = self.transform(sample)
return sample
接下来也是我们最重要的步骤,创建Udacity数据集类,这是获取数据集的关键
class UdacityDataset(Dataset):
def __init__(self, root_dir, hmb_list, transform=None, type_='train'):
#和上一个创建类一样(同理)
self.root_dir = root_dir
self.hmb_list = hmb_list
self.type = type_
self.data = self.get_data()
# self.images = self.create_image_list()
# print(len(self.images))
# self.steering = self.create_steering_list()
# self.filter_images()
# print(len(self.images))
# self.images, self.steering = self.create_image_and_steering_list()
self.transform = transform
def create_image_list(self):
image_csv_list = []
for hmb in self.hmb_list:
df = pd.read_csv(os.path.join(self.root_dir, hmb, 'camera.csv'))
df = df[df['filename'].str.contains('center')]
df['frame_id'] = df['frame_id'].apply(str)
df['HMB'] = hmb.upper()
image_csv_list.append(df)
return pd.concat(image_csv_list)
def create_steering_list(self):
steering_csv_list = []
if self.type == 'train':
for hmb in self.hmb_list:
df = pd.read_csv(os.path.join(self.root_dir, hmb, 'steering.csv'))
df['timestamp'] = df['timestamp'].apply(str)
steering_csv_list.append(df)
return pd.concat(steering_csv_list)
elif self.type == 'test':
df = pd.read_csv('ch2_final_eval.csv')
df['frame_id'] = df['frame_id'].apply(str)
return df
def get_data(self):
data_info = []
if self.type == 'train':
for hmb in self.hmb_list:
df = pd.read_csv(hmb + '.csv')
data_info.append(df)
return pd.concat(data_info)
elif self.type == 'test':
df = pd.read_csv('ch2_final_eval.csv')
df['frame_id'] = df['frame_id'].apply(str)
return df
def create_image_and_steering_list(self):
image_csv_list = []
for hmb in self.hmb_list:
df = pd.read_csv(os.path.join(self.root_dir, hmb, 'camera.csv'))
df = df[df['filename'].str.contains('center')]
df['frame_id'] = df['frame_id'].apply(str)
df['HMB'] = hmb.upper()
image_csv_list.append(df)
image_list = pd.concat(image_csv_list)
steering_csv_list = []
for hmb in self.hmb_list:
df = pd.read_csv(os.path.join(self.root_dir, hmb, 'steering.csv'))
df['timestamp'] = df['timestamp'].apply(str)
steering_csv_list.append(df)
steer_list = pd.concat(steering_csv_list)
# new_image_list = pd.DataFrame({'image_name':[], 'HMB':[]})
return image_list, steer_list
def __len__(self):
# return len(self.images)
return len(self.data)
def __getitem__(self, idx):
sample = {}
# image_name = str(self.images.iloc[idx, 4][7:])
if self.type == 'train':
image_name = self.data.iloc[idx, 0]
steer = self.data.iloc[idx, 1]
hmb = self.data.iloc[idx, 2]
# # hmb = self.images.iloc[idx, 1]
img_address = os.path.join(self.root_dir, hmb, 'center', image_name)
image = io.imread(img_address)
# print(steer)
sample = {'image': image, 'steer': np.array([steer])}
elif self.type == 'test':
image_name = self.data.iloc[idx, 0] + '.jpg'
steer = self.data.iloc[idx, 1]
img_address = os.path.join(self.root_dir, self.hmb_list[0], 'center', image_name)
image = io.imread(img_address)
sample = {'image': image, 'steer': np.array([steer])}
if self.transform:
sample = self.transform(sample)
return sample
对抗样本训练过程adv_training文件
首先是调用第三方库,不用多解释
#验证函数
def exp(net, model_name, attack, test_dataset, device):
#设置训练模型
original_net = None
#设置训练数据集大小为128*128的
image_size = (128, 128)
if model_name == 'baseline':
original_net = BaseCNN()
elif model_name == 'nvidia':
original_net = Nvidia()
elif model_name == 'vgg16':
original_net = Vgg16()
# 将训练好的模型权重,加到新的模型中,也就是original_net中
original_net.load_state_dict(torch.load(model_name + '.pt'))
# 将模型放置在处理器上(GPU上)
original_net = original_net.to(device)
# 模型的验证函数,在进行测试数据集跑的时候,这个是必加的。model.eval() 负责改变batchnorm、dropout的工作方式,如在eval()模式下,dropout是不工作的。
original_net.eval()
# print(ast_ori, ast_dist)
#获取测试集中的转向角的值。
test_y = pd.read_csv('ch2_final_eval.csv')['steering_angle'].values
#图像预处理过程:用于将多个图像转换步骤组合在一起,使其可以按顺序执行.Rescale对图片进行缩放处理,Preprocess(这个我不太清楚是什么意思),ToTensor进行归一化处理也就是将像素[0,255]缩放到[0,1]
test_composed = transforms.Compose([Rescale(image_size), Preprocess('baseline'), ToTensor()])
#调用UdacityDataset类,(图片位置路径,HMB_list,测试图像预处理,测试集)
test_dataset = UdacityDataset(dataset_path, ['testing'], test_composed, 'test')
# 批量加载数据,(要加载的数据、每个批次的数据量、加载时要不要被打乱。)
test_generator = DataLoader(test_dataset, batch_size=64, shuffle=False)
#这个设置目标0.3,我不清楚是什么意思????
target = 0.3
# 下面的先展示不解释。。。。。。。。。。。。。。。。。。。。。
ast_ori, _ = fgsm_ex(test_generator, original_net, model_name, target, device, len(test_dataset))
ast_dist,_ = fgsm_ex(test_generator, net, model_name, target, device, len(test_dataset))
print('fgsm:', ast_ori, ast_dist)
advt_model = model_name + '_' + attack
ast_ori, _ = advGAN_ex(test_generator, original_net, model_name, target, device, len(test_dataset))
ast_dist,_ = advGAN_ex(test_generator, net, advt_model, target, device, len(test_dataset))
print('advGAN:', ast_ori, ast_dist)
advt_model = model_name + '_' + attack
ast_ori, _ = advGAN_uni_ex(test_generator, original_net, model_name, target, device, len(test_dataset))
ast_dist,_ = advGAN_uni_ex(test_generator, net, advt_model, target, device, len(test_dataset))
print('advGAN_uni:', ast_ori, ast_dist)
advt_model = model_name + '_' + attack
ast_ori, _ = opt_uni_ex(test_generator, original_net, model_name, target, device, len(test_dataset))
ast_dist,_ = opt_uni_ex(test_generator, net, advt_model, target, device, len(test_dataset))
print('opt_uni:', ast_ori, ast_dist)
ast_ori, _ = opt_ex(test_dataset, original_net, model_name, target, device, len(test_dataset))
ast_dist,_ = opt_ex(test_dataset, net, model_name, target, device, len(test_dataset))
print('opt:', ast_ori, ast_dist)
训练过程的主函数
# 设置训练批次
batch_size = 32
# 设置学习率
lr = 0.0001
# 设置训练轮数
epochs = 15
# train all models
train = 1
test = 0
#重新定义训练图像的高度和宽度。
resized_image_height = 128
resized_image_width = 128
image_size=(resized_image_width, resized_image_height)
#配置模型运行环境(GPU or CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 设置模型数组,方便后续更改。
models_name = ['baseline','nvidia','vgg16']
# 定义训练数据集位置,位置可以自定义修改
adv_datasets = '../udacity-data/adv_data'
attacks = ['fgsm_attack']
# 数据集位置
dataset_path = '../udacity-data/'
if test:
#好像数据集有5614张
full_indices = list(range(5614))
#随机的从数据集中选择20%作为测试集、剩下的为训练集、并进行图像预处理
test_indices = list(np.random.choice(5614, int(0.2*5614), replace=False))
train_indices = list(set(full_indices).difference(set(test_indices)))
test_composed = transforms.Compose([Rescale((image_size[1],image_size[0])), Preprocess(), ToTensor()])
#获取到测试集的数据
full_dataset = UdacityDataset(dataset_path, ['testing'], test_composed, type_='test')
#从完整数据集中创建出一个子数据集作为训练集和测试集。
train_dataset = torch.utils.data.Subset(full_dataset, train_indices)
test_dataset = torch.utils.data.Subset(full_dataset, test_indices)
# 定义训练模型为Vgg
net = Vgg16()
# net.load_state_dict(torch.load('adv_training_models/' + 'nvidia' + '_' + 'fgsm_attack' + '.pt'))
#加载已经训练好的模型、并对模型进行验证
net.load_state_dict(torch.load('adv_training_models/vgg16_fgsm_attack.pt'))
net = net.to(device)
net.eval()
#test_on_gen(net, 'baseline', dataset_path, 'fgsm', device)
exp(net, 'vgg16', 'fgsm_attack', test_dataset, device)
#(这里的训练模型是把原始数据集和对抗数据集一同训练了,不过这两部分数据集是相同的,只是一次训练了两边。)
if train:
full_indices = list(range(5614))
test_indices = list(np.random.choice(5614, int(0.2*5614), replace=False))
train_indices = list(set(full_indices).difference(set(test_indices)))
test_composed = transforms.Compose([Rescale((image_size[1],image_size[0])), Preprocess(), ToTensor()])
full_dataset = UdacityDataset(dataset_path, ['testing'], test_composed, type_='test')
#请注意,这里的训练数据集不是用于训练模型的数据集,而是进行训练测试对抗样本攻击的训练集。
train_dataset = torch.utils.data.Subset(full_dataset, train_indices)
test_dataset = torch.utils.data.Subset(full_dataset, test_indices)
for model_name in models_name:
if model_name == 'baseline':
net = BaseCNN()
elif model_name == 'nvidia':
net = Nvidia()
elif model_name == 'vgg16':
net = Vgg16()
net = net.to(device)
for attack in attacks:
print(model_name, attack)
# 更新数据集路径,并创建其路径文件
adv_dataset_path = adv_datasets + '/' + model_name + '/' + attack
if not os.path.exists(adv_dataset_path):
os.mkdir(adv_dataset_path)
#对模型名字进行更新
advt_model = model_name + '_' + attack
if train != 0:
#是训练,但已经有训练好的模型
if train == 2:
net.load_state_dict(torch.load('adv_training_models/' + advt_model + '.pt'))
#net.load_state_dict(torch.load(model_name + '.pt'))
#第一次进行训练。
else:
#图像预处理
composed = transforms.Compose([Rescale(image_size), RandFlip(), RandRotation(), Preprocess(model_name), ToTensor()])
#设置训练图像数据集。
dataset = UdacityDataset(dataset_path, ['HMB1', 'HMB2', 'HMB4', 'HMB5','HMB6'], composed)
# adv_composed = transforms.Compose([RandFlip(), RandRotation(), Preprocess(model_name), ToTensor()])
#设置对抗数据集
adv_dataset = AdvDataset(adv_dataset_path, ['HMB1', 'HMB2', 'HMB4', 'HMB5','HMB6'])
#将两数据集合二为一变为数组
concat_dataset = ConcatDataset(dataset, adv_dataset)
#需要进行训练的轮次
steps_per_epoch = int(len(concat_dataset) / batch_size)
#数据集的加载,num_workers=8: 这定义了加载数据时使用的子进程数量
train_generator = DataLoader(concat_dataset, batch_size=batch_size, shuffle=True, num_workers=8)
# 定义损失函数,是一个计算两个输入张量之间的L1距离(即它们之间的绝对值差的均值)的损失函数。
criterion = nn.L1Loss()
#定义优化器
optimizer = optim.Adam(net.parameters(), lr=lr)
#一共15轮
for epoch in range(epochs):
total_loss = 0
#从train_generator数据加载中获得的,enumerate(train_generator): enumerate是一个内置的Python函数,它接受一个可迭代对象(在这里是train_generator)并返回一个新的迭代器。其中step也就代表了我们当前的迭代轮次。sample_batched是我们从train_generator里获取到的数据集批次。
for step, sample_batched in enumerate(train_generator):
if step <= steps_per_epoch:
# sample_batched[0]代表了一般数据集,[1]是对抗数据集。
# x表示图像,y表示转向角
batch1_x = sample_batched[0]['image']
batch1_y = sample_batched[0]['steer']
# 数据张量的数据类型改变,改变为浮点张量
batch1_x = batch1_x.type(torch.FloatTensor)
batch1_y = batch1_y.type(torch.FloatTensor)
#将数据集放入运行环境中
batch1_x = batch1_x.to(device)
batch1_y = batch1_y.to(device)
#对抗数据集也是如此
batch2_x = sample_batched[1]['image']
batch2_y = sample_batched[1]['steer']
batch2_x = batch2_x.type(torch.FloatTensor)
batch2_y = batch2_y.type(torch.FloatTensor)
batch2_x = batch2_x.to(device)
batch2_y = batch2_y.to(device)
#通过用于将多个张量按指定的维度连接在一起,在这行代码中表示的是第一个维度(0)通常表示批量大小。其含义就是是将batch2_x的所有元素添加到batch1_x的末尾,从而形成一个新的、更大的批次。
batch_x = torch.cat((batch1_x, batch2_x), 0)
batch_y = torch.cat((batch1_y, batch2_y), 0)
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
# 放入模型中得到输出结果。
outputs = net(batch_x)
#求出模型损失函数
loss = criterion(outputs, batch_y)
#清除旧的梯度,防止它们在后续迭代中累积(因为你求出的损失梯度是一个累计的形式而不是替换的。所以必须要保证每次迭代要清楚梯度。)
optimizer.zero_grad()
#(关键操作)自动计算损失相对于模型参数的梯度。这是神经网络训练中反向传播算法的核心部分。
loss.backward()
#更新模型参数的关键步骤,这是基于先前计算的梯度来执行的
optimizer.step()
#提取到loss的数值,用于添加损失。
running_loss = loss.item()
#总损失。
total_loss += running_loss
else:
break
print('Epoch %d RMSE loss: %.4f' % (epoch, total_loss / steps_per_epoch))
#将训练好的模型放入到.pt文件当中进行保存。
torch.save(net.state_dict(), 'adv_training_models/' + advt_model + '.pt')
模型验证
#(这一步还是在attack的循环当中)
#设置模型评估模式
net.eval()
#它暂时禁用了自动梯度计算,确保在其内部执行的操作不会跟踪计算图,并因此不会计算梯度(含义是不会影响权重更新。)
with torch.no_grad():
#这个表示为模型对测试输出集的输出结果。
yhat = []
# test_y = []
#获取测试集的输出标签值
test_y = pd.read_csv('ch2_final_eval.csv')['steering_angle'].values
#测试数据集的预处理
test_composed = transforms.Compose([Rescale(image_size), Preprocess('baseline'), ToTensor()])
# 获取测试数据集。
test_dataset_ = UdacityDataset(dataset_path, ['testing'], test_composed, 'test')
#同上
test_generator = DataLoader(test_dataset_, batch_size=1, shuffle=False)
for _,sample_batched in enumerate(test_generator):
batch_x = sample_batched['image']
batch_y = sample_batched['steer']
batch_x = batch_x.type(torch.FloatTensor)
batch_y = batch_y.type(torch.FloatTensor)
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
output = net(batch_x)
#测试数据集的输出结果。
yhat.append(output.item())
#将数组变为张量
yhat = np.array(yhat)
#(相当于求损失函数RMSE)
rmse = np.sqrt(np.mean((yhat-test_y)**2))
print(rmse)
plt.figure(figsize = (32, 8))
plt.plot(test_y, 'r.-', label='target')
plt.plot(yhat, 'b^-', label='predict')
plt.legend(loc='best')
plt.title("RMSE: %.2f" % rmse)
# plt.show()
#画出目标和预测标签并求出相应的损失。
model_fullname = "%s_%s_%d.png" % (model_name, attack, int(time.time()))
# plt.savefig(model_fullname)
#下面是通过已经训练好的模型进行对抗样本攻击。
#这个步骤是设置是否是对抗样本的阈值。。。。
target = 0.3
# generate opt uni noise on advt_model
print('Start universal attack training')
#在对抗样本训练集中添加扰动,
#这里的generate_noise请在《通用对抗攻击中》阅读
perturbation = generate_noise(train_dataset, net, advt_model, device, target)
#将扰动数值保存在'advt_mode'_universal_attack_noise.npy文件中
np.save(advt_model + '_universal_attack_noise', perturbation.detach().cpu().numpy())
print('Finish universal attack training.')
# advGAN training
#获取到训练好的模型位置。
target_model_path = 'adv_training_models/' + advt_model + '.pt'
#if not os.path.exists('./models/' + advt_model + '_netG_epoch_60.pth'):
print('Start advGAN training')
# 对抗模型名称、对抗模型位置路径,扰动阈值,训练数据集。
advGAN = advGAN_Attack(model_name, target_model_path, target + 0.2, train_dataset)
torch.save(advGAN.netG.state_dict(), './models/' + advt_model + '_netG_epoch_60.pth')
print('Finish advGAN training')
# advGAN_uni training
#if not os.path.exists('./models/' + advt_model + '_universal_netG_epoch_60.pth'):
print('Start advGAN_uni training')
advGAN_uni = advGAN_Attack(advt_model,target_model_path, target + 0.2, train_dataset, universal=True)
torch.save(advGAN_uni.netG.state_dict(), './models/' + advt_model + '_universal_netG_epoch_60.pth')
print('Finish advGAN_uni training')
exp(net, model_name, attack, test_dataset, device)
基于优化通用对抗攻击(optimization_universal_attack)
def generate_noise(dataset, model, model_name, device, target):
#调用通用攻击函数,(训练数据集、模型、运行环境、目标)【我们返回出我们成功被误分类的扰动值】
perturbation = universal_attack(dataset, model, device, target)
# 从计算图中分离、移动到CPU(如果它原本在GPU上),并最终将其转换为NumPy数组。
perturbation = perturbation.detach().cpu().numpy()
return perturbation
# 其中np.inf表示正无穷大。。。。。。。。。。。。。。
def universal_attack(dataset, model, device, target, delta=0.3, max_iters = np.inf, xi=10, p=np.inf, max_iter_lbfgs=30):
# 这个可能就是添加的扰动值
v = 0
#扰动率
fooling_rate = 0.0
# 数据集的长度,或者说数量
num_images = len(dataset)
#同上,数据集,处理数据集大小,是否随机(这里是对每一帧的图片进行的处理。)
dataloader = torch.utils.data.DataLoader(dataset,1,True)
itr = 0
#当扰动率<1-0.3=0.7时,则一直运行。
while fooling_rate < 1-delta and itr < max_iters:
# np.random.shuffle(dataset)
print('Starting pass number: ', itr)
#同上。。。。。
for k, data in enumerate(dataloader):
cur_img = data['image']
cur_img = cur_img.type(torch.FloatTensor)
cur_img = cur_img.to(device)
#对原始图片添加扰动
perturbed_image = cur_img + v
#确保你的perturbed_image张量中的所有值都位于指定范围内,也就是[0,1]
perturbed_image = torch.clamp(perturbed_image, 0, 1)
perturbed_image = perturbed_image.to(device)
#片段是否为对抗样本
temp = is_adversary(model, cur_img, perturbed_image, target)
#如果不是对抗样本
if not temp[0]:
#可以使用优化攻击来优化添加噪声的幅度,并将原来的噪声进行修改。【具体请看基于优化对抗攻击的方案。】
_, d_noise, _, _ = optimized_attack(model, temp[1], perturbed_image, device)
v = v + d_noise
#这一步的目的是将扰动v限定在xi之内。
v = proj_lp(v, xi, p)
v = v.to(device)
# 进入下一次迭代
itr += 1
count = 0
for _, data in enumerate(dataloader):
cur_img = data['image']
cur_img = cur_img.type(torch.FloatTensor)
cur_img = cur_img.to(device)
perturbed_image = cur_img + v
perturbed_image = torch.clamp(perturbed_image, 0, 1)
perturbed_image = perturbed_image.to(device)
if (is_adversary(model, cur_img, perturbed_image, target)[0]):
#成功实施扰动攻击的图片个数
count += 1
# 扰动率
fooling_rate = count / num_images
print('Fooling rate: ', fooling_rate)
# demension of v : (1, 3, image_size)
return v
def is_adversary(model, x, x_adv, target):
#求出原始数据集的预测标签和对抗数据集的预测标签
y_pred = model(x).item()
y_adv = model(x_adv).item()
# 求出两个预测结果差距的绝对值是否大于我们设定的阈值 target=0.3,如果没有,则两个预测差距与我们设定的阈值之间的差距。
if (abs(y_adv - y_pred) >= abs(target)):
return [True]
else:
return [False, target - (y_adv - y_pred)]
def proj_lp(v, xi, p):
# Project on the lp ball centered at 0 and of radius xi
# 投影到以 0 为中心、半径为 xi 的 lp 球上
# 求出扰动值。
v_ = v.detach().cpu().numpy()
# SUPPORTS only p = 2 and p = Inf for now
# 目前只支持p=2或P=Inf(这两个过程是一样的,就是代码不相同。)
if p == 2:
#将扰动的大小,限制在xi之内。v_.flatten(1):将扰动转换为一个以为数组,然后再求这个数组的L2范数。(简单理解:如果v_>xi,则将v_值变为xi,如果v_<xi,则扰动值不变。)
v_ = v_ * min(1, xi/np.linalg.norm(v_.flatten(1)))
# v = v / np.linalg.norm(v.flatten(1)) * xi
elif p == np.inf:
#将扰动的大小,限制在xi之内,这里我们设置的是xi=10。其中np.sign()表示求出array数组里面的数字的符合,正数为1,负数为-1.
v_ = np.sign(v_) * np.minimum(abs(v_), xi)
else:
raise ValueError('Values of p different from 2 and Inf are currently not supported...')
return torch.from_numpy(v_)
基于优化对抗攻击(optimization_attack)
# 前文调用_, d_noise, _, _ = optimized_attack(model, temp[1], perturbed_image, device)
#我们指导,temp[1]就是我们成功扰动所需要的扰动值的大小,也就是target值。
def optimized_attack(target_model, target, x, device):
# 也就是图像模型前向传递数据--也就是计算得到的预测值
# 这一步是我们将原来加过一部分扰动,但没有照成攻击成功的输入图片放入模型预测。
y_pred = target_model(x)
y_adv = y_pred
# if y_pred > -0.1:
# y_target = y_pred - target
# else:
# y_target = y_pred + target
#加入所需的扰动值,得到想要的一个目标标签。
y_target = y_pred + target
#创建一个与输入张量x具有相同形状和数据类型的新张量,但是所有元素都被初始化为'0'
perturb = torch.zeros_like(x)
#requires_grad是张量的一个属性,指示是否需要对这个张量计算梯度。如果一个张量的requires_grad属性设置为True,那么在反向传播时,PyTorch将会计算这个张量的梯度,这意味着这个张量涉及的任何操作都会被记录在计算图中。
#简言之,就是将张量perturb设置为需要梯度,通常意味着perturb是一个你希望在优化过程中更新的变量
perturb.requires_grad = True
perturb = perturb.to(device)
# 这段代码创建了一个Adam优化器实例,用于更新和优化perturb张量。在PyTorch中,优化器通常用于神经网络的权重更新,但它们也可以用于优化其他需要梯度的张量。
optimizer = optim.Adam(params=[perturb], lr=0.005)
diff = 0
# while abs(diff) < abs(target):
for i in range(100):
#这里就变得很好理解了,不再细讲(不过要注意,刚开始并没有添加任何扰动,还是原来图片本身自带的扰动)
perturbed_image = x + perturb
perturbed_image = torch.clamp(perturbed_image, 0, 1)
#得出模型预测结果
y_adv = target_model(perturbed_image)
optimizer.zero_grad()
# 求出模型预测结果与想要的一个结果的损失。
loss_y = F.mse_loss(y_adv, y_target)
# 这段代码计算了perturb张量的元素的平方的均值,这通常称为L2范数的平方或平方欧几里得距离(但未取平方根)。具体来说,每个元素都被平方,然后取所有元素的均值。用来当中扰动的损失。
loss_n = torch.mean(torch.pow(perturb, 2))
#扰动的总损失。
loss_adv = loss_y + loss_n
#执行反向传播,根据loss_adv计算其关联的计算图所有可训练参数的梯度。retain_graph=True是告诉PyTorch不要释放计算图的内存,从而允许你再次对它进行反向传播。需要注意的是,这会消耗更多的内存。
loss_adv.backward(retain_graph=True)
#这是神经网络训练循环中的核心步骤,直接影响到模型权重的更新。
optimizer.step()
#求出现在模型预测结果和刚开始时候的模型预测结果的差值,如果是大于目标差距的,说明优化成功。
diff = y_adv.item() - y_pred.item()
if abs(diff) >= abs(target):
break
# print(diff, target)
# 可以返回出,扰动图片,需要再加入的扰动值,刚开始和优化后的预测结果。
return perturbed_image, perturb, y_pred, y_adv
基于GAN的对抗攻击(advGAN_attack)
#判断通用性
def advGAN_Attack(model_name, target_model_path, target, train_dataset, universal=False):
#目前不清楚一下到底是什么意思。
image_nc=3
epochs = 60
#batch_size = 64
BOX_MIN = 0
BOX_MAX = 1
# target = 0.2
# Define what device we are using
print("CUDA Available: ",torch.cuda.is_available())
device = torch.device("cuda" if (torch.cuda.is_available()) else "cpu")
image_size = (128, 128)
#确定一下使用的模型。
if 'baseline' in target_model_path:
targeted_model = BaseCNN().to(device)
#image_size = (128, 128)
elif 'nvidia' in target_model_path:
targeted_model = Nvidia().to(device)
#image_size = (66, 200)
elif 'vgg16' in target_model_path:
targeted_model = Vgg16().to(device)
#image_size = (224, 224)
#加载到训练好的模型
targeted_model.load_state_dict(torch.load(target_model_path))
targeted_model.eval()
# 设置GAN的对抗性攻击
if not universal:
advGAN = AdvGAN_Attack(
device,
targeted_model,
model_name,
target,
image_nc,
BOX_MIN,
BOX_MAX)
else:
advGAN = AdvGAN_Uni_Attack(
device,
targeted_model,
model_name,
image_size,
target,
image_nc,
BOX_MIN,
BOX_MAX)
# GAN训练
advGAN.train(train_dataset, epochs)
return advGAN
advGAN
class AdvGAN_Attack:
def __init__(self,
device,
model,
model_name,
# model_num_labels,
target,
image_nc,
box_min,
box_max):
#目前不知道,image_nc=3
output_nc = image_nc
#将外来数据导入到类中。
self.device = device
self.target = target
# self.model_num_labels = model_num_labels
self.model = model
self.model_name = model_name
#设置输入通道数
self.input_nc = image_nc
#输出通道数
self.output_nc = output_nc
self.box_min = box_min
self.box_max = box_max
#可能表示生成器的输入通道数
self.gen_input_nc = image_nc
#创建模型生成器
self.netG = models.Generator(self.gen_input_nc, image_nc, self.model_name).to(device)
#创建模型鉴别器
self.netDisc = models.Discriminator(image_nc).to(device)
# initialize all weights
#进行权重的初始化。
self.netG.apply(weights_init)
self.netDisc.apply(weights_init)
# initialize optimizers
#进行优化器的初始化。
self.optimizer_G = torch.optim.Adam(self.netG.parameters(),
lr=0.001)
self.optimizer_D = torch.optim.Adam(self.netDisc.parameters(),
lr=0.001)
if not os.path.exists(models_path):
os.makedirs(models_path)
权重的初始化
def weights_init(m):
classname = m.__class__.__name__
# 它的目的是为卷积层(Conv层)的权重设置特定的初始值。
if classname.find('Conv') != -1:
#为指定的张量,m.weight.data进行正态分布或高斯分布的初始化。0.0和0.02分别是正态分布的均值和标准差。
nn.init.normal_(m.weight.data, 0.0, 0.02)
# 的目的是为批量归一化(BatchNorm)层设置特定的初始值。这种特定的初始化策略是基于批量归一化的性质,可以帮助模型更快地收敛。
elif classname.find('BatchNorm') != -1:
nn.init.normal_(m.weight.data, 1.0, 0.02)
#将一些偏置张量初始化为0
nn.init.constant_(m.bias.data, 0)
训练轮次
def train_batch(self, x, y):
# optimize D
#只运行一轮
for i in range(1):
# 将生成器生成的东西作为扰动。
perturbation = self.netG(x)
# add a clipping trick
#先将生成器生成的扰动限制在[-0.3,0.3]之间,并将扰动添加到原始图片当中,形成对抗样本,然后进行第二步处理。
adv_images = torch.clamp(perturbation, -0.3, 0.3) + x
adv_images = torch.clamp(adv_images, self.box_min, self.box_max)
#使用鉴别器的优化器来优化这个添加了扰动的图片与原始图片之间的损失。
self.optimizer_D.zero_grad()
#求出真实的预测结果
pred_real = self.netDisc(x)
# 计算了鉴别器(通常用于生成对抗网络(GAN)中)对真实数据的预测和真实标签之间的均方误差(MSE)损失。以下是详细的步骤解释:torch.ones_like()创建了一个与pred_real具有相同形状的张量,并且所有元素都设置为1。在GAN的上下文中,真实数据通常与标签1关联,而生成数据与标签0关联。计算了鉴别器对真实数据的预测(pred_real)与实际的“真实”标签(即全1的张量)之间的均方误差。这通常用于在GAN的训练中更新鉴别器的权重
loss_D_real = F.mse_loss(pred_real, torch.ones_like(pred_real, device=self.device))
#执行反向传播命令,进而计算关联计算图中所有可训练的参数梯度。
loss_D_real.backward()
#鉴别器上执行前向传递。这个前向传递是对一组可能被篡改或生成的图像进行的.detach()的功能:将张量从当前的计算图中分离出来,使其不被进一步的计算来用于梯度计算。(只使用该张量的值,而不保留器计算历史。)代码的含义是这段代码通过鉴别器模型对一组可能被篡改的图像进行了预测,并将这些预测结果存储在pred_fake中。
pred_fake = self.netDisc(adv_images.detach())
loss_D_fake = F.mse_loss(pred_fake, torch.zeros_like(pred_fake, device=self.device))
loss_D_fake.backward()
#将真实数据的预测和实际真实标签的预测损失 与 对抗扰动的预测和实际对抗扰动预测损失求和。
loss_D_GAN = loss_D_fake + loss_D_real
#优化
self.optimizer_D.step()
# optimize G
#对于生成器来说
for i in range(1):
self.optimizer_G.zero_grad()
# cal G's loss in GAN
#和上段内容一样
pred_fake = self.netDisc(adv_images)
loss_G_fake = F.mse_loss(pred_fake, torch.ones_like(pred_fake, device=self.device))
# 启动反向传播来计算梯度,并确保计算图在反向传播后仍然存在。
loss_G_fake.backward(retain_graph=True)
# calculate perturbation norm
C = 0.1
#一个张量(名为perturbation)中每个元素的L2范数(或欧几里得范数)的平均值
#perturbation被重新整形为一个形状为[batch_size, -1]的二维张量
# 在某些深度学习应用中,这种损失可能用于约束或正则化perturbation的大小,确保它不会过大。
loss_perturb = torch.mean(torch.norm(perturbation.view(perturbation.shape[0], -1), 2, dim=1))
#将扰动图片放入到之前已经训练好的模型中
pred_steer = self.model(adv_images)
#求模型的预测结果与真值的均方误差损失。
loss_adv = F.mse_loss(pred_steer, y)
adv_lambda = 500
pert_lambda = 1
loss_G = adv_lambda * loss_adv + pert_lambda * loss_perturb
loss_G.backward()
self.optimizer_G.step()
# print(loss_D_GAN.item(), loss_G_fake.item(), loss_perturb.item(), loss_adv.item())
return loss_D_GAN.item(), loss_G_fake.item(), loss_perturb.item(), loss_adv.item()*loss_adv.item()
FGSM攻击(fgsm_attack)
def fgsm_attack(model, image, target, device, epsilon=0.01, image_size=(128, 128)):
# 也就是图像模型前向传递数据--也就是计算得到的预测值
# 真实图像的模型预测角度
steer = model(image)
# 创建一个张量的深拷贝,也就是保留原始图像的同时,对其进行修改或篡改。
perturbed_image = image.clone()
# steer = steer.type(torch.FloatTensor)
# if (steer.item() > -0.1):
# target_steer = steer + target
# else:
# target_steer = steer - target
# 预测值与真实结果之间的差距target为我们扰动的阈值。
#也就是将预测结果添加阈值大小的扰动 = 形成了想要的目标预测结果
target_steer = steer - target
target_steer = target_steer.to(device)
# 开始跟踪针对该张量的所有操作,一边后续可以计算自动梯度计算。
image.requires_grad = True
# 真实图像的输出结果。这个和steer有什么区别?
output = model(image)
adv_output = output.clone()
diff = 0
# while abs(diff) < abs(target):
for i in range(5):
#求出真实图像输出结果与添加扰动的想要输出结果的损失。
loss = F.mse_loss(adv_output, target_steer)
model.zero_grad()
loss.backward(retain_graph=True)
#获得图像的梯度数据
image_grad = image.grad.data
#进行基于梯度下降的对抗攻击(换句话说就是对图像进行微调扰动)
perturbed_image = fgsm_attack_fun(perturbed_image, epsilon, image_grad)
#输出添加过扰动的对抗输出结果。
adv_output = model(perturbed_image)
#判断对抗输出和原始输出之间的差距的绝对值。
diff = abs(adv_output.detach().cpu().numpy() - output.detach().cpu().numpy())
#在这里我们是得到了在训练了5轮后,所添加的扰动(噪声)到底是多少。
noise = torch.clamp(perturbed_image - image, 0, 1)
# 返回转向角差距(结果差距)、扰动图片、真实图片的转向角、对抗输出、添加的招生。
return diff, perturbed_image, steer, adv_output, noise
def fgsm_attack_fun(image, epsilon, data_grad):
"""
:param image: 需要攻击的图像
:param epsilon: 扰动值的范围
:param data_grad: 图像的梯度
:return: 扰动后的图像
"""
# 收集数据梯度的元素符号(真值为1,负值为-1,0为0)
sign_data_grad = data_grad.sign()
# 通过调整输入图像的每个像素来创建扰动图像
perturbed_image = image + epsilon * sign_data_grad
# (我认为是一种归一化的表示形式)用来将扰动图像维持在[0,1]的范围中。
perturbed_image = torch.clamp(perturbed_image, 0, 1)
return perturbed_image
train训练文件
if __name__ == "__main__":
# 参数设置:模型训练
parser = argparse.ArgumentParser(description='Model training.')
parser.add_argument("--model_name", type=str, default="baseline")
parser.add_argument("--root_dir", type=str)
parser.add_argument("--batch_size", type=int, default=32)
parser.add_argument("--epochs", type=int, default=100)
parser.add_argument("--train", type=int, default=1)
parser.add_argument("--lr", type=float, default=0.0001)
args = parser.parse_args()
model_name = args.model_name
# 应该是设置摄像机的位置。
camera = 'center'
# 轮次
batch_size = args.batch_size
# 学习率
lr = args.lr
# 轮次
epochs = args.epochs
# 设置模型是否开始训练 1:可以开始训练;0:不能训练
train = args.train
# 重新定义图片高度和宽度(128*128)
resized_image_height = 0
resized_image_width = 0
resized_image_height = 128
resized_image_width = 128
# 设置图片的长和宽
image_size=(resized_image_width, resized_image_height)
# 设置下载数据集的位置。
dataset_path = args.root_dir
# 设置机器学习设备:如果有cuda显卡则使用英伟达显卡来跑代码,否则使用cpu来跑代码
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# print(device)
# 判断模型名称是什么??从而选择使用哪个模型(网络)。
if model_name == 'baseline':
net = BaseCNN()
elif model_name == 'nvidia':
net = Nvidia()
elif model_name == 'vgg16':
net = Vgg16()
# model.apply(fn):将会递归地将函数fn应用到父模块地每个子模块当中
net.apply(weight_init)
# 将模型放置在设备中,以便后期训练使用。
net = net.to(device)
# net.to(device)
if train != 0:
if train == 2:
# 用来加载一个模型地参数字典,使模型恢复到之前训练好地状态,
# 可以用来在训练过程中中断后继续训练,或者在预测过程中加载训练好的模型
net.load_state_dict(torch.load(model_name + '.pt'))
# 串联多个步骤整合在一起,其中它的参数有如下
composed = transforms.Compose([Rescale(image_size), RandFlip(), RandRotation(), Preprocess(model_name), ToTensor()])
# 调用Udacity数据集
dataset = UdacityDataset(dataset_path, ['HMB1', 'HMB2', 'HMB4', 'HMB5','HMB6'], composed)
# 每个步骤的预处理批次
steps_per_epoch = int(len(dataset) / batch_size)
# 数据读取机制
train_generator = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=8)
# 计算平均绝对误差(MAE)。计算方法很简单,取预测值和真实值的绝对误差的平均数即可
criterion = nn.L1Loss()
# criterion = nn.MSELoss()
if model_name == 'vgg16':
# 模型优化。
optimizer = optim.Adam(net.parameters(), lr=lr)
else:
optimizer = optim.Adam(net.parameters(), lr=lr)
#开始正式训练(这里面没有攻击,只是训练好预测模型,说明攻击在预测之后)
for epoch in range(epochs):
total_loss = 0
# 在数据读取块中分为每一论和每一轮中的样本批次
for step, sample_batched in enumerate(train_generator):
if step <= steps_per_epoch:
# 图像
batch_x = sample_batched['image']
# print(batch_x.numpy())
# 标签结果
batch_y = sample_batched['steer']
batch_x = batch_x.type(torch.FloatTensor)
batch_y = batch_y.type(torch.FloatTensor)
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
outputs = net(batch_x)
loss = criterion(outputs, batch_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss = loss.item()
total_loss += running_loss
else:
break
print('Epoch %d RMSE loss: %.4f' % (epoch, total_loss / steps_per_epoch))
#将预训练好的模型保存下来。
torch.save(net.state_dict(), model_name + '_.pt')
else:
net.load_state_dict(torch.load(model_name + '.pt'))
# 模型进行验证
net.eval()
with torch.no_grad():
yhat = []
# test_y = []
#下面的内容我就不再多解释了,和前文相似。
test_y = pd.read_csv('ch2_final_eval.csv')['steering_angle'].values
test_composed = transforms.Compose([Rescale(image_size), Preprocess('baseline'), ToTensor()])
test_dataset = UdacityDataset(dataset_path, ['testing'], test_composed, 'test')
test_generator = DataLoader(test_dataset, batch_size=1, shuffle=False)
for _,sample_batched in enumerate(test_generator):
batch_x = sample_batched['image']
# print(batch_x.size())
# print(batch_x.size())
batch_y = sample_batched['steer']
# print(batch_y)
batch_x = batch_x.type(torch.FloatTensor)
batch_y = batch_y.type(torch.FloatTensor)
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
output = net(batch_x)
yhat.append(output.item())
yhat = np.array(yhat)
#求出验证损失,并进行画图。
rmse = np.sqrt(np.mean((yhat-test_y)**2))
print(rmse)
plt.figure(figsize = (32, 8))
plt.plot(test_y, 'r.-', label='target')
plt.plot(yhat, 'b^-', label='predict')
plt.legend(loc='best')
plt.title("RMSE: %.2f" % rmse)
# plt.show()
model_fullname = "%s_%d.png" % (model_name, int(time.time()))
attack_test攻击测试
#这个过程主要是在一张图片上显示出各个攻击的攻击效果图。
def exp1_fig():
model = BaseCNN()
model_name = 'baseline'
#加载预训练模型
model.load_state_dict(torch.load('baseline.pt'))
device = torch.device("cuda" if (torch.cuda.is_available()) else "cpu")
model = model.to(device)
model.eval()
target = 0.3
#读取图片并进行相应切片
image = imread(
'F:\\udacity-data\\testing\\center\\1479425441182877835.jpg')[200:, :]
image = imresize(image, (128, 128))
image = image / 255.
#将NumPy类型转换为Pytorch张量类型,transpose(),执行了数组的转置操作,将通道维度(channels)放在了数组的最前面,然后是高度(height)和宽度(width)
image = torch.from_numpy(image.transpose((2, 0, 1))).unsqueeze(0)
image =image.type(torch.FloatTensor)
image = image.to(device)
output = model(image)
print(output)
advGAN_generator = Generator(3, 3, model_name).to(device)
advGAN_uni_generator = Generator(3, 3, model_name).to(device)
# fgsm
_, perturbed_image_fgsm, _, adv_output_fgsm, noise_fgsm = fgsm_attack(model, image, target, device)
print('fgsm', adv_output_fgsm)
perturbed_image_fgsm = perturbed_image_fgsm.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
noise_fgsm = noise_fgsm.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
perturbed_image_fgsm = draw(perturbed_image_fgsm, adv_output_fgsm.item(), output.item())
perturbed_image_fgsm = imresize(perturbed_image_fgsm, (128, 128))
# opt
perturbed_image_opt, noise_opt, _, adv_output_opt = optimized_attack(
model, target, image, device)
perturbed_image_opt = perturbed_image_opt.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
print('opt', adv_output_opt)
noise_opt = noise_opt.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
perturbed_image_opt = draw(perturbed_image_opt, adv_output_opt.item(), output.item())
perturbed_image_opt = imresize(perturbed_image_opt, (128, 128))
# optu
noise_optu = np.load(model_name + '_universal_attack_noise.npy')
noise_optu = torch.from_numpy(noise_optu).type(torch.FloatTensor).to(device)
perturbed_image_optu = image + noise_optu
perturbed_image_optu = torch.clamp(perturbed_image_optu, 0, 1)
adv_output_optu = model(perturbed_image_optu)
print('universal', adv_output_optu)
perturbed_image_optu = perturbed_image_optu.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
noise_optu = noise_optu.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
perturbed_image_optu = draw(perturbed_image_optu, adv_output_optu.item(), output.item())
perturbed_image_optu = imresize(perturbed_image_optu, (128, 128))
# advGAN
advGAN_generator.load_state_dict(torch.load(
'./models/' + model_name + '_netG_epoch_60.pth'))
noise_advGAN = advGAN_generator(image)
perturbed_image_advGAN = image + torch.clamp(noise_advGAN, -0.3, 0.3)
perturbed_image_advGAN = torch.clamp(perturbed_image_advGAN, 0, 1)
adv_output_advGAN = model(perturbed_image_advGAN)
print('advGAN', adv_output_advGAN)
perturbed_image_advGAN = perturbed_image_advGAN.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
noise_advGAN = noise_advGAN.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
perturbed_image_advGAN = draw(perturbed_image_advGAN, adv_output_advGAN.item(), output.item())
perturbed_image_advGAN = imresize(perturbed_image_advGAN, (128, 128))
# advGAN_U
advGAN_uni_generator.load_state_dict(torch.load(
'./models/' + model_name + '_universal_netG_epoch_60.pth'))
noise_seed = np.load(model_name + '_noise_seed.npy')
noise_advGAN_U = advGAN_uni_generator(torch.from_numpy(
noise_seed).type(torch.FloatTensor).to(device))
perturbed_image_advGAN_U = image + torch.clamp(noise_advGAN_U, -0.3, 0.3)
perturbed_image_advGAN_U = torch.clamp(perturbed_image_advGAN_U, 0, 1)
adv_output_advGAN_U = model(perturbed_image_advGAN_U)
print('advGAN_uni', adv_output_advGAN_U)
perturbed_image_advGAN_U = perturbed_image_advGAN_U.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
noise_advGAN_U = noise_advGAN_U.squeeze(0).detach().cpu().numpy().transpose(1, 2, 0)
perturbed_image_advGAN_U = draw(perturbed_image_advGAN_U, adv_output_advGAN_U.item(), output.item())
perturbed_image_advGAN_U = imresize(perturbed_image_advGAN_U, (128, 128))