在之前的课程中,我们已经完成了从0建立深层神经网络,并完成正向传播的全过程。本节课开始,我们将以分类深层神经网络为例,为大家展示神经网络的学习和训练过程。在介绍PyTorch的基本工具AutoGrad库时,我们系统地介绍过数学中的优化问题和优化思想,我们介绍了最小二乘法以及梯度下降法这两个入门级优化算法的具体操作,并使用AutoGrad库实现了他们。接下来,我们将从梯度下降法向外拓展,介绍神经网络的损失函数、常用优化算法等信息,实现神经网络的学习和迭代。本节主要讲解神经网络常用的损失函数,并在PyTorch中实现这些函数。

一、机器学习中的优化思想

在之前的学习中,我们建立神经网络时总是先设定好与的值(或者由我们调用的PyTorch类帮助我们随机生成权重向量),接着通过加和求出zzz,再在zzz上嵌套sigmoid或者softmax函数,最终获得神经网络的输出。我们的代码及计算流程,总是从神经网络的左侧向右侧计算的。之前我们提到过,这是神经网络的正向传播过程。但很明显,这并不是神经网络算法的全流程,这个流程虽然可以输出预测结果,但却无法保证神经网络的输出结果与真实值接近。

在讲解线性回归时,我们提起过,线性回归的任务就是构造一个预测函数来映射输入的特征矩阵XXX和标签值yyy的线性关系。构造预测函数核心就是找出模型的权重向量www,并令线性回归的输出结果与真实值相近,也就是求解线性方程组中的www和bbb。对神经网络而言也是如此,我们的核心任务是求解一组最适合的www和bbb,令神经网络的输出结果与真实值接近。找寻这个www和bbb的过程就是“学习”,也叫做“训练”或者“建模”。

那我们如何评价和是否合适呢?我们又如何衡量我们的输出结果与真实值之间的差异大小呢?此时,我们就需要使用机器学习中通用的优化流程了。在讲解autograd的时候,其实我们已经提过这个优化流程,在这里我们稍微复习一下:

1)提出基本模型,明确目标
我们的基本模型就是我们自建的神经网络架构,我们需要求解的就是神经网络架构中的权重向量www。
2)确定损失函数/目标函数
我们需要定义某个评估指标,用以衡量模型权重为www的情况下,预测结果与真实结果的差异。当真实值与预测值差异越大时,我们就认为神经网络学习过程中丢失了许多信息,丢失的这部分被形象地称为”损失“,因此评估真实值与预测值差异的函数被我们称为“损失函数”。

我们希望损失函数越小越好,以此,我们将问题转变为求解函数L(w)L(w)L(w)的最小值所对应的自变量www。但是,损失函数往往不是一个简单的函数,求解复杂函数就需要复杂的数学工具。在这里,我们使用的数学工具可能有两部分:

  • 将损失函数L(w)L(w)L(w)转变成凸函数的数学方法,常见的有拉格朗日变换等
  • 在凸函数上求解L(w)L(w)L(w)的最小值对应的www的方法,也就是以梯度下降为代表的优化算法

3)确定适合的优化算法
4)利用优化算法,最小化损失函数,求解最佳权重(训练)
之前我们在线性回归上走过这个全流程。对线性回归,我们的损失函数是SSE,优化算法是最小二乘法和梯度下降法,两者都是对机器学习来说非常重要的优化算法。但遗憾的是,最小二乘法作为入门级优化算法,有较多的假设和先决条件,不足以应对神经网络需要被应用的各种复杂环境。梯度下降法应用广泛,不过也有很多问题需要改进。接下来,我将主要以分类深层神经网络为例来介绍神经网络中所使用的入门级损失函数及优化算法。

二、回归:误差平方和SSE

对于回归类神经网络而言,最常见的损失函数是SSE(Sum of the Squared Errors),现在已经是我们第三次见到SSE的公式了:
SSE=∑i=1m(zi−z^i)2S S E=\sum_{i=1}^{m}\left(z_{i}-\hat{z}_{i}\right)^{2} SSE=i=1∑m​(zi​−z^i​)2
其中ziz_{i}zi​是样本iii的真实值,而z^i\hat{z}_{i}z^i​是样本iii的预测值。对于全部样本的平均损失,则可以写作:
MSE=1m∑i=1m(zi−z^i)2M S E=\frac{1}{m} \sum_{i=1}^{m}\left(z_{i}-\hat{z}_{i}\right)^{2} MSE=m1​i=1∑m​(zi​−z^i​)2
在PyTorch中,我们可以简单通过以下代码调用MSE:

import torch
from torch.nn import MSELoss #类
yhat = torch.randn(size=(50,),dtype=torch.float32)
y = torch.randn(size=(50,),dtype=torch.float32)
criterion = MSELoss() #实例化
loss = criterion(yhat,y)
loss #没有设置随机数种子,所以每次运行的数字都会不一致
#tensor(1.5714)#在MSELoss中有重要的参数,reduction
#当reduction = "mean" (默认也是mean),则输出MSE
#当reduction = "sum",则输出SSE
criterion = MSELoss(reduction = "mean") #实例化
criterion(yhat,y)
#tensor(1.5714)
criterion = MSELoss(reduction = "sum")
criterion(yhat,y)
#tensor(78.5707)

三、二分类交叉熵损失函数

在这一节中,我们将介绍二分类神经网络的损失函数:二分类交叉熵损失函数(Binary Cross Entropy Loss),也叫做对数损失(log loss)。这个损失函数被广泛地使用在任何输出结果是二分类的神经网络中,即不止限于单层神经网络,还可被拓展到多分类中,因此理解二分类交叉熵损失是非常重要的一环。大多数时候,除非特殊声明为二分类,否则提到交叉熵损失,我们会默认算法的分类目标是多分类。

二分类交叉熵损失函数是由极大似然估计推导出来的,对于有m个样本的数据集而言,在全部样本上的平均损失写作:
L(w)=−∑i=1m(yi∗ln⁡(σi)+(1−yi)∗ln⁡(1−σi))L(w)=-\sum_{i=1}^{m}\left(y_{i} * \ln \left(\sigma_{i}\right)+\left(1-y_{i}\right) * \ln \left(1-\sigma_{i}\right)\right) L(w)=−i=1∑m​(yi​∗ln(σi​)+(1−yi​)∗ln(1−σi​))
在单个样本的损失写作:
L(w)i=−(yi∗ln⁡(σi)+(1−yi)∗ln⁡(1−σi))L(w)_{i}=-\left(y_{i} * \ln \left(\sigma_{i}\right)+\left(1-y_{i}\right) * \ln \left(1-\sigma_{i}\right)\right) L(w)i​=−(yi​∗ln(σi​)+(1−yi​)∗ln(1−σi​))
其中,ln是以自然底数eee为底的对数函数,www表示求解出来的一组权重(在等号的右侧,www在σ\sigmaσ里),m是样本的个数,yiy_{i}yi​是样本i上真实的标签,σi\sigma_{i}σi​是样本i上,基于参数www计算出来的sigmoid函数的返回值,xix_{i}xi​是样本i各个特征的取值。我们的目标,就是求解出使L(w)L(w)L(w)最小的www取值。注意,在神经网络中,特征张量XXX是自变量,权重是www。但在损失函数中,权重www是损失函数的自变量,特征x和真实标签y都是已知的数据,相当于是常数。不同的函数中,自变量和参数各有不同,因此大家需要在数学计算中,尤其是求导的时候避免混淆。

1 极大似然估计求解二分类交叉熵损失

二分类交叉熵损失函数是怎么来的呢?为什么这个函数就能够代表二分类的时候,真实值与预测值的差异呢?

在这里,我们基于极大似然法来推导交叉熵损失,这个推导过程能够帮助我们充分了解交叉熵损失的含义,以及为什么的最小化能够实现模型在数据集上的拟合最好。

在二分类的例子中,我们的“任意事件”就是每个样本的分类都正确,对数似然函数的负数就是我们的损失函数。接下来,我们来看看逻辑回归的对数似然函数是怎样构筑的。

  • 构筑对数似然函数

二分类神经网络的标签是[0,1],此标签服从伯努利分布(即0-1分布),因此可得:

样本i在由特征向量xix_{i}xi​和权重向量www组成的预测函数中,样本标签被预测为1的概率为:

对二分类而言,σ\sigmaσ就是sigmoid函数的结果。
样本i在由特征向量xix_{i}xi​和权重向量www组成的预测函数中,样本标签被预测为0的概率为:

当P1P_{1}P1​值为1的时候,代表样本i的标签被预测为1,当P0P_{0}P0​的值为1的时候,代表样本i的标签被预测为0。P0P_{0}P0​与P1P_{1}P1​相加是一定等于1的。

假设样本i的真实标签yiy_{i}yi​为1,并且P1P_{1}P1​也为1的话,那就说明我们将iii的标签预测为1的概率很大,与真实值一致,那模型的预测就是准确的,拟合程度很高,信息损失很少。相反,如果真实标签为1,我们的P1P_{1}P1​却很接近0,这就说明我们将iii的标签预测为1的概率很小,即与真实值一致的概率很小,那模型的预测就是失败的,拟合程度很低,信息损失很多。当yiy_{i}yi​为0时,也是同样的道理。所以,当yiy_{i}yi​为1的时候,我们希望P1P_{1}P1​非常接近1,当yiy_{i}yi​为0的时候,我们希望P0P_{0}P0​非常接近1,这样,模型的效果就很好,信息损失就很少。

将两种取值的概率整合,我们可以定义如下等式:
P(y^i∣xi,w)=P1yi∗P01−yiP\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)=P_{1}^{y_{i}} * P_{0}^{1-y_{i}} P(y^​i​∣xi​,w)=P1yi​​∗P01−yi​​
这个等式代表同时代表了P1P_{1}P1​和P0P_{0}P0​,在数学上,它被叫做逻辑回归的假设函数。

当样本i的真实标签yiy_{i}yi​为1的时候,1 - yiy_{i}yi​就等于0,P0P_{0}P0​的0次方就是1,所以P(y^i∣xi,w)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)P(y^​i​∣xi​,w)就等于P1P_{1}P1​,这个时候,如果P1P_{1}P1​为1,模型的效果就很好,损失就很小。

同理,当yiy_{i}yi​为0的时候,P(y^i∣xi,w)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)P(y^​i​∣xi​,w)就等于P0P_{0}P0​,此时如果P0P_{0}P0​非常接近1,模型的效果就很好,损失就很小。

所以,为了达成让模型拟合好,损失小的目的,我们每时每刻都希望P(y^i∣xi,w)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)P(y^​i​∣xi​,w)的值等于1。而P(y^i∣xi,w)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)P(y^​i​∣xi​,w)的本质是样本i由特征向量xix_{i}xi​和权重www组成的预测函数中,预测出所有可能的y^i\hat{y}_{i}y^​i​的概率,因此1是它的最大值。也就是说,每时每刻,我们都在追求P(y^i∣xi,w)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)P(y^​i​∣xi​,w)的最大值。而寻找相应的参数www,使得每次得到的预测概率最大,正是极大似然估计的基本方法,不过P(y^i∣xi,w)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)P(y^​i​∣xi​,w)是对单个样本而言的,因此我们还需要将其拓展到多个样本上。

P(y^i∣xi,w)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)P(y^​i​∣xi​,w)是对单个样本i而言的函数,对一个训练集的m个样本来说,我们可以定义如下等式来表达所有样本在特征张量XXX和权重向量www组成的预测函数中,预测出所有可能的y^i\hat{y}_{i}y^​i​的概率P为:
P=∏i=1mP(y^i∣xi,w)=∏i=1m(P1yi∗P01−yi)=∏i=1m(σiyi∗(1−σi)1−yi)\begin{aligned} \boldsymbol{P} &=\prod_{i=1}^{m} P\left(\hat{y}_{i} \mid x_{i}, w\right) \\ &=\prod_{i=1}^{m}\left(P_{1}^{y_{i}} * P_{0}^{1-y_{i}}\right) \\ &=\prod_{i=1}^{m}\left(\sigma_{i}^{y_{i}} *\left(1-\sigma_{i}\right)^{1-y_{i}}\right) \end{aligned} P​=i=1∏m​P(y^​i​∣xi​,w)=i=1∏m​(P1yi​​∗P01−yi​​)=i=1∏m​(σiyi​​∗(1−σi​)1−yi​)​
这个函数就是逻辑回归的似然函数。对该概率P取以e为底的对数,再由log⁡(A∗B)=log⁡A+log⁡B\log (A * B)=\log A+\log Blog(A∗B)=logA+logB和log⁡AB=Blog⁡A\log A^{B}=B \log AlogAB=BlogA可得到逻辑回归的对数似然函数:
ln⁡P=ln⁡∏i=1m(σiyi∗(1−σi)1−yi)=∑i=1mln⁡(σiyi∗(1−σi)1−yi)=∑i=1m(ln⁡σiyi+ln⁡(1−σi)1−yi)=∑i=1m(yi∗ln⁡(σi)+(1−yi)∗ln⁡(1−σi))\begin{aligned} \ln \boldsymbol{P} &=\ln \prod_{i=1}^{m}\left(\sigma_{i}^{y_{i}} *\left(1-\sigma_{i}\right)^{1-y_{i}}\right) \\ &=\sum_{i=1}^{m} \ln \left(\sigma_{i}^{y_{i}} *\left(1-\sigma_{i}\right)^{1-y_{i}}\right) \\ &=\sum_{i=1}^{m}\left(\ln \sigma_{i}^{y_{i}}+\ln \left(1-\sigma_{i}\right)^{1-y_{i}}\right) \\ &=\sum_{i=1}^{m}\left(y_{i} * \ln \left(\sigma_{i}\right)+\left(1-y_{i}\right) * \ln \left(1-\sigma_{i}\right)\right) \end{aligned} lnP​=lni=1∏m​(σiyi​​∗(1−σi​)1−yi​)=i=1∑m​ln(σiyi​​∗(1−σi​)1−yi​)=i=1∑m​(lnσiyi​​+ln(1−σi​)1−yi​)=i=1∑m​(yi​∗ln(σi​)+(1−yi​)∗ln(1−σi​))​
这就是我们的二分类交叉熵函数。为了数学上的便利以及更好地定义”损失”的含义,我们希望将极大值问题转换为极小值问题,因此我们对lnPlnPlnP取负,并且让权重www作为函数的自变量,就得到了我们的损失函数 :
L(w)=−∑i=1m(yi∗ln⁡(σi)+(1−yi)∗ln⁡(1−σi))L(w)=-\sum_{i=1}^{m}\left(y_{i} * \ln \left(\sigma_{i}\right)+\left(1-y_{i}\right) * \ln \left(1-\sigma_{i}\right)\right) L(w)=−i=1∑m​(yi​∗ln(σi​)+(1−yi​)∗ln(1−σi​))
现在,我们已经将模型拟合中的“最小化损失”问题,转换成了对函数求解极值的问题。这就是一个,基于逻辑回归的返回值σi\sigma_{i}σi​的概率性质以及极大似然估计得出的损失函数。在这个函数上,我们只要追求最小值,就能让模型在训练数据上的拟合效果最好,损失最低

在极大似然估计中,我们只要在对数似然函数上对权重www求导,再令导数为0,就可以求解出最合适的www,但是对于像交叉熵这样复杂的损失函数,加上神经网络中复杂的权重组合,令所有权重的导数为0并一个个求解方程的难度很大。因此我们要使用优化算法,这部分我们下一章展开来聊。

2 用tensor实现二分类交叉熵损失

现在,让我们在PyTorch中来实现二分类交叉熵损失函数。首先使用基本的tensor方法来试试看,以加深我们对二分类交叉熵损失的印象:

import torch
import time
N = 3*pow(10,3)
torch.random.manual_seed(420)
X = torch.rand((N,4),dtype=torch.float32)
w = torch.rand((4,1),dtype=torch.float32,requires_grad=True)
y = torch.randint(low=0,high=2,size=(N,1),dtype=torch.float32) #high取不到
zhat = torch.mm(X,w)
sigma = torch.sigmoid(zhat)
Loss = -(1/N)*torch.sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma)) #底数默认为e
Loss
#tensor(0.7962, grad_fn=<MulBackward0>)

注意,在写损失函数这样的复杂函数时,除了普通的加减乘除以外的全部计算,都要使用torch中的函数,因为tensor的运算速度是远远超过普通Python代码,甚至是NumPy的。你可以试着比较在样本量为300W时,以下两行代码运行的时间差异:

#你可以试着比较在样本量为300W时,以下两行代码运行的时间差异。这段代码不需要GPU。
#如果你的电脑内存或计算资源有限,可以试着将样本量调小为30W或3W
N = 3*pow(10,6)
torch.random.manual_seed(420)
X = torch.rand((N,4),dtype=torch.float32)
w = torch.rand((4,1),dtype=torch.float32,requires_grad=True)
y = torch.randint(low=0,high=2,size=(N,1),dtype=torch.float32)
zhat = torch.mm(X,w)
sigma = torch.sigmoid(zhat)start = time.time()
L1 = -(1/N)*torch.sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma))
now = time.time() #seconds
print(now - start)
#0.03389596939086914start = time.time()
L2 = -(1/N)*sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma))
now = time.time() #seconds
print(now - start)
#11.579372882843018

从运行结果来看,除了加减乘除,我们应该尽量避免使用任何Python原生的计算方法。如果可能的话,让PyTorch处理一切。

3 用PyTorch中的类实现二分类交叉熵损失

在PyTorch当中,我们有多种方式可以调用二分类交叉熵损失函数。

对于二分类交叉熵损失,nn提供了两个类:BCEWithLogitsLoss以及BCELoss。虽然PyTorch官方没有直接明确,但实际上两个函数所需要输入的参数不同。

BCEWithLogitsLoss内置了sigmoid函数与交叉熵函数,它会自动计算输入值的sigmoid值,因此需要输入zhat与真实标签,且顺序不能变化,zhat必须在前。

相对的,BCELoss中只有交叉熵函数,没有sigmoid层,因此需要输入sigma与真实标签,且顺序不能变化。

同时,这两个函数都要求预测值与真实标签的数据类型以及结构(shape)必须相同,否则运行就会报错。

接下来,我们来看看这两个类是如何使用的:

import torch.nn as nn
#调用nn模块下的类
criterion = nn.BCELoss() #实例化
loss = criterion(sigma,y) #真实标签在后
loss
#tensor(0.8685, grad_fn=<BinaryCrossEntropyBackward0>)criterion2 = nn.BCEWithLogitsLoss() #实例化
loss = criterion2(zhat,y) #真实标签在后
loss
#tensor(0.8685, grad_fn=<BinaryCrossEntropyWithLogitsBackward0>)

可以看出,两个类的结果是一致的。根据PyTorch官方的公告,他们更推荐使用BCEWithLogitsLoss这个内置了sigmoid函数的类。内置的sigmoid函数可以让精度问题被缩小(因为将指数运算包含在了内部),以维持算法运行时的稳定性,即是说当数据量变大、数据本身也变大时,BCELoss类产生的结果可能有精度问题。所以,当我们的输出层使用sigmoid函数时,我们就可以使用BCEWithLogitsLoss作为损失函数

与MSELoss相同,二分类交叉熵的类们也有参数reduction,默认是”mean“,表示求解所有样本平均的损失,也可换为”sum”,要求输出整体的损失。以及,还可以使用选项“none”,表示不对损失结果做任何聚合运算,直接输出每个样本对应的损失矩阵。

criterion2 = nn.BCEWithLogitsLoss(reduction = "mean")
loss = criterion2(zhat,y)
loss
#tensor(0.8685, grad_fn=<BinaryCrossEntropyWithLogitsBackward0>)criterion2 = nn.BCEWithLogitsLoss(reduction = "sum")
loss = criterion2(zhat,y)
loss
#tensor(2605616.5000, grad_fn=<BinaryCrossEntropyWithLogitsBackward0>)criterion2 = nn.BCEWithLogitsLoss(reduction = "none")
loss = criterion2(zhat,y)
loss
#tensor([[1.3102],
#        [0.3155],
#        [0.4247],
#        ...,
#        [0.1727],
#        [0.1716],
#        [0.1673]], grad_fn=<BinaryCrossEntropyWithLogitsBackward0>)

第二种方法很少用,我们了解一下即可:

和nn中的类们相似,名称中带有Logits的是内置了sigmoid功能的函数,没有带Logits的,是只包含交叉熵损失的函数。对于含有sigmoid功能的函数,我们需要的输入是zhat与标签,不含sigmoid的函数我们则需要输入sigma与标签。同样的,这两个函数对输入有严格的要求,输入的预测值必须与标签结构一致、数据类型一致。我们来看看他们的运行结果:

from torch.nn import functional as F #直接调用functional库中的计算函数
F.binary_cross_entropy_with_logits(zhat,y)
#tensor(0.8685, grad_fn=<BinaryCrossEntropyWithLogitsBackward0>)F.binary_cross_entropy(sigma,y)
#tensor(0.8685, grad_fn=<BinaryCrossEntropyBackward0>)

在这里,两个函数的运行结果是一致的。同样的,PyTorch官方推荐的是内置sigmoid功能的函数binary_cross_entropy_with_logits。通常来说,我们都使用类,不使用函数。虽然代码会因此变得稍稍有点复杂,但为了代码的稳定性与日后维护,使用类是更好的选择。当然,在进行代码演示和快速测算的时候,使用函数或者类都没有问题。

四、多分类交叉熵损失函数

1 由二分类推广到多分类

二分类交叉熵损失可以被推广到多分类上,但在实际处理时,二分类与多分类却有一些关键的区别。依然使用极大似然估计的推导流程,首先我们来确定单一样本概率最大化后的似然函数。

对于多分类的状况而言,标签不再服从伯努利分布(0-1分布),因此我们可以定义,样本i在由特征向量xix_{i}xi​和权重向量www组成的预测函数中,样本标签被预测为类别k的概率为:
Pk=P(y^i=k∣xi,w)=σP_{k}=P\left(\hat{y}_{i}=k \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)=\sigma Pk​=P(y^​i​=k∣xi​,w)=σ
对于多分类算法而言, 就是softmax函数返回的对应类别的值。

假设一种最简单的情况:我们现在有三分类[1, 2, 3],则样本i被预测为三个类别的概率分别为:

假设样本的真实标签为1,我们就希望P1P_{1}P1​最大,同理,如果样本的真实标签为其他值,我们就希望其他值所对应的概率最大。在二分类中,我们将y和1-y作为概率PPP的指数,以此来融合真实标签为0和为1的两种状况。但在多分类中,我们的真实标签可能是任意整数,无法使用y和1-y这样的结构来构建似然函数。所以我们认为,如果多分类的标签也可以使用0和1来表示就好了,这样我们就可以继续使用真实标签作为指数的方式。

因此,我们对多分类的标签做出了如下变化:

原本的真实标签y是含有[1, 2, 3]三个分类的列向量,现在我们把它变成了标签矩阵,每个样本对应一个向量。(如果你熟悉机器学习或统计学,你能够一眼看出这其实就是独热编码one-hot)。在矩阵中,每一行依旧对应样本,但却由三分类衍生出了三个新的列,分别代表:真实标签是否等于1、等于2以及等于3。在矩阵中,我们使用“1”标注出样本的真实标签的位置,使用0表示样本的真实标签不是这个标签。不难注意到,这个标签矩阵的结构其实是和softmax函数输出的概率矩阵的结构一致,并且一一对应的。

回顾下二分类的似然函数:
P(y^i∣xi,w)=P1yi∗P01−yiP\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)=P_{1}^{y_{i}} * P_{0}^{1-y_{i}} P(y^​i​∣xi​,w)=P1yi​​∗P01−yi​​
当我们把标签整合为标签矩阵后,我们就可以将单个样本在总共K个分类情况整合为以下的似然函数:
P(y^i∣xi,w)=P1yi(k=1)∗P2yi(k=2)∗P3yi(k=3)∗…∗PKyi(k=K)P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)=P_{1}^{y_{i(k=1)}} * P_{2}^{y_{i(k=2)}} * P_{3}^{y_{i(k=3)}} * \ldots * P_{K}^{y_{i(k=K)}} P(y^​i​∣xi​,w)=P1yi(k=1)​​∗P2yi(k=2)​​∗P3yi(k=3)​​∗…∗PKyi(k=K)​​
其中P就是样本标签被预测为某个具体值的概率,而右上角的指数就是标签矩阵中对应的值,即这个样本的真实标签是否为当前标签的判断(是就是1,否就是0)。

更具体的,小k代表y的真实取值,K代表总共有K个分类(此处不是非常严谨,按道理说若K代表总共有K个类别,则不应该再使用K代表某个具体类别,但在这里,由于我们使用的类别编号与类别本身相同,所以为了公式的简化,使用了这样不严谨的表示方式)。虽然是连乘,但对于一个样本,除了自己所在的真实类别指数会是1之外,其他类别的指数都为0,所以被分类为其他类别的概率在这个式子里就都为0。所以我们可以将式子简写为:
P(y^i∣xi,w)=Pjyi(k=j),j为样本 i所对应的真实标签的编号 P\left(\hat{y}_{i} \mid \boldsymbol{x}_{i}, \boldsymbol{w}\right)=P_{j}^{y_{i(k=j)}}, j \text { 为样本 } i \text { 所对应的真实标签的编号 } P(y^​i​∣xi​,w)=Pjyi(k=j)​​,j 为样本 i 所对应的真实标签的编号 
对一个训练集的m个样本来说,我们可以定义如下等式来表达所有样本在特征张量XXX和权重向量www组成的预测函数中,预测出所有可能的y^\hat{y}y^​的概率P为:
P=∏i=1mP(y^i∣xi,w)=∏i=1mPjyi(k=j)=∏i=1mσjyi(k=j)\begin{aligned} \boldsymbol{P} &=\prod_{i=1}^{m} P\left(\hat{y}_{i} \mid x_{i}, w\right) \\ &=\prod_{i=1}^{m} P_{j}^{y_{i(k=j)}} \\ &=\prod_{i=1}^{m} \sigma_{j}^{y_{i(k=j)}} \end{aligned} P​=i=1∏m​P(y^​i​∣xi​,w)=i=1∏m​Pjyi(k=j)​​=i=1∏m​σjyi(k=j)​​​
这就是多分类状况下的似然函数。与二分类一致,似然函数解出来后,我们需要对似然函数求对数:
ln⁡P=ln⁡∏i=1mσjyi(k−j)=∑i=1mln⁡(σjyi(k=j))=∑i=1myi(k=j)ln⁡σi\begin{aligned} \ln \boldsymbol{P} &=\ln \prod_{i=1}^{m} \sigma_{j}^{y_{i(k-j)}} \\ &=\sum_{i=1}^{m} \ln \left(\sigma_{j}^{y_{i(k=j)}}\right) \\ &=\sum_{i=1}^{m} y_{i(k=j)} \ln \sigma_{i} \end{aligned} lnP​=lni=1∏m​σjyi(k−j)​​=i=1∑m​ln(σjyi(k=j)​​)=i=1∑m​yi(k=j)​lnσi​​
这个函数就是我们之前提到过的交叉熵函数。不难看出,二分类的交叉熵函数其实是多分类的一种特殊情况。

交叉熵函数十分特殊,虽然我们求解过程中,取对数的操作是在确定了似然函数后才进行的,但从计算结果来看,对数操作其实只对softmax函数的结果σ\sigmaσ起效。因此在实际操作中,我们把ln⁡(softmax⁡(z))\ln (\operatorname{softmax}(z))ln(softmax(z))这样的函数单独定义了一个功能做logsoftmax,PyTorch中可以直接通过nn.logsoftmax类调用这个功能。同时,我们把对数之外的,乘以标签、加和、取负等等过程打包起来,称之为负对数似然函数(Negative Log Likelihood function),在PyTorch中可以使用nn.NLLLoss来进行调用。也就是说,在计算损失函数时,我们不再需要使用单独的softmax函数了。

2 用PyTorch实现多分类交叉熵损失

在PyTorch中实现交叉熵函数的时候,有两种办法:

  • 调用logsoftmax和NLLLoss实现
import torch
import torch.nn as nn
N = 3*pow(10,2)
torch.random.manual_seed(420)
X = torch.rand((N,4),dtype=torch.float32)
w = torch.rand((4,3),dtype=torch.float32,requires_grad=True)
#定义y时应该怎么做?应该设置为矩阵吗?
y = torch.randint(low=0,high=3,size=(N,),dtype=torch.float32)
zhat = torch.mm(X,w) #从这里开始调用softmax和NLLLosslogsm = nn.LogSoftmax(dim=1) #实例化
logsigma = logsm(zhat)criterion = nn.NLLLoss() #实例化
#由于交叉熵损失需要将标签转化为独热形式,因此不接受浮点数作为标签的输入
#对NLLLoss而言,需要输入logsigma
criterion(logsigma,y.long()) #y一维、整型
#tensor(1.1591, grad_fn=<NllLossBackward0>)

更加简便的方法是:

  • 直接调用CrossEntropyLoss
criterion = nn.CrossEntropyLoss()
#对打包好的CorssEnrtopyLoss而言,只需要输入zhat
criterion(zhat,y.long()) #一维、整型
#tensor(1.1591, grad_fn=<NllLossBackward0>)

可以发现,两种输出方法得到的损失函数结果是一致的。与其他损失函数一致,CrossEntropyLoss也有参数reduction,可以设置为mean、sum以及None,大家可以自行尝试其代码并查看返回结果。

无论时二分类还是多分类,PyTorch都提供了包含输出层激活函数和不包含输出层激活函数的类两种选择。在实际神经网络建模中,类可以被放入定义好的Model类中去构建神经网络的结构,因此是否包含激活函数,就需要由用户来自行选择。

  • 重视展示网络结构和灵活性,应该使用不包含输出层激活函数的类

通常在Model类中,__init__中层的数量与forward函数中对应的激活函数的数量是一致的,如果我们使用内置sigmoid/logsoftmax功能的类来计算损失函数,forward函数在定义时就会少一层(输出层),网络结构展示就不够简单明了,对于结构复杂的网络而言,结构清晰就更为重要。同时,如果激活函数是单独写的,要修改激活函数就变得很容易,如果混在损失函数中,要修改激活函数时就得改掉整个损失函数的代码,不利于维护。

  • 重视稳定性和运算精度,使用包含输出层激活函数的类

如果在一个Model中,很长时间我们都不会修改输出层的激活函数,并且模型的稳定运行更为要紧,我们就使用内置了激活函数的类来计算损失函数。同时,就像之前提到的,内置激活函数可以帮助我们推升运算的精度。

因此,选择哪种损失函数的实现方式,最终还要看我们的需求。

有了损失函数,我们终于要开始进行求解了。下一部分我们来讲解神经网络的入门级优化算法:小批量随机梯度下降。

LESSON 10.110.210.3 SSE与二分类交叉熵损失函数二分类交叉熵损失函数的pytorch实现多分类交叉熵损失函数相关推荐

  1. 阶梯电价计算:电价分三个档次,一档:0~110(含110)度电,每度电0.5元;二挡:110~210(含210)度电,超出110部分每度电0.55元;三挡:超过210度电,超出210部分每度电0.70

    标题 阶梯电价计算描述 电价分三个档次,一档:0~110(含110)度电,每度电0.5元:二挡:110~210(含210)度电,超出110部分每度电0.55元:三挡:超过210度电,超出210部分每度 ...

  2. 与10.110.12.29mask255.255.255.224属于同一网段的主机ip地址是

    与10.110.12.29mask255.255.255.224属于同一网段的主机ip地址是(D) A.10.110.12.0 B.10.110.12.32 C.10.110.12.31 D.10.1 ...

  3. 10.1-10.31推荐文章汇总

    10.1-10.31推荐文章汇总 [移动开发] Android ViewGroup拦截触摸事件详解        Mr-Simp1e Android 实现形态各异的双向侧滑菜单 自定义控件来袭     ...

  4. Quartz_2.2.X学习系列十:Tutorials - Lesson 10: Configuration, Resource Usage and SchedulerFactory

    第10课小结: 在Quartz完成其工作之前需要配置的主要组件是: • ThreadPool • JobStore • DataSources (if necessary) • The Schedul ...

  5. 10.1-10.12-广州软件所-实习工作日记

    这周比较忙就记得简单些 10.1-10.7 国庆假期 10.8 早上出现跳线连接配置问题,硬件跳线的连接应该保留纸面记录.命令测试正常,等待服务器端那边调试完毕,以进一步联调 10.9 请假进行奖学金 ...

  6. 备份恢复Lesson 10. Restore and Recovery Concepts

    备份恢复Lesson 10. Restore and Recovery Concepts 1. 请解释 RESTORE 和 RECOVER 这两个 RMAN 命令的具体使用? 10-6 10-7 2. ...

  7. C语言基础知识之define宏定义表达式,undef,内存对齐,a和a的区别,数组知识点,int (*)[10] p,二维数组参数与二维指针参数,函数指针数组,常见的内存错误及对策

    一.用define宏定义表达式 1.定义一年有多少秒: #define SEC_A_YEAR 60*60*24*365 //上述描述不可靠,没有考虑到在16位系统下把这样一个数赋给整型变量的时候可能会 ...

  8. 新概念英语(第四册,新版)学习(原文及全文翻译)——Lesson 10 - Silicon Valley(硅谷)

    注:这里只列出旧版中未包含的文章. Lesson 10(New Version) - Silicon Valley Technology trends may push Silicon Valley ...

  9. 系统管理Lesson 10. Managing Data Concurrency

    系统管理Lesson 10. Managing Data Concurrency 1. 请对 Oracle 数据库锁机制做一个完整的说明. 10-4 2. 事务在获取排他的行锁的同时,为什么要同时获取 ...

最新文章

  1. python如何定义类_Python 面向对象
  2. 安装Exchange2013,FMS服务无法达到启动状态
  3. 数据集标注工具_如何提高数据标注质量,提供精细化标注数据集?丨曼孚科技...
  4. 【AWSL】之Linux系统安全及应用(su、PAM、sudo、GRUB、JR、NMAP)
  5. elasticsearch的update_by_query
  6. 解读NoSQL最新现状和趋势:云NoSQL数据库将成重要增长引擎
  7. 4020-基于链地址法的散列表的插入(C++,附思路以及头插法,尾插法两种代码)
  8. 日出时的画面_如何拍摄日出日落,老摄影家近30年创作经验分享
  9. 真实的布兰妮,有点壮
  10. 华为首秀 AI 全栈软件平台!
  11. java生成可执行文件_关于打包java文件并生成可执行文件的问题
  12. .sh 编译 java_build-java.sh
  13. 关于单点登录的实现方法
  14. 【控制】动力学建模举例 --> 牛顿-欧拉法
  15. Maven的下载和安装
  16. 通达信买入离场信号选股公式,精准买卖点 不加密无未来
  17. 贵圈似乎有点乱:“5G手机”居然比5G网络先到了?
  18. 抖音橱窗or抖音小店?这3点,新手开店必看!
  19. 诺基亚联手迪信通 力推内置仙剑三版5230手机
  20. 高中数学必修2试题:直线平面平行的判定及其性质

热门文章

  1. python做方差分析和卡方检验
  2. python把列表样式的字符串重新转换为列表
  3. C++中的指针特征操作符重载
  4. 历史为什么丑化隋朝_隋朝于中国历史,到底处于什么样的地位,为何它一直被低估...
  5. linux 进程原理内存,linux进程通信之共享内存原理(基于linux 1.2.13)
  6. linux 系统监控 php,Linux系统资源监控命令简介
  7. 洛谷P1040 加分二叉树运用区间DP(动态规划)求解
  8. 局部图像描述子——Harris角点检测器
  9. 图分区技术基本概念【1】
  10. original_keras_version = f.attrs[‘keras_version‘].decode(‘utf8‘)