使用CNN在MNIST上实现简单的攻击样本

王 茂南 2019年5月28日07:14:18
评论
12840字阅读42分48秒
摘要关于测试MNIST,并且生成一些攻击样本。尝试一下这个方法。

简单原理

其实这个的原理和CNN可视化Convolutional Features很相似,简单来说,要看某个filter在检测什么,我们让通过其后的输出激活值较大。想要一些样本,有一个简单的想法就是让最后结果中是某一类的概率值变大,接着进行反向传播(固定住网络的参数),去修改input. 下面有一个简单的图示:

使用CNN在MNIST上实现简单的攻击样本

下面看一下具体的实验.

具体实现

训练一个分类网络

下面的方法是一套比较通用的写法,训练别的网络的时候也是可以加以参考的。

首先我们训练一个CNN网络,可以用于手写数字的分类。首先导入需要使用的库。

  1. import time
  2. import csvos
  3. import numpy as np
  4. import pandas as pd
  5. import matplotlib.pyplot as plt
  6. import cv2
  7. from cv2 import resize
  8. import torch
  9. import torch.nn as nn
  10. import torch.nn.functional as F
  11. from torch.utils.data.sampler import SubsetRandomSampler
  12. from torch.autograd import Variable
  13. import torch.utils.data as Data
  14. from sklearn import preprocessing
  15. import copycopy
  16. import torchvision
  17. import torchvision.transforms as transforms

接着定义绘制loss图像的函数

  1. # ---------------
  2. # Visual The Loss
  3. # ---------------
  4. def draw_loss_acc(train_list,validation_list,mode='Loss'):
  5.     plt.style.use('seaborn')
  6.     fig = plt.figure(figsize=(10,5))
  7.     ax = fig.add_subplot(1, 1, 1)
  8.     # 设置间隔
  9.     data_len = len(train_list)
  10.     x_ticks = np.arange(1,data_len+1)
  11.     plt.xticks(x_ticks)
  12.     if mode == 'Loss':
  13.         plt.plot(x_ticks,train_list,label='Train Loss')
  14.         plt.plot(x_ticks,validation_list,label='Validation Loss')
  15.         plt.xlabel('Epoch')
  16.         plt.ylabel('Loss')
  17.         plt.legend()
  18.         plt.savefig('Epoch_loss.jpg')
  19.     elif mode == 'Accuracy':
  20.         plt.plot(x_ticks,train_list,label='Train Accuracy')
  21.         plt.plot(x_ticks,validation_list,label='Validation Accuracy')
  22.         plt.xlabel('Epoch')
  23.         plt.ylabel('Accuracy')
  24.         plt.legend()
  25.         plt.savefig('Epoch_Accuracy.jpg')

接着定义训练时需要使用的函数

  1. # ----------------
  2. # Train the model
  3. # ----------------
  4. def train_model(model, criterion, optimizer, dataloaders, train_datalengths, scheduler=None, num_epochs=2):
  5.     """传入的参数分别是:
  6.     1. model:定义的模型结构
  7.     2. criterion:损失函数
  8.     3. optimizer:优化器
  9.     4. dataloaders:training dataset
  10.     5. train_datalengths:train set和validation set的大小, 为了计算准确率
  11.     6. scheduler:lr的更新策略
  12.     7. num_epochs:训练的epochs
  13.     """
  14.     since = time.time()
  15.     # 保存最好一次的模型参数和最好的准确率
  16.     best_model_wts = copycopy.deepcopy(model.state_dict())
  17.     best_acc = 0.0
  18.     train_loss = [] # 记录每一个epoch后的train的loss
  19.     train_acc = []
  20.     validation_loss = []
  21.     validation_acc = []
  22.     for epoch in range(num_epochs):
  23.         print('Epoch [{}/{}]'.format(epoch+1, num_epochs))
  24.         print('-' * 10)
  25.         # Each epoch has a training and validation phase
  26.         for phase in ['train', 'val']:
  27.             if phase == 'train':
  28.                 if scheduler != None:
  29.                     scheduler.step()
  30.                 model.train()  # Set model to training mode
  31.             else:
  32.                 model.eval()   # Set model to evaluate mode
  33.             running_loss = 0.0 # 这个是一个epoch积累一次
  34.             running_corrects = 0 # 这个是一个epoch积累一次
  35.             # Iterate over data.
  36.             total_step = len(dataloaders[phase])
  37.             for i, (inputs, labels) in enumerate(dataloaders[phase]):
  38.                 # inputs = inputs.reshape(-1, 28*28).to(device)
  39.                 inputs = inputs.to(device)
  40.                 labels = labels.to(device)
  41.                 # zero the parameter gradients
  42.                 optimizer.zero_grad()
  43.                 # forward
  44.                 # track history if only in train
  45.                 with torch.set_grad_enabled(phase == 'train'):
  46.                     outputs = model(inputs)
  47.                     _, preds = torch.max(outputs, 1) # 使用output(概率)得到预测
  48.                     loss = criterion(outputs, labels) # 使用output计算误差
  49.                     # backward + optimize only if in training phase
  50.                     if phase == 'train':
  51.                         loss.backward()
  52.                         optimizer.step()
  53.                 # statistics
  54.                 running_loss += loss.item() * inputs.size(0)
  55.                 running_corrects += torch.sum(preds == labels.data)
  56.                 if (i+1)%100==0:
  57.                     # 这里相当于是i*batch_size的样本个数打印一次, i*100
  58.                     iteration_loss = loss.item()/inputs.size(0)
  59.                     iteration_acc = 100*torch.sum(preds == labels.data).item() / len(preds)
  60.                     print ('Mode {}, Epoch [{}/{}], Step [{}/{}], Accuracy: {}, Loss: {:.4f}'.format(phase, epoch+1, num_epochs, i+1, total_step, iteration_acc, iteration_loss))
  61.             epoch_loss = running_loss / train_datalengths[phase]
  62.             epoch_acc = running_corrects.double() / train_datalengths[phase]
  63.             if phase == 'train':
  64.                 train_loss.append(epoch_loss)
  65.                 train_acc.append(epoch_acc)
  66.             else:
  67.                 validation_loss.append(epoch_loss)
  68.                 validation_acc.append(epoch_acc)
  69.             print('*'*10)
  70.             print('Mode: [{}], Loss: {:.4f}, Acc: {:.4f}'.format(
  71.                 phase, epoch_loss, epoch_acc))
  72.             print('*'*10)
  73.             # deep copy the model
  74.             if phase == 'val' and epoch_acc > best_acc:
  75.                 best_acc = epoch_acc
  76.                 best_model_wts = copycopy.deepcopy(model.state_dict())
  77.         print()
  78.     time_elapsed = time.time() - since
  79.     print('*'*10)
  80.     print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
  81.     print('Best val Acc: {:4f}'.format(best_acc))
  82.     print('*'*10)
  83.     # load best model weights
  84.     final_model = copycopy.deepcopy(model) # 最后得到的model
  85.     model.load_state_dict(best_model_wts) # 在验证集上最好的model
  86.     draw_loss_acc(train_list=train_loss,validation_list=validation_loss,mode='Loss') # 绘制Loss图像
  87.     draw_loss_acc(train_list=train_acc,validation_list=validation_acc,mode='Accuracy') # 绘制准确率图像
  88.     return (model,final_model)

定义全局变量,learning_rate与num_epochs等.

  1. # --------------------
  2. # Device configuration
  3. # --------------------
  4. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  5. # ----------------
  6. # Hyper-parameters 
  7. # ----------------
  8. num_classes = 10
  9. num_epochs = 3
  10. batch_size = 100
  11. validation_split = 0.05 # 每次训练集中选出10%作为
  12. learning_rate = 0.001

接着我们进行加载数据.

  1. # -------------
  2. # MNIST dataset 
  3. # -------------
  4. train_dataset = torchvision.datasets.MNIST(root='./',
  5.                                         train=True,
  6.                                         transform=transforms.ToTensor(),
  7.                                         download=True)
  8. test_dataset = torchvision.datasets.MNIST(root='./',
  9.                                         train=False,
  10.                                         transform=transforms.ToTensor())
  11. # -----------
  12. # Data loader
  13. # -----------
  14. test_len = len(test_dataset) # 计算测试集的个数
  15. test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
  16.                                         batch_size=batch_size,
  17.                                         shuffle=False)
  18. for (inputs,labels) in test_loader:
  19.     print(inputs.size())
  20.     print(labels.size())
  21.     break
  22. # ------------------
  23. # 下面切分validation
  24. # ------------------
  25. dataset_len = len(train_dataset)
  26. indices = list(range(dataset_len))
  27. # Randomly splitting indices:
  28. val_len = int(np.floor(validation_split * dataset_len)) # validation的长度
  29. validation_idx = np.random.choice(indices, size=val_len, replace=False# validatiuon的index
  30. train_idx = list(set(indices) - set(validation_idx)) # train的index
  31. ## Defining the samplers for each phase based on the random indices:
  32. train_sampler = SubsetRandomSampler(train_idx)
  33. validation_sampler = SubsetRandomSampler(validation_idx)
  34. train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
  35.                                             sampler=train_sampler,
  36.                                             batch_size=batch_size)
  37. validation_loader = torch.utils.data.DataLoader(train_dataset,
  38.                                             sampler=validation_sampler,
  39.                                             batch_size=batch_size)
  40. train_dataloaders = {"train": train_loader, "val": validation_loader} # 使用字典的方式进行保存
  41. train_datalengths = {"train": len(train_idx), "val": val_len} # 保存train和validation的长度

模型的定义与模型的初始化.

  1. # -------------------------------------------------------
  2. # Convolutional neural network (two convolutional layers)
  3. # -------------------------------------------------------
  4. class ConvNet(nn.Module):
  5.     def __init__(self, num_classes=10):
  6.         super(ConvNet, self).__init__()
  7.         self.layer1 = nn.Sequential(
  8.             nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, stride=1, padding=2),
  9.             nn.BatchNorm2d(16),
  10.             nn.ReLU(),
  11.             nn.MaxPool2d(kernel_size=2, stride=2))
  12.         self.layer2 = nn.Sequential(
  13.             nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1, padding=2),
  14.             nn.BatchNorm2d(32),
  15.             nn.ReLU(),
  16.             nn.MaxPool2d(kernel_size=2, stride=2))
  17.         self.fc = nn.Linear(7*7*32, num_classes)
  18.     def forward(self, x):
  19.         out = self.layer1(x)
  20.         out = self.layer2(out)
  21.         out = out.reshape(out.size(0), -1)
  22.         out = self.fc(out)
  23.         return out
  24. # 模型初始化
  25. model = ConvNet(num_classes=num_classes).to(device)

定义损失函数与优化器,进行训练

  1. # -------------------
  2. # Loss and optimizer
  3. # ------------------
  4. criterion = nn.CrossEntropyLoss()
  5. optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
  6. # -------------
  7. # 进行模型的训练
  8. # -------------
  9. (best_model,final_model) = train_model(model=model,criterion=criterion,optimizer=optimizer,dataloaders=train_dataloaders,train_datalengths=train_datalengths,num_epochs=num_epochs)
使用CNN在MNIST上实现简单的攻击样本

我们测试一下模型的效果。选取test中的第一个数据,为7.

使用CNN在MNIST上实现简单的攻击样本

接着带入模型进行预测,查看模型的结果。

  1. # 带入模型进行预测
  2. inputdata = test_dataset.data[0].view(1,1,28,28).float()/255
  3. inputdata = inputdata.to(device)
  4. outputs = model(inputdata)
  5. outputs
使用CNN在MNIST上实现简单的攻击样本
  1. # 查看预测结果
  2. torch.max(outputs, 1)
使用CNN在MNIST上实现简单的攻击样本

控制输出的概率, 看输出是什么

这里我们希望做的是:

  1. 初始随机一张图片
  2. 我们希望让分类中某个数的概率最大
  3. 最后看一下这个网络认为什么样的图像是这个数字

所以最终的步骤大概如下所示

  1. 初始化一张图片, 28*28
  2. 使用上面训练好的网络,固定网络参数;
  3. 最后一层因为是10个输出(相当于是概率), 我们loss设置为某个的负的概率值
  4. 梯度下降, 使负的概率下降,即相对应的概率值上升,我们最终修改的是初始的图片
  1. # hook住模型
  2. layer = 2
  3. activations = SaveFeatures(list(model.children())[layer])
  4. # 超参数
  5. lr = 0.05 # 学习率
  6. opt_steps = 100 # 迭代次数
  7. upscaling_factor = 10 # 放大的倍数(为了最后图像的保存)
  8. # 保存迭代后的数字
  9. true_nums = []
  10. # 带入网络进行迭代
  11. for true_num in range(0,10):
  12.     # 初始化随机图片(数据定义和优化器一定要在一起)
  13.     # 定义数据
  14.     sz = 28
  15.     img = np.uint8(np.random.uniform(0, 255, (1, sz, sz)))/255
  16.     img = torch.from_numpy(img[None]).float().to(device)
  17.     img_var = Variable(img, requires_grad=True)
  18.     # 定义优化器
  19.     optimizer = torch.optim.Adam([img_var], lr=lr, weight_decay=1e-6)
  20.     for n in range(opt_steps):  # optimize pixel values for opt_steps times
  21.         optimizer.zero_grad()
  22.         model(img_var) # 正向传播
  23.         loss = -(activations.features[0, true_num]-activations.features[0].mean()) # loss相当于最大该层的激活的值
  24.         # loss = -activations.features[0, true_num]
  25.         loss.backward()
  26.         optimizer.step()
  27.     # 打印最后的img的样子
  28.     print(activations.features[0, true_num])
  29.     print(activations.features[0])
  30.     print('========')
  31.     img = img_var.cpu().clone()
  32.     img = img.squeeze(0)
  33.     # 图像的裁剪(确保像素值的范围)
  34.     img=1
  35.     img=0
  36.     true_nums.append(img)
  37.     unloader = transforms.ToPILImage()
  38.     img = unloader(img)
  39.     img = cv2.cvtColor(np.asarray(img),cv2.COLOR_RGB2BGR)
  40.     sz = int(upscaling_factor * sz)  # calculate new image size
  41.     img = cv2.resize(img, (sz, sz), interpolation = cv2.INTER_CUBIC)  # scale image up
  42.     cv2.imwrite('num_{}.jpg'.format(true_num),img)
  43. # 移除hook
  44. activations.close()

我们看最后输出的两个的效果,分别如下所示。

使用CNN在MNIST上实现简单的攻击样本

我们将生成的图片,重新放回进行预测,查看预测的结果:

使用CNN在MNIST上实现简单的攻击样本

可以看到,除了第一个数字0会预测错误,其他都是能正确分类。关于第一个分类错误的原因,是由于我们进行了像素的裁剪(小于0的等于0,大于1的等于1)

让正确的图片分类错误

这里的步骤基本是和上面相同的,除了初始化的步骤不相同。上面使用随机图片进行初始化,这里使用特定数字的图片进行初始化

  1. 使用特定数字的图片,如数字7(初始化方式与前面的不一样)
  2. 使用上面训练好的网络,固定网络参数;
  3. 最后一层因为是10个输出(相当于是概率), 我们loss设置为某个的负的概率值
  4. 梯度下降, 使负的概率下降,即相对应的概率值上升,我们最终修改的是初始的图片
  5. 这样使得网络认为这张图片识别的数字的概率增加
  1. # hook住模型
  2. layer = 2
  3. activations = SaveFeatures(list(model.children())[layer])
  4. # 超参数
  5. lr = 0.005 # 学习率
  6. opt_steps = 70 # 迭代次数
  7. upscaling_factor = 10 # 放大的倍数(为了最后图像的保存)
  8. # 保存迭代后的数字
  9. true_nums = []
  10. # 带入网络进行迭代
  11. for true_num in range(0,10):
  12.     # 初始化随机图片(数据定义和优化器一定要在一起)
  13.     # 定义数据
  14.     sz = 28
  15.     # 将7变成0,1,2,3,4,5,6,7,8,9
  16.     img = test_dataset.data[0].view(1,1,28,28).float()/255
  17.     img = img.to(device)
  18.     img_var = Variable(img, requires_grad=True)
  19.     # 定义优化器
  20.     optimizer = torch.optim.Adam([img_var], lr=lr, weight_decay=1e-6)
  21.     for n in range(opt_steps):  # optimize pixel values for opt_steps times
  22.         optimizer.zero_grad()
  23.         model(img_var) # 正向传播
  24.         # 这里的loss确保某个值的输出大, 并且与原图不会相差很多
  25.         loss = -activations.features[0, true_num] + F.mse_loss(img_var,img)
  26.         loss.backward()
  27.         optimizer.step()
  28.     # 打印最后的img的样子
  29.     print(activations.features[0, true_num])
  30.     print(activations.features[0])
  31.     print('========')
  32.     img = img_var.cpu().clone()
  33.     img = img.squeeze(0)
  34.     # 图像的裁剪(确保像素值的范围)
  35.     img=1
  36.     img=0
  37.     true_nums.append(img)
  38.     unloader = transforms.ToPILImage()
  39.     img = unloader(img)
  40.     img = cv2.cvtColor(np.asarray(img),cv2.COLOR_RGB2BGR)
  41.     sz = int(upscaling_factor * sz)  # calculate new image size
  42.     img = cv2.resize(img, (sz, sz), interpolation = cv2.INTER_CUBIC)  # scale image up
  43.     cv2.imwrite('real7_regonize{}.jpg'.format(true_num),img)
  44. # 移除hook
  45. activations.close()

我们看一下最终的效果,最终会在原始的7的图片上进行修改,得到下面的图片,使得网络分类错误。

使用CNN在MNIST上实现简单的攻击样本

其实大致的思想都是类似的,都是固定网络参数,希望输出的某个概率最大,于是进行反向传播,更改输入的值,达到想要的效果。

代码仓库 : MNIST攻击样本

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2019年5月28日07:14:18
  • 转载请务必保留本文链接:https://mathpretty.com/10499.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: