1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > CNN经典之VGG网络+PyTorch复现

CNN经典之VGG网络+PyTorch复现

时间:2023-06-28 11:31:46

相关推荐

CNN经典之VGG网络+PyTorch复现

一、前情说明:

写在前面的话

本系列博客在于汇总CSDN的精华帖,类似自用笔记,方便以后的复习回顾,博文中的引用都注明出处,并点赞收藏原博主。

博客大致分为两部分,第一部是转载于《雪饼》大侠的博客/rogerchen1983/article/details/79549085(值得推荐,深入浅出,但少部分涉及较深或者错误的没贴出),第二部分是对网络的复现,代码主要参考bilibili大神《霹雳吧啦Wz》/WZMIAOMIAO/deep-learning-for-image-processing(代码做了部分改动,不完全一样,也十分推荐他的视频)

二、论文解读:

,牛津大学计算机视觉组(Visual Geometry Group)和Google DeepMind公司的研究员一起研发出了新的深度卷积神经网络:VGGNet,并取得了ILSVRC比赛分类项目的第二名(第一名是GoogLeNet,也是同年提出的)和定位项目的第一名。

VGGNet探索了卷积神经网络的深度与其性能之间的关系,成功地构筑了16~19层深的卷积神经网络,证明了增加网络的深度能够在一定程度上影响网络最终的性能,使错误率大幅下降,同时拓展性又很强,迁移到其它图片数据上的泛化性也非常好。到目前为止,VGG仍然被用来提取图像特征。

一、VGG的特点

先看一下VGG的结构图

VGG由5层卷积层、3层全连接层、softmax输出层构成,层与层之间使用max-pooling(最大化池)分开,所有隐层的激活单元都采用ReLU函数。

2、小卷积核和多卷积子层

VGG使用多个较小卷积核(3x3)的卷积层代替一个卷积核较大的卷积层,一方面可以减少参数,另一方面相当于进行了更多的非线性映射,可以增加网络的拟合/表达能力。

小卷积核是VGG的一个重要特点,虽然VGG是在模仿AlexNet的网络结构,但没有采用AlexNet中比较大的卷积核尺寸(如7x7),而是通过降低卷积核的大小(3x3),增加卷积子层数来达到同样的性能(VGG:从1到4卷积子层,AlexNet:1子层)。

VGG的作者认为两个3x3的卷积堆叠获得的感受野大小,相当一个5x5的卷积;而3个3x3卷积的堆叠(卷积核级联)获取到的感受野相当于一个7x7的卷积。这样可以增加非线性映射,也能很好地减少参数(例如7x7的参数为49个,而3个3x3的参数为27),如下图所示:

3、小池化核

相比AlexNet的3x3的池化核,VGG全部采用2x2的池化核。

4、通道数多

VGG网络第一层的通道数为64,后面每层都进行了翻倍,最多到512个通道,通道数的增加,使得更多的信息可以被提取出来。

5、层数更深、特征图更宽

由于卷积核专注于扩大通道数、池化专注于缩小宽和高,使得模型架构上更深更宽的同时,控制了计算量的增加规模。

二、VGG的网络结构

下图是来自论文《Very Deep Convolutional Networks for Large-Scale Image Recognition》(基于甚深层卷积网络的大规模图像识别)的VGG网络结构,正是在这篇论文中提出了VGG,如下图:

上图中,池化层都不算入层数中,例vgg-16为13个conv+3个FC组成。

在这篇论文中分别使用了A、A-LRN、B、C、D、E这6种网络结构进行测试,这6种网络结构相似,都是由5层卷积层、3层全连接层组成,其中区别在于每个卷积层的子层数量不同,从A至E依次增加(子层数量从1到4),总的网络深度从11层到19层(添加的层以粗体显示),表格中的卷积层参数表示为“conv⟨感受野大小⟩-通道数⟩”,例如con3-128,表示使用3x3的卷积核,通道数为128。为了简洁起见,在表格中不显示ReLU激活功能。

其中,网络结构D就是著名的VGG16,网络结构E就是著名的VGG19。

以网络结构D(VGG16)为例,介绍其处理过程如下,请对比上面的表格和下方这张图,留意图中的数字变化,有助于理解VGG16的处理过程:

整个input→output过程如下:

1、输入224x224x3的图片,经2个3x3的卷积核(out_channel=64)作两次卷积+ReLU,卷积后的尺寸变为224x224x64

2、作max pooling(最大化池化),池化单元尺寸为2x2(stride=2,根据特征图大小计算公式进行计算:/weixin_45829462/article/details/106581152(如下图),效果为图像尺寸减半),池化后的尺寸变为112x112x64

3、经2个3x3的卷积核(out_channel=128)作两次卷积+ReLU,尺寸变为112x112x128

4、作2x2的max pooling池化,尺寸变为56x56x128

5、经3个3x3的卷积核(out_channel=256)作三次卷积+ReLU,尺寸变为56x56x256

6、作2x2的max pooling池化,尺寸变为28x28x256

7、经3个3x3的卷积核(out_channel=512)作三次卷积+ReLU,尺寸变为28x28x512

8、作2x2的max pooling池化,尺寸变为14x14x512

9、经3个3x3的卷积核作三次卷积+ReLU,尺寸变为14x14x512

10、作2x2的max pooling池化,尺寸变为7x7x512

11、与两层1x1x4096,一层1x1x1000进行全连接+ReLU(共三层)

12、通过softmax输出1000个预测结果(softmax函数的作用是输出概率最大,也就是最有可能的那个类)

从上面的过程可以看出VGG网络结构还是挺简洁的,都是由小卷积核、小池化核、ReLU组合而成。其简化图如下(以VGG16为例):

但是,唯一不能忍受的是vgg产生的网络参数实在是太多了,且大部分都在全连接层,特别是第一个全连接层(标红部分),你难以能想象一个16层的模型产生的预训练权重就有500m之大(darknet53的53层神经也才200m左右)。

A、A-LRN、B、C、D、E这6种网络结构的深度虽然从11层增加至19层,但参数量变化不大,这是由于基本上都是采用了小卷积核(3x3,只有9个参数),这6种结构的参数数量(百万级)并未发生太大变化,这是因为在网络中,参数主要集中在全连接层。

经作者对A、A-LRN、B、C、D、E这6种网络结构进行单尺度的评估,错误率结果如下:

从上表可以看出:

1、LRN层无性能增益(A-LRN)

VGG作者通过网络A-LRN发现,AlexNet曾经用到的LRN层(local response normalization,局部响应归一化)并没有带来性能的提升,因此在其它组的网络中均没再出现LRN层。

2、随着深度增加,分类性能逐渐提高(A、B、C、D、E)

从11层的A到19层的E,网络深度增加对top1和top5的错误率下降很明显。

3、多个小卷积核比单个大卷积核性能好(B)

VGG作者做了实验用B和自己一个不在实验组里的较浅网络比较,较浅网络用conv5x5来代替B的两个conv3x3,结果显示多个小卷积核比单个大卷积核效果要好。

最后进行个小结:

1、通过增加深度能有效地提升性能;

2、最佳模型:VGG16,从头到尾只有3x3卷积与2x2池化,简洁优美;

3、卷积可代替全连接,可适应各种尺寸的图片

三、模型复现

3.1 下载数据集集:’/example_images/flower_photos.tgz

3.2 数据集处理:

数据集下载后,解压到data_set文件夹,另外,新建sample,output,flower_data文件加夹,使用split_data将数据集分成训练集和验证集,比例在9:1之间,分好后的样本自动存入flower_data文件夹中,总的文件如下:

3.3模型构建

建立model.py,构建神经网络:

import torchimport torch.nn as nnclass VGG(nn.Module):def __init__(self, features, num_classes=1000, init_weights=True):super(VGG, self).__init__()self.features = featuresself.avgpool = nn.AdaptiveAvgPool2d((7, 7))self.classifier = nn.Sequential(nn.Linear(512 * 7 * 7, 4096),nn.ReLU(True),nn.Dropout(),nn.Linear(4096, 4096),nn.ReLU(True),nn.Dropout(),nn.Linear(4096, num_classes),)if init_weights:self._initialize_weights()def forward(self, x):x = self.features(x)x = self.avgpool(x)x = torch.flatten(x, 1)x = self.classifier(x)return xdef _initialize_weights(self):for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')if m.bias is not None:nn.init.constant_(m.bias, 0)elif isinstance(m, nn.BatchNorm2d):nn.init.constant_(m.weight, 1)nn.init.constant_(m.bias, 0)elif isinstance(m, nn.Linear):nn.init.normal_(m.weight, 0, 0.01)nn.init.constant_(m.bias, 0)def make_layers(cfg, batch_norm=False):layers = []in_channels = 3for 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)if batch_norm:layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]else:layers += [conv2d, nn.ReLU(inplace=True)]in_channels = vreturn nn.Sequential(*layers)cfgs = {'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],}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_layers(cfg), **kwargs)return model

建立train.py,用来训练模型,记住,由于博主电脑性能较老旧,所以博主采用的是迁移学习的训练方式,硬件设施允许的情况下,还是可以试试从零开始训练的:

import torchimport torch.nn as nnfrom torchvision import transforms, datasetsimport torch.optim as optimfrom model import vggimport osimport jsonimport timeimport torchvision.models.vggdevice = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")print(device)data_transform = {"train": pose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),"val": pose([transforms.Resize((224, 224)), # cannot 224, must (224, 224)transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}data_root = os.path.abspath(os.path.join(os.getcwd(), "/content/gdrive/My Drive")) # get data root pathimage_path = data_root + "/flower_data/" # flower data set pathtrain_dataset = datasets.ImageFolder(root=image_path + "/train",transform=data_transform["train"])train_num = len(train_dataset)# {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}flower_list = train_dataset.class_to_idxcla_dict = dict((val, key) for key, val in flower_list.items())# write dict into json filejson_str = json.dumps(cla_dict, indent=4)with open('class_indices.json', 'w') as json_file:json_file.write(json_str)batch_size = 32train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size, shuffle=True,num_workers=0)validate_dataset = datasets.ImageFolder(root=image_path + "/val",transform=data_transform["val"])val_num = len(validate_dataset)validate_loader = torch.utils.data.DataLoader(validate_dataset,batch_size=32, shuffle=True,num_workers=0)model_name = "vgg16"net = vgg(model_name=model_name, init_weights=True)# 加载预训练模型model_weights_path = './vgg16.pth'missing_keys, unexpected_keys = net.load_state_dict(torch.load(model_weights_path), strict=False)net.to(device)loss_function = nn.CrossEntropyLoss()# pata = list(net.parameters())optimizer = optim.Adam(net.parameters(), lr=0.0002)if __name__ == '__main__':save_path = './vgg.pth'best_acc = 0.0for epoch in range(20):# trainnet.train()running_loss = 0.0t1 = time.perf_counter()for step, data in enumerate(train_loader, start=0):images, labels = dataimages = images.to(device)labels = labels.to(device)optimizer.zero_grad()outputs = net(images)loss = loss_function(outputs, labels)loss.backward()optimizer.step()# print statisticsrunning_loss += loss.item()# print train processrate = (step + 1) / len(train_loader)a = "*" * int(rate * 50)b = "." * int((1 - rate) * 50)print("\rtrain loss: {:^3.0f}%[{}->{}]{:.3f}".format(int(rate * 100), a, b, loss), end="")print()print(time.perf_counter() - t1)# validatenet.eval()acc = 0.0 # accumulate accurate number / epochwith torch.no_grad():for val_data in validate_loader:val_images, val_labels = val_dataoutputs = net(val_images.to(device))predict_y = torch.max(outputs, dim=1)[1]acc += (predict_y == val_labels.to(device)).sum().item()val_accurate = acc / val_numif val_accurate > best_acc:best_acc = val_accuratetorch.save(net.state_dict(), save_path)print('[epoch %d] train_loss: %.3f test_accuracy: %.3f' %(epoch + 1, running_loss / step, val_accurate))print('Finished Training')

加载预训练模型关键代码:

model_weights_path = ‘./vgg16.pth’ missing_keys, unexpected_keys = net.load_state_dict(torch.load(model_weights_path),

3.4 结果如下:

可以看到,使用了预训练模型,网络收敛非常的快,,随着迭代次数的增加,精度也在不断的上升,检测后的结果保存在了output文件夹,检测效果如下:

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。