Pytorch从零开始实战13

Pytorch从零开始实战——ResNet与DenseNet探索

本系列来源于365天深度学习训练营

原作者K同学

文章目录

  • Pytorch从零开始实战——ResNet与DenseNet探索
    • 环境准备
    • 数据集
    • 模型选择
    • 开始训练
    • 可视化
    • 总结

环境准备

本文基于Jupyter notebook,使用Python3.8,Pytorch2.0.1+cu118,torchvision0.15.2,需读者自行配置好环境且有一些深度学习理论基础。本次实验的目的是探索ResNet和DenseNet结合。
第一步,导入常用包

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn.functional as F
import random
from time import time
import numpy as np
import pandas as pd
import datetime
import gc
import os
import copy
import warnings
os.environ['KMP_DUPLICATE_LIB_OK']='True'  # 用于避免jupyter环境突然关闭
torch.backends.cudnn.benchmark=True  # 用于加速GPU运算的代码

设置随机数种子

torch.manual_seed(428)
torch.cuda.manual_seed(428)
torch.cuda.manual_seed_all(428)
random.seed(428)
np.random.seed(428)

检查设备对象

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device, torch.cuda.device_count() # # (device(type='cuda'), 2)

数据集

本次数据集是使用鸟的图片,分别有四种类别的鸟,根据鸟的类别名称存放在不同的文件夹中。
使用pathlib查看类别

import pathlib
data_dir = './data/bird_photos/'
data_dir = pathlib.Path(data_dir) # 转成pathlib.Path对象
data_paths = list(data_dir.glob('*')) 
classNames = [str(path).split("/")[2] for path in data_paths]
classNames # ['Black Throated Bushtiti', 'Cockatoo', 'Black Skimmer', 'Bananaquit']

使用transforms对数据集进行统一处理,并且根据文件夹名映射对应标签

all_transforms = transforms.Compose([transforms.Resize([224, 224]),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 标准化
])total_data = datasets.ImageFolder("./data/bird_photos/", transform=all_transforms)
total_data.class_to_idx# {'Bananaquit': 0,# 'Black Skimmer': 1,# 'Black Throated Bushtiti': 2,# 'Cockatoo': 3}

随机查看5张图片

def plotsample(data):fig, axs = plt.subplots(1, 5, figsize=(10, 10)) #建立子图for i in range(5):num = random.randint(0, len(data) - 1) #首先选取随机数,随机选取五次#抽取数据中对应的图像对象,make_grid函数可将任意格式的图像的通道数升为3,而不改变图像原始的数据#而展示图像用的imshow函数最常见的输入格式也是3通道npimg = torchvision.utils.make_grid(data[num][0]).numpy()nplabel = data[num][1] #提取标签 #将图像由(3, weight, height)转化为(weight, height, 3),并放入imshow函数中读取axs[i].imshow(np.transpose(npimg, (1, 2, 0))) axs[i].set_title(nplabel) #给每个子图加上标签axs[i].axis("off") #消除每个子图的坐标轴plotsample(total_data)

在这里插入图片描述
根据8比2划分数据集和测试集,并且利用DataLoader划分批次和随机打乱

train_size = int(0.8 * len(total_data))
test_size  = len(total_data) - train_size
train_ds, test_ds = torch.utils.data.random_split(total_data, [train_size, test_size])batch_size = 32
train_dl = torch.utils.data.DataLoader(train_ds,batch_size=batch_size,shuffle=True,)
test_dl = torch.utils.data.DataLoader(test_ds,batch_size=batch_size,shuffle=True,)len(train_dl.dataset), len(test_dl.dataset) # (452, 113)

模型选择

本次模型采用模型集成,也就是说将一个输入特征分别送到两个不同的模型中去学习,到最后将输出特征融合后进行分类,每个模型可能对数据集的不同方面有更强的学习能力,因此通过集成,可以期望获得更全面、更强大的特征表示。
具体而言,本次实验使用了一个 DenseNet 和一个 ResNet,它们在网络结构和特征提取方面有所不同。DenseNet 使用密集连接的结构,充分利用了每一层的特征,而 ResNet 使用残差连接,有助于解决梯度消失问题,使得网络更易训练。

先建立ResNet模型,首先实现Block块,首先进行预激活层,包括标准化和ReLu激活函数,接着进行shortcut操作,如果conv_shortcut为True,会使用一个1x1卷积层进行变换,否则,如果stride为1,则进行恒等映射,否则使用1x1的最大池化。随后经过三个卷积层。在forward方法中,输入 x 经过预激活层,然后进行三个卷积操作,最后将shortcut和经过卷积的结果相加。这种结构使得梯度更容易反向传播,从而有助于训练深层网络。

class Block2(nn.Module):def __init__(self, in_channels, filters, kernel_size=3, stride=1, conv_shortcut=False):super(Block2, self).__init__()self.preact = nn.Sequential(nn.BatchNorm2d(in_channels),nn.ReLU())if conv_shortcut:self.shortcut = nn.Conv2d(in_channels, 4 * filters, kernel_size=1, stride=stride)else:if stride == 1:self.shortcut = nn.Identity()else: self.shortcut = nn.MaxPool2d(1, stride=stride)self.conv1 = nn.Sequential(nn.Conv2d(in_channels, filters, kernel_size=1, stride=1, bias=False),nn.BatchNorm2d(filters),nn.ReLU())self.conv2 = nn.Sequential(nn.ZeroPad2d(padding=(1, 1, 1, 1)),nn.Conv2d(filters, filters, kernel_size=kernel_size, stride=stride, bias=False),nn.BatchNorm2d(filters),nn.ReLU())self.conv3 = nn.Conv2d(filters, 4 * filters, kernel_size=1)def forward(self, x):preact = self.preact(x)shortcut = self.shortcut(preact)x = self.conv1(preact)x = self.conv2(x)x = self.conv3(x)out = shortcut + xreturn out

下面实现堆叠块,通过传入不同的参数去调用Block块,其中[Block2(4 * filters, filters) for i in range(0, blocks)],使用 Python 中的列表解析创建了 blocks 个残差块。这些残差块的输入通道数为 4 * filters,以匹配前一个残差块的输出通道数。

class Stack2(nn.Module):def __init__(self, in_channels, filters, blocks, stride1=2):super(Stack2, self).__init__()self.blocks = nn.Sequential(Block2(in_channels, filters, conv_shortcut=True),*[Block2(4 * filters, filters) for i in range(0, blocks)],Block2(4 * filters, filters, stride=stride1))def forward(self, x):return self.blocks(x)

下面实现ResNet网络主体,去掉全连接层。

class ResNetPart(nn.Module):def __init__(self, include_top=True, preact=True, num_classes=1000):super().__init__()self.conv1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),nn.BatchNorm2d(64),nn.ReLU())self.pool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.conv2 = Stack2(64, 64, 3)self.conv3 = Stack2(256, 128, 4)self.conv4 = Stack2(512, 256, 6)self.conv5 = Stack2(1024, 512, 3, stride1=1)self.post = nn.Sequential(nn.BatchNorm2d(2048),nn.ReLU())self.include_top = include_topif include_top:self.avg_pool = nn.AdaptiveAvgPool2d(1)def forward(self, x):x = self.conv1(x)x = self.pool1(x)x = self.conv2(x)x = self.conv3(x)x = self.conv4(x)x = self.conv5(x)x = self.post(x)if self.include_top:x = self.avg_pool(x)x = torch.flatten(x, 1)return x

下面实现DenseNet部分。首先对DenseLayer类定义,本次实验使用add_module函数,默认是用于向类中添加一个子模块,第一个参数为模块名,第二个参数为模块实例,其实相当于加到父类的nn.Sequential里面,所以调用的时候使用super().forward(x),这段的核心是将输入 x 与新特征 t 进行通道维度上的连接,完成密集连接。

class DenseLayer(nn.Sequential):def __init__(self, num_input_features, growth_rate, bn_size, drop_rate):super().__init__()self.add_module("norm1", nn.BatchNorm2d(num_input_features))self.add_module("relu1", nn.ReLU(inplace=True))self.add_module("conv1", nn.Conv2d(num_input_features, bn_size * growth_rate, kernel_size=1, stride=1, bias=False))self.add_module("norm2", nn.BatchNorm2d(bn_size * growth_rate))self.add_module("relu2", nn.ReLU(inplace=True))self.add_module("conv2", nn.Conv2d(bn_size*growth_rate, growth_rate, kernel_size=3, stride=1, padding=1, bias=False))self.drop_rate = drop_ratedef forward(self, x):t = super().forward(x)if self.drop_rate > 0:t = F.dropout(t, p=self.drop_rate, training=self.training)return torch.cat([x, t], 1)

下面是DenseBlock的实现,通过循环创建了多个DenseLayer。其中的 num_input_features + i * growth_rate 用于指定输入通道的数量,确保每个DenseLayer的输入通道数逐渐增加。将新创建的DenseLayer添加为 DenseBlock 的子模块。循环结束后,DenseBlock 就包含了多个DenseLayer,每个DenseLayer都具有逐渐增加的输入通道数量。

class DenseBlock(nn.Sequential):def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):super().__init__()for i in range(num_layers):layer = DenseLayer(num_input_features + i * growth_rate, growth_rate, bn_size, drop_rate)self.add_module("denselayer%d" % (i + 1), layer)

下面是Transition,实现过渡的功能,是在块之间降低通道数量和空间维度。

class Transition(nn.Sequential):def __init__(self, num_input_feature, num_output_features):super().__init__()self.add_module("norm", nn.BatchNorm2d(num_input_feature))self.add_module("relu", nn.ReLU(inplace=True))self.add_module("conv", nn.Conv2d(num_input_feature, num_output_features, kernel_size=1, stride=1, bias=False))self.add_module("pool", nn.AvgPool2d(2, stride=2))

实现深度学习网络主体,将不同的输出特征进行融合,完成分类。

from collections import OrderedDict
class Model(nn.Module):def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64,bn_size=4, compression_rate=0.5, drop_rate=0, num_classes=1000):super().__init__()self.features = nn.Sequential(OrderedDict([("conv0", nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False)),("norm0", nn.BatchNorm2d(num_init_features)),("relu0", nn.ReLU(inplace=True)),("pool0", nn.MaxPool2d(3, stride=2, padding=1))]))num_features = num_init_featuresfor i, num_layers in enumerate(block_config):block = DenseBlock(num_layers, num_features, bn_size, growth_rate, drop_rate)self.features.add_module("denseblock%d" % (i + 1), block)num_features += num_layers * growth_rateif i != len(block_config) - 1:transition = Transition(num_features, int(num_features * compression_rate))self.features.add_module("transition%d" % (i + 1), transition)num_features = int(num_features * compression_rate)self.features.add_module("norm5", nn.BatchNorm2d(num_features))self.features.add_module("relu5", nn.ReLU(inplace=True))self.classifier = nn.Linear(num_features + 2048, num_classes)for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight)elif isinstance(m, nn.BatchNorm2d):nn.init.constant_(m.bias, 0)nn.init.constant_(m.weight, 1)elif isinstance(m, nn.Linear):nn.init.constant_(m.bias, 0)self.part = ResNetPart()def forward(self, x):t = self.part(x)features = self.features(x)out = F.avg_pool2d(features, 7, stride=1).view(features.size(0), -1)out = torch.cat([out, t], dim=1) out = self.classifier(out)return out

使用summary查看网络
在这里插入图片描述

开始训练

定义训练函数

def train(dataloader, model, loss_fn, opt):size = len(dataloader.dataset)num_batches = len(dataloader)train_acc, train_loss = 0, 0for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)opt.zero_grad()loss.backward()opt.step()train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()train_loss += loss.item()train_acc /= sizetrain_loss /= num_batchesreturn train_acc, train_loss

定义测试函数

def test(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)test_acc, test_loss = 0, 0with torch.no_grad():for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)test_acc += (pred.argmax(1) == y).type(torch.float).sum().item()test_loss += loss.item()test_acc /= sizetest_loss /= num_batchesreturn test_acc, test_loss

定义学习率、损失函数、优化算法

loss_fn = nn.CrossEntropyLoss()
learn_rate = 0.0002
opt = torch.optim.Adam(model.parameters(), lr=learn_rate)

开始训练,epoch设置为30

import time
epochs = 20
train_loss = []
train_acc = []
test_loss = []
test_acc = []T1 = time.time()best_acc = 0
best_model = 0for epoch in range(epochs):model.train()epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)model.eval() # 确保模型不会进行训练操作epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)if epoch_test_acc > best_acc:best_acc = epoch_test_accbest_model = copy.deepcopy(model)train_acc.append(epoch_train_acc)train_loss.append(epoch_train_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)print("epoch:%d, train_acc:%.1f%%, train_loss:%.3f, test_acc:%.1f%%, test_loss:%.3f"% (epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss))T2 = time.time()
print('程序运行时间:%s秒' % (T2 - T1))PATH = './best_model.pth'  # 保存的参数文件名
if best_model is not None:torch.save(best_model.state_dict(), PATH)print('保存最佳模型')
print("Done")

由于数据量小,所以略微有些过拟合。
在这里插入图片描述

可视化

可视化训练过程和测试过程

import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率epochs_range = range(epochs)plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

在这里插入图片描述

总结

由于作者水平有限,所以本次模型之间的结合探索采用模型集成,这其中的优势有:1.每个模型通过不同的方式学习数据的表示,将它们集成可以得到更全面、更丰富的特征表示。2.不同的模型可能对数据集中的不同样本和模式有更好的泛化能力,集成可以减少过拟合的风险。这样的模型可以提供更强的鲁棒性,但一定有很多更好的结合方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/278431.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Excel中MATCH和INDEX函数的用法详解,以及Vlookup的数组用法

match函数 目的:查询函数,范围单元格中搜索特定的项,然后返回该项在此区域中的相对位置。 For example:让 match 去【隔壁办公室】找【老张】 Match 回复:【老张】坐在【隔壁办公室】第【四】个座位上 公式:【 mat…

Redis 的常见使用场景

01 缓存 作为 Key-Value 形态的内存数据库,Redis 最先会被想到的应用场景便是作为数据缓存。而使用 Redis 缓存数据非常简单,只需要通过 string 类型将序列化后的对象存起来即可,不过也有一些需要注意的地方: 必须保证不同对象的…

服务器数据恢复-EqualLogic PS存储硬盘坏道导致存储不可用的数据恢复案例

服务器数据恢复环境: 一台DELL EqualLogic PS系列存储,存储中有一组由16块SAS硬盘组成的RAID5。上层是VMFS文件系统,存放虚拟机文件。存储上层分了4个卷。 服务器故障&检测: 存储上有2个硬盘指示灯显示黄色,磁盘出…

微服务架构之争:Quarkus VS Spring Boot

在容器时代(“Docker时代”),无论如何,Java仍然活着。Java在性能方面一直很有名,主要是因为代码和真实机器之间的抽象层,多平台的成本(一次编写,随处运行——还记得吗?&a…

【MySQL学习之基础篇】约束

文章目录 1. 概述2. 基础约束3. 外键约束3.1. 介绍3.2. 外键的添加3.3. 外键删除和更新行为 1. 概述 概念: 约束是作用于表中字段上的规则,用于限制存储在表中的数据。     目的: 保证数据库中数据的正确、有效性和完整性。 分类&#x…

路由器原理

目录 一.路由器 1.路由器的转发原理 2.路由器的工作原理 二.路由表 1.路由表的形成 2.路由表表头含义 直连: 非直连: 静态 静态路由的配置 负载均衡(浮动路由) 默认路由 动态 三.交换与路由对比 一.路由器 1.路由器…

Cockpit upload文件上传漏洞(CVE-2023-1313)

0x01 产品简介 Cockpit 是一个自托管、灵活且用户友好的无头内容平台,用于创建自定义数字体验。 0x02 漏洞概述 Cockpit assetsmanager/upload接口处存在文件上传漏洞,攻击者可通过该漏洞在服务器端任意上传代码,写入后门,获取服务器权限,进而控制整个web服务器。 0x0…

C语言----文件操作(二)

在上一篇文章中我们简单介绍了在C语言中文件是什么以及文件的打开和关闭操作,在实际工作中,我们不仅仅是要打开和关闭文件,二是需要对文件进行增删改写。本文将详细介绍如果对文件进行安全读写。 一,以字符形式读写文件&#xff…

柔性数组(结构体成员)

目录 前言: 柔性数组: 给柔性数组分配空间: 调整柔性数组大小: 柔性数组的好处: 前言: 柔性数组?可能你从未听说,但是确实有这个概念。听名字,好像就是柔软的数…

git checkout进行更改分支

git clone https://gitee.com/yaleguo1/minit-learning-demo.git下载代码。 cd minit-learning-demo/进入目录里边。 ls -l看一下当前分支的内容。 git checkout geek_chapter02更改分支到geek_chapter02。 ls -l看一下目录里边的内容。

docker小白第四天

docker小白第一天 什么是镜像 1、是一种轻量级、可执行的独立软件包,它包含运行某个软件所需的所有内容,我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等),这个打包好的运行环境就…

磁盘坏道扫描工具 Macrorit Disk Scanner v6.7.0 中文免费版 -供大家学习研究参考

非常方便实用的磁盘坏道修复软件。Wipe Bad Disk功能强大好用,通过特殊的算法来强制将硬盘的坏道删除清空格式化,从而拯救因产生坏道而不敢继续使用的硬盘!要注意的是经过这块软件清空的硬盘数据基本上是不能被恢复的,所以操作前请一定要备份…