自适应学习率
在训练神经网络时,我们经常会遇到损失函数不再显著下降的情况,这通常被称为"临界点"。然而,临界点并不总是意味着遇到了最大的障碍。
损失下降过程:在训练初期,损失通常较大,随着参数的更新,损失逐渐减小。
临界点:当损失停止显著下降时,我们达到了临界点。这并不一定意味着梯度变得很小。
梯度与损失关系:即使损失不再下降,梯度的范数(即梯度向量的长度)可能仍然较大。这表明模型可能没有达到局部最小值或鞍点。
鞍点与局部最小值:在鞍点或局部最小值,梯度会非常小。但在某些情况下,即使梯度较大,损失也可能不再下降。
震荡现象:在某些情况下,模型可能会在误差表面的山谷两侧来回震荡,导致损失不再下降。如图所示,
临界点的识别:在训练过程中,我们可以通过观察特征值的正负来判断一个临界点是鞍点还是局部最小值。正的特征值表示局部最小值,而负的特征值可能指示鞍点。
梯度下降的局限性:传统的梯度下降方法在达到鞍点或局部最小值时存在困难。通常,梯度下降会在梯度仍然较大时使损失降低到一个较低的水平,但不会进一步达到临界点。
训练中的挑战:实际上,训练一个网络到达临界点是困难的。大多数情况下,训练会在达到临界点之前停止,因为损失已经降低到一个可接受的水平。
误差表面的形状:以图 为例,误差表面是凸的,其等高线呈椭圆形状。在横轴方向上,梯度非常小,表面非常平坦;而在纵轴方向上,梯度变化大,表面非常陡峭。
初始点的影响:如果从黑点(初始点)开始梯度下降,由于误差表面的特殊形状,训练可能会沿着横轴方向快速下降,而在纵轴方向上进展缓慢。
特别方法的需求:为了更有效地训练网络并达到临界点,可能需要采用特别的方法,如动量(momentum)或自适应学习率算法(如Adam)等。
AdaGrad 是一种自适应学习率优化算法,它的核心思想是根据参数的梯度历史来调整每个参数的学习率。以下是对 AdaGrad 的简要介绍和其工作原理的说明:
自适应学习率:AdaGrad 能够自动调整每个参数的学习率,使得优化过程更加灵活和高效。
梯度依赖性:AdaGrad 的学习率调整依赖于参数的梯度。如果某个参数的梯度较大,AdaGrad 会减小该参数的学习率,以避免在梯度较大的方向上过快更新,可能导致的震荡或不稳定。相反,如果梯度较小,学习率会增加,以加快收敛速度。
优点:AdaGrad 能够处理稀疏数据和不同参数的不同梯度规模,使得每个参数都可以根据其历史梯度进行优化。
缺点:尽管 AdaGrad 在某些情况下表现良好,但它也有缺点。随着迭代次数的增加,累积的梯度平方会越来越大,导致学习率迅速减小,这可能会使训练过程在后期变得非常缓慢。
根据参数梯度的不同调整学习率:
RMSProp 优化算法,这是一种自适应学习率的算法,旨在解决 AdaGrad 学习率在训练过程中逐渐减小的问题。
RMSProp 算法:RMSProp 是一种自适应学习率方法,它通过一个衰减平均(moving average)来调整每个参数的学习率。
α \alpha α 的作用:RMSProp 引入了一个超参数 α \alpha α,它决定了新梯度 相对于之前累积梯度 的重要性。 α \alpha α 值较小时,新梯度的影响较大; α \alpha α 值较大时,新梯度的影响较小。
动态调整步伐:在误差表面的平坦区域(如 AB 段),RMSProp 允许采取较大的步伐,因为此时梯度较小。当梯度变大时(如 BC 段),RMSProp 可以快速调整学习率,减小步伐,实现快速“踩刹车”。
Adam 优化器是一种流行的自适应学习率优化算法,它结合了动量(Momentum)和 RMSProp 的优点。
动量和自适应学习率:Adam 优化器结合了动量方法和自适应学习率调整机制,使用动量来加速参数更新的方向,同时根据历史梯度调整学习率。
动量项(Momentum):动量项计算了过去梯度的指数加权平均,有助于减少训练过程中的噪声,并使参数更新更加稳定。
平方根项(RMSProp:平方根项计算了过去梯度平方的指数加权平均,类似于 RMSProp,有助于调整学习率,特别是在训练初期。
在回归问题中,目标是根据输入向量 预测一个连续的输出值 使其尽可能接近真实的标签 。在某些情况下,可以将分类问题当作回归问题处理。
数字表示类别:在这种方法中,类别被直接用数字表示,例如类 1 用数字 1 表示,类 2 用数字 2 表示,依此类推。
方法的局限性:如果类别之间存在某种关系或层次结构,使用数字直接表示类别可能会引入错误的假设,即数字相近的类别在逻辑上也相近。例如,在预测年级的例子中,一年级和二年级可能在逻辑上更接近,而与三年级较远。
独热编码:为了解决这个问题,可以引入独热编码(One-Hot Encoding)来表示类别。独热编码将每个类别转换为一个二进制向量,向量中只有一个位置是 1,其余位置是 0。这样,类别之间的关系就不会被错误地假设为数字之间的关系。
独热编码的优势:使用独热编码可以更准确地表示类别之间的关系,特别是在类别之间没有自然顺序或层次结构的情况下。
分类问题中的常见做法:在实际的分类问题中,使用独热编码表示类别是一种常见做法,尤其是在使用深度学习模型时,如多分类问题中的全连接层输出。
损失函数的选择:当使用独热编码时,通常使用交叉熵损失函数(Cross-Entropy Loss)来衡量模型预测与真实标签之间的差异,并指导模型训练。
分类损失函数是评估分类模型预测结果与真实标签之间差异的一种度量方式,它在训练过程中用于指导模型的优化。以下是几种常见的分类损失函数:
- 二元交叉熵损失(Binary Cross-Entropy Loss):
- 用于二分类问题。
- 公式:
- 交叉熵损失(Categorical Cross-Entropy Loss):
- 用于多分类问题。
- 公式:
平均交叉熵损失(Mean Categorical Cross-Entropy Loss): - 计算所有样本的交叉熵损失的均值,常用于多分类问题。
加权交叉熵损失(Weighted Cross-Entropy Loss:
- 对交叉熵损失进行加权,以解决类别不平衡问题,给予少数类别更高的权重。
对数损失(Log Loss):
- 另一种称呼,通常指的就是二元或多类的交叉熵损失。
合页损失(Hinge Loss):
- 用于 SVM(支持向量机)分类器,特别是在二分类问题中。
- 公式:[ \text{Hinge Loss} = \max(0, 1 - y \cdot \hat{y}) ]
- 其中 ( y ) 是真实标签,( \hat{y} ) 是模型的预测值(通常经过某种变换,如经过一个线性函数)。
指数损失(Exponential Loss):
- 用于 AdaBoost 集成学习算法中,衡量预测值与真实标签的差异。
Focal Loss:
- 用于解决类别不平衡问题,特别是当存在大量负样本和少数正样本时。
Softmax 与 Cross-Entropy Loss 的组合:
- 在神经网络中,通常将 softmax 激活函数与交叉熵损失结合使用,softmax 负责将原始输出转换为概率分布,然后交叉熵损失根据这些概率计算损失值。
选择哪种损失函数取决于具体的分类问题、数据特性以及模型的需求。交叉熵损失因其直观的概率解释和在多分类问题中的广泛应用,成为深度学习中最常见的分类损失函数之一。
实际操作:
1.通过阿里云的PAI-DSW获取算力,之后git clone代码,可以一键运行训练代码。
2.
class FoodDataset(Dataset):
"""
用于加载食品图像数据集的类。
该类继承自Dataset,提供了对食品图像数据集的加载和预处理功能。
它可以自动从指定路径加载所有的jpg图像,并对这些图像应用给定的变换。
"""
def __init__(self, path, tfm=test_tfm, files=None):
"""
初始化FoodDataset实例。
参数:
- path: 图像数据所在的目录路径。
- tfm: 应用于图像的变换方法(默认为测试变换)。
- files: 可选参数,用于直接指定图像文件的路径列表(默认为None)。
"""
super(FoodDataset).__init__()
self.path = path
# 列出目录下所有jpg文件,并按顺序排序
self.files = sorted([os.path.join(path, x) for x in os.listdir(path) if x.endswith(".jpg")])
if files is not None:
self.files = files # 如果提供了文件列表,则使用该列表
self.transform = tfm # 图像变换方法
def __len__(self):
"""
返回数据集中图像的数量。
返回:
- 数据集中的图像数量。
"""
return len(self.files)
def __getitem__(self, idx):
"""
获取给定索引的图像及其标签。
参数:
- idx: 图像在数据集中的索引。
返回:
- im: 应用了变换后的图像。
- label: 图像对应的标签(如果可用)。
"""
fname = self.files[idx]
im = Image.open(fname)
im = self.transform(im) # 应用图像变换
# 尝试从文件名中提取标签
try:
label = int(fname.split("/")[-1].split("_")[0])
except:
label = -1 # 如果无法提取标签,则设置为-1(测试数据无标签)
return im, label
这部分代码FoodDataset
类是为食品图像数据集定制的 PyTorch Dataset
类的子类,它专门负责加载和预处理图像数据。
在初始化方法 __init__
中,FoodDataset
接收图像存储路径 path
、一个变换方法 tfm
(默认为测试时使用的变换),以及一个可选的文件列表 files
。如果提供了 files
参数,类将使用这个列表;否则,它会扫描 path
目录下所有的 .jpg
图像文件,并按名称排序。类变量 self.transform
存储了传入的变换方法,用于后续对图像进行预处理。__len__
方法返回数据集中图像的总数,这是 self.files
列表中元素的数量。__getitem__
方法根据索引 idx
加载并返回图像及其标签。它首先通过 Image.open
打开指定索引的图像文件,并应用之前存储的变换方法。接着,尝试从图像文件名中解析出标签,如果解析失败,则将标签设为 -1
,这通常用于无标签的测试数据。 FoodDataset
类与 PyTorch 的 DataLoader
协同工作,支持多线程数据加载和批处理,从而优化了数据的加载速度和训练过程的效率。
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) # 通过全连接神经网络得到最终输出
这段代码定义了一个名为 Classifier
的类,它继承自 PyTorch 的 nn.Module
。这个类实现了一个用于图像分类的卷积神经网络(CNN)。
Classifier
类用于构建一个图像分类器,包含卷积层、批归一化层、激活函数、池化层和全连接层。
使用 super()
调用父类 nn.Module
的初始化方法。
定义了一个名为 cnn
的 nn.Sequential
容器,它按顺序包含多个卷积层、批归一化层、ReLU 激活函数和最大池化层。这些层用于从输入图像中提取特征。定义了一个名为 fc
的 nn.Sequential
容器,包含全连接层。这些层用于在特征提取后进行分类。
卷积层参数 nn.Conv2d
:卷积层,参数依次为输入通道数、输出通道数、卷积核大小、步长、填充。 网络共有五组卷积层,每组卷积层后都紧跟一个批归一化层 nn.BatchNorm2d
和一个 ReLU 激活函数 nn.ReLU
,以及一个最大池化层 nn.MaxPool2d
。nn.Linear
:全连接层,参数为输入特征数和输出特征数。网络包含三个全连接层,前两层后接 ReLU 激活函数。
前向传播方法 forward
接收输入数据 x
,其形状为 (batch_size, 3, 128, 128)
,即批量大小、颜色通道数、图像高度和宽度通过 self.cnn
处理输入数据,得到特征图。
使用 view
方法将特征图展平,以匹配全连接层的输入要求。展平后的形状为 (batch_size, 512*4*4)
,这是基于假设经过所有卷积和池化操作后,特征图的大小变为 4x4
(实际大小需要根据网络结构调整)。
将展平的特征数据传递给 self.fc
,得到最终的分类结果,其形状为 (batch_size, 11)
,表示有 11 个类别的概率输出。
最后得到评估模型,进行预测。
参考资料:
1.https://www.bilibili.com/video/BV1JA411c7VT/?p=5
2.https://github.com/datawhalechina/leedl-tutorial
3.Datawhale学习指南