深度学习之学习率和分类训练——Datawhale X 李宏毅苹果书 AI夏令营

82 阅读8分钟

主要学习任务: 1、学习率;2、分类;3、分类实战

学习率

‌‌‌‌  训练网络到临界点的时候,意味着梯度非常小,但损失不再下降的时候,梯度并没有真的变得很小。图中横轴是迭代次数,竖轴是梯度的范数(norm),即梯度这个向量的长度。随着迭代次数增多,虽然损失不再下降,但是梯度的范数并没有真的变得很小。 ‌‌‌‌ image.png

‌‌‌‌  训练一个网络,训练到后来发现损失不再下降的时候,有时候不是卡在局部最小值或鞍点,只是单纯的损失无法再下降,如下图:梯度在山谷的两个谷壁间,不断地来回“震荡”,这个时候损失不会再下降。 image.png|239 ‌‌‌‌  实际中,要走到一个临界点其实是比较困难的,多数时候训练在还没有走到临界点的时候就已经停止了。

‌‌‌‌  学习率决定了更新参数的时候的步伐,学习率设太大,步伐太大就无法慢慢地滑到山谷里面,设太小参数会滑到山谷底后左转(坡度已经非常平坦了,小的学习率无法再让训练前进),但是这个训练永远走不到终点,因为学习率已经太小了。

‌‌‌‌  在梯度下降里面,所有的参数都是设同样的学习率,这显然是不够的,应该要为每一个参数定制化学习率,即引入自适应学习率(adaptive learning rate)的方法,给每一个参数不同的学习率。也就是说,如果在某一个方向上,梯度的值很小,非常平坦,我们会希望学习率调大一点;如果在某一个方向上非常陡峭,坡度很大,我们会希望学习率可以设得小一点。

三种常见的自适应学习率

(1)AdaGrad ‌‌‌‌  AdaGrad(Adaptive Gradient)是典型的自适应学习率方法,其能够根据梯度大小自动调整学习率。AdaGrad 可以做到梯度比较大的时候,学习率就减小,梯度比较小的时候,学习率就放大。

‌‌‌‌  自动调整学习率示例如图,θ1坡度小,梯度算出来的值比较小,计算得到的σ小,步伐(参数更新的量)就比较大,学习率就大。θ2坡度大,梯度算出来的值比较大,计算得到的σ大,步伐(参数更新的量)就比较小,学习率就小。 image.png|325

(2)RMSProp ‌‌‌‌  RMSProp通过调整每个参数的学习率来解决梯度下降过程中的振荡问题。它利用梯度的平方的均值来调整学习率,从而使得每个参数都有一个独立的学习率。 image.png

import torch.optim as optim
optimizer = optim.RMSprop(model.parameters(), lr=0.001, alpha=0.9)

(3)Adam

‌‌‌‌  最常用的优化的策略或者优化器(optimizer)是Adam(Adaptive moment estima-tion)。Adam 可以看作 RMSprop 加上动量,其使用动量作为参数更新方向,并且能够自适应调整学习率。通常能更好地处理非平稳目标和稀疏梯度。 ‌‌‌‌  总体来说,Adam通常是更先进和更通用的选择,但RMSProp也是一个非常有效的优化器,尤其在某些特定问题中可能表现更好。

学习率调度

‌‌‌‌  学习率调度中最常见的策略是学习率衰减(learning rate decay),也称为学习率退火(learning rate annealing)。随着参数的不断更新,让 η 越来越小, ‌‌‌‌  使用自适应学习率AdaGrad 方法优化的结果如左图所示,一开始优化的时候很顺利,在左转的时候,因为横轴方向的梯度很小,所以学习率会自动变大,步伐就可以变大,从而不断前进,最后快走到终点的时候突然“爆炸”了。通过学习率调度(learning rate scheduling)可以解决这个问题。 image.png ‌‌‌‌  除了学习率下降以外,还有另外一个经典的学习率调度的方式———预热。预热的方法是让学习率先变大后变小,至于变到多大、变大的速度、变小的速度是超参数。残差网络里面是有预热的,在残差网络里面,学习率先设置成 0.01,再设置成 0.1,并且其论文还特别说明,一开始用 0.1 反而训练不好。除了残差网络,BERT 和 Transformer 的训练也都使用了预热。

2 分类

分类与回归的关系

‌‌‌‌  回归是输入一个向量 x,输出 ˆy,我们希望 ˆy 跟某一个标签 y 越接近越好,y 是要学习的目标。

‌‌‌‌  分类可当作回归来看,输入 x 后,输出仍然是一个标量 ˆy,要让它跟正确答案的那个类越接近越好。但假设三个类本身没有特定的关系,类 1 是 1,类 2 是 2, 类 3 是 3。这种情况,需要引入独热向量来表示类。实际上,在做分类的问题的时候,比较常见的做法也是用独热向量表示类。

‌‌‌‌  独热向量是一种表示分类数据的编码方法,其中每个类别都用一个二进制向量表示。在该向量中,只有一个位置的值为1,其余位置的值都为0。这个位置对应于类别的索引。在Python中,独热向量可以使用 pandassklearn 库来生成。例如,使用 pandasget_dummies 方法。

‌‌‌‌  假设我们有一个分类问题,其中类别有三种:猫、狗和兔子。我们可以使用独热向量对这些类别进行编码:

  • : [1, 0, 0]
  • : [0, 1, 0]
  • 兔子: [0, 0, 1] ‌‌‌‌  这种表示方法在神经网络训练中尤为重要,因为它将类别信息转换为模型可以理解的格式,而不会误导模型关于类别的顺序或距离。

image.png

带有 softmax 的分类

image.png ‌‌‌‌  为什么分类过程中要加上 softmax 函数? ‌‌‌‌  一个比较简单的解释是,y 是独热向量,所以其里面的值只有 0 跟 1,但是 ˆy 里面有任何值。既然目标只有 0 跟 1,但 ˆy 有任何值,可以先把它归一化到 0 到 1 之间,这样才能跟标签的计算相似度。

‌‌‌‌  取指数→归一化 image.png ‌‌‌‌  一般有两个类的时候,不套 softmax,而是直接取 sigmoid。当只有两个类的时候,sigmoid 和 softmax 是等价的。

分类损失

‌‌‌‌  当我们把 x 输入到一个网络里面产生 ˆy 后,通过 softmax 得到 y′,再去计算 y′跟 y 之间的距离 e。计算 y′跟 y 之间的距离可以使用均方误差或者交叉熵,不过从优化的角度来说明相较于均方误差,交叉熵是被更常用在分类上。 image.png

3 一个简单的卷积神经网络——分类

‌‌‌‌  构建深度神经网络模型的步骤:

  1. 导入所需要的库/工具包
  2. 数据准备与预处理
  3. 定义模型
  4. 定义损失函数和优化器等其他配置
  5. 训练模型
  6. 评估模型
  7. 进行预测

‌‌‌‌  接下来使用云算力平台跑通一个分类的CNN算法。

# 下载
git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/LeeDL-HW3-CNN.git

image.png

模型结构

class Classifier(nn.Module):
    """
    定义一个图像分类器类,继承自PyTorch的nn.Module。
    该分类器包含卷积层和全连接层,用于对图像进行分类。
    """
    def __init__(self):
        """
        初始化函数,构建卷积神经网络的结构。
        包含一系列的卷积层、批归一化层、激活函数和池化层。
        """
        super(Classifier, self).__init__()
        # 定义卷积神经网络的序列结构
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),  # 输入通道3,输出通道64,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(64),        # 批归一化,作用于64个通道
            nn.ReLU(),                 # ReLU激活函数
            nn.MaxPool2d(2, 2, 0),      # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(64, 128, 3, 1, 1), # 输入通道64,输出通道128,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(128),        # 批归一化,作用于128个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(128, 256, 3, 1, 1), # 输入通道128,输出通道256,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(256),        # 批归一化,作用于256个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(256, 512, 3, 1, 1), # 输入通道256,输出通道512,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(512),        # 批归一化,作用于512个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),       # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(512, 512, 3, 1, 1), # 输入通道512,输出通道512,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(512),        # 批归一化,作用于512个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),       # 最大池化,池化窗口大小2,步长2,填充0
        )
        # 定义全连接神经网络的序列结构
        self.fc = nn.Sequential(
            nn.Linear(512*4*4, 1024),    # 输入大小512*4*4,输出大小1024
            nn.ReLU(),
            nn.Linear(1024, 512),        # 输入大小1024,输出大小512
            nn.ReLU(),
            nn.Linear(512, 11)           # 输入大小512,输出大小11,最终输出11个类别的概率
        )

    def forward(self, x):
        """
        前向传播函数,对输入进行处理。
        
        参数:
        x -- 输入的图像数据,形状为(batch_size, 3, 128, 128)
        
        返回:
        输出的分类结果,形状为(batch_size, 11)
        """
        out = self.cnn(x)               # 通过卷积神经网络处理输入
        out = out.view(out.size()[0], -1)  # 展平输出,以适配全连接层的输入要求
        return self.fc(out)             # 通过全连接神经网络得到最终输出

所有类别的t-SNE可视化 image.png