【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

相关文章

SQL中如何添加数据(常见方法及示例)

《SQL中如何添加数据(常见方法及示例)》SQL全称为StructuredQueryLanguage,是一种用于管理关系数据库的标准编程语言,下面给大家介绍SQL中如何添加数据,感兴趣的朋友一起看看吧... 目录在mysql中,有多种方法可以添加数据。以下是一些常见的方法及其示例。1. 使用INSERT I

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

MySQL 删除数据详解(最新整理)

《MySQL删除数据详解(最新整理)》:本文主要介绍MySQL删除数据的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、前言二、mysql 中的三种删除方式1.DELETE语句✅ 基本语法: 示例:2.TRUNCATE语句✅ 基本语

MyBatisPlus如何优化千万级数据的CRUD

《MyBatisPlus如何优化千万级数据的CRUD》最近负责的一个项目,数据库表量级破千万,每次执行CRUD都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊MyBatisPl... 目录背景一、MyBATis Plus 简介二、千万级数据的挑战三、优化 CRUD 的关键策略1. 查

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

mysql中的数据目录用法及说明

《mysql中的数据目录用法及说明》:本文主要介绍mysql中的数据目录用法及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、版本3、数据目录4、总结1、背景安装mysql之后,在安装目录下会有一个data目录,我们创建的数据库、创建的表、插入的

Navicat数据表的数据添加,删除及使用sql完成数据的添加过程

《Navicat数据表的数据添加,删除及使用sql完成数据的添加过程》:本文主要介绍Navicat数据表的数据添加,删除及使用sql完成数据的添加过程,具有很好的参考价值,希望对大家有所帮助,如有... 目录Navicat数据表数据添加,删除及使用sql完成数据添加选中操作的表则出现如下界面,查看左下角从左

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

SpringBoot中4种数据水平分片策略

《SpringBoot中4种数据水平分片策略》数据水平分片作为一种水平扩展策略,通过将数据分散到多个物理节点上,有效解决了存储容量和性能瓶颈问题,下面小编就来和大家分享4种数据分片策略吧... 目录一、前言二、哈希分片2.1 原理2.2 SpringBoot实现2.3 优缺点分析2.4 适用场景三、范围分片

Redis分片集群、数据读写规则问题小结

《Redis分片集群、数据读写规则问题小结》本文介绍了Redis分片集群的原理,通过数据分片和哈希槽机制解决单机内存限制与写瓶颈问题,实现分布式存储和高并发处理,但存在通信开销大、维护复杂及对事务支持... 目录一、分片集群解android决的问题二、分片集群图解 分片集群特征如何解决的上述问题?(与哨兵模