Pytorch从零开始实战09

news2024/10/6 23:41:19

Pytorch从零开始实战——YOLOv5-Backbone模块实现

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

原作者K同学

文章目录

  • Pytorch从零开始实战——YOLOv5-Backbone模块实现
    • 环境准备
    • 数据集
    • 模型选择
    • 开始训练
    • 可视化
    • 模型预测
    • 总结

环境准备

本文基于Jupyter notebook,使用Python3.8,Pytorch2.0.1+cu118,torchvision0.15.2,需读者自行配置好环境且有一些深度学习理论基础。本次实验的目的理解YOLOv5-C3模块。
第一步,导入常用包

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
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)

创建设备对象,并且查看GPU数量

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

数据集

本次实验与上次实验仅仅是网络上的差异,其他并没有什么不同,本次数据集还是使用天气识别的数据集,分别有四个类别,cloudy、rain、shine、sunrise,不同的类别存放在不同的文件夹中,文件夹名是类别名。
使用pathlib查看类别

import pathlib
data_dir = './data/weather_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 # ['cloudy', 'sunrise', 'shine', 'rain']

使用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/weather_photos/", transform=all_transforms)
total_data.class_to_idx # {'cloudy': 0, 'rain': 1, 'shine': 2, 'sunrise': 3}

随机查看五张图片

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) # (901, 226)

模型选择

此次模型借用K同学所绘制的模型图
在这里插入图片描述
定义了一个autopad函数,用于确定卷积操作的填充,如果提供了p参数,则函数将使用提供的填充大小,否则函数中的填充计算将根据卷积核的大小k来确定,如果k是整数,那么将应用方形卷积核,填充大小将设置为k // 2,如果k是一个包含两个整数的列表,那么将应用矩形卷积核,填充大小将分别设置为列表中两个值的一半。

def autopad(k, p=None): 
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k] 
    return p

定义自定义卷积层,在init方法中:创建了一个卷积层 self.conv,使用了 nn.Conv2d,该卷积层接受输入通道数 c1,输出通道数 c2,卷积核大小 k,步幅 s,填充 p,分组数 g,并且没有偏置项(bias=False)。创建了一个批归一化层 self.bn,用于规范化卷积层的输出。创建了一个激活函数层 self.act,其类型取决于 act 参数。如果 act 为 True,它将使用 SiLU(Sigmoid Linear Unit)激活函数;如果 act 为其他的 nn.Module 类,它将直接使用提供的激活函数;否则,它将使用恒等函数(nn.Identity)作为激活函数。其中,SiLU激活函数为SiLU(x) = x * sigmoid(x)。

class Conv(nn.Module):
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

定义Bottleneck类,这个模块的作用是实现标准的残差连接,以提高网络性能。在 init方法中:计算了中间隐藏通道数 c_,它是输出通道数 c2 乘以扩张因子 e 的整数部分。创建了两个 Conv 模块 self.cv1 和 self.cv2,分别用于进行卷积操作。self.cv1 使用 1x1 的卷积核,将输入特征图的通道数从 c1 变换为 c_。self.cv2 使用 3x3 的卷积核,将通道数从 c_ 变换为 c2。创建了一个布尔值 self.add,用于指示是否应用残差连接。self.add 为 True 的条件是 shortcut 为 True 且输入通道数 c1 等于输出通道数 c2。
在forward 方法中,首先,通过 self.cv1(x) 将输入 x 传递给第一个卷积层,然后通过self.cv2(self.cv1(x)) 将结果传递给第二个卷积层。最后,根据 self.add 的值来决定是否应用残差连接。如果 self.add 为 True,将输入 x 与第二个卷积层的输出相加,否则直接返回第二个卷积层的输出。这样模块在需要时应用残差连接,以保留和传递更多的信息。

class Bottleneck(nn.Module):
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e) 
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

C3模块整体如上图所示,cv1 和 cv2 是两个独立的卷积操作,它们的输入通道数都是 c1,并经过相应的卷积操作后,输出通道数变为 c_。这是为了将输入特征映射进行降维和变换。cv3 接受 cv1 和 cv2 的输出,并且希望在这两部分特征上进行进一步的操作。为了能够将它们连接起来,cv3 的输入通道数必须匹配这两部分特征的输出通道数的总和,因此是 2 * c_。其中这个模块可以叠加n个Bottleneck块。

class C3(nn.Module):
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e) 
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1) 
        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

    def forward(self, x):
        return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))

下面定义了SPPF模块,初始化SPPF模块时,c1 是输入通道数,c2 是输出通道数,k 是池化核的大小,默认为5。在初始化过程中,通过除以2来得到隐藏通道数 c_。同时,定义了两个卷积层 cv1 和 cv2,以及一个最大池化层 m。
forward 函数中,首先,输入通过卷积层 cv1 进行处理,然后通过最大池化层 m 进行两次池化,分别得到 y1 和 y2。最后,将原始输入 x、y1、y2 以及 y2 经过一次最大池化后的结果进行通道拼接,并通过卷积层 cv2 处理,得到最终输出。
这个模块实现了一种特殊的池化操作(空间金字塔池化),有助于提取输入特征的不同尺度信息。

class SPPF(nn.Module):
    def __init__(self, c1, c2, k=5): 
        super().__init__()
        c_ = c1 // 2 
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * 4, c2, 1, 1)
        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

    def forward(self, x):
        x = self.cv1(x)
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')  
            y1 = self.m(x)
            y2 = self.m(y1)
            return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))

YOLOv5——backbone模块将上面模块组合起来,包括一系列卷积、C3、SPPF和全连接层

class YOLOv5_backbone(nn.Module):
    def __init__(self):
        super(YOLOv5_backbone, self).__init__()
        
        self.Conv_1 = Conv(3, 64, 3, 2, 2) 
        self.Conv_2 = Conv(64, 128, 3, 2) 
        self.C3_3   = C3(128,128)
        self.Conv_4 = Conv(128, 256, 3, 2) 
        self.C3_5   = C3(256,256)
        self.Conv_6 = Conv(256, 512, 3, 2) 
        self.C3_7   = C3(512,512)
        self.Conv_8 = Conv(512, 1024, 3, 2) 
        self.C3_9   = C3(1024, 1024)
        self.SPPF   = SPPF(1024, 1024, 5)
        
        self.classifier = nn.Sequential(
            nn.Linear(in_features=65536, out_features=100),
            nn.ReLU(),
            nn.Linear(in_features=100, out_features=4)
        )
        
    def forward(self, x):
        x = self.Conv_1(x)
        x = self.Conv_2(x)
        x = self.C3_3(x)
        x = self.Conv_4(x)
        x = self.C3_5(x)
        x = self.Conv_6(x)
        x = self.C3_7(x)
        x = self.Conv_8(x)
        x = self.C3_9(x)
        x = self.SPPF(x)
        
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)

        return x

device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))
model = YOLOv5_backbone().to(device)

使用summary查看模型架构

from torchsummary import summary
summary(model, input_size=(3, 224, 224))

在这里插入图片描述

开始训练

定义训练函数

def train(dataloader, model, loss_fn, opt):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    train_acc, train_loss = 0, 0

    for 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 /= size
    train_loss /= num_batches
    return train_acc, train_loss

定义测试函数

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_acc, test_loss = 0, 0
    with 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 /= size
    test_loss /= num_batches
    return test_acc, test_loss

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

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

开始训练,epoch设置为30

import time
epochs = 30
train_loss = []
train_acc = []
test_loss = []
test_acc = []

T1 = time.time()

best_acc = 0
best_model = 0

for 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_acc
        best_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()

在这里插入图片描述

模型预测

定义预测函数

from PIL import Image 

classes = list(total_data.class_to_idx)

def predict_one_image(image_path, model, transform, classes):
    
    test_img = Image.open(image_path).convert('RGB')
    plt.imshow(test_img)  # 展示预测的图片

    test_img = transform(test_img)
    img = test_img.to(device).unsqueeze(0)
    
    model.eval()
    output = model(img)

    _,pred = torch.max(output,1)
    pred_class = classes[pred]
    print(f'预测结果是:{pred_class}')

预测一张图片

predict_one_image(image_path='./data/weather_photos/cloudy/cloudy10.jpg', 
                  model=model, 
                  transform=all_transforms, 
                  classes=classes) # 预测结果是:cloudy

查看一下最佳模型的epoch_test_acc, epoch_test_loss

best_model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, best_model, loss_fn)
epoch_test_acc, epoch_test_loss # (0.9690265486725663, 0.09377550333737972)

总结

本次实验了解到模型由卷积、批归一化和残差连接组成,可以进一步提取和强化特征,同时又了解到SPPF层的实现,其用于捕获不同尺度的上下文信息。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyBind11五分钟入门【Python/C++调用】

从 Python 调用 C 基本上有两种方法:使用 PyBind11 C 库生成 Python 模块,或使用 cytpes Python 包访问已编译的共享库。 使用 PyBind11 我们可以更轻松地共享许多数据类型,而使用 ctypes 是一种低级 C 风格的解决方案。 在线工具推荐&#x…

synchronized的原理和Callable接口

目录 ♫synchronized原理 ♪锁升级 ♪锁优化 ♫Callable接口 ♫synchronized原理 我们知道synchronized锁可以控制多个线程对共享资源的访问,两个线程针对同一变量访问就会产生阻塞等待。而synchronized锁并不是一成不变的,它会根据情况进行一次升级。…

MySQL---存储过程

存储过程的相关概念 是一组为了完成特定功能的sql语句的集合,类似于函数 写好了一个存储过程之后,我们可以像函数一样随时调用sql的集合。 复杂的,需要很多sql语句联合执行完成的任务 存储过程再执行上比sql语句的执行速度更快&#xff0c…

CS224W5.2——Relational and Iterative Classification

本节中,我们介绍用于节点分类的关系分类器和迭代分类。 从关系分类器开始,我们展示了如何基于邻居的标签迭代更新节点标签的概率。然后讨论迭代分类,通过根据邻居的标签及其特征预测节点标签来改进集体分类。 文章目录 1. 框架2. 关系分类3.…

基于SpringBoot的SSMP整合案例(开启日志与分页查询条件查询功能实现)

开启事务 导入Mybatis-Plus框架后,我们可以使用Mybatis-Plus自带的事务,只需要在配置文件中配置即可 使用配置方式开启日志,设置日志输出方式为标准输出mybatis-plus:global-config:db-config:table-prefix: tb_id-type: autoconfiguration:…

【黑客】最适合小白的学习顺序

一、黑客是什么 原是指热心于计算机技术,水平高超的电脑专家,尤其是程序设计人员。但后来,黑客一词已被用于泛指那些专门利用电脑网络搞破坏或者恶作剧的家伙。 二、学习黑客技术的原因 其实,网络信息空间安全已经成为海陆空之…

Python基础教程:类--继承和方法的重写

嗨喽,大家好呀~这里是爱看美女的茜茜呐 什么是继承 继承就是让类与类之间产生父子关系,子类可以拥有父类的静态属性和方法 继承就是可以获取到另一个类中的静态属性和普通方法(并非所有成员) 在python中,新建的类可…

【紫光同创国产FPGA教程】——【PGL22G第十一章】以太网传输实验例程

本原创教程由深圳市小眼睛科技有限公司创作,版权归本公司所有,如需转载,需授权并注www.meyesemi.com) 适用于板卡型号: 紫光同创PGL22G开发平台(盘古22K) 一:盘古22K开发板(紫光…

建筑能源管理(9)——公共建筑能源管理技术

现阶段,在我国经济高速发展的同时,也面临着资源有限、能源消费急剧增长、能源供给与需求之间的矛盾日益突出等问题。数据显示,现阶段我国单位GDP的能耗水平是发达国家的3倍左右,这正是能源总体利用率较低所造成的。建筑能耗作为我…

2023年初学者入门 CV 指南概述

计算机视觉,是一个迅速发展的领域,将让你大开眼界。它的核心是教计算机像我们人类一样看和理解视觉信息。这份全面指南,将为我们揭示计算机视觉的基本概念,探索流行的应用程序,并瞥见计算机视觉的未来趋势。 计算机视觉…

FM8317-USB TYPE-C PD 多协议控制器

产品描述: FM8317是一款集成了USB Type-C、USB Power Delivery(PD3.0)、PPS的多协议端口控制器,为AC-DC适配器、车载充电器等设备提供高性价比的USB Type-C 端口充电解决方案。 FM8317内置的Type-C协议可以支持Type-C设备插入自动…

SparkAi创作系统ChatGPT网站源码+详细搭建部署教程+AI绘画系统+支持GPT4.0+Midjourney绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

Leetcode-145 二叉树的后序遍历

递归 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this…

字节和美团软件测试面试1000问(含文档)

一、Linux系统应用和环境配置 1、Linux系统的操作命令给我说10个,一般用什么工具远程连接Linux服务器? 2、Linux中的日志存储在哪里?怎么查看日志内容? 3、Linux中top和ps命令的区别? 4、Linux命令运行的结果如何写…

iOS 17.2更新:15Pro支持拍摄空间视频!

苹果又为开发者预览版用户推送了iOS 17.2 Beta2测试版的更新,已经注册Apple Beta版软件计划的用户只需打开设置--通用--软件更新即可在线OTA升级至最新的iOS 17.2测试版。 本次更新包大小为750M左右,内部版本号为(21C5040g)&#…

c语言,将奇数和偶数分类

题目:输入一个整数数组,实现一个函数,来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,所有偶数位于数组的后半部分。 思路:像冒泡排序那样,相邻两个数比较,两个都是偶数则不…

(免费版?)CLion Nova 强势登陆 C 和 C++ 开发领域

系列文章目录 文章目录 系列文章目录前言一、CLion Nova二、目标三、优势和改进四、显著差异五、如何安装 CLion Nova六、分享您的反馈意见总结 阿纳斯塔西娅-卡扎科娃 2023 年 11 月 9 日 前言 今天,我们宣布推出免费的 CLion 早期预览版,它使用 ReSh…

【Linux】Linux 中关于文件和文件夹的常用命令

Linux 中关于文件和文件夹的常用命令 讲解 Linux 常用命令的文章已经非常多了,而且有的文章也说的非常清楚详细。我们可能不会记住所有的命令,但对于工作中常用的命令应该熟记于心,最好的方式就是多多实践。 我们可以直接或者通过虚机的方式…

24 _ 二叉树基础(下):有了如此高效的散列表,为什么还需要二叉树?

上一节我们学习了树、二叉树以及二叉树的遍历,今天我们再来学习一种特殊的二叉树,二叉查找树。二叉查找树最大的特点就是,支持动态数据集合的快速插入、删除、查找操作。 我们之前说过,散列表也是支持这些操作的,并且散列表的这些操作比二叉查找树更高效,时间复杂度是O(…

Leetcode-101 对称二叉树

递归:主要思想:对称二叉树是左子树的左孩子右子树的右孩子,左子树的右孩子右子树的左孩子,递归实现思路较为清晰 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* Tr…