【Week-P2】CNN彩色图片分类-CIFAR10数据集

2023-12-24 09:45

本文主要是介绍【Week-P2】CNN彩色图片分类-CIFAR10数据集,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、环境配置
  • 二、准备数据
  • 三、搭建网络结构
  • 四、开始训练
  • 五、查看训练结果
  • 六、总结
    • 3.1 ⭐ `torch.nn.Conv2d()`详解
    • 3.2 ⭐ `torch.nn.Linear()`详解
    • 3.3 ⭐`torch.nn.MaxPool2d()`详解
    • 3.4 ⭐ 关于卷积层、池化层的计算
    • 4.2.1 `optimizer.zero_grad()`说明
    • 4.2.2 `loss.backward()`说明
    • 4.2.3 `optimizer.step()`说明
    • 4.4.1 `model.train()`说明
    • 4.4.2 `model.eval()`说明

本文采用CIFAR10数据集,通过简单CNN来实现彩色图片识别。

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊 | 接辅导、项目定制

一、环境配置

# 1. 设置环境
import sys
from datetime import datetimeimport torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvisionprint("---------------------1.配置环境------------------")
print("Start time: ", datetime.today())
print("Pytorch version: " + torch.__version__)
print("Python version: " + sys.version)device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

在这里插入图片描述

二、准备数据

导入数据的方式和【Week P1】中的方法是一致的,都是通过dataset下载数据集、通过dataloader加载数据集。

'''
2. 导入数据使用dataset下载CIFAR10数据集,并划分好训练集与测试集使用dataloader加载数据,并设置好基本的batch_size
'''
print("---------------------2.1 下载CIFAR10数据集,并划分训练集和测试集------------------")
train_ds = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), # 将数据类型转化为Tensordownload=True)test_ds  = torchvision.datasets.CIFAR10('data', train=False, transform=torchvision.transforms.ToTensor(), # 将数据类型转化为Tensordownload=True)print("---------------------2.2 设置batch_size------------------")
batch_size = 32train_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)print("---------------------2.2.1 取一个批次查看数据格式------------------")
# 取一个批次查看数据格式
# 数据的shape为:[batch_size, channel, height, weight]
# 其中batch_size为自己设定,channel,height和weight分别是图片的通道数,高度和宽度。
imgs, labels = next(iter(train_dl))
imgs.shapeprint("---------------------2.3 数据可视化------------------")
import numpy as np# 指定图片大小,图像大小为20宽、5高的绘图(单位为英寸inch)
plt.figure(figsize=(20, 5)) 
for i, imgs in enumerate(imgs[:20]):# 维度缩减npimg = imgs.numpy().transpose((1, 2, 0))# 将整个figure分成2行10列,绘制第i+1个子图。plt.subplot(2, 10, i+1)plt.imshow(npimg, cmap=plt.cm.binary)plt.axis('off')#plt.show()  如果你使用的是Pycharm编译器,请加上这行代码

等待漫长的4h35min后:
在这里插入图片描述

三、搭建网络结构

对于一般的CNN网络来说,都是由特征提取网络和分类网络构成,其中特征提取网络用于提取图片的特征,分类网络用于将图片进行分类。

用到的运算主要有:卷积、池化

网络结构:
在这里插入图片描述

以下几点涉及到的内容,统一在文末说明:
3.1 ⭐ torch.nn.Conv2d()详解
3.2 ⭐ torch.nn.Linear()详解
3.3 ⭐torch.nn.MaxPool2d()详解
3.4 ⭐ 关于卷积层、池化层的计算

print("---------------------3.1 定义简单CNN网络,要点:卷积和池化运算------------------")
import torch.nn.functional as Fnum_classes = 10  # 图片的类别数class Model(nn.Module):def __init__(self):super().__init__()# 特征提取网络self.conv1 = nn.Conv2d(3, 64, kernel_size=3)   # 第一层卷积,卷积核大小为3*3self.pool1 = nn.MaxPool2d(kernel_size=2)       # 设置池化层,池化核大小为2*2self.conv2 = nn.Conv2d(64, 64, kernel_size=3)  # 第二层卷积,卷积核大小为3*3   self.pool2 = nn.MaxPool2d(kernel_size=2) self.conv3 = nn.Conv2d(64, 128, kernel_size=3) # 第二层卷积,卷积核大小为3*3   self.pool3 = nn.MaxPool2d(kernel_size=2) # 分类网络self.fc1 = nn.Linear(512, 256)          self.fc2 = nn.Linear(256, num_classes)# 前向传播def forward(self, x):x = self.pool1(F.relu(self.conv1(x)))     x = self.pool2(F.relu(self.conv2(x)))x = self.pool3(F.relu(self.conv3(x)))x = torch.flatten(x, start_dim=1)x = F.relu(self.fc1(x))x = self.fc2(x)return xprint("---------------------3.2 加载和打印网络结构------------------")
from torchinfo import summary
# 将模型转移到GPU中(我们模型运行均在GPU中进行)
model = Model().to(device)summary(model)

在这里插入图片描述

四、开始训练

4.2 编写训练函数中,用到的函数有:

  • optimizer.zero_grad()
  • loss.backward()
  • optimizer.step()

在文末说明每个函数的使用方法

4.3 编写测试函数中:

  • 测试函数和训练函数大致相同,但是由于不进行梯度下降对网络权重进行更新,所以不需要传入优化器

4.4 正式训练中,使用的训练方法包括:

  • model.train():作用是启用 Batch Normalization 和 Dropout
  • model.eval():作用是不启用 Batch Normalization 和 Dropout
# 4. 训练模型
print("---------------------4.1 设置超参数------------------")
loss_fn    = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-2 # 学习率
opt        = torch.optim.SGD(model.parameters(),lr=learn_rate)print("---------------------4.2 编写训练函数-----------------")
# 训练循环
# 训练循环
def train(dataloader, model, loss_fn, optimizer):size = len(dataloader.dataset)  # 训练集的大小,一共60000张图片num_batches = len(dataloader)   # 批次数目,1875(60000/32)train_loss, train_acc = 0, 0  # 初始化训练损失和正确率for X, y in dataloader:  # 获取图片及其标签X, y = X.to(device), y.to(device)# 计算预测误差pred = model(X)          # 网络输出loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失# 反向传播optimizer.zero_grad()  # grad属性归零loss.backward()        # 反向传播optimizer.step()       # 每一步自动更新# 记录acc与losstrain_acc  += (pred.argmax(1) == y).type(torch.float).sum().item()train_loss += loss.item()train_acc  /= sizetrain_loss /= num_batchesreturn train_acc, train_lossprint("---------------------4.3 编写测试函数-----------------")
def test (dataloader, model, loss_fn):size        = len(dataloader.dataset)  # 测试集的大小,一共10000张图片num_batches = len(dataloader)          # 批次数目,313(10000/32=312.5,向上取整)test_loss, test_acc = 0, 0# 当不进行训练时,停止梯度更新,节省计算内存消耗with torch.no_grad():for imgs, target in dataloader:imgs, target = imgs.to(device), target.to(device)# 计算losstarget_pred = model(imgs)loss        = loss_fn(target_pred, target)test_loss += loss.item()test_acc  += (target_pred.argmax(1) == target).type(torch.float).sum().item()test_acc  /= sizetest_loss /= num_batchesreturn test_acc, test_lossprint("---------------------4.4 正式训练-----------------")
epochs     = 10
train_loss = []
train_acc  = []
test_loss  = []
test_acc   = []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)train_acc.append(epoch_train_acc)train_loss.append(epoch_train_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%,Test_loss:{:.3f}')print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))
print('Done')

在这里插入图片描述

五、查看训练结果

print("---------------------5. 查看训练结果-----------------")
import matplotlib.pyplot as plt
#隐藏警告
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()

在这里插入图片描述
可以看到,训练10个epoch后的效果是非常差的,训练准确率和测试准确率都不到60%。

六、总结

3.1 ⭐ torch.nn.Conv2d()详解

函数原型:

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)

关键参数说明:

  • in_channels ( int ):输入图像中的通道数
  • out_channels ( int ) : 卷积产生的通道数
  • kernel_size ( int or tuple ) :卷积核的大小
  • stride ( int or tuple , optional ) :卷积的步长。默认值:1
  • padding ( int , tuple或str , optional ) : 添加到输入的所有四个边的填充。默认值:0
  • dilation (int or tuple, optional):膨胀操作,控制kernel点(卷积核点)的间距,默认值:1。
  • padding_mode (字符串,可选) : ‘zeros’, ‘reflect’, ‘replicate’或’circular’. 默认:‘zeros’
  • 关于dilation参数图解:
    在这里插入图片描述

3.2 ⭐ torch.nn.Linear()详解

函数原型:

torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)

关键参数说明:

  • in_features:每个输入样本的大小
  • out_features:每个输出样本的大小

3.3 ⭐torch.nn.MaxPool2d()详解

函数原型:

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

关键参数说明:

  • kernel_size:最大的窗口大小
  • stride:窗口的步幅,默认值为kernel_size(核的大小)
  • padding:填充值,默认为0
  • dilation:控制窗口中元素步长的参数

3.4 ⭐ 关于卷积层、池化层的计算

下面的网络数据shape变化过程为:

3, 32, 32(输入数据)→ 64, 30, 30(经过卷积层1)→ 64, 15, 15(经过池化层1)→ 64, 13, 13(经过卷积层2)→ 64, 6, 6(经过池化层2)→ 128, 4, 4(经过卷积层3) → 128, 2, 2(经过池化层3)→ 512 -> 256→ num_classes(10)

计算过程如下:
(1)卷积输出shape公式:
在这里插入图片描述
输入数据为:[3, 32, 32],即图片矩阵大小为32*32,卷积核大小为3,填充步长为默认值0,步长为默认值1,代入计算得到输出的大小为:30*30,输出通道不变,所以输入数据[3, 32, 32]经过Conv1层后得到的shape为·[64, 30, 30]·。

(2)池化输出公式:
在这里插入图片描述
输入的数据格式(从Conv1得到)是:[64, 30, 30] [C*Hin*Win],已知:Hin=30,padding=0,dilation=1,kernel_size=2,stride=2(即kernel_size),代入上述池化公式,可得Hout=15
同理,Wout=15,C保持不变,故而output.shape [64, 15, 15]
在这里插入图片描述

4.2.1 optimizer.zero_grad()说明

  • optimizer.zero_grad()函数会遍历模型的所有参数,通过内置方法截断反向传播的梯度流,再将每个参数的梯度值设为0,即上一次的梯度记录被清空。

4.2.2 loss.backward()说明

  • PyTorch的反向传播(即tensor.backward())是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。

  • 具体来说,torch.tensorautograd包的基础类,如果设置tensorrequires_gradsTrue,就会开始跟踪在这个tensor上的所有运算,如果做完运算后使用tensor.backward(),所有的梯度就会自动运算,tensor的梯度将会累加到它的.grad属性里面去。

  • 更具体地说,损失函数loss是由模型的所有权重w经过一系列运算得到的,若某个wrequires_gradsTrue,则w的所有上层参数(后面层的权重w)的.grad_fn属性中就保存了对应的运算,然后在使用loss.backward()后,会一层层的反向传播计算每个w的梯度值,并保存到该w.grad属性中。

  • 如果没有进行tensor.backward()的话,梯度值将会是None因此loss.backward()要写在optimizer.step()之前

4.2.3 optimizer.step()说明

  • step()函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。因为梯度下降是基于梯度的,所以在执行optimizer.step()函数前应先执行loss.backward()函数来计算梯度。

  • 注意:optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()方法产生的。

4.4.1 model.train()说明

  • model.train()的作用是:启用 Batch NormalizationDropout

  • 如果模型中有BN层(Batch Normalization)和Dropout,需要在训练时添加model.train()

  • model.train()是保证BN层能够用到每一批数据的均值和方差。

  • 对于Dropout,model.train()是随机取一部分网络连接来训练更新参数。

4.4.2 model.eval()说明

  • model.eval()的作用是:不启用 Batch NormalizationDropout

  • 如果模型中有BN层(Batch Normalization)和Dropout,在测试时添加model.eval()

  • model.eval()是保证BN层能够用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变。

  • 对于Dropout,model.eval()是将所有网络连接都利用起来,即不进行随机舍弃神经元。

  • 训练完train样本后,生成的模型model要用来测试样本。在model(test)之前,需要加上model.eval(),否则的话,有输入数据,即使不训练,它也会改变权值。这是model中含有BN层和Dropout所带来的的性质。

这篇关于【Week-P2】CNN彩色图片分类-CIFAR10数据集的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/531374

相关文章

8种快速易用的Python Matplotlib数据可视化方法汇总(附源码)

《8种快速易用的PythonMatplotlib数据可视化方法汇总(附源码)》你是否曾经面对一堆复杂的数据,却不知道如何让它们变得直观易懂?别慌,Python的Matplotlib库是你数据可视化的... 目录引言1. 折线图(Line Plot)——趋势分析2. 柱状图(Bar Chart)——对比分析3

Spring Boot 整合 Redis 实现数据缓存案例详解

《SpringBoot整合Redis实现数据缓存案例详解》Springboot缓存,默认使用的是ConcurrentMap的方式来实现的,然而我们在项目中并不会这么使用,本文介绍SpringB... 目录1.添加 Maven 依赖2.配置Redis属性3.创建 redisCacheManager4.使用Sp

Python Pandas高效处理Excel数据完整指南

《PythonPandas高效处理Excel数据完整指南》在数据驱动的时代,Excel仍是大量企业存储核心数据的工具,Python的Pandas库凭借其向量化计算、内存优化和丰富的数据处理接口,成为... 目录一、环境搭建与数据读取1.1 基础环境配置1.2 数据高效载入技巧二、数据清洗核心战术2.1 缺失

Python处理超大规模数据的4大方法详解

《Python处理超大规模数据的4大方法详解》在数据的奇妙世界里,数据量就像滚雪球一样,越变越大,从最初的GB级别的小数据堆,逐渐演变成TB级别的数据大山,所以本文我们就来看看Python处理... 目录1. Mars:数据处理界的 “变形金刚”2. Dask:分布式计算的 “指挥家”3. CuPy:GPU

使用Vue-ECharts实现数据可视化图表功能

《使用Vue-ECharts实现数据可视化图表功能》在前端开发中,经常会遇到需要展示数据可视化的需求,比如柱状图、折线图、饼图等,这类需求不仅要求我们准确地将数据呈现出来,还需要兼顾美观与交互体验,所... 目录前言为什么选择 vue-ECharts?1. 基于 ECharts,功能强大2. 更符合 Vue

Java如何根据word模板导出数据

《Java如何根据word模板导出数据》这篇文章主要为大家详细介绍了Java如何实现根据word模板导出数据,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... pom.XML文件导入依赖 <dependency> <groupId>cn.afterturn</groupId>

Python实现获取带合并单元格的表格数据

《Python实现获取带合并单元格的表格数据》由于在日常运维中经常出现一些合并单元格的表格,如果要获取数据比较麻烦,所以本文我们就来聊聊如何使用Python实现获取带合并单元格的表格数据吧... 由于在日常运维中经常出现一些合并单元格的表格,如果要获取数据比较麻烦,现将将封装成类,并通过调用list_exc

Mysql数据库中数据的操作CRUD详解

《Mysql数据库中数据的操作CRUD详解》:本文主要介绍Mysql数据库中数据的操作(CRUD),详细描述对Mysql数据库中数据的操作(CRUD),包括插入、修改、删除数据,还有查询数据,包括... 目录一、插入数据(insert)1.插入数据的语法2.注意事项二、修改数据(update)1.语法2.有

SpringBoot实现接口数据加解密的三种实战方案

《SpringBoot实现接口数据加解密的三种实战方案》在金融支付、用户隐私信息传输等场景中,接口数据若以明文传输,极易被中间人攻击窃取,SpringBoot提供了多种优雅的加解密实现方案,本文将从原... 目录一、为什么需要接口数据加解密?二、核心加解密算法选择1. 对称加密(AES)2. 非对称加密(R

详解如何在SpringBoot控制器中处理用户数据

《详解如何在SpringBoot控制器中处理用户数据》在SpringBoot应用开发中,控制器(Controller)扮演着至关重要的角色,它负责接收用户请求、处理数据并返回响应,本文将深入浅出地讲解... 目录一、获取请求参数1.1 获取查询参数1.2 获取路径参数二、处理表单提交2.1 处理表单数据三、