Pytorch系列之——损失函数、优化器
权值初始化
前面我们介绍了如何搭建网络模型,在模型搭建好之后一个非常重要的步骤就是对模型当中的权值进行初始化。正确的权值初始化可以加快模型的收敛,而不恰当的权值初始化可能会引发梯度的消失或爆炸,最终导致模型无法训练。
- 梯度消失与爆炸
- Xavier方法与Kaiming方法
- 常用初始化方法
梯度消失与爆炸
首先观察模型是怎么对W2进行求导的,我们有:
H1是上一层神经元的输出值,我们会发现W2的梯度会依赖于上一层的输出,如果H1的输出非常小比如趋向于0的话,那么W2的梯度也就会趋向于0,从而导致了梯度消失;如果H1趋向于无穷大,那么W2的梯度也就会趋向于无穷大,从而导致了梯度爆炸。一旦发生梯度消失或者梯度爆炸,那么就会导致模型无法正常训练。所以若要控制模型的梯度消失或梯度爆炸,就要严格控制网络输出层的输出值的一个尺度范围,也就是不能让每一层网络层的输出值太大或者太小。
接下来我们通过方差的公式推导来观察为什么网络层输出的标准差会越来越大?最终会超出我们可表示的范围?在进行方差公式推导之前,我们先来复习三个基本的公式:
通过以上三个公式我们可以推导出:
其中若E(X) = 0,E(Y) = 0,那么:
下面我们来观察网络层神经元它的标准差是什么样的?
这里将网络模型第一个隐藏层的第一个神经元记为H11,我们将对这个神经元进行举例说明:我们假设该隐藏层第一个神经元的输入和权重的分布都是服从均值0,标准差为1的标准正态分布。从公式的推导我们可以发现,第一个隐藏层H11它的输出值的方差变为了n,而我们输入的数据的方差是1,经过一个网络层的前向传播,它的方差就扩大了n倍,它的标准差就扩大了根号n倍,如果再往下传播以此类推,那么输入数据的尺度范围都会被扩大,最终会超出我们精度可表示的范围。因此我们需要采用合适的权值初始化使每一层神经元输出数据的方差为1。
针对以上具有奇偶函数的问题,有研究人员探讨了有**函数时应如何设计初始化,在其发表的文献中结合方差一致性原则,也就是保持数据尺度维持在恰当的范围,通常使其方差为1,由此提出了Xavier初始化方法,适用于**函数采用sigmoid和tanh。(有关详细的推导可以参考文献:《Understanding the difficulty of training deep feedforward neural networks》)…
此外,还有何凯明等人提出的Kaiming初始化方法,适用于**函数采用ReLU及其变种,这里依然是根据方差一致性原则。(有关详细推导可以参考文献:《Delving deep into rectifiers:Surpassing human-level performance on ImageNet Classification》)
十种初始化方法
- Xavier均匀分布
- Xavier标准正态分布
- Kaiming均匀分布
- Kaiming标准正态分布
- 均匀分布
- 正态分布
- 常数分布
- 正交矩阵初始化
- 单位矩阵初始化
- 稀疏矩阵初始化
参考链接:https://pytorch.org/docs/stable/nn.init.html
至于要选择哪种方法进行权值初始化,这个就需要具体问题具体分析了,但要始终保证输出数据的方差一致性原则,也就是使方差既不能太大又不能太小。
损失函数(一)
- 损失函数概念
- 交叉熵损失函数
- NLL/BCE/BCEWithLogits Loss
什么是损失函数?
损失函数是衡量模型输出与真实标签之间的差异。
上面这个图是一元线性回归的过程,其中绿色的点是训练的样本也就是真实数据,而蓝色的线是我们训练好的一个模型,我们可以看到这条蓝色的线并没有很好地去拟合每一个数据点,因此数据点会产生一个loss。可以看到,针对loss这个误差该模型是采用了绝对误差损失函数, 用来衡量模型输出与真实数据的一个差异。
我们知道在模型训练中通常会用到损失函数(Loss function)、代价函数(Cost function)和目标函数(Objective function),那么这三者的区别主要是什么呢?
-
损失函数:
损失函数是计算一个样本的差异 -
代价函数:
代价函数是计算整个训练集的loss的平均值 -
目标函数:
目标函数其实是一个更广泛的概念,在机器学习模型训练当中目标函数其实就是我们最终的一个目标,通常这个目标会包括Cost和Regularization:
我们在追求模型输出与真实标签之间差异尽量小时,同时也要对这个模型做一些限制,做一些约束。而在机器学习当中这个约束项就称之为正则项。
在我们衡量模型输出和真实标签差异的时候通常用损失函数来代替代价函数:
可以看到pytorch中的loss仍然是继承于Module,因此Loss可以相当于是一个网络层。那么在人民币二分类问题中,我们是采用了nn.CrossEntropyLoss()交叉熵损失函数,下面依然通过代码断点调试来查看其运行机制:
两步stepinto之后,程序会进入到_Loss这个类:
我们stepout这个函数并由loss_function(outputs,labels)处stepinto进入,可以看到具体的调用函数:
由于Loss其实也是一个Module所以它也会有forward()函数,可以看到在__call__()函数中确实有forward()函数的调用。再Run on cursor and stepinto,可以看到cross_entropy函数就是在这里调用的:
这就是Loss function的构建以及使用过程。
接下来详细地介绍nn.CrossEntropyLoss的使用:
nn.CrossEntropyLoss
功能:nn.LogSoftmax()与nn.NLLLoss()结合,进行交叉熵计算
主要参数:
- weight:各类别的loss设置权值
- ignore_index:忽略某个类别
- reduction:计算模式,可为none/sum/mean
none-逐个元素计算
sum-所有元素求和,返回标量
mean-加权平均,返回标量
交叉熵 = 信息熵 + 相对熵
交叉熵:
交叉熵:
自信息:
熵:
相对熵:
其中P是真实的样本分布,而Q是模型的输出分布。所以在机器学习模型当中,最小化交叉熵就是等价于最小化相对熵的。因为训练集是固定的所以H§也就是固定不变的,也就是一个常数,可以忽略掉。
nn.NLLLoss
功能:实现负对数似然函数中的负号功能
主要参数:
- weight:各类别的loss设置权值
- ignore_index:忽略某个类别
- reduction:计算模式,可为none/sum/mean
none-逐个元素计算
sum-所有元素求和,返回标量
mean-加权平均,返回标量
nn.BCELoss
功能:二分类交叉熵
注意事项:输入值取值在[0,1]
主要参数:
- weight:各类别的loss设置权值
- ignore_index:忽略某个类别
- reduction:计算模式,可为none/sum/mean
none-逐个元素计算
sum-所有元素求和,返回标量
mean-加权平均,返回标量
nn.BCEWithLogitsLoss
功能:结合Sigmoid与二分类交叉熵
注意事项:网络最后不加sigmoid函数
主要参数:
- pos_weight:正样本的权值
- weight:各类别的loss设置权值
- ignore_index:忽略某个类别
- reduction:计算模式,可为none/sum/mean
none-逐个元素计算
sum-所有元素求和,返回标量
mean-加权求和,返回标量
其他的14种损失函数:
- nn.L1Loss
- nn.MSELoss
- nn.SmothL1Loss
- nn.PoissonNLLLoss
- nn.KLDivLoss
- nn.MarginRankingLoss
- nn.MultiLabelMarginLoss
- nn.SoftMarginLoss
- nn.MultiLabelSoftMarginLoss
- nn.MultiMarginLoss
- nn.TripletMarginLoss
- nn.HingeEmbeddingLoss
- nn.CosineEmbeddingLoss
- nn.CTCLoss
由于这14种损失函数在一般的模型训练中使用的并不频繁,所以在这里不过多赘述,需要时可以查阅官方文档详细理解。
优化器 Optimizer(一)
- 什么是优化器
- optimizer的属性
- optimizer的方法
我们依然来回顾一下前面说过的机器学习模型训练的五大步骤:数据模块、模型模块、损失函数、优化器和迭代训练。这里我们已经到了优化器这部分。由前面可知在损失函数这部分会得到一个loss值来衡量真实值与模型输出值的差异,之后会根据模型的autograd自动求导功能求参数的梯度,在之后优化器会获取这个梯度并根据一些优化策略对其进行优化,更新模型的参数使loss值不断减小。
优化器
pytorch中的优化器:管理并更新模型中可学习参数的值,使得模型输出更接近真实标签(通常选择梯度下降来更新模型的参数)。那么什么是梯度呢?梯度是一个向量,它的向量的方向就是使得方向导数最大的那个方向。方向导数就是指定方向上的变化率。梯度的负方向就是函数值下降最快的方向,所以在更新参数的时候通常采用梯度下降这个思路去更新权值,使得函数值降低。(模型训练中函数值就是loss值)
pytorh中的优化器:
基本属性:
-
defaults:优化器超参数
-
state:参数的缓存,如momentum的缓存
-
param_groups:管理的参数组
-
_step_count:记录更新次数,学习率调整中使用
基本方法: -
zero_grad():清空所管理参数的梯度
-
step:执行一步更新
pytorch特性:张量梯度不自动清零!
基本方法: -
add_param_group():添加参数组(为不同的参数分别设置不同的学习率等等,常在fine-tune中使用)
基本方法: -
state_dict():获取优化器当前状态信息字典
-
load_state_dict():加载状态信息字典
接下来通过代码断点调试来进行查看,仍然是stepinto到具体的实现方法:
可以看到这里一开始就会对学习率等超参数进行一系列的判断,判断其合法性:
程序跳转到optimizer这个具体实现类中的__init__()函数,这里首先对之前提过的defaults参数进行了设置,在Debugger一栏我们可以看到,deafults属性是一个字典其中包括了学习率、momentum、weight_decay等超参数。
优化器 Optimizer(二)
- learning rate学习率
- momentum 动量
- torch.optim.SGD
- Pytorch的十种优化器
学习率
其实梯度下降的过程就是通过不断地将参数减去其梯度值来更新其自身的值,但在未设置学习率时,参数的更新方式通常是这样的:
如果模型参数(权重、偏差)仅以这种方式进行逐步更新的话,得到的结果其实是函数值也就是loss值非但没有逐步减小反而越来越大,这和我们最开始希望loss值随着参数的更新可以不断减小这个意愿是相违背的,考虑其原因可能是因为g(wi)这个值有些过大了,从而导致函数值(loss值)不能降低。解决方法通常是会在g(wi)这项前乘以一个系数来缩小它的尺度:
这个LR就是学习率,用来控制参数更新的步伐。
Momentum(动量,冲量)
结合当前梯度与上一次更新信息,用于当前更新。
距离当前时刻越近,影响越大,权重越大;距离当前时刻越远,影响越小,权重就越小。其中的参数Beta的作用是用来控制记忆周期,Beta值越大记忆的越远,其值越小记忆的越短。
pytorch中加上momentum后参数的更新公式:
其中vi是更新量,m是momentum系数。
常用优化器
- optim.SGD
主要参数: - params:管理的参数组
- lr:初始学习率
- momentum:动量系数,贝塔
- weight_decay:L2正则化系数
- nesterov:是否采用NAG
NAG参考文献:《On the importance of initialization and momentum in deep learning》
pytorch所提供的其他优化器:
- optim.SGD:随机梯度下降法
- optim.Adagrad:自适应学习率梯度下降法
- optim.RMSprop:Adagrad的改进
- optim.Adadelta:Adagrad的改进
- optim.Adam:RMSProp结合Momentum
- optim.Adamax:Adam增加学习率上限
- optim.SparseAdam:稀疏版的Adam
- optim.ASGD:随机平均梯度下降
- optim.Rprop:弹性反向传播
- optim.LBFGS:BFGS的改进
参考资料
深度之眼训练营——pytorch课程笔记