1.什么是VGG16
VGG16 是一个经典的卷积神经网络(CNN)架构,由牛津大学视觉几何组(Visual Geometry Group)在2014年提出。VGG16的名字来源于它的深度——16个权重层(13个卷积层和3个全连接层)。
VGG16 结构:
卷积层:VGG16由多个卷积层组成,卷积核大小固定为3x3,步幅为1,padding保持输入大小不变。每经过几个卷积层后,使用2x2的最大池化层(Max Pooling)来减小特征图的尺寸。
全连接层:在卷积层之后,VGG16有3个全连接层,最后一个全连接层输出1000维的向量(用于ImageNet分类任务中的1000个类)。
激活函数:每个卷积层和全连接层后都使用ReLU激活函数。
总参数量:VGG16的参数量大约为138百万,尽管结构相对简单,但由于其深度和较大的网络尺寸,计算需求较高。
VGG16在图像分类任务中表现非常好,是许多计算机视觉任务的基础架构。
2.对VGG16进行修改
首先加载出VGG16原始结构:
import torch
import torchvision
from torchvision import datasets, transforms, models
from torch.utils.data import Dataset, DataLoader
from torch import nn
# datasets = torchvision.datasets.ImageNet("E:\\PyCharm_Project\\Pytorch_2.3.1\\ALL_Datasets\\ImageNet",split = 'train',download=True , transform = torchvision.transforms.ToTensor())
vgg16 = torchvision.models.vgg16(pretrained=True)
print(vgg16)
获得以下模型结构:
VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace=True)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace=True)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): ReLU(inplace=True)
(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): ReLU(inplace=True)
(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace=True)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU(inplace=True)
(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): ReLU(inplace=True)
(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): ReLU(inplace=True)
(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): ReLU(inplace=True)
(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): ReLU(inplace=True)
(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): ReLU(inplace=True)
(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(27): ReLU(inplace=True)
(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): ReLU(inplace=True)
(30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(classifier): Sequential(
(0): Linear(in_features=25088, 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=1000, bias=True)
)
)
修改操作 1:改变输入通道数
# 修改第一层卷积层的输入通道数
vgg16.features[0] = nn.Conv2d(in_channels=4, out_channels=64, kernel_size=3, padding=1)
得到
VGG(
(features): Sequential(
(0): Conv2d(4, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 输入通道数从3改为4
(1): ReLU(inplace=True)
修改操作 2:调整全连接层
调整最后一个全连接层(classifier)的输出,使模型适应不同的分类任务。例如,从原来的1000类改为10类:
# 替换最后的全连接层
vgg16.classifier[6] = nn.Linear(in_features=4096, out_features=10)
(classifier): Sequential(
(0): Linear(in_features=25088, 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) # 输出类别从1000改为10
)
修改操作 3:插入新的层
在模型中插入新层,例如在全连接层之间加入一个 BatchNorm 层来稳定训练过程:
# 在全连接层的第二层之后插入一个 BatchNorm 层
new_classifier = list(vgg16.classifier)
new_classifier.insert(4, nn.BatchNorm1d(4096))
# 更新模型的全连接层
vgg16.classifier = nn.Sequential(*new_classifier)
(classifier): Sequential(
(0): Linear(in_features=25088, 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): BatchNorm1d(4096, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) # 插入的 BatchNorm 层
(5): ReLU(inplace=True)
(6): Dropout(p=0.5, inplace=False)
(7): Linear(in_features=4096, out_features=10, bias=True)
)
修改操作 4:使用自定义的激活函数
用 LeakyReLU 替换掉原来的 ReLU 激活函数,可以这样做:
# 替换所有的 ReLU 激活函数为 LeakyReLU
for i, layer in enumerate(vgg16.features):
if isinstance(layer, nn.ReLU):
vgg16.features[i] = nn.LeakyReLU(inplace=True)
for i, layer in enumerate(vgg16.classifier):
if isinstance(layer, nn.ReLU):
vgg16.classifier[i] = nn.LeakyReLU(inplace=True)
# 输出修改后的模型结构
print(vgg16)
VGG(
...
(features): Sequential(
(0): Conv2d(4, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): LeakyReLU(inplace=True) # ReLU 改为 LeakyReLU
...
)
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): LeakyReLU(inplace=True) # ReLU 改为 LeakyReLU
...
)
)
修改操作 5:增加或删除层
可以直接增加或删除模型中的层。例如,删除最后一个卷积层:
# 删除最后一个卷积层
vgg16.features = nn.Sequential(*list(vgg16.features.children())[:-1])
(25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(26): ReLU(inplace=True)
(27): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(28): ReLU(inplace=True) # 最后一个卷积层已删除
(29): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)