Pytorch CIFAR10图像分类 ZFNet篇

Posted 风信子的猫Redamancy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Pytorch CIFAR10图像分类 ZFNet篇相关的知识,希望对你有一定的参考价值。

Pytorch CIFAR10图像分类 ZFNet篇

文章目录


再次介绍一下我的专栏,很适合大家初入深度学习或者是Pytorch和Keras,希望这能够帮助初学深度学习的同学一个入门Pytorch或者Keras的项目和在这之中更加了解Pytorch&Keras和各个图像分类的模型。

他有比较清晰的可视化结构和架构,除此之外,我是用jupyter写的,所以说在文章整体架构可以说是非常清晰,可以帮助你快速学习到各个模块的知识,而不是通过python脚本一行一行的看,这样的方式是符合初学者的。

除此之外,如果你需要变成脚本形式,也是很简单的。
这里贴一下汇总篇:汇总篇

4. 定义网络(ZFNet)

ZFNet介绍

首先简单介绍一下ZFNet吧,ZFNet来源于2013的Matthew D. Zeiler和Rob Fergus的Visualizing and Understanding Convolutional Networks论文,为什么叫ZFNet也很简单,作者的两个名的首字母加起来就是啦,这里也给出论文地址,有兴趣可以看看论文https://arxiv.org/abs/1311.2901v3

在 2013 年 ImageNet 大规模视觉识别挑战赛 (ILSVRC) 中,ZFNet 比 AlexNet 有了显着改进,成为众人瞩目的焦点。 这篇论文是非常重要的基石,它提供了许多概念的起点,例如深度特征可视化、特征不变性、特征演化和特征重要性。

在我看来,他在深度学习初期描述了几个很重要的点

  • 提出了ZFNet,一种比AlexNet性能更好的网络架构
  • 与其在模型中不断试错,不如可视化了中间的feature maps,进行了深度特征可视化,并据此来分析和理解网络
  • 进行了许多的消融实验,让CNN的解释性更加形象化,还做了很多特征演化的实验,都是非常好的。
  • 探究了预训练大模型的泛化能力,可以进行fine tuning,类似于迁移学习

实际上来说,在近期的发展来说,我们也知道,海量数据,大模型是一种趋势,早在2013年的时候,实际上大家就已经知道预训练大模型,然后迁移到其他相关任务上是非常有用的,即使放在现在来看,我认为这些任务都是make sense的。

ZFNet结构

实际上的ZFNet是改进了一下AlexNet,第一个11x11的卷积核换为了7x7的卷积核,并且步长从4换成了2,为什么这样做呢,实际上是因为,作者通过对AlexNet的特征进行可视化,文章作者发现第2层出现了aliasing,有些卷积核是无效的,而换成7x7后,无效的卷积核少了,并且步长为4的时候,会出现一些网格状的feature map,这是不利于网络进行学习和判断的,所以也将步长为4换成了2。

最后可以总结一下ZFNet的改进

  • 第1个卷积层,kernel size从11减小为7,将stride从4减小为2(这将导致feature map增大1倍)
  • 为了让后续feature map的尺寸保持一致,第2个卷积层的stride从1变为2

判断是否使用GPU

首先我们还是得判断是否可以利用GPU,因为GPU的速度可能会比我们用CPU的速度快20-100倍左右,特别是对卷积神经网络来说,更是提升特别明显。

device = 'cuda' if torch.cuda.is_available() else 'cpu'

在定义的时候,为了和论文中的一样,虽然我这里不做反卷积的可视化,但是为了大家可视化ZFNet的feature map,我还是实现了论文中的Switch,他可以记录Maxpool后的最大值的位置,以方便反卷积的时候复原,恢复到原来的像素空间,到我们可以理解的空间中去

class ZFNet(nn.Module):
    def __init__(self, num_classes=10):
        super(ZFNet, self).__init__()

        self.features = nn.Sequential(
            # layer 1
            nn.Conv2d(3, 96, kernel_size=7, stride=2, padding=1),
            nn.ReLU(inplace=True),
            # Local contrast norm.
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1, return_indices=True), # return_indices – if True, will return the max indices along with the outputs. Useful for torch.nn.MaxUnpool2d later

            # layer 2
            nn.Conv2d(96, 256, kernel_size=5, stride=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1, return_indices=True),

            # layer 3
            nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),

            # layer 4
            nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),

            # layer 5
            nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, return_indices=True),
        )
        self.classifier = nn.Sequential(
            # layer 6
            nn.Linear(9216, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),

            # layer 7
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),

            nn.Linear(4096, num_classes),
        )

        self.feature_outputs = [0]*len(self.features)
        self.switch_indices = dict()
        self.sizes = dict()

    def forward(self, x):

        for i, layer in enumerate(self.features):
            if isinstance(layer, nn.MaxPool2d):
                x, indices = layer(x)
                self.feature_outputs[i] = x
                self.switch_indices[i] = indices
            else:
                x = layer(x)
                self.feature_outputs[i] = x

        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x
net = ZFNet(num_classes=10).to(device)

summary查看网络

我们可以通过summary来看到,模型的维度的变化,这个也是和论文是匹配的,经过层后shape的变化,是否最后也是输出(batch,shape)

summary(net,(2,3,224,224))
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
ZFNet                                    [2, 10]                   --
├─Sequential: 1-1                        --                        --
│    └─Conv2d: 2-1                       [2, 96, 110, 110]         14,208
│    └─ReLU: 2-2                         [2, 96, 110, 110]         --
│    └─MaxPool2d: 2-3                    [2, 96, 55, 55]           --
│    └─Conv2d: 2-4                       [2, 256, 26, 26]          614,656
│    └─ReLU: 2-5                         [2, 256, 26, 26]          --
│    └─MaxPool2d: 2-6                    [2, 256, 13, 13]          --
│    └─Conv2d: 2-7                       [2, 384, 13, 13]          885,120
│    └─ReLU: 2-8                         [2, 384, 13, 13]          --
│    └─Conv2d: 2-9                       [2, 384, 13, 13]          1,327,488
│    └─ReLU: 2-10                        [2, 384, 13, 13]          --
│    └─Conv2d: 2-11                      [2, 256, 13, 13]          884,992
│    └─ReLU: 2-12                        [2, 256, 13, 13]          --
│    └─MaxPool2d: 2-13                   [2, 256, 6, 6]            --
├─Sequential: 1-2                        [2, 10]                   --
│    └─Linear: 2-14                      [2, 4096]                 37,752,832
│    └─ReLU: 2-15                        [2, 4096]                 --
│    └─Dropout: 2-16                     [2, 4096]                 --
│    └─Linear: 2-17                      [2, 4096]                 16,781,312
│    └─ReLU: 2-18                        [2, 4096]                 --
│    └─Dropout: 2-19                     [2, 4096]                 --
│    └─Linear: 2-20                      [2, 10]                   40,970
==========================================================================================
Total params: 58,301,578
Trainable params: 58,301,578
Non-trainable params: 0
Total mult-adds (G): 2.33
==========================================================================================
Input size (MB): 1.20
Forward/backward pass size (MB): 24.25
Params size (MB): 233.21
Estimated Total Size (MB): 258.67
==========================================================================================

测试和定义网络

我们也可以打印出我们的模型观察一下

print(net)
ZFNet(
  (features): Sequential(
    (0): Conv2d(3, 96, kernel_size=(7, 7), stride=(2, 2), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    (3): Conv2d(96, 256, kernel_size=(5, 5), stride=(2, 2))
    (4): ReLU(inplace=True)
    (5): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    (6): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU(inplace=True)
    (8): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU(inplace=True)
    (10): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (0): Linear(in_features=9216, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=10, bias=True)
  )
)

接下来可以简单测试一下,是否输入后能得到我们的正确的维度shape

test_x = torch.randn(2,3,224,224).to(device)
test_y = net(test_x)
print(test_y.shape)
torch.Size([2, 10])

定义网络和设置类别

net = ZFNet(num_classes=10)

5. 定义损失函数和优化器

pytorch将深度学习中常用的优化方法全部封装在torch.optim之中,所有的优化方法都是继承基类optim.Optimizier

损失函数是封装在神经网络工具箱nn中的,包含很多损失函数

这里我使用的是AdamW算法,并且我们损失函数定义为交叉熵函数,除此之外学习策略定义为动态更新学习率,如果5次迭代后,训练的损失并没有下降,那么我们便会更改学习率,会变为原来的0.5倍,最小降低到0.00001

如果想更加了解优化器和学习率策略的话,可以参考以下资料

这里决定迭代10次

import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=1e-3, momentum=0.9, weight_decay=5e-4)
optimizer = optim.AdamW(net.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', factor=0.94 ,patience = 1,min_lr = 0.000001) # 动态更新学习率
# scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[75, 150], gamma=0.5)
import time
epoch = 10

6. 训练及可视化(增加TensorBoard可视化)

首先定义模型保存的位置

import os
if not os.path.exists('./model'):
    os.makedirs('./model')
else:
    print('文件已存在')
save_path = './model/ZFNet.pth'
文件已存在

这次更新了tensorboard的可视化,可以得到更好看的图片,并且能可视化出不错的结果

# 使用tensorboard
from torch.utils.tensorboard import SummaryWriter
os.makedirs("./logs", exist_ok=True)
tbwriter = SummaryWriter(log_dir='./logs/ZFNet', comment='ZFNet')  # 使用tensorboard记录中间输出
tbwriter.add_graph(model= net, input_to_model=torch.randn(size=(2, 3, 224, 224)))

如果存在GPU可以选择使用GPU进行运行,并且可以设置并行运算

if device == 'cuda':
    net.to(device)
    net = nn.DataParallel(net) # 使用并行运算

开始训练

我定义了一个train函数,在train函数中进行一个训练,并保存我们训练后的模型,这一部分一定要注意,这里的utils文件是我个人写的,所以需要下载下来

或者可以参考我们的工具函数篇,我还更新了结果和方法。

from utils import plot_history
from utils import train
Acc, Loss, Lr = train(net, trainloader, testloader, epoch, optimizer, criterion, scheduler, save_path, tbwriter, verbose = True)
Train Epoch 1/10: 100%|██████████| 196/196 [02:18<00:00,  1.41it/s, Train Acc=0.335, Train Loss=1.79]
Test Epoch 1/10: 100%|██████████| 40/40 [00:08<00:00,  4.92it/s, Test Acc=0.465, Test Loss=1.47]


Epoch [  1/ 10]  Train Loss:1.785667  Train Acc:33.51% Test Loss:1.467122  Test Acc:46.50%  Learning Rate:0.001000


Train Epoch 2/10: 100%|██████████| 196/196 [02:18<00:00,  1.42it/s, Train Acc=0.532, Train Loss=1.3] 
Test Epoch 2/10: 100%|██████████| 40/40 [00:08<00:00,  4.96it/s, Test Acc=0.606, Test Loss=1.13]


Epoch [  2/ 10]  Train Loss:1.299966  Train Acc:53.20% Test Loss:1.129006  Test Acc:60.57%  Learning Rate:0.001000


Train Epoch 3/10: 100%|██████████| 196/196 [02:18<00:00,  1.42it/s, Train Acc=0.624, Train Loss=1.06]
Test Epoch 3/10: 100%|██████████| 40/40 [00:08<00:00,  4.96it/s, Test Acc=0.641, Test Loss=1.02] 


Epoch [  3/ 10]  Train Loss:1.061436  Train Acc:62.44% Test Loss:1.019601  Test Acc:64.07%  Learning Rate:0.001000


Train Epoch 4/10: 100%|██████████| 196/196 [02:18<00:00,  1.42it/s, Train Acc=0.682, Train Loss=0.913]
Test Epoch 4/10: 100%|██████████| 40/40 [00:08<00:00,  4.91it/s, Test Acc=0.677, Test Loss=0.94] 


Epoch [  4/ 10]  Train Loss:0.912664  Train Acc:68.17% Test Loss:0.940289  Test Acc:67.74%  Learning Rate:0.001000


Train Epoch 5/10: 100%|██████████| 196/196 [02:18<00:00,  1.42it/s, Train Acc=0.722, Train Loss=0.792]
Test Epoch 5/10: 100%|██████████| 40/40 [00:08<00:00,  4.90it/s, Test Acc=0.716, Test Loss=0.841]


Epoch [  5/ 10]  Train Loss:0.791988  Train Acc:72.17% Test Loss:0.840765  Test Acc:71.60%  Learning Rate:0.001000


Train Epoch 6/10: 100%|██████████| 196/196 [02:18<00:00,  1.41it/s, Train Acc=0.765, Train Loss=0.676]
Test Epoch 6/10: 100%|██████████| 40/40 [00:08<00:00,  4.94it/s, Test Acc=0.704, Test Loss=0.872]


Epoch [  6/ 10]  Train Loss:0.675561  Train Acc:76.46% Test Loss:0.871565  Test Acc:70.39%  Learning Rate:0.001000


Train Epoch 7/10: 100%|██████████| 196/196 [02:18<00:00,  1.42it/s, Train Acc=0.797, Train Loss=0.581]
Test Epoch 7/10: 100%|██████████| 40/40 [00:08<00:00,  4.88it/s, Test Acc=0.716, Test Loss=0.858]


Epoch [  7/ 10]  Train Loss:0.580848  Train Acc:79.69% Test Loss:0.857795  Test Acc:71.61%  Learning Rate:0.001000


Train Epoch 8/10: 100%|██████████| 196/196 [02:18<00:00,  1.41it/s, Train Acc=0.828, Train Loss=0.49] 
Test Epoch 8/10: 100%|██████████| 40/40 [00:08<00:00,  4.87it/s, Test Acc=0.724, Test Loss=0.856]


Epoch [  8/ 10]  Train Loss:0.489913  Train Acc:82.77% Test Loss:0.855748  Test Acc:72.39%  Learning Rate:0.001000


Train Epoch 9/10: 100%|██████████| 196/196 [02:18<00:00,  1.42it/s, Train Acc=0.854, Train Loss=0.418]
Test Epoch 9/10: 100%|██████████| 40/40 [00:08<00:00,  4.94it/s, Test Acc=0.733, Test Loss=0.873]


Epoch [  9/ 10]  Train Loss:0.418106  Train Acc:85.44% Test Loss:0.872757  Test Acc:73.29%  Learning Rate:0.001000


Train Epoch 10/10: 100%|██████████| 196/196 [02:18<00:00,  1.42it/s, Train Acc=0.871, Train Loss=0.367]
Test Epoch 10/10: 100%|██████████| 40/40 [00:08<00:00,  4.92it/s, Test Acc=0.738, Test Loss=0.854]


Epoch [ 10/ 10]  Train Loss:0.367293  Train Acc:87.15% Test Loss:0.853627  Test Acc:73.83%  Learning Rate:0.001000

训练曲线可视化

plot_history(epoch ,Acc, Loss, Lr)

可以运行以下代码进行tensorboard可视化

tensorboard --logdir logs

7. 测试

查看准确率

correct = 0   # 定义预测正确的图片数,初始化为0
total = 0     # 总共参与测试的图片数,也初始化为0

for data in testloader:  # 循环每一个batch
    images, labels = data
    images = images.to(device)
    labels = labels.to(device)
    net.eval()  # 把模型转为test模式
    if hasattr(torch.cuda, 'empty_cache'):
        torch.cuda.empty_cache()
    outputs = net(images)  # 输入网络进行测试
    
    # outputs.data是一个4x10张量,将每一行的最大的那一列的值和序号各自组成一个一维张量返回,第一个是值的张量,第二个是序号的张量。
    _, predicted = torch.max(outputs.data, 1)
    total += labels.size(0)          # 更新测试图片的数量
    correct += (predicted == labels).sum() # 更新正确分类的图片的数量

print('Accuracy of the network on the 10000 test images: %.2f %%' % (100 * correct / total))
 
Accuracy of the network on the 10000 test images: 73.95 %

程序中的 torch.max(outputs.data, 1) ,返回一个tuple (元组)

而这里很明显,这个返回的元组的第一个元素是image data,即是最大的 值,第二个元素是label, 即是最大的值 的 索引!我们只需要label(最大值的索引),所以就会有_,predicted这样的赋值语句,表示忽略第一个返回值,把它赋值给 _, 就是舍弃它的意思;

查看每一类的准确率

 # 定义2个存储每类中测试正确的个数的 列表,初始化为0
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))

net.eval()
with torch.no_grad():
    for data in testloader:
        images, labels = data
        images = images.to(device)
        labels = labels.to(device)
        if hasattr(torch.cuda, 'empty_cache'):
            torch.cuda.empty_cache()
        outputs = net(images)

        _, predicted = torch.max(outputs.data, 1)
        #(batch_size)数据中,输出于label相同的,标记为1,否则为0
        c = (predicted == labels).squeeze()
        for i in range(len(images)):      # 因为每个batch都有len(iamges)张图片,所以还需要一个len(iamges)的小循环
            label = labels[i]   # 对各个类的进行各自累加
            class_correct[label] += c[i]
            class_total[label] += 1
 
 
for i in range(10):
    print('Accuracy of %5s : %.2f %%' % (classes[i], 100 * class_correct[i] / class_total[i]))
Accuracy of airplane : 75.30 %
Accuracy of automobile : 83.70 %
Accuracy of  bird : 66.50 %
Accuracy of   cat : 62.10 %
Accuracy of  deer : 66.70 %
Accuracy of   dog : 62.80 %
Accuracy of  frog : 78.50 %
Accuracy of horse : 75.80 %
Accuracy of  ship : 84.20 %
Accuracy of truck : 83.90 %

抽样测试并可视化一部分结果

dataiter = iter(testloader)
images, labels = dataiter.next()

images_,labels = images.to(device),labels.to(device)
val_output = net(images_)

_, val_preds = torch.max(val_output, 1)
correct = torch.sum(val_preds == labels.data).item()
val_preds,labels = val_preds.cpu(),labels.cpu()

print("Accuracy Rate = %".format(correct/len(images) * 100))

fig = plt.figure(figsize=(25,25))
for idx in np.arange(64):    
    ax = fig.add_subplot(8, 8, idx+1, xticks=[], yticks=[])
    imshow(images[idx])
    ax.set_title(", ()".format(classes[val_preds[idx].item()], classes[labels[idx].item()]), 
                 color = ("green" if val_preds[idx].item()==labels[idx].item() else "red"))
Accuracy Rate = 75.390625%

8. 保存模型

这里保存成前面设定的模型的名字

torch.save(net,save_path[:-4]+'_'+str(epoch)+'.pth')

9. 预测

import torch
from PIL import Image
from torch.autograd import Variable
import torch.nn.functional as F
from torchvision import datasets, transforms
import numpy as np
 
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

model = ZFNet(num_classes=10)

model = torch.load(save_path, map_location="cpu")  # 加载模型
model.to(device)
model.eval()  # 把模型转为test模式
DataParallel(
  (module): ZFNet(
    (features): Sequential(
      (0): Conv2d(3, 96, kernel_size=(7, 7), stride=(2, 2), padding=(1, 1))
      (1): ReLU(inplace=True)
      (2): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
      (3): Conv2d(96, 256, kernel_size=(5, 5), stride=(2, 2))
      (4): ReLU(inplace=True)
      (5): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
      (6): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      (7): ReLU(inplace=True)
      (8): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      (9): ReLU(inplace=True)
      (10): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      (11): ReLU(inplace=True)
      (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    )
    (classifier): Sequential(
      (0): Linear(in_features=9216, out_features=4096, bias=True)
      (1): ReLU(inplace=True)
      (2): Dropout(p=0.5, inplace=False)
      (3): Linear(in_features=4096, out_features=4096, bias=True)
      (4): ReLU(inplace=True)
      (5): Dropout(p=0.5, inplace=False)
      (6): Linear(in_features=4096, out_features=10, bias=True)
    )
  )
)

并且为了方便,定义了一个predict函数,简单思想就是,先resize成网络使用的shape,然后进行变化tensor输入即可,不过这里有一个点,我们需要对我们的图片也进行transforms,因为我们的训练的时候,对每个图像也是进行了transforms的,所以我们需要保持一致

def predict(img):
    trans = transforms.Compose([transforms.Resize((224,<

以上是关于Pytorch CIFAR10图像分类 ZFNet篇的主要内容,如果未能解决你的问题,请参考以下文章

Pytorch CIFAR10图像分类 ZFNet篇

Pytorch CIFAR10图像分类 ZFNet篇

Pytorch CIFAR10图像分类 ZFNet篇

Pytorch CIFAR10图像分类 ZFNet篇

Pytorch CIFAR10 图像分类篇 汇总

Pytorch CIFAR10 图像分类篇 汇总