Pytorch从零开始实战08

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

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

原作者K同学

文章目录

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

环境准备

本文基于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))

最后定义我们的模型

class model_K(nn.Module):
    def __init__(self):
        super(model_K, self).__init__()
        
        # 卷积模块
        self.Conv = Conv(3, 32, 3, 2) 
        
        # C3模块1
        self.C3_1 = C3(32, 64, 3, 2)
        
        # 全连接网络层,用于分类
        self.classifier = nn.Sequential(
            nn.Linear(in_features=802816, out_features=100),
            nn.ReLU(),
            nn.Linear(in_features=100, out_features=4)
        )
        
    def forward(self, x):
        x = self.Conv(x)
        x = self.C3_1(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)

        return x

model = model_K().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.9469026548672567, 0.18065014126477763)

总结

通过这个实验可以了解如何在模型中实现残差连接,这对于训练深度神经网络特别有用。残差连接允许在模块之间传递和保留信息,有助于缓解梯度消失问题和训练更深的网络。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在Node.js中,什么是中间件(middleware)?它们的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

NodeJS 安装及环境配置

下载地址:https://nodejs.org/zh-cn/download/ 安装 NodeJS 根据自己电脑系统及位数选择,一般都选择 windows 64位 .msi 格式安装包。 所用命令: node -v npm -v PS:如果以上两条命令都能执行成功,表示安装完成&#…

虹科荣誉 | 喜讯!虹科成功入选“广州首届百家新锐企业”!!

文章来源:虹科品牌部 阅读原文:虹科荣誉 | 喜讯!虹科成功入选“广州首届百家新锐企业”!! 近日,由中共广州市委统战部、广州市工商业联合会、广州市工业和信息化局、广州市人民政府国有资产监督管理委员会…

linux系统SQL server数据库定时收缩

问题现象 出现下图问题,导致连接该数据库的程序不能正常启动 解决办法 定时收缩数据库 数据库定时收缩脚本 需要三个脚本文件 linux_sqlcmd_timing_task_shrink.sh:主脚本文件 # 设置数据库名称、用户名、密码等信息 # db_name"volador"…

Python:字符串格式化

文章目录 %用法使用format方法进行格式化 %用法 格式字符说明%s字符串%c单个字符%d十进制整数%o八进制整数%x十六进制整数%e指数(基底写为e)%E指数(基底写为E) x 1235 print(%o % x) print(%d % x) print(%x % x) print(%e % x) print(%s % 65) print(%c % a)使用format方法…

单链表的建立(头插法、尾插法)(数据结构与算法)

如果要把很多个数据元素存到一个单链表中,如何操作? 1.初始化一个单链表 2. 每次取一个数据元素,插入到表尾/表头 1. 尾插法建立单链表 尾插法建立的单链表元素顺序与输入数据集合的顺序相同,即按照输入数据的顺序排列。 使用尾插…

心理咨询预约小程序

随着微信小程序的日益普及,越来越多的人开始关注如何利用小程序来提供便捷的服务。对于心理咨询行业来说,搭建一个心理咨询预约小程序可以大大提高服务的效率和用户体验。本文以乔拓云平台为例,详细介绍如何轻松搭建一个心理咨询预约小程序。…

甘特图组件DHTMLX Gantt用例 - 如何拆分任务和里程碑项目路线图

创建一致且引人注意的视觉样式是任何项目管理应用程序的重要要求,这就是为什么我们会在这个系列中继续探索DHTMLX Gantt图库的自定义。在本文中我们将考虑一个新的甘特图定制场景,DHTMLX Gantt组件如何创建一个项目路线图。 DHTMLX Gantt正式版下载 用…

R语言的DICE模型实践技术

随着温室气体排放量的增大和温室效应的增强,全球气候变化问题受到日益的关注。我国政府庄严承诺在2030和2060年分别达到“碳达峰”和“碳中和”,因此气候变化和碳排放已经成为科研人员重点关心的问题之一。气候变化问题不仅仅是科学的问题,同…

百度百科怎么创建?百科创建需要注意哪些(一文看懂品牌/企业/人物百科创建)

随着互联网的不断发展,许多企业或品牌都选择创建百度百科作为一种很好的展示方式。百度百科可以被视为一张网络名片,拥有它能够提高人物、企业、品牌的知名度和影响力。那么人物百科、企业百科、品牌百科到底怎么创建呢? 大家创建百科前建议先…

【Python】

列表 列表中元素的类型可以不同,列表内部存储方式是元素值存储在不连续的空间,但是把他们的指针存在一块连续的空间 列表的创建 1.list1[] 创建一个空列表 2.用list函数 3.split函数截取 列表的更新 1.通过索引[]改变 2.切片修改 3.列表方法更新 列表…

高级 Python:函数

伊利亚拉扎列维奇 一、说明 读完标题后,你可能会问自己一些类似的事情,“Python 中的函数是一个高级概念?如何?所有课程都引入了功能作为语言的基本块。你既是对的,也是错的。 大多数关于 Python 的课程都将函数作为基…

银行账单转换beancount

用了beancount来记账后,发现每月的账单手动记是一件极其麻烦的事情。 然后再github搜索一通后,有double-entry-generator(https://github.com/deb-sig/double-entry-generator)能转换支付宝/微信的账单,但是没有自己用…

Prometheus+Node_exporter+Grafana实现监控主机

PrometheusNode_exporterGrafana实现监控主机 如果没有安装相关的配置,首先要进行安装配置,环境是基于Linux,虚拟机的相关环境配置在文末给出,现在先讲解PrometheusNode_exporterGrafana的安装和使用。 一.Prometheus安装 虽然…

vue-浏览器安装Vue开发者工具

极简插件:下载->开发者模式->拖曳安装->插件详情允许访问文件 网址:https://chrome.zzzmh.cn/index 搜索Vue Devtools 下载下来的安装包先解压 然后点击chrome浏览器的右上角三个点的按钮在里面找到扩展程序这个选项,然后点进去管理…

我在Vscode学OpenCV 基本的加法运算

根据上一篇我们可知__图像的属性 链接:《我在Vscode学OpenCV 处理图像》 属性— API 形状 img.shape 图像大小 img.size 数据类型 img.dtype  shape:如果是彩色图像,则返回包含行数、列数、通道数的数组;如果是二值图像或者灰度…

Java高级互联网架构师之路:垃圾回收器的介绍

本文重点 从本文开始我们将开启垃圾回收器的介绍了,我们知道垃圾回收算法是逻辑改变,而垃圾回收器是具体的实现。我们前面介绍的垃圾回收器有7个,本文将在添加三个,但是这三个目前来看不是很常用,我们只了解一下,我们主要还是讲解这7个垃圾回收器。 十个垃圾回收器 目…

【漏洞库】XXL-JOB 默认accessToken权限绕过导致RCE

文章目录 漏洞描述漏洞编号漏洞评级影响版本漏洞复现- EXP 编写 漏洞挖掘修复建议 漏洞描述 XXL-JOB 是一款开源的分布式任务调度平台,用于实现大规模任务的调度和执行。 XXL-JOB 默认配置下,用于调度通讯的 accessToken 不是随机生成的,而…

如何记录每天的工作日程?电脑手机通用的日程管理软件

在工作时间有限,但工作任务愈加繁多的现在职场中,要求每一个职场人士做好高效日程管理。通过高效管理日程,我们可以更好地组织和安排任务,合理分配时间和优先级,这有助于我们更专注地进行工作,减少时间的浪…

手写操作系统篇:实现裸机应用程序

文章目录 前言操作系统执行环境创建裸机平台项目Rust的Core库移除标准库依赖Qemu 启动流程内存布局编译流程内核的初始指令调整内核的内存布局手动加载内核可执行文件使用RustSBI提供的服务添加bootloader模块添加Makefile运行停止总体架构 前言 我们既然是手写操作系统&#…