当前位置:   article > 正文

三万字硬核详解:卷积神经网络CNN(原理详解 + 项目实战 + 经验分享)_卷积神经网络实战

卷积神经网络实战

文章目录


卷积神经网络的发展历史(超详细介绍)

一、构建网络模型

卷积神经网络(Convolutional Neural Network,CNN):是一种前馈神经网络,最早可追溯到1986年的BP算法。

  • 三大网络结构:卷积层(Conv2d)、池化层(MaxPool2d)、全连接层(Linear)
  • 两大特点:稀疏连接、权值共享。可以用更少的参数,获得更高的性能。

下面是一个简单的CNN模型:
在这里插入图片描述

Python实现上述模型:

import torch
import torch.nn as nn
import torch.nn.functional as F


class CNNNet(nn.Module):
    """类对象:网络模型"""
    def __init__(self):
        """函数功能:网络层定义"""
        super(CNNNet, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1)         # (二维)卷积层1
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)                                      # (二维)最大池化层1
        self.conv2 = nn.Conv2d(in_channels=16, out_channels=36, kernel_size=3, stride=1)        # (二维)卷积层2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)                                      # (二维)最大池化层2
        self.fc1 = nn.Linear(1296, 128)                                                         # 全连接层1
        self.fc2 = nn.Linear(128, 10)                                                           # 全连接层2

    def forward(self, x):
        """函数功能:前向传播"""
        x = self.pool1(F.relu(self.conv1(x)))       # 卷积层1 -> 激活函数(relu) -> 池化层1
        x = self.pool2(F.relu(self.conv2(x)))       # 卷积层2 -> 激活函数(relu) -> 池化层2
        x = x.view(-1, 36*6*6)                      # 改变数组形状(参数-1表示自动调整size)
        x = F.relu(self.fc2(F.relu(self.fc1(x))))   # 全连接层1 -> 激活函数(relu) -> 全连接层2 -> 激活函数(relu)
        return x


device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')     # 检测是否有可用的GPU,有则使用,否则使用CPU。
net = CNNNet()              # 网络模型实例化
net = net.to(device)        # 将构建的张量或者模型分配到相应的设备上。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

函数定义详解:Pytorch之(__ call__)、( __ init__)、(forward)


二、前向传播(Forward propagation)

函数定义:def forward(self, x):
函数调用:outputs = net(inputs)

作用:实现信息的前向传导。
过程:将数据从输入层,依次经过多个隐藏层(如果有),最终到达输出层。其中,数据每经过一个网络层,其节点输出的值所代表的信息层次就越高阶和概括。

例如:人脸识别。

  • (1)输入图像:每个像素值,只表示灰度值;
  • (2)经过第一个网络层:(可能)第二层中每个节点输出的值,表示边缘特征(如:脸部轮廓);
  • (3)经过第二个网络层:(可能)第三层中每个节点输出的值,表示局部特征(如:鼻子、眼睛);
  • (4)最后输出层:表示网络对输入图像的判断结果,即是否是一张人脸。

备注:节点中输出的值是通过与其相连的前一层中所有的节点输出值的加权求和处理后的结果。

2.1、卷积层:torch.nn.Conv2d()

torch.nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1, padding=0)

卷积神经网络的核心网络层是卷积层,而卷积层的核心是卷积。

PyTorch:torch.nn.Conv2d()函数的图解
PyTorch:conv1D,conv2D和conv3D

2.1.1、卷积运算 —— 提取特征

卷积运算就是互相关( cross-correlation )运算。
在这里插入图片描述
卷积运算卷积核中的 2 分别乘以输入中的每个值,然后输出卷积结果。
其中,输入、输出和卷积核都是张量,卷积核(kernel)又叫权重过滤器,简称过滤器(Filter)。

卷积运算公式
(1)假设输入图像大小是input x input,卷积核大小是kernel,补0的圈数为padding,步长为stride,卷积后输出特征图大小为output x output
在这里插入图片描述
(2)图像高度和宽度的计算公式:
在这里插入图片描述
备注:若输入图像为 m x n,想要转换为 m x m 或 n x n 的图像,则可以采用裁剪、拼接、填充的方法。

2.1.2、稀疏连接 —— 降低了模型的复杂度,防止过拟合

【输入条件】:(1)输入单通道图像大小 3 x 3;(2)卷积核大小 2 x 2;
【分析说明】

  • 卷积运算的详细过程:(1)取(3 x 3)输入图像中的(2 x 2)矩阵,然后与(2 x 2)卷积核进行卷积运算,得到一个计算结果0 * 0 + 1 * 1 + 3 * 2 + 4 * 3 = 19);(2)通过滑窗,循环计算得到输出特征图(2 x 2)。
  • 特征图中的每个元素(即:神经元)只与上一层的所有神经元中的4个进行连接。

【结论】:稀疏连接相比于全连接,大大缩减了权重参数数量,也极大避免了过拟合。
在这里插入图片描述

2.1.3、权值共享 —— 减少权值数量,模型易优化

在这里插入图片描述
输入图像

  • (1)输入彩色图像Input = 8 * 8 * 3;其中,3表示RGB三个通道
  • (2)卷积层的卷积核个数为Kernels = 5每一个 Kernel 都有 3 个channel(3 * 3 * 3),即共有 5 个特征图(3 * 3 * 3 * 5)

卷积运算(多通道)

  • 1、Kernels卷积核计算
    • 每个 Kernel 的 3 个channle(R/G/B)分别与 Input 对应的 3 个channle(R/G/B)进行卷积运算,最后每个 Kernel 得到 3 个(6 * 6) feature_map_0
  • 2、Feature Maps特征图计算
    • 将 3 个(6 * 6)的 feature_map0 逐元素相加(即通道融合)得到 1 个 feature_map_1
    • 然后将 feature_map1 的每个元素都加上其对应的偏置 b1,得到该 Kernel 的 FeatureMap1
      • 同一个 Kernel1 对应的 3 个 channle 共享一个偏置 b1(权值共享1)
      • 同一个 FeatureMap1 的所有元素共享一个卷积核 Kernel1(权值共享2)
    • 同理其余Kernel,最后得到(6 * 6 * 5) 个 Feature Maps(即最终输出的 channel 数等于 Kernel 的个数)。
  • 3、Feature Vector向量计算
    • 采用全局最大池化,分别计算每一个Feature Map,最终得到一个(1 * 1 * 5)Feature Vector。

结论
(0)权值共享:大大减少了模型的权重参数。
(1)权值共享:当前隐藏层中的所有神经元都在检测图像不同位置的同一个特征,即检测特征相同。因此也将输入层到隐藏层的这种映射称为特征映射。
(2)多个卷积核分别用于识别图像不同位置的特征。通过全连接层将所有特征整合,输出一个值(猫 / 狗)。

在这里插入图片描述

2.1.4、权值初始化 —— 决定算法是否收敛

(1)深度学习为何要初始化? —— 影响算法结果

  • 深度学习算法:由于迭代训练 + 网络层数多 + 权重参数多
  • 机器学习算法:影响小。

(2)初始化对训练的影响?—— 决定算法是否收敛

  • (1)若参数太大,将导致在前向传播或反向传播中产生爆炸的值;
  • (2)若参数太小,将导致信息丢失;
  • (3)适当的参数初始化,能加快收敛速度。

偏置初始化

  • 采用启发式的方法挑选常数,进而设置每个单元的偏置。

权重初始化

  • 实验表明:正态分布初始化(高斯分布)、正交分布初始化、均匀分布初始化的效果更好。
  • 权重初始化的方法:零值初始化、随机初始化、均匀分布初始化、正态分布初始化和正交分布初始化等。

2.1.5、感受野 —— 在原始图像上映射的区域大小

感受野(Receptive Field,RF)每个网络层的输出特征图上的像素点,在原始图像上映射的区域大小。
在这里插入图片描述
注意1:感受野的计算不考虑" 边界填充 " 。
注意2:最后一层(卷积层或池化层)输出特征图的感受野大小等于卷积核的大小。
注意3:第(i)层卷积层的感受野大小和卷积核大小,与步长、第(i+1)层的感受野大小都有关。

详细计算过程:根据卷积得到的最后一层感受野,反算前一层感受野,然后循环且逐层的传递到第一层。
公式如下:
在这里插入图片描述

  • 【举例1】:两层3 * 3的卷积核,卷积操作之后的感受野是5 * 5。其中,卷积核的步长为1、padding为0。
    计算如下:(1-1)* 1+3 = 3 => (3-1)*1+3 = 5
    在这里插入图片描述
  • 【举例2】:三层3 * 3卷积核,卷积操作之后的感受野是7 * 7。其中,卷积核的步长为1,padding为0。
    计算如下:(1-1)* 1+3 = 3 => (3-1)*1+3 = 5 => (5-1)*1+3 = 7
    在这里插入图片描述
  • 【举例3】:神经网络
    在这里插入图片描述

❤️ 感受野与卷积核的关系,是否卷积核越大越好?

  • 如果堆叠3个3 * 3的卷积核,并且保持步长为1,其感受野等于7 * 7;那么这和直接使用7 * 7卷积核的结果是一样的,为什么要如此麻烦呢?

在这里插入图片描述

2.1.6、边界填充Padding —— 图像尺寸对齐,保持边界信息

torch.nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1, padding=1)

padding=1:表示在输入图像的边界填充 1 圈,全 0 元素。
作用:

  • (1)图像尺寸对齐:对图像进行扩展,扩展区域补零。
  • (2)保持边界信息:若没有padding,卷积核将对输入图像的边界像素只卷积一次,而中间像素卷积多次,导致边界信息丢失。

在这里插入图片描述

假设输入图像大小是 input x input,卷积核大小是 kernel,步长为stride,补0的圈数为padding。
在这里插入图片描述
Python深度学习pytorch神经网络填充和步幅的理解

2.2、激活函数:torch.nn.Sigmoid() + ReLU() + softmax() —— 增加非线性能力

(1)用于神经网络的隐含层与输出层之间(如:卷积层+激活函数+池化层),为神经网络提供非线性建模能力
(2)若没有激励函数,则模型只能处理线性可分问题。与最原始的感知机相当,拟合能力有限。

左图A - 线性可分:一条直线将图像A中的蓝点和绿点完全分开(最理想、最简单的二分类问题)
右图B - 线性不可分:一条封闭曲线将图像B中的蓝点和绿点完全分开
在这里插入图片描述

深度学习的四种激活函数:PyTorch中激活函数的方法示例在这里插入图片描述
在这里插入图片描述
如何选择激活函数

  • (1)任选其一若网络层数不多
  • (2)ReLU若网络层数较多
    • 不宜选择sigmoid、tanh,因为它们的导数都小于1,sigmoid的导数更是在[0, 1/4]之间。
    • 根据微积分链式法则,随着网络层数增加,导数或偏导将指数级变小。
    • 所以,网络层数较多的激活函数其导数不宜小于1也不能大于1,大于1将导致梯度爆炸,导数为1最好,而relu正好满足这个要求。
  • (3)Softmax用于多分类神经网络输出层一分钟理解softmax函数(超简单)

2.3、池化层:torch.nn.MaxPool2d() / AvgPool2d() —— 降采样,增大感受野,防止过拟合。

为什么要进行池化操作?

  • 通过卷积层获得的图像特征,可以直接训练分类,但很容易导致过拟合。

池化(Pooling)又叫下采样、降采样。
作用: 降采样(减少参数),增大感受野,提高运算速度及减小噪声影响,防止过拟合。
分类:

  • (1)局部池化
    • 最大池化(Max Pooling):取Pooling窗口内的最大值作为采样值。
    • 均值池化(Average Pooling):取Pooling窗口内的所有值相加取均值作为采样值。
      • torch.nn.MaxPool2d(kernel_size=2, stride=1, padding=0)
      • torch.nn.AvgPool2d(kernel_size=2, stride=1, padding=0)
  • (2)全局池化
    • 全局最大池化(Global Max Pooling):取以整个特征图为单位的最大值作为采样值。
    • 全局均值池化(Global Average Pooling):取以整个特征图为单位的所有值相加取均值作为采样值。
      • torch.nn.AdaptiveMaxPool2d(output_size=(2, 2))
      • torch.nn.AdaptiveAvgPool2d(output_size=(2, 2))

备注:全局池化相比局部池化能减少更多参数,且泛化能力比较好,但唯一的不足之处是收敛速度比较慢。
在这里插入图片描述
Python:池化的用法及代码(示例)

2.4、全连接层:torch.nn.Linear() —— 分类器

全连接层(Fully Connected Layers,FC)torch.nn.Linear(n_hidden, classification_num)

  • 卷积层、池化层、激活函数 —— 将原始数据映射到隐层特征空间,提取特征

  • 全连接层 —— 将学到的 “分布式特征” 映射到样本标记空间,进行分类

全连接层FC的缺点

  • (1)由于忽略了空间结构,故不适用于目标检测。如:分割任务中的FCN采用卷积层替换全连接层。
  • (2)全连接层的参数过多,导致模型复杂度提升,容易过拟合。如:ResNet、GoogleNet采用全局平均池化替换全连接层。

全连接层详解
一文带你了解CNN(卷积神经网络)

  • 详解1:神经网络在经过卷积+激活后,得到 3 x 3 x 5 的输出结果,它是怎么转换为1 x 4096?在这里插入图片描述
  • 详解2:可以理解为在中间做了一个卷积操作,详细步骤如下:
    • (1)将【3 x 3 x 5特征图】分别与对应的【3 x 3 x 5卷积核】进行【卷积convolution】【不带偏置】,然后通过全连接FC得到一个预测结果【即一个神经元的输出 1 x 1 】
    • (2)当我们有4096个神经元,将得到【4092个神经元的输出 1 x 4096】。

全连接层中的每个神经元都可以看成是一个加权平均且不带偏置的多项式。可以简单写成:y = W * x。

在这里插入图片描述

全连接层FC的作用:这一步卷积还有一个非常重要的作用,就是将分布式特征representation映射到样本标记空间。简单来说,就是将输入图像的所有特征整合到一起,输出一个值(猫 / 狗)
效果:大大减少目标位置的不同,而对分类结果带来的影响。
在这里插入图片描述
下张图是上面任意一张图的图解:
在这里插入图片描述
由图可得:

  • (1)猫虽然在不同的位置输出的特征值相同,但可能分类结果不同。
  • (2)全连接层FC:忽略了空间结构特性。即目标不管在什么位置,只要图像中有这个猫,那么就能判断它是猫。

三、反向传播(Backward propagation)

反向传播:loss.backward()

作用:迭代训练,降低损失(loss)
介绍:与最优化方法(如:梯度下降法)结合使用的,用来训练神经网络的常见方法。

计算过程:通过损失函数,计算模型中所有权重参数的梯度,并反馈给优化算法进行梯度(权值)更新。迭代训练 N 次,获得最小损失。
单次反向传播的过程:首先是离输出层最近的网络层E,然后是网络层D,并按照EDCBA的依次顺序进行反向传播,直到所有层都完成一次。

3.1、损失函数:torch.nn.CrossEntropyLoss() / MSELoss() —— 衡量模型预测值与真实值之间的差异

损失函数:用来衡量模型预测值与真实值之间的差异。

  • 损失函数越小说明模型和参数越符合训练样本。
  • 任何能够衡量模型预测值与真实值之间差异的函数都可以叫做损失函数。
  • 为了避免过拟合,一般在损失函数的后面添加正则化项(F = 损失函数 + 正则化项)

常用的损失函数:

  • (1)交叉熵(Cross Entropy),用于分类:反应两个概率分布的距离(不是欧式距离)。
    交叉熵又称为对数似然损失、对数损失;二分类时还可称为逻辑回归损失。
    torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
    在这里插入图片描述
    其中:c表示损失值;
        n表示样本数量,也就是batchsize;
        x表示预测向量维度,主要是因为需要在输出的特征向量维度上一个个计算并求和;
        y表示真实值,对应x维度上的标签(1 或 0);
        a表示输出的预测概率值(0~1之间,总和为1)。
  • (2)均方误差(Mean Squared error,MSE),用于回归:反应预测值与实际值之间的欧氏距离。
    torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
    在这里插入图片描述

3.2、正则化:torch.nn.Dropout() —— 提升模型的泛化能力,防止过拟合。

正则化(Regularization):对于减少测试误差的方法的统称。
作用: 降低模型的复杂度,减少模型对训练样本的依赖度,提升模型的泛化能力,防止过拟合。
在这里插入图片描述

思路在损失函数的后面添加一个惩罚项。
作用:正则化系数 λ :控制模型的可学习参数,防止过拟合。
计算过程(求解线性模型):找到一个 θ 值,然后对 J(θ) 进行求导,使得代价函数 J(x) 取得最小值(导数为0)。
在这里插入图片描述

常用的惩罚项:L1正则化、L2正则化、Dropout正则化

  • 在机器学习的线性回归模型中,使用L1正则化得到的模型叫Lasso模型,使用L2正则化得到的模型叫岭回归(Ridge regression)

3.2.1、L1正则化(特征选择) —— 冗余参数置零

  • L1正则化(L1范数),通常表示为:||W||1指权值向量 W 中各个元素的绝对值之和。
  • 特点:又叫特征选择,产生稀疏权值矩阵。即提取权重值最大的前N个值,并将冗余的权重值置0,直接删除。

3.2.2、L2正则化(权重衰减) —— 过拟合参数趋近于0

  • L2正则化(L2范数),通常表示为:||W||2指权值向量 W 中各个元素的平方和,然后求平方根。
  • 特点:又叫权重衰减。即抑制模型中产生过拟合的参数,使其趋近于0(而不等于0),影响变小。
  • 特点:倾向于让所有参数的权值尽可能小。

若不关心显式特征(哪些特征重要或不重要),L2正则化的性能往往优于L1正则化。

3.2.3、Dropout正则化 —— 随机删除一定比例的神经元

torch.nn.Dropout(p=0.5) # 表示随机删除 50% 的神经元

  • Dropout正则化在训练过程中,随机删除一定比例的神经元(比例参数可设置)。
  • 一般只在训练阶段使用,测试阶段不使用。
  • 一般控制在20% ~ 50%。太低没有效果,太高则会导致模型欠学习。

应用场景
(1)在大型网络模型上效果显著
(2)在输入层和隐藏层都使用Dropout
(3)当学习率和冲量值较大时:如学习率扩大10 ~ 100倍,冲量值调高到0.9 ~ 0.99。
(4)用于限制网络模型的权重时:学习率越大,权重值越大。
在这里插入图片描述

3.3、优化器:torch.optim.SGD() / Adam()

optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

优化器(optimizer):通过优化策略(梯度下降)来更新可学习参数(权值W和偏置bias),使得损失函数Loss值逐步降低,输出的模型更接近真实标签。
影响因素:(1)梯度方向(2)学习率
 
常用优化器

  • 00、经典的梯度下降法。
  • 11、梯度下降优化算法:SGD、SGDM、NAG
       缺点:缓解了参数空间的方向问题,但需要新增参数,且对学习率的控制也不太理想。
  • 22、自适应优化算法:AdaGrad(累积梯度平方)、RMSProp(累积梯度平方的滑动平均)、Adam(带动量项的RMSProp)
       而自适应优化算法,学习率不再是一个固定不贬值,它会根据实际情况自动调整以适应环境。

3.3.1、梯度下降 —— 使得loss值逐步降低

梯度清零:optimizer.zero_grad()
梯度更新:optimizer.step()

梯度下降的公式如下图:
在这里插入图片描述
其中:
   θ(t+1):表示参数θ在t+1次迭代时需要更新的参数;
   θ(t):表示参数θ在第t次迭代时更新的参数值;
   J(θt):表示目标函数(损失函数)在θ(t)该点上的梯度;
   lr:表示学习率。其控制参数更新速度以及模型学习速度。

梯度下降:沿着目标函数梯度的反方向搜索极小值。在这里插入图片描述
梯度下降算法原理讲解

  • 经典的梯度下降法先假设一个学习率,参数沿梯度的反方向移动。
  • 计算:每次迭代过程中,采用所有的训练数据的平均损失来近似目标函数。
  • 特点:(1)靠近极值点时收敛速度减慢;(2)可能会" 之 "字形的下降。(3)对学习率非常敏感,难以驾驭;(4)对参数空间的方向没有解决方法。
    在这里插入图片描述
  • 左图一 学习率过小,将导致收敛速度慢,耗时长;
  • 中图二 当学习率取值恰当时,可以收敛到全面最优点(凸函数)或局部最优点(非凸函数)。
  • 右图三 学习率过大,将导致越过极值点(如:在平坦的区域,因梯度接近于0,可能导致训练提前终止)
    备注: 可能出现在迭代过程中保持不变,容易造成算法被卡在鞍点的位置。

3.3.2、SGD、SGDM、NAG

  • SGD(随机梯度下降法)
    计算:每次只使用一个数据样本,去计算损失函数,求梯度,更新参数。
    特点:(1)计算速度快,但是梯度下降慢(2)可能会在最低处两边震荡,停留在局部最优。
  • SGDM(SGM with Momentum:动量梯度下降)
    计算:对已有的梯度进行指数加权平均,然后加上(1 - beta)。即在现有梯度信息的基础上,加入一个惯性。而梯度方向,由之前与现在的梯度方向共同决定。
    优缺点:(1)与SGD相比,训练过程的震荡幅度会变小,速度变快。(2)SGDM速度没Adam快,但泛化能力好。
  • NAG(Nesterov Accelerated Gradient)
    计算:先使用累积的动量计算一次,得到下一次的梯度方向,再把下一个点的梯度方向,与历史累积动量相结合,计算现在这个时刻的累计动量。
    在这里插入图片描述

3.3.3、AdaGrad、RMSProp、Adam

不同优化算法(理论推导)

  • AdaGrad(Adaptive Gradient,自适应步长)累积梯度平方
    • 计算:在现有的(梯度 * 步长)上,添加了一个系数:1 /(历史梯度的平方和,再开根号)。
    • 优缺点:(1)适合处理稀疏数据,可以对稀疏特征进行大幅更新。(2)学习率单调递减,最终会趋于0,学习提前终止。
  • RMSProp(root mean square prop,梯度平方根)累积梯度平方的滑动平均
    • 计算:该方法和Adagrad的区别就是分母不一样,使得系数不会因为前几步的梯度太大而导致分母太大,从而导致系数变得太小而走不动。
  • Adam(Adaptive Moment Estimation,自适应估计)利用梯度的一阶、二阶矩估计
    • 优缺点:Adam是一种在深度学习模型中用来替代随机梯度下降的优化算法。它是SGDM和RMSProp算法的结合,训练速度快,泛化能力不太行。

MNIST数据集:多种优化器的收敛效果对比
在这里插入图片描述

3.4、学习率:Learning Rate —— 控制可学习参数的更新速度

optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

学习率(learning rate,lr)用于控制权重参数的学习速度一般情况下,学习率随着epoch的增大而逐渐减小,可以达到更好的训练效果。

学习率学习速度适用时期适用范围缺点
模型刚训练时[0.01,0.001]易震荡;模型不收敛;
模型训练后期[0.001,0.00001]局部收敛,找不到最优解;

为什么加入学习率?

  • (1)神经网络通过梯度下降+反向传播进行参数更新
    • 主要流程:将可学习参数(权值W和偏置bias)反向传播给网络层,使得损失函数Loss值逐步降低,输出的模型更接近真实标签。
  • (2)神经网络在参数学习过程中,需要多轮训练,而每一论训练得到的可学习参数,需要通过学习率来控制学习速度,使得下一轮训练时的损失更小,最终达到最优模型

3.4.1、自定义更新学习率(根据epoch)

for epoch in range(num_epoch):
    if epoch % 5 == 0:      
        optimizer.param_groups[0]['lr'] *= 0.1       
  • 1
  • 2
  • 3

3.4.2、动态调整学习率:torch.optim.lr_scheduler

  • optimizer.step()用于在每 1 个 batch_size 中,更新一次参数;
  • scheduler_StepLR.step()用于在每 1 / N个 epoch 中,更新一次参数
""" torch.optim.lr_scheduler 动态调整学习率 """
torch.optim.lr_scheduler.LambdaLR()				:通过自定义 Lambda 函数来设置每个参数组的学习率。
torch.optim.lr_scheduler.MultiplicativeLR()		:通过指定乘法因子来调整每个参数组的学习率。
torch.optim.lr_scheduler.StepLR()				:在每个给定的步骤上降低学习率,步骤由用户定义。
torch.optim.lr_scheduler.MultiStepLR()			:在每个给定的步骤上降低学习率,步骤由用户定义。
torch.optim.lr_scheduler.ExponentialLR()		:每个周期按指数衰减学习率。
torch.optim.lr_scheduler.CosineAnnealingLR()	:每个周期按余弦函数衰减。
# scheduler.step()  			# 上述方法不需要输入参数

torch.optim.lr_scheduler.ReduceLROnPlateau()	:根据验证集的性能指标来调整学习率。
# scheduler.step(metrics)  		# metrics参数:表示验证集的性能指标
# 若验证集的损失值在连续 patience 个 epoch 中都没有减少,则学习率会乘以 factor 参数(通常是一个小于 1 的值)以降低学习率。

"""###########################################################################################
# 函数功能:在每个指定的步骤(epoch)之后,将学习率降低一个给定的因子(factor)。
# 函数说明:torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1)
# 参数说明:
# 		(1)optimizer :			需要调整学习率的优化器。
# 		(2)step_size(int):		每训练step_size个epoch,更新一次参数;
# 		(3)gamma(float):		学习率调整的乘法因子;
# 		(4)last_epoch (int):	最后一个epoch的index。
#               当训练N个epoch后中断,然后继续训练时,该值等于加载模型的epoch。
#               默认为-1:表示从头开始训练,即从epoch=1开始。
###########################################################################################"""

"""###########################################################################################
# 函数功能:根据验证集的性能指标(如损失值或准确率)来自动调整学习率(下降)。
# 函数说明:scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5,
#                           verbose=True, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08)
# 参数说明:
#       optimizer:      需要调整学习率的优化器。
#       mode:           监控指标的模式,可以是 "min" 或 "max",分别表示监控指标越小越好,还是越大越好。
#       factor:         学习率调整的缩放因子,即在性能不再改善时将学习率乘以该因子。
#       patience:       指标不再改善时等待的周期数,等待 patience 个周期后学习率才会被调整。
#       threshold:      控制学习率是否被调整的阈值,例如如果 mode='min',当指标监控的值下降小于 threshold 时,学习率会被调整。
#       threshold_mode: 阈值模式,可以是 'rel' 或 'abs',分别表示阈值是相对值还是绝对值。
#       cooldown:       调整学习率后等待的周期数,防止在连续多个周期内反复调整学习率。
#       min_lr:         学习率的下限,学习率不会被调整到低于这个值。
#       eps:            防止除零错误的小值。
#
# 备注:若验证集的损失值在连续 patience 个 epoch 中都没有减少,则学习率会乘以 factor 参数(通常是一个小于 1 的值)以降低学习率。
###########################################################################################"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
import torch.nn as nn
import torch.optim.lr_scheduler


class Model(nn.Module):
    # 构建网络模型
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3)

    def forward(self, x):
        pass


if __name__ == '__main__':
    # (1)超参数初始化
    lr = 0.1
    epochs = 5
    batch_size = 64

    # (2)模型实例化
    model = Model()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)  # 优化器

    # scheduler_StepLR = torch.optim.lr_scheduler.StepLR(optimizer_1, step_size=3, gamma=0.1)
    # scheduler_LambdaLR = torch.optim.lr_scheduler.LambdaLR(optimizer_1, lr_lambda=lambda epoch: 1/(epoch+1))
    # scheduler_MultiStepLR = torch.optim.lr_scheduler.MultiStepLR(optimizer_1, milestones=[3, 7], gamma=0.1)
    scheduler_ExponentialLR = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.1)
    # scheduler_ReduceLROnPlateau = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer_1, mode='min', factor=0.1, patience=2)
    print("初始化的学习率:", optimizer.defaults['lr'])

    # (3)迭代训练
    for epoch in range(epochs):
        for batch in range(batch_size):
            model.train()  # train模型
            optimizer.zero_grad()  # 提取清零
            optimizer.step()  # 参数更新

        # scheduler_StepLR.step()  # 学习率更新
        # scheduler_LambdaLR.step()
        # scheduler_MultiStepLR.step()
        scheduler_ExponentialLR.step()
        # scheduler_ReduceLROnPlateau.step()
        print("第%d个epoch的学习率:%f" % (epoch, optimizer.param_groups[0]['lr']))

"""
初始化的学习率: 0.1
第0个epoch的学习率:0.010000
第1个epoch的学习率:0.001000
第2个epoch的学习率:0.000100
第3个epoch的学习率:0.000010
第4个epoch的学习率:0.000001
"""

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

四、项目实战

Files already downloaded and verified
Files already downloaded and verified

Epoch 1, Learning Rate: 0.0009000000000000001
Epoch 1, Test Accuracy: 58.11%
Epoch 2, Learning Rate: 0.0008100000000000001
Epoch 2, Test Accuracy: 66.32%
Epoch 3, Learning Rate: 0.000729
Epoch 3, Test Accuracy: 71.07%
...
...

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(1)模型训练 + 模型验证
(2)保存模型 + 加载模型 + 加载模型的状态字典
(3)模型测试(可视化结果)

import os
import numpy as np
import matplotlib.pyplot as plt

import torch
from torch.utils.data import DataLoader
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets

if __name__ == '__main__':
    """******************** (1)数据预处理 ********************"""
    transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
    epochs = 100
    batch_size = 1024
    learning_rate = 0.001

    """******************** (2)下载数据集(CIFAR-10) ********************"""
    train_dataset = datasets.CIFAR10(root='./data', train=True, transform=transform, download=True)
    test_dataset = datasets.CIFAR10(root='./data', train=False, transform=transform, download=True)

    """******************** (3)数据加载器 ********************"""
    train_loader = DataLoader(train_dataset, batch_size=batch_size, num_workers=0, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=batch_size, num_workers=0, shuffle=False)
    print(f"CPU核心数={os.cpu_count()}, 批次数量={len(train_loader.batch_sampler)}, 样本数量={len(train_loader.sampler)}")

    """******************** (4)模型初始化 ********************"""
    # device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    # device = 'cpu'
    model = models.resnet18()  # 定义模型
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)  # 优化器
    scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9)  # 学习率调度器(每个周期按指数衰减学习率)
    # model.to(device=device)  # 模型加载到指定设备上

    """******************** (5)模型训练 + 模型验证 ********************"""
    train_losses = []  # 保存训练损失
    test_accuracies = []  # 保存测试准确率
    for epoch in range(epochs):
        import time
        start_time = time.time()
        # 训练模式
        model.train()
        epoch_loss = 0.0
        for input, target in train_loader:
            optimizer.zero_grad()
            output = model(input)
            # output = model(input.to(device=device))
            loss = torch.nn.CrossEntropyLoss()(output, target)
            loss.backward()
            optimizer.step()
            epoch_loss += loss.item()  # 累加每个 batch 的损失值
        train_losses.append(epoch_loss / len(train_loader))  # 计算平均训练损失值

        # 评估模式
        model.eval()
        correct = 0
        total = 0
        with torch.no_grad():
            for input, target in test_loader:
                output = model(input)
                # output = model(input.to(device=device))
                _, predicted = torch.max(output.data, 1)
                total += target.size(0)
                correct += (predicted == target).sum().item()
            test_accuracy = 100 * correct / total
            test_accuracies.append(test_accuracy)  # 记录测试准确率

        scheduler.step()
        print(f"Epoch {epoch+1}, Learning Rate: {scheduler.get_last_lr()[0]}, Train Loss: {train_losses[-1]}, Test Accuracy: {test_accuracies[-1]}")
        print(f"总运行时间:{time.time() - start_time}")

    """******************** (6)可视化损失图 ********************"""
    # 绘制曲线图
    plt.figure(figsize=(10, 5))
    plt.subplot(1, 2, 1)
    plt.plot(range(1, 21), train_losses, label='Train Loss')
    plt.xlabel('Epoch')
    plt.ylabel('Train Loss')
    plt.title('Train Loss vs. Epoch')
    plt.legend()
    plt.subplot(1, 2, 2)
    plt.plot(range(1, 21), test_accuracies, label='Test Accuracy', color='r')
    plt.xlabel('Epoch')
    plt.ylabel('Test Accuracy (%)')
    plt.title('Test Accuracy vs. Epoch')
    plt.legend()
    plt.tight_layout()
    plt.show()

    """******************** (7)保存模型 + 加载模型 + 加载模型的状态字典 ********************"""
    torch.save(model.state_dict(), 'resnet18_cifar10.pth')  # 保存模型
    model = models.resnet18()  # 加载模型
    model.load_state_dict(torch.load('resnet18_cifar10.pth'))  # 加载模型的状态字典

    """******************** (8)模型测试(可视化部分结果) ********************"""
    import numpy as np
    images, labels = next(iter(test_loader))  # 获取一批测试集样本
    model.eval()  # 将模型设置为评估模式
    with torch.no_grad():
        outputs = model(images)  # 对图像进行预测
    _, predicted = torch.max(outputs, 1)  # 将输出转换为类别预测
    # 随机选择一些图像进行可视化
    num_images_to_visualize = 5
    indices = np.random.choice(len(images), num_images_to_visualize, replace=False)

    # 显示图像及其真实标签和预测结果
    plt.figure(figsize=(15, 10))
    for i, index in enumerate(indices, 1):
        image = images[index].permute(1, 2, 0) / 2 + 0.5  # 将图像转换为可视化格式
        label = labels[index]
        prediction = predicted[index]

        plt.subplot(1, num_images_to_visualize, i)
        plt.imshow(image)
        plt.axis('off')
        plt.title(f'Label: {label.item()}\nPrediction: {prediction.item()}')
    plt.show()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118

五、模型训练

5.1、超参数 —— 调参寻找模型最优解

超参数:影响网络模型的性能。通过参数调整(调参),以寻找模型的全局最优解。

  • 调参是一门技术活。
  • 超参数是模型训练前设置的参数,而不是训练后得到的参数。
超参数名称经验范围介绍
Epoch\一个Epoch:将所有训练集完成一次的训练过程
Batch_size批大小\将所有训练集分成 N 个 Batch_size 数据集,然后迭代训练。
Learing Rate学习率0.01,0.001在训练过程中,学习率(先)取较大值以加快训练;其后需逐渐衰减,使模型收敛。
weight decay权重衰减[0, 0.0001]用于抑制损失函数的优化参数,避免过拟合;通常使用建议值,不必过多尝试。
Dropout正则化[0,0.5]随机删除一定比例的神经元,避免过拟合。
momentum优化器的动量[0, 1]控制了梯度更新的速度和方向,即当前方向考虑之前更新方向的程度比。常用值=0.9
kernel_size卷积核大小1x1、3x3、5x5(1)在相同感受野下,卷积核与参数越小,则计算量越小;(2)一般取卷积核=3;(3)【=1】不改变感受野;(4)通常取奇数,【=偶数】无法保证输入与输出的特征图尺寸不变;
kernel_channels卷积层的通道数1、3、5(1)数量越多,提取特征越多;(2)网络性能先上升后下降
超参数类型介绍
优化器SGD、Adam(1)逐步降低Loss,更接近真实标签;(2)例如:Adam优化器中的参数β1,β2,ϵ(通常取默认值);
池化层最大池化层、均值池化层(1)降采样,避免过拟合(2)卷积核大小:取一半
激活函数Sigmoid、ReLu(1)增加非线性能力;(2)依据网络层的深度,选择类型
网络层数\(1)网络层数越多,更" 接近现实 ",但也更容易过拟合。(2)网络层数越多,训练难度越大,模型越难以收敛。

5.1.1、超参数 —— Epoch

一个Epoch(周期)将整个训练样本都完成一次训练的过程。
即:将整个训练集在模型上进行一次完整的前向传播和反向传播的过程。

多个 Epoch 训练(目的):实现模型的迭代学习,能够充分学习数据集中的特征并提高性能,直到收敛到最佳性能或达到停止条件。

  • 在训练过程中,可以根据模型的性能和训练效果来决定训练的 Epoch 数量。
  • 通常情况下,Epoch 数量越多,模型的性能可能会越好,但也可能会导致过拟合的问题。因此,在选择 Epoch 数量时需要进行适当的调整和权衡。

5.1.2、超参数 —— Batch Size

Batch Size(批大小)在训练过程中,一次性输入模型的样本数量。
Batch Size 经验值:8、16、32、64、128 等(GPU对2的幂次的batch可以发挥更佳的性能)

如何选择 Batch Size ? 对不同 batch_size 进行测试,综合考虑数据集大小、硬件资源、训练速度、收敛速度和模型稳定性等因素,并根据具体问题进行调整和优化,从而找到适合任务的最佳设置。

Batch Size:决定了梯度下降的方向。

  • (1)Batch_Size = 1 (一个样本) —— 导致梯度震荡,模型不收敛。
  • (2)Batch_Size 逐渐增大
    • 噪声影响变小备注:减小batch换来的收敛速度提升,远不如引入大量噪声导致的性能下降。
      • 若 Batch_Size 较小,噪声数据可能会对模型产生显著的影响。由于数据量不足,模型更容易受到噪声数据的影响,导致模型产生偏差,降低了模型的泛化性能。
      • 若 Batch_Size 较大,噪声数据对模型的影响会变得相对较小。由于随着数据量的增加,模型更容易学习到数据的真实分布,从而更容易过滤掉噪声数据的影响。
    • 训练速度提升:使用GPU多线程并行处理,加速参数更新过程,直到达到时间最优。
      • 前提:显卡显存支持,否则明显减慢。 —— 若超过显存(专用 GPU 内存),则超出部分转移到(共享 GPU 内存),此时速度会明显减慢。
      • 若(线程的时间开销)小于(将Batch_size数据从CPU上传到GPU),则加速。 —— 多线程与多进程的启动和销毁需要一定的时间。
    • 收敛速度变慢:导致梯度估计的不准确性和方差较大,使得模型需要更多的 Epoch 来收敛到较优的解。
    • 泛化性能增强:提供更多的样本信息来估计梯度,使得模型更容易学习数据集的特征,并提高泛化性能。而较小的 batch-size 下更易受到噪声影响。
    • 最终收敛精度提升:可以减少梯度的方差,使得参数更新的方向更加稳定,从而更容易收敛到较优的解。
  • (3)较小的 Batch Size(适用于中 / 小样本) —— 如:几百到几千个样本
    • batch_size经验范围:几十以内
    • 优点:降低内存消耗、模型收敛速度更快
    • 缺点:梯度的方差较大,使得参数更新的方向不稳定(梯度震荡大),从而增加训练的不确定性。
  • (4)较大的 Batch Size(适用于大样本) —— 如:几万到几百万个样本,甚至更多。
    • batch_size经验范围:从几十到几百不等,但一般不会超过几千。
    • 减少梯度的方差,使得参数更新的方向更加稳定(梯度震荡小),但也可能陷入局部最优。
    • 充分利用硬件资源:使用多线程并行计算,提高训练效率。
    • 缺点:内存占用增加,特别是在处理大型数据集或单个数据较大的情况下。
  • (5)Batch_Size = all(所有样本) —— 全数据集的方向能够更好的代表样本总体,确定其极值所在。只适用于小样本,大样本有内存限制。

5.1.3、超参数 —— Epoch与Batch Size的关系

训练神经网络时,每个 epoch 表示将整个数据集按照批次大小(batch size)划分为若干个批次(batches),而 batch size 表示每个批次包含的随机样本的数量。

  • 一个 Epoch 的训练:在每个 Epoch 中,模型将依次处理每个批次样本(batch size),通过前向传播计算损失函数,并通过反向传播更新模型的权重参数,以最小化损失函数,直到整个数据集的所有样本都被处理完毕。
  • 数据集重新随机划分(提高模型的泛化能力): 在当前 Epoch 结束后,重新随机打乱数据集的顺序,以确保下一个 Epoch 中每个批次的数据都是随机选择的。
  • 继续下一个 Epoch 的训练:重复上述步骤,直到完成指定数量的 Epoch 或达到停止条件。

5.1.4、影响因素:时耗

  • 模型的网络层数:网络层越多,耗时约长。如:Unet与轻量级Unet
  • DataLoader数据加载器的耗时问题
    • (1)在每个epoch开始时,数据加载器会重新创建并启动。因此,前几个batch_size的耗时总是很高。
    • (2)参数 - batch_size:对于小样本数据,批大小越大,时耗越高。对于大样本则相反。
    • (3)参数 - num_workers:多进程加载数据
      • 缺点:增加内存占用量(较大,根据加载数据的数量而定)
    • (4)参数 - pin_memory:加快数据从主机内存到GPU内存的传输速度。
      • 缺点:增加内存占用量(较少)
  • image.to(device)将图像(或batch_image)从CPU上传GPU的过程,会产生一定的耗时。与数据传输带宽、图像大小和 GPU 设备性能等有关。
    • (1)若上传图所需内存超过了专用 GPU 内存(显存),则超出部分将转移到共享 GPU 内存中,以防止程序奔溃,但由于共享GPU内存是主存划分出来与GPU共享的内存,因此速度会明显减慢。
    • (2)显存性能测试(batch=8):3060TI - 耗时=0.9秒,3090TI=0.3秒
    • (3)显存性能测试(batch=8、16、32、64):
      • (1)batch=8 / 16:所需内存在专用 GPU 内存范围内;
      • (2)batch=32及以上:所需内存超过专用 GPU 内存,则超出部分转移到共享 GPU 内存中。

5.2、数据分配:训练集 + 验证集 + 测试集

  • 数据随机分配:
  • (1)训练集、验证集、测试集(比例为8 : 1 : 1),用于防止模型过拟合。
  • (2)若没有验证集,则训练集与验证集的划分比例为7 : 3
  • 图像前处理(预处理):
    • (1)若训练集的全部数据处理,则训练集 + 验证集 + 测试集必须保持一致。
      • 如:图像归一化,用于降低不同图像的灰度强度对模型的影响力。
    • (2)若训练集的部分数据处理,则训练集 + 验证集一致,而测试集不应用。
      • 如:随机高斯噪声、明亮度调整。用于提高模型的泛化性。
  • 不同数据集的作用:
    • 训练集(Training set):用于迭代以得到最优化模型,使模型更接近真实数据。
    • 验证集(Validation set)用于测试训练集效果并通过多次调参(超参数),使模型泛化性能更好。
    • 测试集(Test set):用来测试最终模型的效果。此时,既不参与权值的学习(权值更新),也不参与超参数的选择(调参)。

备注:如果把测试集当做验证集,通过调参去拟合测试集,等于在作弊。

5.3、数据增强(Data Augmentation)

在现有数据集下,通过数据增强扩增 N 倍的训练集,而不增加额外数据集,从而实现大样本训练。

  • 作用一:扩大训练集,降低模型对某些属性的依赖,提高模型的泛化能力。
  • 作用二:减少模型对物体位置的依赖性,并降低模型对色彩的敏感度等。
    • 如:CNN物体分类,不会因为其位置、角度、大小、照明等(或以上的组合)而发生改变,因此CNN具有不变性属性。

torchvision.transforms:图像变换

  • 单样本数据增强
    • (1)常用的几何变换方法:翻转,旋转,裁剪,缩放,平移,抖动
    • (2)常用的像素变换方法:椒盐噪声,高斯噪声,高斯模糊,亮度 / 饱和度,直方图均衡化,白平衡等。
  • 多样本数据增强
    • (1)mixup(分类任务):将随机的两张样本按比例混合(分类的结果按比例分配)
    • (2)cutmix(分类任务)将一部分区域cut减掉,但不填充0像素,而是随机填充训练集中的其他数据的区域像素值(分类结果按一定的比例分配)
    • (3)cutout(分类、检测、识别):随机的将样本中的部分区域cut减掉,并填充0像素值(分类的结果不变)

5.4、过拟合 + 与欠拟合

❤️ 泛化性:用于衡量训练集与测试集之间的误差。若相差无几,则具有泛化性,反之亦然。

欠拟合(underfitting)训练集与测试集的准确率都低

  • 原因:(1)训练样本数量少;(2)模型复杂度太低;(3)参数还未收敛就停止循环;
  • 解决办法
    • (1)增加样本数量
    • (2)增加权重参数
    • (3)提高模型复杂度
    • (4)增加训练次数
    • (5)查看是否是学习率过高导致模型无法收敛;

过拟合(overfitting)训练集的准确率高,而测试集的准确率低,泛化能力差。

  • 原因:(1)数据的噪声太大;(2)权重参数太多;(3)模型太复杂无法收敛。
  • 解决办法
    • (1)数据预处理,剔除冗余特征的样本数据;
    • (2)减少权重参数,避免学习冗余特征;
    • (3)降低模型复杂度,避免学习冗余特征;
    • (4)添加Dropout,避免对特定权重参数的依赖性;
    • (5)降低L2正则化参数,降低权重参数的影响;
    • (6)数据归一化,消除权重参数 W 的缩放影响。

5.5、梯度消失 + 梯度爆炸 —— 解决方法:激活函数 + BN批标准化

梯度(gradient)激活函数求导后,与权重参数 W 相乘后的结果。

  • 梯度爆炸(gradient > 1):当层数增多,梯度以指数形式增加,使得参数无法确定更新方向,导致无法优化代价函数。
  • 梯度消失(gradient < 1):当层数增多,梯度以指数形式衰减。使得参数一下子跳过最优点,导致学习过程极不稳定。

如何解决梯度消失和梯度爆炸:

5.6、归一化 + 标准化 + 正态分布

标准化:既是归一化也是正态分布。

❤️ 归一化(Normaliation)将数据范围归整到(0,1)或(-1,1)之间的小数。

  • Min-Max 归一化
    • ( 0,1):X' = ( X - Min ) / ( Max - Min )
    • (-1,1):X' = ( X - Mean ) / ( Max - Min ) * 2 - 1

❤️ 标准化(Standardization)具有均值为0,标准差为1的标准正态分布,取值范围 (+∞,−∞)。

  • 均值标准化(Z-score standardization):X' = ( X - Mean ) / ( Standard deviation )

❤️ 正态分布(Normal distribution),又名高斯分布(Gaussian distribution)。
在这里插入图片描述

  • μ:均数,决定了正态分布的位置。
    • 与μ越近,被取到的概率就越大,反之越小。
  • σ:标准差,表示正态分布的离散程度。
    • σ越大,数据分布越分散,曲线越扁平;
    • σ越小,数据分布越集中,曲线越陡峭。
  • 正态分布是一种概率分布。曲线与横轴之间的面积恒等于1
  • 正态分布的曲线特征:以均数为中心,左右对称,且曲线的两端无限趋近于横轴,但永不相交。

在这里插入图片描述

  • 正态分布:满足均值=μ,方差=σ^2 —— 记为N(μ,σ^2)
  • 标准正态分布:满足均值=0,方差=1 —— 记为N(0,1)

5.7、网络层:定义为具有参数计算的模块

  • 具有参数计算的网络层卷积层、全连接层
    • 可学习参数:是模型通过训练学习到的权重和偏置
  • 不具有参数计算的操作:激活函数、池化层

5.8、区别:model.train() + model.eval()

【Pytorch】model.train()和model.eval()用法和区别,以及model.eval()和torch.no_grad()的区别

Pytorch控制模型状态的两个方法:

  • model.train():将模型设置为训练模式,用于在训练集上参数计算。
    • 状态:
      • (1)启用 Dropout 和 Batch Normalization —— 增加模型的泛化能力和训练过程的稳定性。
      • (2)启用梯度计算 —— 在反向传播过程中计算梯度,并通过优化器更新模型的参数。
  • model.eval():将模型设置为评估模式,用于在验证集上评估性能。
    • 状态:
      • (2)关闭 Dropout 和 Batch Normalization —— 确保模型的输出稳定性和可重复性
      • (2)关闭梯度计算 —— 避免在推理或验证过程中对模型参数进行更新

备注:在训练过程中,模型需要保持两种状态的切换,以确保在不同阶段获得正确的行为。

5.9、保存模型 + 加载模型 + 加载模型的状态字典

import torch
import torchvision.models as models

model = models.resnet18()                                                   # 加载模型
torch.save(obj=model.state_dict(), f='model.pth')                           # 保存模型

state_dict = torch.load(f='model.pth', map_location=torch.device('cpu'))    # 加载模型
model.load_state_dict(state_dict=state_dict)                                # 加载模型的状态字典

print(model)
print(state_dict)

"""##############################################################################################
# 函数功能:用于加载模型或张量。
# 函数说明:model = torch.load(f, map_location=None, pickle_module=pickle)
# 参数说明:
#         - f:                    模型文件的路径。支持的扩展名:.pt或.pth。
#         - map_location(可选):   将数据加载到指定的设备上。
#                         (0)map_location=None                  :若不指定,则加载到默认设备上。
#                         (1)map_location='cpu'                 :将数据加载到CPU上
#                         (2)map_location=torch.device('cuda')  :将数据加载到默认的CUDA设备上。
#         - pickle_module(可选):  使用Python的内置 pickle 模块来反序列化对象。用于兼容不同的Python版本或自定义的序列化格式。
# 返回参数:
#         返回一个包含模型或张量的Python对象。———— 若加载的文件包含一个字典,则该字典将包含从保存的对象名称到对象本身的映射。
##################################################################
# 预训练模型文件通常包含以下信息:
#       (1)模型的权重参数:     如:各个层的权重矩阵和偏置向量。
#       (2)模型的网络结构:     如:层的类型、层的连接方式等。
#       (3)模型的优化器状态:    如: 学习率、动量、权重衰减等优化器相关的信息。
##############################################################################################"""

"""##############################################################################################
# 函数功能:用于加载预训练模型的状态字典。———— 状态字典是一个Python字典对象,其中包含了模型的所有权重参数(如:weights和biases)。
# 函数说明:model.load_state_dict(state_dict, strict=True)
# 参数说明:
#         - model:                 加载权重参数的PyTorch预训练模型对象。
#         - state_dict:            预训练模型权重参数的状态字典。
#         - strict(可选):          是否严格匹配state_dict中的键与当前模型的键。
#                         (1)默认True,表示要求严格匹配。
#                         (2)若为False,则允许state_dict中的键只是当前模型键的子集,或者键的顺序不同。
#                                   但是,若state_dict中的键不包含在当前模型中,或者形状不匹配,则加载失败。
##############################################################################################"""

"""##############################################################################################
# 函数功能:用于保存模型
# 函数说明:torch.save(obj, f, pickle_module=pickle)
# 参数说明:
#          - obj:                   要保存的对象。
#                         (1)整个模型:model
#                         (2)模型的状态字典:model.state_dict()
#          - f:                     文件路径(字符串类型)或类文件对象(比如一个已打开的文件)。
#          - pickle_module(可选):   使用Python的内置 pickle 模块来序列化对象。用于兼容不同的Python版本或自定义的序列化格式。
##############################################################################################"""

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/816600
推荐阅读
相关标签
  

闽ICP备14008679号