经典神经网络(3)VGG_使用块的网络
1 VGG的简述
1.1 VGG的概述
- VGG-Net是牛津大学计算机视觉组和- DeepMind公司共同研发一种深度卷积网络,并且在2014年在- ILSVRC比赛上获得了分类项目的第二名和定位项目的第一名。通过使⽤循环和⼦程序,可以很容易地在任何现代深度学习框架的代码中实现这些重复的架构。
- VGG-Net的主要贡献是:- 证明了小尺寸卷积核(3x3)的深层网络要优于大尺寸卷积核的浅层网络。
- 证明了深度对网络的泛化性能的重要性。
- 块的使⽤导致定义的网络⾮常简洁。使用块可以有效地设计复杂的网络。
- 验证了尺寸抖动scale jittering这一数据增强技术的有效性。
 
- 证明了小尺寸卷积核(
- VGG-Net最大的问题在于参数数量,- VGG-19基本上是参数数量最多的卷积网络架构。
1.2 AlexNet和VGG的对比

-  与AlexNet、LeNet⼀样,VGG⽹络可以分为两部分:第⼀部分主要由卷积层和汇聚层组成,第⼆部分由全连接层组成。 
-  VGG神经网络第⼀部分由几个VGG块连接而成。全连接模块则与AlexNet中的相同。 
-  与 AlexNet相比,VGG-Net在第一个全连接层的输入feature map较大:7x7 vs 6x6,512 vs 256。
1.3 VGG神经网络的5组结构
VGG-Net 一共有五组结构(分别表示为:A~E ), 每组结构都类似,区别在于网络深度上的不同。
-  结构中不同的部分用黑色粗体给出。 
-  卷积层的参数为 convx-y,其中x为卷积核大小,y为卷积核数量。如: conv3-64表示64个3x3的卷积核。
-  卷积层的通道数刚开始很小(64通道),然后在每个池化层之后的卷积层通道数翻倍,直到512。 
-  每个卷积层之后都跟随一个 ReLU激活函数。

-  输入层:固定大小的 224x224的RGB图像。
-  卷积层:卷积步长均为1。 -  填充方式:填充卷积层的输入,使得卷积前后保持同样的空间分辨率。 - 3x3卷积:- same填充,即:输入的上下左右各填充1个像素。
- 1x1卷积:不需要填充。
 
-  卷积核尺寸:有 3x3和1x1两种。-  3x3卷积核:这是捕获左右、上下、中心等概念的最小尺寸。
-  1x1卷积核:用于输入通道的线性变换。在它之后接一个 ReLU激活函数,使得输入通道执行了非线性变换。
 
-  
 
-  
-  池化层:采用最大池化。 - 池化层连接在卷积层之后,但并不是所有的卷积层之后都有池化。
- 池化窗口为2x2,步长为 2 。
 
-  网络最后四层为::三个全连接层 + 一个 softmax层。- 前两个全连接层都是 4096个神经元,第三个全连接层是 1000 个神经元(因为执行的是 1000 类的分类)。
- 最后一层是softmax层用于输出类别的概率。
 
-  所有隐层都使用 ReLU激活函数。
1.4 VGG神经网络5组结构的参数量
其中第一个全连接层的参数数量为:7x7x512x4096=1.02亿 ,因此网络绝大部分参数来自于该层。
| 网络 | A , A-LRN | B | C | D | E | 
|---|---|---|---|---|---|
| 参数数量 | 1.13亿 | 1.33亿 | 1.34亿 | 1.38亿 | 1.44 | 
1.5 VGG-11的实现
import torch.nn as nn
import torch
'''
原始VGG⽹络有5个卷积块,其中前两个块各有⼀个卷积层,后三个块各包含两个卷积层。
第⼀个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。
由于该⽹络使⽤8个卷积层和3个全连接层,因此它通常被称为VGG-11。
'''
class Vgg11Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = self.vgg()
    
    def forward(self, X):
        X = self.model(X)
        return X
    def vgg(self):
        conv_blks = []
        # 输入通道的数量,初始化为1
        in_channels = 1
        # 卷积层部分,一共有5个vgg块,其中前两个块各有⼀个卷积层,后三个块各包含两个卷积层
        conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
        for (num_convs, out_channels) in conv_arch:
            # 添加vgg块
            conv_blks.append(self.vgg_block(num_convs, in_channels, out_channels))
            in_channels = out_channels
        return nn.Sequential(
            *conv_blks,
            nn.Flatten(),
            # 全连接层部分,和AlexNet一致
            # 第一个全连接层的参数数量为:7x7x512x4096=1.02亿,因此网络绝大部分参数来自于该层
            nn.Linear(512 * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(4096, 10)
        )
    def vgg_block(self,num_convs,in_channels,out_channels):
        """
        :param num_convs:    卷积层的数量
        :param in_channels:  输⼊通道的数量
        :param out_channels: 输出通道的数量
        :return: vgg块
        """
        layers = []
        for _ in range(num_convs):
            # 卷积层
            layers.append(
                # 填充方式:填充卷积层的输入,使得卷积前后保持同样的空间分辨率
                nn.Conv2d(in_channels,out_channels,kernel_size=3,padding=1)
            )
            layers.append(nn.ReLU())
            in_channels = out_channels
        # 汇聚层
        layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
        return nn.Sequential(*layers)
if __name__ == '__main__':
    net = Vgg11Net()
    # 测试神经网络是否可运行
    # inputs = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
    # outputs = net(inputs)
    # print(outputs.shape)
    # 查看每一层输出的shape
    X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
    for layer in net.model:
        X = layer(X)
        print(layer.__class__.__name__, 'output shape:', X.shape)
# 1、5个卷积块,其中前两个块各有⼀个卷积层,后三个块各包含两个卷积层。
# 第⼀个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
# 2、和AlexNet一样的3个全连接层
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
2 VGG论文的创新点
论文的下载地址为:https://arxiv.org/pdf/1409.1556.pdf
2.1 权重的初始化
由于网络深度较深,因此网络权重的初始化很重要,设计不好的初始化可能会阻碍学习。
- 论文的权重初始化方案为:先训练结构A。当训练更深的配置时,使用结构A的前四个卷积层和最后三个全连接层来初始化网络,网络的其它层被随机初始化。
- 作者后来指出:可以通过 Xavier均匀初始化来直接初始化权重而不需要进行预训练。
2.2 局部响应归一化层LRN
- 分类误差随着网络深度的增加而减小。
- 从A-LRN和A的比较发现:局部响应归一化层LRN对于模型没有任何改善。
2.3 通道像素零均值化
- 先统计训练集中全部样本的通道均值:所有红色通道的像素均值a 、所有绿色通道的像素均值b 、所有蓝色通道的像素均值c
- 对每个样本:红色通道的每个像素值减去a ,绿色通道的每个像素值减去b ,蓝色通道的每个像素值减去c。
论文中还有多尺度训练、多尺度测试等内容,有兴趣的可以阅读一下原文。
3 VGG-11在Fashion-MNIST数据集上的应用示例
3.1 创建VGG-11网络模型
注意:由于VGG-11⽐AlexNet计算量更⼤,因此我们构建了⼀个通道数较少的⽹络,⾜够⽤于训练Fashion-MNIST数据集。
import torch.nn as nn
import torch
'''
原始VGG⽹络有5个卷积块,其中前两个块各有⼀个卷积层,后三个块各包含两个卷积层。
第⼀个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。
由于该⽹络使⽤8个卷积层和3个全连接层,因此它通常被称为VGG-11。
'''
class Vgg11Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = self.vgg()
    def forward(self, X):
        X = self.model(X)
        return X
    def vgg(self):
        conv_blks = []
        # 输入通道的数量,初始化为1
        in_channels = 1
        # 卷积层部分,一共有5个vgg块,其中前两个块各有⼀个卷积层,后三个块各包含两个卷积层
        conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
        # 1、由于VGG-11⽐AlexNet计算量更⼤,因此我们构建了⼀个通道数较少的⽹络,⾜够⽤于训练Fashion-MNIST数据集。
        small_conv_arch = [(pair[0], pair[1] // 4) for pair in conv_arch]
        for (num_convs, out_channels) in small_conv_arch:
            # 添加vgg块
            conv_blks.append(self.vgg_block(num_convs, in_channels, out_channels))
            in_channels = out_channels
        return nn.Sequential(
            *conv_blks,
            nn.Flatten(),
            # 全连接层部分,和AlexNet一致
            # 2、注意,这里从512改为128
            nn.Linear(128 * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(4096, 10)
        )
    def vgg_block(self,num_convs,in_channels,out_channels):
        """
        :param num_convs:    卷积层的数量
        :param in_channels:  输⼊通道的数量
        :param out_channels: 输出通道的数量
        :return: vgg块
        """
        layers = []
        for _ in range(num_convs):
            # 卷积层
            layers.append(
                nn.Conv2d(in_channels,out_channels,kernel_size=3,padding=1)
            )
            layers.append(nn.ReLU())
            in_channels = out_channels
        # 汇聚层
        layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
        return nn.Sequential(*layers)
if __name__ == '__main__':
    net = Vgg11Net()
    # 测试神经网络是否可运行
    # inputs = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
    # outputs = net(inputs)
    # print(outputs.shape)
    # 查看每一层输出的shape
    X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
    for layer in net.model:
        X = layer(X)
        print(layer.__class__.__name__, 'output shape:', X.shape)
3.2 读取Fashion-MNIST数据集
其他所有的函数,与经典神经网络(1)LeNet及其在Fashion-MNIST数据集上的应用完全一致。
'''
Fashion-MNIST图像的分辨率(28×28像素)低于ImageNet图像。为了解决这个问题,增加到224×224
'''
batch_size = 128
train_iter,test_iter = get_mnist_data(batch_size,resize=224)
3.3 在GPU上进行模型训练
from _03_Vgg11Net import Vgg11Net
# 初始化模型
net = Vgg11Net()
lr, num_epochs = 0.05, 10
train_ch(net, train_iter, test_iter, num_epochs, lr, try_gpu())

 注:Vgg-Net用GPU才能跑,如果自己电脑没有合适的GPU,可以参考下面文章进行租借
AutoDL平台租借GPU详解



















