【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

相关文章

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Python将大量遥感数据的值缩放指定倍数的方法(推荐)

《Python将大量遥感数据的值缩放指定倍数的方法(推荐)》本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像... 本文介绍基于python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动