pytorch搭建VGG网络
- CNN 感受野
- VGG-16
- 搭建VGG网络
- model.py
- train.py
- predict.py
VGG 网络的创新点:通过堆叠多个小卷积核来替代大尺度卷积核,可以减少训练参数,同时能保证相同的感受野。
例如,可以通过堆叠两个 3×3 的卷积核替代 5x5 的卷积核,堆叠三个 3×3 的卷积核替代 7x7 的卷积核。
CNN 感受野
输出层 feature map 上的一个单元对应输入层上的区域大小,被称作感受野(receptive field)
如下图所示,输出层 layer3 中一个单元对应输入层 layer2 上区域大小为 2×2 (池化操作),对应输入层 layer1 上大小为5×5
感受野的计算公式为:
- F(i) 为第 i 层感受野
- Stride 为第 i 层的步距
- Ksize 为 卷积核 或 池化核 尺寸
以图中例子计算:可得F(3) = 1、F(2)=(1 − 1) × 2 + 2 = 2、F (1) = (2 − 1) × 2 + 3 = 5,可以理解为 layer2 中 2×2 区域中的每一块对应一个 3×3 的卷积核,又因为 stride=2,所以 layer1 的感受野为 5×5
思考:堆叠3×3卷积核后训练参数是否真的减少了?
CNN参数个数 = 卷积核尺寸 × 卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度
现假设 输入特征矩阵深度 = 输出特征矩阵深度 = C
使用7×7卷积核所需参数个数:7 × 7 × C × C = 49C²
堆叠三个3×3的卷积核所需参数个数:3 × 3 × C × C + 3 × 3 × C × C + 3 × 3 × C × C = 27C²
VGG-16
网络结构如下图所示:
搭建VGG网络
model.py
跟 AlexNet 网络模型一样,VGG网络也是分为 卷积层提取特征 和 全连接层进行分类
import torch.nn as nn
import torch
class VGG(nn.Module):
def __init__(self, features, num_classes=1000, init_weights=False):
super(VGG, self).__init__()
self.features = features # 卷积层提取特征
self.classifier = nn.Sequential( # 全连接层进行分类
nn.Dropout(p=0.5),
nn.Linear(512*7*7, 2048),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(2048, 2048),
nn.ReLU(True),
nn.Linear(2048, num_classes)
)
if init_weights:
self._initialize_weights()
def forward(self, x):
# N x 3 x 224 x 224
x = self.features(x)
# N x 512 x 7 x 7
x = torch.flatten(x, start_dim=1)
# N x 512*7*7
x = self.classifier(x)
return x
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
# nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
# nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
VGG网络有 VGG-13、VGG-16等多种网络结构,其全连接层完全一样,卷积层只有卷积核个数稍有不同,可以将这几种结构统一在一个模型中。
# vgg网络模型配置列表,数字表示卷积核个数,'M'表示最大池化层
cfgs = {
'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], # 模型A
'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], # 模型B
'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'], # 模型D
'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'], # 模型E
}
# 卷积层提取特征
def make_features(cfg: list): # 传入的是具体某个模型的参数列表
layers = []
in_channels = 3 # 输入的原始图像(rgb三通道)
for v in cfg:
# 最大池化层
if v == "M":
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
# 卷积层
else:
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
layers += [conv2d, nn.ReLU(True)]
in_channels = v
return nn.Sequential(*layers) # 单星号(*)将参数以元组(tuple)的形式导入
def vgg(model_name="vgg16", **kwargs): # 双星号(**)将参数以字典的形式导入
try:
cfg = cfgs[model_name]
except:
print("Warning: model number {} not in cfgs dict!".format(model_name))
exit(-1)
model = VGG(make_features(cfg), **kwargs)
return model
train.py
训练脚本跟另一篇 AlexNet 基本一致,需要注意的是实例化网络的过程:
model_name = "vgg16"
net = vgg(model_name=model_name, num_classes=5, init_weights=True)
predict.py
预测脚本跟另一篇 AlexNet 一致