VGG 论文精读&&代码逐行解析

文章目录

  • 前言
  • 一、论文精读
    • 0.标题,摘要和结论
    • 1.Introduction
    • 2.Convnet Configurations
    • 3.Classification Framework
    • 4.Classification Experiments
  • 二、代码阅读
    • 1.定义超参数
    • 2. CIFAR-10
    • 3.定义网络模型
    • 4.创建模型
    • 5.定义优化器和损失函数
    • 6.定义轮数
    • 7.保存模型
    • 8.加载模型
    • 9.测试
    • 10.显示每一类预测的概率
    • 11.输出正确率


VGG 论文精读&&代码逐行解析_第1张图片

前言

笔者从人工智能小白的角度,力求能够从原文中解析出最高效率的知识。
之前看了很多博客去学习AI,但发现虽然有时候会感觉很省时间,但到了复现的时候就会傻眼,因为太多实现的细节没有提及。而且博客具有很强的主观性,因此我建议还是搭配原文来看。

请下载原文《Very Deep Convolutional Networks for Large-Scale Image Recognition》搭配阅读本文,会更高效哦!

一、论文精读

0.标题,摘要和结论

《Very Deep Convolutional Networks for Large-Scale Image Recognition》首先,看完标题,摘要和结论,我了解到了以下信息:
(1)创新点:多层33 小卷积核取代了大卷积核,并采用22池化层。
(2)网络深度至19层,宽度逐层加宽。VGG-16,VGG-19迁移学习基模型。
(3)模型简洁,常规经典CNN结构的极致,但参数量比较大,集中在FCL略显臃肿。

1.Introduction

(1)深度学习发展的三推力:数据、算力、算法。
(2)增加深度,采用3*3的小卷积核来降低参数量。
(3)具有很好的迁移泛化能力,仅仅用VGG做特征提取(4096维向量)(本身不需要微调),再用SVM,就可以用ImageNet训练好的模型,解决VOC数据集上的问题。

2.Convnet Configurations

(1)唯一预处理:图像每一个像素减去所在训练集上所有对应像素的RGB均值。
(2)卷积核为33,为什么?因为只有33才能够捕获左右上下和中心的最小尺寸。
(3)为了卷积后W和H不变:W ‘= (W +2P-K)/S + 1,设置Padding = 1,池化层2*2,步长为2。
(4)最后层用了soft-max归一化,激活层为ReLU,不再使用Local Response Normalization(AlexNet曾使用过),第四章证明无用,不会提高性能,反而会导致内存和计算的消耗。
(5)结构:D:VGG16,E:VGG19。
分为五个block,每个block进行了一次下采样,但每次下采样后卷积核都变大一倍,为了把长宽的像素信息变小,把深度通道的语义信息逐渐加宽。然后是三个全连接层FC,最后是一个soft-max分类输出层。
VGG 论文精读&&代码逐行解析_第2张图片

(6)相比于其他卷积核为77的模型,33卷积核就可以充分地提取到图像的特征信息。从感受野来理解,3个33的卷积核才相当于一个77的卷积核,但是参数量会少一些,可以加深层数,有更好的非线性的表达能力,相当于添加正则化,可以防止过拟合。(这个思路被GoogleNet采用。)

3.Classification Framework

(1)采用SGD+momentu(0.9),权重衰减(L2正则化),前两个连接层使用了Dropout(0.5)防止过拟合。初始学习率10 ^-2,性能停止提升,就除以10(AlexNet,ResNet)。先大跨步,接近min值后小碎步。训练74epoches后停止。
思考:为什么Dropout可以减少过拟合?
回答:
① 记忆随机抹去,不再死记硬背。
② Dropout减少了神经元之间的联合依赖性。
③ 添加了随机性,dropout导致两个神经元不一定每次都在一个dropout网络中出现,提高了robust。
(2)训练策略:更深更小的卷积核,某些层使用了权重初始化策略,先训练浅模型,然后把模型参数用到深层的模型之中。
(3)为了解决梯度消失等训练不动的问题,先跑了A模型,然后把参数拿出来做初始值,在预训练好的层中并没有减少学习率,所以放在网络中一起训练。
(4)确定Training image size:
方案一:固定S。
方案二:多尺度S。
每张图片S都可能变化,从[Smin,Smax]中随机选取,通过尺度抖动也就是缩放,也相当于数据增强,不同尺度训练就可以识别不同尺度的物体。
(5)Testing(看了很多资料,存疑状态,目前理解):确定测试尺度Q,把测试尺度最短边缩放为Q尺度,Q不一定等于S,训练时S可以变动。整个网络在图像上密集地进行预测,把全连接层转成了77的卷积层,最后全连接层转成了11的卷积层,这个网络结构就可以接受不同尺寸的数据,如果丢进去数据尺寸为Q,比S 大,那全连接层输出就是特征图。然后再把每个通道进行全局平均池化,这样就算最后输出是441000也可以转化为111000。然后把图像翻转,再输出一次,把两次结果进行soft-max然后取平均。
(6)采用数据计算并行而非模型并行的策略。

4.Classification Experiments

4.1 single scale evaluation(Q为固定值)
(1)如果S固定,Q=S。如果S在范围内变动,Q = 0.5(Smin+Smax)。存在模型退化的问题,即深度高于19层,性能上反而会下降(后被ResNet解决)。
(2)深层小卷积核比浅层大卷积核效果好。
(3)训练时,变动S的尺度非常重要,相对于固定S的效果都要好,这样训练可以帮助模型识别不同尺度的数据。
4.2 Multi-Scale Evaluation(Q为范围值)
(1)训练集尺度与测试集尺度不能相差太大,否则会显著降低性能。最好是来自同一分布,尺寸差异最好在32以内。
(2)S为范围值时表现更好,同上。
4.3 Multi-Corp Evaluation
(1)比对了dense及crop的方法,发现结合使用后,误差更低。这是因为假设两种方法的边界条件不一样。

二、代码阅读

import torch
import torch.nn as nn
from torch import optim
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets
from tqdm import tqdm

transform = transforms.Compose([
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor()
    ])

1.定义超参数

BATCH_SIZE = 128 # 批的大小

2. CIFAR-10

train_dataset = datasets.CIFAR10('./CIFAR10', train=True, transform=transform, download=False)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=0, pin_memory=True)
test_dataset = datasets.CIFAR10('./CIFAR10', train=False, transform=transform, download=False)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=0, pin_memory=True)
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

3.定义网络模型

class VGG16(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16, self).__init__()
        self.features = nn.Sequential(
            # 1
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(True),
            # 2
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 3
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(True),
            # 4
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 5
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(True),
            # 6
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(True),
            # 7
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 8
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(True),
            # 9
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(True),
            # 10
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 11
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(True),
            # 12
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(True),
            # 13
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            # nn.BatchNorm2d(512),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.classifier = nn.Sequential(
            # 14
            nn.Linear(512, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            # 15
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            # 16
            nn.Linear(4096, num_classes),
        )
        # self.classifier = nn.Linear(512, 10)

    def forward(self, x):
        out = self.features(x)
        out = out.view(out.size(0), -1)
        out = self.classifier(out)
        return out

4.创建模型

net = VGG16().to('cuda')

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

criterion = nn.CrossEntropyLoss()  # 交叉式损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)  # 优化器

6.定义轮数

EPOCHS = 50

for epoch in range(EPOCHS):
    train_loss = 0.0
    for i, (datas, labels) in tqdm(enumerate(train_loader)):
        datas, labels = datas.to('cuda'), labels.to('cuda')
        # 梯度置零
        optimizer.zero_grad()
        # 训练
        outputs = net(datas)
        # 计算损失
        loss = criterion(outputs, labels)
        # 反向传播
        loss.backward()
        # 参数更新
        optimizer.step()
        # 累计损失
        train_loss += loss.item()
    print("Epoch : {} , Batch :{} , Loss : {:.3f}".format(epoch+1, i+1, train_loss/len(train_loader.dataset)))

7.保存模型

PATH = 'cifar_net.pth'
torch.save(net.state_dict(), PATH)

8.加载模型

model = net.to('cuda')
model.load_state_dict(torch.load(PATH))     # .load_state_dict() 加载模型

9.测试

correct = 0
total = 0
with torch.no_grad():
    for i, (datas, labels) in enumerate(test_loader):
        datas, labels = datas.to('cuda'), labels.to('cuda')
        # 输出
        outputs = model(datas)  # outputs.data.shape --> torch.Size([128, 10])
        _, predicted = torch.max(outputs.data, dim=1)   # 第一个是值的张量,第二个是序号的张量
        # 累计数据量
        total += labels.size(0)     # labels.size() --> torch.Size([128]), labels.size(0) --> 128
        # 比较有多少个预测正确
        correct += (predicted == labels).sum()  # 相同为1,不同为0,利用sum()求总和
    print('在10000张测试集图片上的准确率:{:.3f}'.format(correct / total * 100))

10.显示每一类预测的概率

class_correct = list(0. for i in range(10))
total = list(0. for i in range(10))
with torch.no_grad():
    for (images, labels) in test_loader:
        # 输出
        outputs = model(images)
        # 获取到每一行最大值的索引
        _, predicted = torch.max(outputs, dim=1)
        c = (predicted == labels).squeeze()     # squeeze() 去掉0维[默认], unsqueeze() 增加一维
        if labels.shape[0] == 128:
            for i in range(BATCH_SIZE):
                label = labels[i]   # 获取每一个label
                class_correct[label] += c[i].item()     # 累计True的个数,注意 1+True=2, 1+False=1
                total[label] += 1   # 该类总的个数

11.输出正确率

for i in range(10):
    print('正确率 : %5s : %2d %%' % (classes[i], 100 * class_correct[i] / total[i]))

VGG 论文精读&&代码逐行解析_第3张图片

你可能感兴趣的:(深度学习,计算机视觉,人工智能,神经网络,cnn)