深度学习基础案例2--从0到1构建CNN卷积神经网络(以识别CIFR10为例)

基础阶段目标目标

  • 熟悉CNN、RNN神经网络,了解yolo、transfomer等模型
  • 熟练使用Pytorch框架,了解tensorflow

本次目标

  • 了解CNN神经网络构建思路
  • 学习pytroch框架
  • 本文在训练10轮的情况下,预测准确率达到了百分之60多

1、前期准备

1、导入GPU

作用:导入必要的库和检查是否支持cuda

import numpy as np
import torch
import torchvision    # 视觉库
import torch.nn as nn  # 神经网络库
import matplotlib.pylab as plt

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
print(torch.__version__)
print(torchvision.__version__)

输出结果如下:

cuda
2.4.0
0.19.0

2、导入数据

train_ds = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), download=True)

test_ds = torchvision.datasets.CIFAR10('data', train=False, transform=torchvision.transforms.ToTensor(), download=True)
  • 函数原型:

  • torchvision.datasets.CIFAR10(root, train=True, transform=None, target_transform=None, download=False)
    
    • root:数据地址,如果从网上下载数据,则自动会创建文件目录data并且数据存入
    • train:训练集还是测试集,训练集为True,训练集为False
    • transform:数据类型
    • taget_transform:接受目标并对其进行转换的函数/转换
    • download是否从网上下载数据(pytorch有个好处就是可以自动从网上下载数据)
# 动态加载数据
batch_size = 32  # 每一批加载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)
  • 注意:每一批加载的数据是随机划分的
  • DataLoader是pytorch的动态数据加载器支持并行化,可以加快存储数据

查看自己上面划分数据的信息:

# 取一个批次查看数据
# 数据得shape为:[batch_size, channel, heigh, weight]
# batch_size 是自己设定得,channel,height,weight分别是图片得通道数,高度,宽度
imgs, labels = next(iter(train_dl))
imgs.shape

数据信息输出如下:

torch.Size([32, 3, 32, 32])

3、数据可视化

展示20张图片:

# 输出一部分图片,可视化

plt.figure(figsize=(20,5))
for i, imgs in enumerate(imgs[: 20]):
    # 进行轴变换
    npimg = imgs.numpy().transpose((1, 2, 0))
    # 拆分子图
    plt.subplot(2, 10, i + 1)
    plt.imshow(npimg, cmap=plt.cm.binary)
    plt.axis('off')

在这里插入图片描述

2、构建CNN神经网络

​ CNN网络一般由三部分构成

  • 卷积层:提取图片特征
  • 池化层:用于数据降维
  • 全连接层:特征提取,用来输出想要的结果,即作用是降维,可以将数据降到自己想要的维度

1、卷积层原理介绍与计算

# 函数原型
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:输入通道数

  • out_channels:输出通道数

  • kernel_size:卷积层大小

  • stride:卷积步伐大小

  • padding:添加到输入的所有四个边的填充。默认值:0

  • dilation :扩张操作,控制kernel点(卷积核点)的间距,默认值:1。

  • groups:将输入通道分组成多个子组,每个子组使用一组卷积核来处理。默认值为 1,表示不进行分组卷积。

  • padding_mode:‘zeros’, ‘reflect’, ‘replicate’或’circular’. 默认:‘zeros’

卷积维度的计算

卷积输出维度计算公式如下:

$o=\lfloor\frac{(w-k+2p)}s+1\rfloor $

  • 输入图片矩阵大小:w * w
  • 卷积核大小:k * k
  • 步长:s
  • 填充大小:p

本文模型构建的卷积计算在下面模型构建流程中

2、池化层原理介绍与计算

函数原型

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:控制窗口中元素步幅的参数,默认为1

池化层的计算

  •   I n p u t :   ( N ,   C ,   H i n ,   W i n )   o r   ( C ,   H i n ,   W i n )   O u t p u t :   ( N ,   C ,   H o u t ,   W o u t )   o r   ( C ,   H o u t ,   W o u t ) \begin{aligned}&\mathrm{~Input:~(N,~C,~H_{in},~W_{in})~or~(C,~H_{in},~W_{in})}\\&\mathrm{~Output:~(N,~C,~H_{out},~W_{out})~or~(C,~H_{out},~W_{out})}\end{aligned}  Input: (N, C, Hin, Win) or (C, Hin, Win) Output: (N, C, Hout, Wout) or (C, Hout, Wout)

H o u t = ⌊ H i n   + 2 ∗   p a d d i n g   [ 0 ] −   d i l a t i o n   [ 0 ] × (   k e r n e l s i z e   [ 0 ] − 1 ) − 1 s t r i d e [ 0 ] + 1 ⌋ W o u t = ⌊ W i n   + 2 ∗   p a d d i n g   [ 1 ] −   d i l a t i o n   [ 1 ] × (   k e r n e l s i z e   [ 1 ] − 1 ) − 1 s t r i d e   [ 1 ] + 1 ⌋ \begin{gathered} H_{out} =\left\lfloor\frac{H_{\mathrm{in~}}+2*\mathrm{~padding~}[0]-\mathrm{~dilation~}[0]\times(\mathrm{~kernel_size~}[0]-1)-1}{\mathrm{stride}[0]}+1\right\rfloor \\ W_{\mathrm{out}} =\left\lfloor\frac{W_{\mathrm{in~}}+2*\mathrm{~padding~}[1]-\mathrm{~dilation~}[1]\times(\mathrm{~kernel_size~}[1]-1)-1}{\mathrm{stride~}[1]}+1\right\rfloor \end{gathered} Hout=stride[0]Hin +2 padding [0] dilation [0]×( kernelsize [0]1)1+1Wout=stride [1]Win +2 padding [1] dilation [1]×( kernelsize [1]1)1+1

案例解释在下面模型构建流程中

3、模型构建流程

  • 本文构建的CNN神经网络,有三层卷积层和三层池化层,分类类别为10类。

  • Model的构建函数,作用是分别构建卷积层、池化层和全连接层

  • Model中forward()函数,作用是构建CNN神经网络,构建思路如下图:

  • 在这里插入图片描述

  • 卷积层的计算:以卷积层一nn.Conv2d(3, 64, kernel_size=3)为例

    • 参数解释:输入通道为3,输出通道为64,卷积核大小为3
    • 输入图片通道和大小(3, 32, 32),通过卷积层一得到**(64, 30, 30)**,30计算过程如下:
      • [(32 - 3 + 2 * 0) / 1 + 1] == 30
  • 池化层的计算:一池化层一nn.MaxPool2d(kernel_size=2)为例

    • 输入参数:池化核大小为2
    • 通过卷积层一得到数据(64, 30, 30),在通过池化层一得到结果(64, 15, 15),结合**(2、池化层的计算公式可知)**, 15就算过程如下(以Hout为例):
      • Hin:30, padding[0]:0,dilation[0]:1,kernelize:2,stride:1,带入公式有:[(30 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1] == 30
  • 构建模型代码如下

import torch.nn.functional as F

num_classes = 10   # 一共有10种类别

class Model(nn.Module):
    def __init__(self):
        super().__init__() 
        
        # 特征提起网络
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3)  # 第一层卷积层,卷积核大小 3 * 3
        self.pool1 = nn.MaxPool2d(kernel_size=2)      # 第一层池化层,池化核大小 2 * 2
        self.conv2 = nn.Conv2d(64, 64, kernel_size=3) # 第二层卷积层,卷积核大小为 3 * 3
        self.pool2 = nn.MaxPool2d(kernel_size=2)      # 第二层池化层,池化核大小 2 * 2
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3) # 第三层卷积层,卷积大小 3 * 3
        self.pool3 = nn.MaxPool2d(kernel_size=2)     # 第三层池化层,卷积大小 2 * 2
        
        # 分类网络
        self.fc1 = nn.Linear(512, 256)  # 512维度降到256维度
        self.fc2 = nn.Linear(256, num_classes)  # 256维度降到 10 维度,对应分成10类
        
        # 前向传播,即构建cnn网络:卷积、池化…… 全连接
    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 多维数据 ** 展开到一维 **
            
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
            
        return x

4、将模型导入GPU中

# 打印并且加载模型
from torchinfo import summary

# 将模型转移到GPU种
model = Model().to(device)

summary(model)

构建的模型参数如下:

=================================================================
Layer (type:depth-idx)                   Param #
=================================================================
Model                                    --
├─Conv2d: 1-1                            1,792
├─MaxPool2d: 1-2                         --
├─Conv2d: 1-3                            36,928
├─MaxPool2d: 1-4                         --
├─Conv2d: 1-5                            73,856
├─MaxPool2d: 1-6                         --
├─Linear: 1-7                            131,328
├─Linear: 1-8                            2,570
=================================================================
Total params: 246,474
Trainable params: 246,474
Non-trainable params: 0
=================================================================

3、模型训练

1、设置超参数

loss_fn = nn.CrossEntropyLoss()  # 插件损失函数
learn_rate = 1e-2  # 设置学习率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)   # 初始化梯度下降
  1. loss_fn = nn.CrossEntropyLoss()

    这一行代码创建了一个CrossEntropyLoss对象,它是PyTorch中用于多分类任务的标准损失函数。CrossEntropyLoss结合了log_softmaxNLLLoss(负对数似然损失)的功能,它可以接收未归一化的预测输出(通常来自模型的输出层)和真实的标签,然后计算损失。在多分类问题中,目标是使模型预测的类别的概率最大,而CrossEntropyLoss正好可以衡量模型预测概率分布与真实标签之间的差距。

  2. learn_rate = 1e-2

    这里设置了学习率(learn_rate)为1e-2,即0.01。**学习率是梯度下降算法中的一个重要超参数,**它决定了权重更新的步长。较高的学习率可以让模型快速收敛,但可能会导致训练不稳定;较低的学习率可以使训练过程更加稳定,但可能需要更多的迭代次数才能收敛。

  3. opt = torch.optim.SGD(model.parameters(), lr = learn_rate)

    这一行代码初始化了一个随机梯度下降(Stochastic Gradient Descent, SGD)优化器,它将用于更新模型的参数以最小化损失函数。model.parameters()返回模型的所有可训练参数,这些参数将在每次迭代中根据计算出的梯度进行更新。lr参数指定了学习率,这里是之前定义的learn_rate

2、编写训练函数

训练函数通常有一个循环,里面包含有(与深度学习基础一案例一样):

  • 前向传播:将输入数据通过模型得到预测输出
  • 计算损失:使用loss_fn来计算预测值和实际值的损失
  • 反向传播:使用loss.backward()来自动计算损失关于模型参数的梯度
  • 更新权重:使用opt.step()来更具计算出的梯度和学习率更新模型参数
  • 清理梯度:在每一次迭代前开始,调用opt.zero_grad()来清理梯度缓存,防止梯度积累

训练函数参数解析

  • dataloader,传入的是动态加载的数据
  • model,前面构建好的模型(导入了GPU)
  • loss_fn,损失函数
  • optimizer,初始化梯度下降
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 训练集的大小,一共60000张图片
    num_batches = len(dataloader)   # 批次数目,60000/32
    
    train_acc, train_loss = 0, 0   # 初始化准确率和损失
    
    for X, y in dataloader:   # 获取数据和标签
        X, y = X.to(device), y.to(device)   # 模型已经导入GPU中,
        
        # 计算acc和loss
        pred = model(X)   # 网络输出
        loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距
        
        # 反向传播
        optimizer.zero_grad()  # grad属性归零,即梯度归零
        loss.backward()     # 反向传播
        optimizer.step()  # 更新权重
        
        # 记录 acc 和 loss
        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
        

训练函数API介绍

  • optimizer.zero_grad():会遍历模型所有参数,降反向传播的梯度设置为0(数据经过神经网络的一个神经元中,如果没有达到要求回反向传播继续计算)
  • loss.backward():反向传播中自动跟新梯度,必须在optimizer.step()前
  • optimizer.step():通过梯度下降法更新模型参数的值

3、编写测试函数

测试函数和训练函数思路大概一样

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)   # 测试集的大小,一共 10000张图片
    num_batches = len(dataloader)   # 批次数目,10000/32
    
    test_loss, test_acc = 0, 0
    
    # 当不进行训练的时候,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)
            
            # 计算loss
            target_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 /= size
    test_loss /= num_classes
    
    return test_acc, test_loss

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')
Epoch: 1, Train_acc:14.4%, Train_loss:2.273, Test_acc:22.1%,Test_loss:66.199
Epoch: 2, Train_acc:26.6%, Train_loss:1.982, Test_acc:27.9%,Test_loss:62.358
Epoch: 3, Train_acc:36.3%, Train_loss:1.747, Test_acc:39.5%,Test_loss:51.392
Epoch: 4, Train_acc:42.3%, Train_loss:1.585, Test_acc:40.7%,Test_loss:50.576
Epoch: 5, Train_acc:46.2%, Train_loss:1.481, Test_acc:46.0%,Test_loss:46.421
Epoch: 6, Train_acc:50.2%, Train_loss:1.384, Test_acc:52.3%,Test_loss:41.735
Epoch: 7, Train_acc:53.8%, Train_loss:1.303, Test_acc:54.8%,Test_loss:39.633
Epoch: 8, Train_acc:56.3%, Train_loss:1.235, Test_acc:55.8%,Test_loss:39.389
Epoch: 9, Train_acc:58.6%, Train_loss:1.174, Test_acc:59.2%,Test_loss:36.206
Epoch:10, Train_acc:60.8%, Train_loss:1.120, Test_acc:61.0%,Test_loss:34.810
Done

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 Accurary')
plt.plot(epochs_range, test_acc, label='Test Accurary')
plt.legend(loc='lower right')
plt.title('Training and Validation Accurary')

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

在这里插入图片描述

  • 通过上面分析,在准确度上,训练姐和测试集都达到了百分之60多,但是在损失函数上,训练集的loss函数非常特别,理解不了

6、总结

1、CNN模型总结

  • CNN模型主要由卷积层、池化层、全连接层组成,卷积层用于特征提取,池化层用于降维,全连接层用于将维度展开计算与分类,进行特征提取原因:每一图片有很多地方是相同的,通过特征提取可以降低计算难度。
  • 一张图片在pytorch可以存储为:(颜色通道数量,图片长维度,图宽度维度)
  • 熟悉了卷积层如何进行、池化层如何进行的数学原理

2、API总结

  • optimizer.zero_grad():在每一次反向传播的时候梯度设置为0
  • loss.backward():反向传播中自动跟新梯度,必须在optimizer.step()前
  • optimizer.step():通过梯度下降法更新模型参数的值

3、待解决

卷积神经网络参数不太明白

  • padding:添加到输入的所有四个边的填充。默认值:0

  • dilation :扩张操作,控制kernel点(卷积核点)的间距,默认值:1。

  • groups:将输入通道分组成多个子组,每个子组使用一组卷积核来处理。默认值为 1,表示不进行分组卷积。

  • padding_mode:‘zeros’, ‘reflect’, ‘replicate’或’circular’. 默认:‘zeros’

  • 训练集的loss函数非常特别,理解不了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值