主要学习任务: 1、学习率;2、分类;3、分类实战
学习率
训练网络到临界点的时候,意味着梯度非常小,但损失不再下降的时候,梯度并没有真的变得很小。图中横轴是迭代次数,竖轴是梯度的范数(norm),即梯度这个向量的长度。随着迭代次数增多,虽然损失不再下降,但是梯度的范数并没有真的变得很小。
训练一个网络,训练到后来发现损失不再下降的时候,有时候不是卡在局部最小值或鞍点,只是单纯的损失无法再下降,如下图:梯度在山谷的两个谷壁间,不断地来回“震荡”,这个时候损失不会再下降。
实际中,要走到一个临界点其实是比较困难的,多数时候训练在还没有走到临界点的时候就已经停止了。
学习率决定了更新参数的时候的步伐,学习率设太大,步伐太大就无法慢慢地滑到山谷里面,设太小参数会滑到山谷底后左转(坡度已经非常平坦了,小的学习率无法再让训练前进),但是这个训练永远走不到终点,因为学习率已经太小了。
在梯度下降里面,所有的参数都是设同样的学习率,这显然是不够的,应该要为每一个参数定制化学习率,即引入自适应学习率(adaptive learning rate)的方法,给每一个参数不同的学习率。也就是说,如果在某一个方向上,梯度的值很小,非常平坦,我们会希望学习率调大一点;如果在某一个方向上非常陡峭,坡度很大,我们会希望学习率可以设得小一点。
三种常见的自适应学习率
(1)AdaGrad AdaGrad(Adaptive Gradient)是典型的自适应学习率方法,其能够根据梯度大小自动调整学习率。AdaGrad 可以做到梯度比较大的时候,学习率就减小,梯度比较小的时候,学习率就放大。
自动调整学习率示例如图,θ1坡度小,梯度算出来的值比较小,计算得到的σ小,步伐(参数更新的量)就比较大,学习率就大。θ2坡度大,梯度算出来的值比较大,计算得到的σ大,步伐(参数更新的量)就比较小,学习率就小。
(2)RMSProp
RMSProp通过调整每个参数的学习率来解决梯度下降过程中的振荡问题。它利用梯度的平方的均值来调整学习率,从而使得每个参数都有一个独立的学习率。
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)可以解决这个问题。
除了学习率下降以外,还有另外一个经典的学习率调度的方式———预热。预热的方法是让学习率先变大后变小,至于变到多大、变大的速度、变小的速度是超参数。残差网络里面是有预热的,在残差网络里面,学习率先设置成 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中,独热向量可以使用 pandas 或 sklearn 库来生成。例如,使用 pandas 的 get_dummies 方法。
假设我们有一个分类问题,其中类别有三种:猫、狗和兔子。我们可以使用独热向量对这些类别进行编码:
- 猫: [1, 0, 0]
- 狗: [0, 1, 0]
- 兔子: [0, 0, 1] 这种表示方法在神经网络训练中尤为重要,因为它将类别信息转换为模型可以理解的格式,而不会误导模型关于类别的顺序或距离。
带有 softmax 的分类
为什么分类过程中要加上 softmax 函数?
一个比较简单的解释是,y 是独热向量,所以其里面的值只有 0 跟 1,但是 ˆy 里面有任何值。既然目标只有 0 跟 1,但 ˆy 有任何值,可以先把它归一化到 0 到 1 之间,这样才能跟标签的计算相似度。
取指数→归一化
一般有两个类的时候,不套 softmax,而是直接取 sigmoid。当只有两个类的时候,sigmoid 和 softmax 是等价的。
分类损失
当我们把 x 输入到一个网络里面产生 ˆy 后,通过 softmax 得到 y′,再去计算 y′跟 y 之间的距离 e。计算 y′跟 y 之间的距离可以使用均方误差或者交叉熵,不过从优化的角度来说明相较于均方误差,交叉熵是被更常用在分类上。
3 一个简单的卷积神经网络——分类
构建深度神经网络模型的步骤:
- 导入所需要的库/工具包
- 数据准备与预处理
- 定义模型
- 定义损失函数和优化器等其他配置
- 训练模型
- 评估模型
- 进行预测
接下来使用云算力平台跑通一个分类的CNN算法。
# 下载
git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/LeeDL-HW3-CNN.git
模型结构
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可视化