五、损失函数
损失函数,又叫目标函数,是编译一个神经网络模型必须的两个参数之一。另一个必不可少的参数是优化器。
损失函数是指用于计算标签值和预测值之间差异的函数,在机器学习过程中,有多种损失函数可供选择,典型的有距离向量,绝对值向量等。
上图是一个用来模拟线性方程自动学习的示意图。粗线是真实的线性方程,虚线是迭代过程的示意,w1 是第一次迭代的权重,w2 是第二次迭代的权重,w3 是第三次迭代的权重。随着迭代次数的增加,我们的目标是使得 wn 无限接近真实值。
那么怎么让 w 无限接近真实值呢?其实这就是损失函数和优化器的作用了。图中 1/2/3 这三个标签分别是 3 次迭代过程中预测 Y 值和真实 Y 值之间的差值(这里差值就是损失函数的意思了,当然了,实际应用中存在多种差值计算的公式),这里的差值示意图上是用绝对差来表示的,那么在多维空间时还有平方差,均方差等多种不同的距离计算公式,也就是损失函数了。
这里示意的是一维度方程的情况,扩展到多维度,就是深度学习的本质了。
下面介绍几种常见的损失函数的计算方法,pytorch 中定义了很多类型的预定义损失函数,需要用到的时候再学习其公式也不迟。
我们先定义两个二维数组,然后用不同的损失函数计算其损失值。
import torch from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F sample = Variable(torch.ones(2,2)) a=torch.Tensor(2,2) a[0,0]=0 a[0,1]=1 a[1,0]=2 a[1,1]=3 target = Variable (a)
sample 的值为:[[1,1],[1,1]]。
target 的值为:[[0,1],[2,3]]。
5.1 nn.L1Loss
L1Loss 计算方法很简单,取预测值和真实值的绝对误差的平均数即可。
criterion = nn.L1Loss() loss = criterion(sample, target) print(loss)
最后的结果为1,计算步骤为:
先计算绝对差总和:|0-1|+|1-1|+|2-1|+|3-1|=4;
然后再平均:4/4=1。
5.2 nn.SmoothL1Loss
SmoothL1Loss 也叫作 Huber Loss,误差在 (-1,1) 上是平方损失,其他情况是 L1 损失。
criterion = nn.SmoothL1Loss() loss = criterion(sample, target) print(loss)
最后结果是:0.625。
5.3 nn.MSELoss
平方损失函数。其计算公式是预测值和真实值之间的平方和的平均数。
criterion = nn.MSELoss() loss = criterion(sample, target) print(loss)
最后结果是:1.5。
5.4 nn.BCELoss
二分类用的交叉熵,其计算公式较复杂,这里主要是有个概念即可,一般情况下不会用到。
最后结果是:-50。
5.5 nn.CrossEntropyLoss
交叉熵损失函数
该公式用的也较多,比如在图像分类神经网络模型中就常常用到该公式。
criterion = nn.CrossEntropyLoss() loss = criterion(sample, target) print(loss)
最后结果是:2.0794。
看文档我们知道 nn.CrossEntropyLoss 损失函数是用于图像识别验证的,对输入参数有各式要求,这里有这个概念就可以了,在后续的图像识别方向中会有正确的使用方法。
5.6 nn.NLLLoss
负对数似然损失函数(Negative Log Likelihood)
在前面接上一个 LogSoftMax 层就等价于交叉熵损失了。注意这里的xlabel 和上个交叉熵损失里的不一样,这里是经过 log 运算后的数值。
这个损失函数一般也是用在图像识别模型上。
criterion = F.nll_loss() loss = criterion(sample, target) print(loss) loss=F.nll_loss(sample,target)
最后结果为报错,看来不能直接调用。
Nn.NLLLoss 和 nn.CrossEntropyLoss 的功能是非常相似的。通常都是用在多分类模型中,实际应用中我们一般用 NLLLoss 比较多。
5.7 nn.NLLLoss2d
和上面类似,但是多了几个维度,一般用在图片上。
input, (N, C, H, W) target, (N, H, W)
比如用全卷积网络做分类时,最后图片的每个点都会预测一个类别标签。
最后结果报错,看来不能直接这么用。
六、优化器Optim
优化器用通俗的话来说就是一种算法,是一种计算导数的算法。
各种优化器的目的和发明它们的初衷其实就是能让用户选择一种适合自己场景的优化器。
优化器的最主要的衡量指标就是优化曲线的平稳度,最好的优化器就是每一轮样本数据的优化都让权重参数匀速的接近目标值,而不是忽上忽下跳跃的变化。因此损失值的平稳下降对于一个深度学习模型来说是一个非常重要的衡量指标。
pytorch 的优化器都放在 torch.optim 包中。常见的优化器有:SGD,Adam,Adadelta,Adagrad,Adamax 等。如果需要定制特殊的优化器,pytorch 也提供了定制化的手段,不过这里我们就不去深究了,毕竟预留的优化器的功能已经足够强大了。
6.1 SGD
SGD 指stochastic gradient descent,即随机梯度下降,随机的意思是随机选取部分数据集参与计算,是梯度下降的 batch 版本。SGD 支持动量参数,支持学习衰减率。SGD 优化器也是最常见的一种优化器,实现简单,容易理解。
6.1.1 用法
optimizer = optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)
6.1.2 参数
lr:大于 0 的浮点数,学习率。
momentum:大于 0 的浮点数,动量参数。
parameters:Variable 参数,要优化的对象。
对于训练数据集,我们首先将其分成 n 个 batch,每个 batch 包含 m 个样本。我们每次更新都利用一个 batch 的数据 ,而非整个训练集,即:
xt+1=xt+Δxt
Δxt=-ηgt
其中,η 为学习率,gt 为 x 在 t 时刻的梯度。
6.1.3 好处
这么做的好处在于:
(1)当训练数据太多时,利用整个数据集更新往往时间上不现实。batch的方法可以减少机器的压力,并且可以更快地收敛。
(2)当训练集有很多冗余时(类似的样本出现多次),batch 方法收敛更快。以一个极端情况为例,若训练集前一半和后一半梯度相同,那么如果前一半作为一个 batch,后一半作为另一个 batch,那么在一次遍历训练集时,batch 的方法向最优解前进两个 step,而整体的方法只前进一个 step。
6.2 RMSprop
RMSProp 通过引入一个衰减系数,让 r 每回合都衰减一定比例,类似于Momentum 中的做法,该优化器通常是面对递归神经网络时的一个良好选择。
6.2.1 具体实现
需要:全局学习速率 ϵ,初始参数 θ,数值稳定量 δ,衰减速率 ρ。
中间变量:梯度累计量 r(初始化为 0)。
6.2.2 每步迭代过程
(1)从训练集中的随机抽取一批容量为 m 的样本 {x1,…,xm} 以及相关的输出 yi 。
(2)计算梯度和误差,更新 r,再根据 r 和梯度计算参数更新量 。
6.2.3 用法
keras.optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=1e-06)
6.2.4 参数
lr:大于 0 的浮点数,学习率。
rho:大于 0 的浮点数。
epsilon:大于 0 的小浮点数,防止除 0 错误。
6.3 Adagrad
AdaGrad 可以自动变更学习速率,只是需要设定一个全局的学习速率 ϵ,但是这并非是实际学习速率,实际的速率是与以往参数的模之和的开方成反比的。也许说起来有点绕口,不过用公式来表示就直白的多:
其中 δ 是一个很小的常量,大概在 10-7,防止出现除以 0 的情况.。
6.3.1 具体实现
需要:全局学习速率 ϵ,初始参数 θ,数值稳定量 δ 。
中间变量:梯度累计量 r(初始化为 0) 。
6.3.2 每步迭代过程
(1) 从训练集中的随机抽取一批容量为 m 的样本 {x1,…,xm} 以及相关。
(2)计算梯度和误差,更新r,再根据 r 和梯度计算参数更新量 。
6.3.3 优点
能够实现学习率的自动更改。如果这次梯度大,那么学习速率衰减的就快一些;如果这次梯度小,那么学习速率衰减的就慢一些。
6.3.4 缺点
仍然要设置一个变量 ϵ 。
经验表明,在普通算法中也许效果不错,但在深度学习中,深度过深时会造成训练提前结束。
6.3.5 用法
keras.optimizers.Adagrad(lr=0.01, epsilon=1e-06)
6.3.6 参数
lr:大于 0 的浮点数,学习率。
epsilon:大于 0 的小浮点数,防止除 0 错误。
6.4 Adadelta
Adagrad 算法存在三个问题:
(1)其学习率是单调递减的,训练后期学习率非常小。
(2)其需要手工设置一个全局的初始学习率。
(3)更新 xt 时,左右两边的单位不统一。
Adadelta 针对上述三个问题提出了比较漂亮的解决方案。
首先,针对第一个问题,我们可以只使用 adagrad 的分母中的累计项离当前时间点比较近的项,如下式:
这里ρ 是衰减系数 ,通过这个衰减系数,我们令每一个时刻的 gt 随时间按照 ρ 指数衰减,这样就相当于我们仅使用离当前时刻比较近的gt信息,从而使得还很长时间之后,参数仍然可以得到更新。
针对第三个问题,其实 sgd 跟 momentum 系列的方法也有单位不统一的问题。sgd、momentum 系列方法中:
类似的,adagrad 中,用于更新 Δx 的单位也不是 x 的单位,而是 1。
而对于牛顿迭代法:
其中 H 为 Hessian 矩阵,由于其计算量巨大,因而实际中不常使用。其单位为:
注意,这里f 无单位 。因而,牛顿迭代法的单位是正确的。
所以,我们可以模拟牛顿迭代法来得到正确的单位。注意到:
这里,在解决学习率单调递减的问题的方案中,分母已经是 ∂f/∂x 的一个近似了。这里我们可以构造 Δx 的近似,来模拟得到 H-1 的近似,从而得到近似的牛顿迭代法。具体做法如下:
可以看到,如此一来adagrad 中分子部分需要人工设置的初始学习率也消失了 ,从而顺带解决了上述的第二个问题。
6.4.1 用法
keras.optimizers.Adadelta(lr=1.0, rho=0.95, epsilon=1e-06)
建议保持优化器的默认参数不变。
6.4.2 参数
lr:大于 0 的浮点数,学习率。
rho:大于 0 的浮点数。
epsilon:大于 0 的小浮点数,防止除 0 错误。
6.5 Adam
Adam 是一种基于一阶梯度来优化随机目标函数的算法。
Adam 这个名字来源于 adaptive moment estimation,自适应矩估计。概率论中矩的含义是:如果一个随机变量 X 服从某个分布,X 的一阶矩是E(X),也就是样本平均值,X 的二阶矩就是 E(X^2),也就是样本平方的平均值。
Adam 算法根据损失函数对每个参数的梯度的一阶矩估计和二阶矩估计动态调整针对于每个参数的学习速率。Adam 也是基于梯度下降的方法,但是每次迭代参数的学习步长都有一个确定的范围,不会因为很大的梯度导致很大的学习步长,参数的值比较稳定。
Adam(Adaptive Moment Estimation)本质上是带有动量项的 RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。Adam 的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
6.5.1 具体实现
需要:步进值 ϵ,初始参数 θ,数值稳定量 δ,一阶动量衰减系数 ρ1,二阶动量衰减系数 ρ2 。
其中几个取值一般为:δ=10-8,ρ1=0.9,ρ2=0.999 。
中间变量:一阶动量 s,二阶动量 r,都初始化为 0 。
6.5.2 每步迭代过程
(1)从训练集中的随机抽取一批容量为 m 的样本 {x1,…,xm} 以及相关的输出 yi 。
(2)计算梯度和误差,更新 r 和 s,再根据 r 和 s 以及梯度计算参数更新量 。
6.5.3 用法
keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
6.5.4 参数
lr:大于 0 的浮点数,学习率。
beta_1/beta_2:浮点数, 0<beta<1,通常很接近 1。
epsilon:大于 0 的小浮点数,防止除0错误。
6.6 Adamax
keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
Adamax 优化器的方法是基于无穷范数的 Adam 方法的变体。
6.6.1 参数
lr:大于 0 的浮点数,学习率。
beta_1/beta_2:浮点数, 0<beta<1,通常很接近 1。
epsilon:大于 0 的小浮点数,防止除 0 错误。