1.前言
图像预处理时,通常使用transforms.Normalize(mean, std)对图像按通道进行标准化,即减去均值,再除以方差。这样做可以加快模型的收敛速度。其中参数mean和std分别表示图像每个通道的均值和方差序列。
2.具体代码
import os
import torch
from torchvision import datasets, transforms
def calculate_mean_std(dataset):
"""
计算数据集的均值和方差
:param dataset: 数据集
:return: mean, std
"""
# init mean and std
mean = 0.
std = 0.
total_samples = len(dataset)
print("tran dataset len:", total_samples)
# Traversing the dataset calculates the mean and std
for data, _ in dataset:
"""
data张量是一个代表图像的张量,通常具有三个维度:(通道, 高度, 宽度)。
dim=(1, 2)参数指定了要在高度和宽度维度上进行求均值的操作。
计算每个通道上的像素值的平均值,得到的结果是一个包含每个通道上的平均值的张量。
"""
mean += torch.mean(data, dim=(1, 2))
std += torch.std(data, dim=(1, 2))
# Calculate the population mean and std
mean /= total_samples
std /= total_samples
return mean, std
if __name__ == "__main__":
# 定义数据转换
transform = transforms.Compose([transforms.ToTensor()])
# get data root path
data_root = os.path.abspath(os.path.join(os.getcwd(), "../"))
# flower data set path
image_path = os.path.join(data_root, "data_set", "flower_data")
# 加载数据集
dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), transform=transform)
# calculate mean and std
mean, std = calculate_mean_std(dataset)
print("Mean:", mean)
print("Std:", std)
3.问题层层解析
(1)怎样加速收敛数度?
通过标准化,模型在训练期间更容易学习到数据的分布,从而提高了梯度下降的效率。
(2)为什么可以提高梯度下降的效率?
1.更快的收敛速度
在神经网络训练中,如果输入数据的尺度相差很大,可能会导致梯度更新不平衡,进而影响训练速度。通过标准化,可以确保输入数据的尺度基本一致,有助于更快地找到全局最优解或局部最优解。
2.更好的数值稳定性
标准化可以使得输入数据的均值接近于零,从而使得激活函数的输入更接近于线性变换的范围,有助于避免梯度消失或梯度爆炸等数值问题,提高了梯度下降算法的稳定性。
3.减少训练时的振荡
在梯度下降的过程中,如果数据的尺度不一致,可能会导致训练过程中的振荡现象,即在损失函数表面上来回跳动。通过标准化,可以减少这种振荡,使得训练过程更加稳定。
4.更好的收敛路径
标准化后的数据使得损失函数的形状更加规则,更接近于一个圆形或球形,而不是一个细长的椭圆形。这样可以使得梯度下降更容易找到最优解,从而提高了训练的效率。
(3) 什么叫数据的尺度一致?为什么激活函数的输入要近于线性变换的范围?为什么损失函数表面上来回跳动?为什么损失函数的形状是一个圆形或球形?
1.数据的尺度一致
数据的尺度一致意味着不同特征之间的取值范围差异较小。在机器学习中,不同特征可能具有不同的量纲和尺度。如果不同特征之间的尺度相差很大,比如一个特征的取值范围在 0 到 1 之间,另一个特征的取值范围在 100 到 1000 之间,这种情况下模型可能会因为不同尺度的影响而训练不稳定,收敛速度慢。
2.激活函数的输入近于线性变换的范围
大多数深度学习模型使用的激活函数(如ReLU、Sigmoid、Tanh等)在输入较大或较小的时候,会表现出较小的梯度,这可能导致梯度消失或梯度爆炸的问题。如果输入数据被标准化,使得输入的均值接近于零,标准差接近于1,那么激活函数的输入就会落在接近线性变换的范围内,这有助于避免梯度消失或梯度爆炸。
3.损失函数表面上来回跳动
这种现象通常出现在损失函数表面非常不规则、有很多局部最小值和鞍点的情况下。当学习率过高时,梯度下降算法可能会在损失函数表面上来回跳动,而不是朝着收敛方向移动。这种情况下,模型的训练过程会变得不稳定,收敛速度变慢。
4.损失函数的形状是一个圆形或球形
当数据经过标准化处理后,不同特征之间的尺度一致,且取值范围较小,这会使得损失函数的形状更加规则。在高维空间中,这样的数据点分布形成的超球面(hypersphere)形状,通常会使得损失函数的形状更接近于一个圆形或球形。这种形状对于优化算法来说更容易处理,因为它没有像椭圆形那样的突起或陡峭区域,从而减少了算法在搜索最优解时的困难程度。
(4)激活函数的作用
1.什么是激活函数,为什么需要激活函数?
激活函数是在神经网络层间输入与输出之间的一种函数变换,目的是为了加入非线性因素,增强模型的表达能力。
2.了解那些激活函数以及应用?
主要分两类(饱和/非饱和),以及应用场景等。有时候可能特定到具体经典模型,
比如LSTM用到Tanh,Transfromer中用到的ReLU,Bert中的GeLU,YOLO的Leaky ReLU等。
3.梯度消失与梯度爆炸现象与原因以及解决办法?
梯度消失是指在反向传播过程中,梯度值逐层减小,最终变得非常接近于零。
-
- 使用更合适的激活函数:ReLU 和其变种(如 Leaky ReLU、ELU)在输入较大时具有较大的梯度,可以一定程度上缓解梯度消失问题。
- 使用 Batch Normalization:Batch Normalization 在每一层的输出进行标准化处理,有助于缓解梯度消失问题。
- 使用残差连接(Residual Connections):在深度神经网络中使用残差连接可以使得梯度更容易地传播,从而减轻梯度消失的影响。
- 使用梯度裁剪(Gradient Clipping):设置梯度的阈值,防止梯度爆炸。
梯度爆炸是指在反向传播过程中,梯度值逐层增大,最终变得非常大。这通常发生在模型中存在梯度值异常增大的情况,例如网络权重初始化不合适、学习率过高等
-
- 使用梯度裁剪(Gradient Clipping):设置梯度的阈值,当梯度超过阈值时进行裁剪,以避免梯度爆炸。
- 使用更小的学习率:减小学习率可以减缓梯度的增长速度,从而降低梯度爆炸的可能性。
- 使用合适的权重初始化方法:合适的权重初始化可以避免初始时梯度爆炸的发生。例如,使用 Xavier 初始化或 He 初始化。
- 使用稳定的优化器:一些优化器(如 Adam、RMSProp)具有自适应学习率的特性,可以更好地处理梯度爆炸问题。