文章目录
前言
在深度学习的旅程中,我们经常被各种炫酷的实战项目所吸引——从图像识别、语义分割到强化学习应用,这些实践确实能快速提升我们的工程能力。然而,正如一位技艺精湛的匠人需要理解手中工具的特性一般,要想真正驾驭深度学习,我们必须深入理解组成它的基石单元。
损失函数(Loss Function),就是这样一个看似平凡却至关重要的"工具"。它如同模型训练过程中的导航仪,决定着优化方向的对错优劣。而在众多损失函数中,交叉熵损失(Cross-Entropy Loss) 凭借其在分类任务中无可替代的表现,成为深度学习领域当之无愧的"明星"。
那么,二分类交叉熵损失函数究竟是如何工作的?它为何能成为二分类任务的黄金准则?接下来的内容将为您详细解答。
一、重新审视:优化问题与损失函数的本质
-
提出基本模型,明确目标
我们基本模型就是我们自建的神经网络结构,我们需要求解的就是网络架构中的权重向量w。 -
确定损失函数/目标函数
我们需要定义某个评估指标时,用以衡量模型权重为w的情况下,预测结果与真是结果的差异。当真实值与预测值差异越大时,我们就认为神经网络学习过程中丢失了许多信息,丢失的这部分被形象地称为“损失”,因此评估真实值与预测值差异的函数被我们称为“损失函数”。
关键概念:损失函数
衡量真实值与预测结果的差异,评价模型学习过程中产生损失的函数
在数学上,表示为需要求解的权重向量w为自变量的函数L(w)
如果损失函数的值很小,则说明模型预测值与真实值很接近,模型在数据集上表现优异,权重优秀
如果损失函数的值大,则说明模型预测值与真实值差异很大,模型在数据集上表现差劲,权重糟糕
我们希望损失函数越小越好,以此我们将问题转变为求解函数L(W)的最小值所对应的自变量w。但是,损失函数往往不是一个简单的函数,求解复杂函数就需要复杂的数学工具。在这里,我们使用的数学工具可能有两部分。
1)将损失函数L(w)转变成凸函数的数学方法,常见的有拉格朗日变换等
2)在凸函数上求解L(w)的最小值对应的w的方法,也就是以梯度下降为代表的优化算法
- 确定合适的优化算法
- 利用优化算法,最小化损失函数,求解最佳权重w(训练)
线性回归中,我们通过最小二乘法求解均方误差(SSE),其虽直观高效,却受限于线性假设和正态分布等前提。而梯度下降法虽适用于非线性场景,但其收敛速度、局部最优等问题在深度模型中尤为突出。 接下来,我将主要以分类深层神经网络为例来介绍神经网络中所使用的入门级损失函数及优化算法。
二、 回归类神经网络的损失函数:SSE与MSE
1. 平方误差和(SSE)
对于回归类神经网络,最常见的损失函数是平方误差和(Sum of the Squared Errors):
S
S
E
=
∑
i
=
1
m
(
z
i
−
z
^
i
)
2
SSE = \sum_{i=1}^{m}(z_i - \hat{z}_i)^2
SSE=i=1∑m(zi−z^i)2
- z i z_i zi:样本 i i i的真实值
- z ^ i \hat{z}_i z^i:样本 i i i的预测值
2. 均方误差(MSE)
所有样本的平均损失称为均方误差(Mean Squared Error):
M
S
E
=
1
m
∑
i
=
1
m
(
z
i
−
z
^
i
)
2
MSE = \frac{1}{m}\sum_{i=1}^{m}(z_i - \hat{z}_i)^2
MSE=m1i=1∑m(zi−z^i)2
- m m m:样本总数
- 与SSE相比,MSE通过除以样本数消除规模影响
SSE(平方误差和) 和 MSE(均方误差) 的核心区别在于:SSE 是误差的总和,而 MSE 是误差的平均值。SSE与样本量强相关,如果样本量很大,SSE会变得非常大,即使模型表现不错,也可能因为样本多而显得误差“高”,这也就是为什么我们在机器学习的回归类问题中多用MSE作为评估指标。
在PyTorch中,我们可以简单通过以下代码调用MSE:
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)
print(loss) #没有设置随机数种子,所以每次运行的数字都会不一致
#在MSELoss中有重要的参数,reduction
#当reduction = "mean" (默认也是mean),则输出MSE
#当reduction = "sum",则输出SSE
criterion = MSELoss(reduction = "mean") #实例化
print(criterion(yhat,y))
criterion = MSELoss(reduction = "sum")
print(criterion(yhat,y))
运行后,我们会发现前两个输出结果是一样的,这就是当reduction = “mean” (默认也是mean),则输出也是MSE。
三、 极大似然估计求解二分类交叉熵损失
如果MSE是均匀的标尺,交叉熵就是精密的游标卡尺——分类任务需要测量的是概率距离,而非欧氏距离。
在回归任务中,SSE和MSE通过衡量预测值与真实值的几何距离来优化模型。但当面对分类问题时,距离度量失效了——预测癌症概率0.9和0.91的差距,远小于0.51和0.49的决策鸿沟。此时我们需要一种能感知概率边界敏感性的损失函数:当预测值逼近决策边界(如0.5)时自动放大误差,远离边界时则宽容处理。这正是交叉熵损失的精妙之处,它通过信息论视角重构了分类问题的优化本质。
二分类交叉熵损失函数,也叫做对数损失。这个损失函数被广泛地使用在任何输出结果是二分类的神经网络中,即不止限于单层神经网络,还可被拓展到多分类中,因此理解二分类交叉熵损失是非常重要的一环。大多数时候,除非特殊声明为二分类,否则提到交叉熵损失,我们会默认算法的分类目标是多分类。
二分类交叉熵损失函数是由极大似然估计推导出来的。对于包含m个样本的数据集而言:
全部样本上的平均损失函数
L
(
w
)
=
−
1
m
∑
i
=
1
m
[
y
i
ln
(
σ
i
)
+
(
1
−
y
i
)
ln
(
1
−
σ
i
)
]
L(w) = -\frac{1}{m}\sum_{i=1}^{m} \left[ y_i \ln(\sigma_i) + (1-y_i) \ln(1-\sigma_i) \right]
L(w)=−m1i=1∑m[yiln(σi)+(1−yi)ln(1−σi)]
单个样本的损失函数
L
(
w
)
i
=
−
[
y
i
ln
(
σ
i
)
+
(
1
−
y
i
)
ln
(
1
−
σ
i
)
]
L(w)_i = - \left[ y_i \ln(\sigma_i) + (1-y_i) \ln(1-\sigma_i) \right]
L(w)i=−[yiln(σi)+(1−yi)ln(1−σi)]
公式说明:
- y i y_i yi:第 i i i个样本的真实标签(取值为0或1)
- σ i \sigma_i σi:第 i i i个样本的预测概率(取值范围为0到1)
- w w w:模型参数
1、极大似然估计求解二分类交叉熵损失
二分类交叉熵损失函数是怎么来的呢?为什么这个函数就能够代表二分类的时候,真实值与预测值的差异呢?
二分类交叉熵损失的本质源于概率建模的极大似然估计(MLE),其推导过程完美体现了“通过优化概率模型拟合真实分布”的核心思想。这个推导过程能够帮助我们充分了解交叉熵损失的含义,以及为什么L(W)的最小化能够实现模型在数据集上的拟合最好。
极大似然估计的感性认知
如果一个事件的发生概率很大,那这个事件应该很容易发生。相应的,如果依赖于权重w的任意事件就是我们的目标,那我们只要勋章令其发生概率最大化的权重w就可以了。寻找相应的权重w,使得目标事件的发生概率最大,这就是极大似然估计的基本方法。
其步骤如下:
1、构筑似然函数P(w),用于评估目标事件发生的概率,该函数被设计成目标事件发生时的概率。
2、对整体似然函数求对数,构成对数似然函数lnP(w)。
3、在对数似然函数上对权重w求导,并使导数为0,对权重进行求解极值。
对数函数单调递增,最大化ln L(w)等价于最大化L(w),在机器学习中习惯最小化损失函数,因此取负数。所以对数似然函数的负数就是我们的损失函数。接下来,我们来看看二分类问题的对数似然函数是如何构筑的。
问题建模:二分类的概率视角
设样本标签
y
y
y 服从伯努利分布(Bernoulli Distribution):
- y = 1 y=1 y=1 的概率为 p p p
- y = 0 y=0 y=0 的概率为 1 − p 1-p 1−p
模型的任务是:学习一个概率函数 σ ( x ; w ) \sigma(\mathbf{x};\mathbf{w}) σ(x;w),使其输出值逼近真实概率 p p p。
2、写出似然函数
二分类预测概率表示
在机器学习二分类任务中,给定特征向量 x i x_i xi 和权重向量 w w w 组成的预测函数,样本标签预测概率的计算方式如下:
样本标签预测为1的概率
P
1
=
P
(
y
^
i
=
1
∣
x
i
,
w
)
=
σ
P_1 = P(\hat{y}_i = 1 \mid x_i, w) = \sigma
P1=P(y^i=1∣xi,w)=σ
样本标签预测为0的概率
P
0
=
P
(
y
^
i
=
0
∣
x
i
,
w
)
=
1
−
σ
P_0 = P(\hat{y}_i = 0 \mid x_i, w) = 1 - \sigma
P0=P(y^i=0∣xi,w)=1−σ
对二分类而言, σ就是sigmoid函数的结果。
当
P
1
P_1
P1 的值为1的时候,代表样本 i 的标签背预测为1,当
P
0
P_0
P0 的值为1的时候,代表样本 i 的标签被预测为0。
P
1
P_1
P1 和
P
0
P_0
P0 相加一定是等于1的。
假设样本i的真实标签
y
i
y_i
yi 为1,并且
P
1
P_1
P1 也为1的话,那就说明我们将 i 的标签预测为1的概率很大,与真实值一致,那模型的预测就是准确的,拟合程度很高,信息损失很少。相反,如果真实标签
y
i
y_i
yi 为1,我们的
P
1
P_1
P1 却很接近0,这就说明我们将 i 的标签预测为1的概率很小,即与真实值一样的概率很小,那么模型的预测就是失败的,拟合程度很低,信息损失很高。当
y
i
y_i
yi 为0时,也是同样的道理。所以当
y
i
y_i
yi 为1的时候,我们希望
P
1
P_1
P1 非常接近1,当
y
i
y_i
yi 为0的时候,我们希望
P
0
P_0
P0 ,这样,模型的效果就很好,信息损失就很少。
| 真实标签 yi | 被预测为1的概率 P1 | 被预测为0的概率 P0 | 样本被预测为? | 与真实值一致吗? | 拟合状况 | 信息损失 |
|---|---|---|---|---|---|---|
| 1 | 0 | 1 | 0 | 否 | 坏 | 大 |
| 1 | 1 | 0 | 1 | 是 | 好 | 小 |
| 0 | 0 | 1 | 0 | 是 | 好 | 小 |
| 0 | 1 | 0 | 1 | 否 | 坏 | 大 |
所以,为了达成让模型拟合好,损失小的目的,我们每时每刻都希望 P ( y ^ i ∣ x i , w ) P(\hat{y}_i | x_i, w) P(y^i∣xi,w) 的值等于 1。而 P ( y ^ i ∣ x i , w ) P(\hat{y}_i | x_i, w) P(y^i∣xi,w) 的本质是样本 i 由特征向量 x i x_i xi 和权重 w w w 组成的预测函数中,预测出所有可能的 y ^ i \hat{y}_i y^i 的概率,因此 1 是它的最大值。也就是说,每时每刻,我们都在追求 P ( y ^ i ∣ x i , w ) P(\hat{y}_i | x_i, w) P(y^i∣xi,w) 的最大值。而寻找相应的参数 w w w,使得每次得到的预测概率最大,正是极大似然估计的基本方法,不过 P ( y ^ i ∣ x i , w ) P(\hat{y}_i | x_i, w) P(y^i∣xi,w) 是对单个样本而言的,因此我们还需要将其拓展到多个样本上。
P ( y ^ i ∣ x i , w ) P(\hat{y}_i | x_i, w) P(y^i∣xi,w) 是对单个样本而言的函数,对一个训练集的 m 个样本来说,我们可以定义如下等式来表达所有样本在特征张量 X X X 和权重向量 w w w 组成的预测函数中,预测出所有可能的 y ^ \hat{y} y^ 的概率 P P P 为:
P = ∏ i = 1 m P ( y ^ i ∣ x i , w ) = ∏ i = 1 m ( P 1 y i ∗ P 0 1 − y i ) = ∏ i = 1 m ( σ i y i ∗ ( 1 − σ i ) 1 − y i ) \begin{aligned} P & = \prod_{i=1}^{m} P(\hat{y}_i | x_i, w) \\ & = \prod_{i=1}^{m} (P_1^{y_i} * P_0^{1-y_i}) \\ & = \prod_{i=1}^{m} (\sigma_i^{y_i} * (1 - \sigma_i)^{1-y_i}) \end{aligned} P=i=1∏mP(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 = B log A 可得到逻辑回归的对数似然函数:
ln P = ln ∏ i = 1 m ( σ i y i ⋅ ( 1 − σ i ) 1 − y i ) = ∑ i = 1 m ln ( σ i y i ⋅ ( 1 − σ i ) 1 − y i ) = ∑ i = 1 m ( ln σ i y i + ln ( 1 − σ i ) 1 − y i ) = ∑ i = 1 m ( y i ⋅ ln ( σ i ) + ( 1 − y i ) ⋅ ln ( 1 − σ i ) ) \begin{aligned} \ln P &= \ln \prod_{i=1}^{m} (\sigma_i^{y_i} \cdot (1 - \sigma_i)^{1 - y_i}) \\ &= \sum_{i=1}^{m} \ln (\sigma_i^{y_i} \cdot (1 - \sigma_i)^{1 - y_i}) \\ &= \sum_{i=1}^{m} (\ln \sigma_i^{y_i} + \ln (1 - \sigma_i)^{1 - y_i}) \\ &= \sum_{i=1}^{m} (y_i \cdot \ln (\sigma_i) + (1 - y_i) \cdot \ln (1 - \sigma_i)) \end{aligned} lnP=lni=1∏m(σiyi⋅(1−σi)1−yi)=i=1∑mln(σ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))
3. 负对数似然 → 损失函数
这就是我们的二分类交叉熵函数。为了数学上的便利以及更好地定义“损失”的含义,我们希望将极大值问题转换为极小值问题,因此我们对 ln P \ln P lnP 取负,并且让权重 w w w 作为函数的自变量,就得到了我们的损失函数 L ( w ) L(w) L(w):
L ( w ) = − ln P = − ∑ i = 1 m ( y i ⋅ ln ( σ i ) + ( 1 − y i ) ⋅ ln ( 1 − σ i ) ) L(w) = -\ln P = -\sum_{i=1}^{m} \left( y_i \cdot \ln (\sigma_i) + (1 - y_i) \cdot \ln (1 - \sigma_i) \right) L(w)=−lnP=−i=1∑m(yi⋅ln(σi)+(1−yi)⋅ln(1−σi))
现在,我们已经将模型拟合中的“最小化损失”问题,转换成了对函数求解极值的问题。这就是一个,基于逻辑回归的返回值σ i的概率性质以及极大似然估计得出的损失函数。在这个函数上,我们只要追求最小值,就能让模型在训练数据上的拟合效果最好,损失最低。
4、用pytorch实现二分类交叉熵
首先使用基本的tensor方法来试试看,以加深我们对二分类交叉熵损失的印象
import torch
import time
N = 3*pow(10,3)
torch.random.manual_seed(1)
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)
Loss = -(1/N)*torch.sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma))
注意,在写损失函数这样的复杂函数时,除了普通的加减乘除以外的全部计算,都要使用torch中的函数,因为tensor的运算速度是远远超过普通Python代码,甚至是NumPy的。你可以试着比较在样本量为300W时,以下两行代码运行的时间差异
N = 3*pow(10,6)
torch.random.manual_seed(1)
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() # 输出为0.022488117218017578(单位为秒)
print(now - start)
start = time.time()
L2 = -(1/N)*sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma))
now = time.time() # 输出为14.347856998443604(单位为秒)
print(now - start)
从运行结果来看,除了加减乘除,我们应该尽量避免使用任何Python原生的计算方法。如果可能的话,让PyTorch处理一切。
接下来,我们用PyTorch中的类实现二分类交叉熵损失
在pytorch中有两个类可以实现二分类交叉熵损失,一个是BCEWithLogitsLoss,一个是BCELoss。这两个类所需要输入的参数不同。
BCEWithLogitsLoss内置了sigmoid函数与交叉熵函数,它会自动计算输入值的sigmoid值,因此需要输入
zhat与真实标签,且顺序不能变化,zhat必须在前。
相对的,BCELoss中只有交叉熵函数,没有sigmoid层,因此需要输入sigma与真实标签,且顺序不能变化。
同时,这两个函数都要求预测值与真实标签的数据类型以及结构(shape)必须相同,否则运行就会报错。
接下来,我们来看看这两个类是如何使用的:
#调用nn模块下的类
criterion = nn.BCELoss() #实例化
loss1 = criterion(sigma,y)
criterion2 = nn.BCEWithLogitsLoss() #实例化
loss2 = criterion2(zhat,y)
print("loss1:",loss1) # 输出为:loss2: tensor(0.8541, grad_fn=<BinaryCrossEntropyWithLogitsBackward0>)
print("loss2:",loss2) # 输出为:loss2: tensor(0.8541, grad_fn=<BinaryCrossEntropyWithLogitsBackward0>)
可以看出两个类的输出是一样的,根据PyTorch官方的公告,他们更推荐使用BCEWithLogitsLoss这个内置了sigmoid函数的类。内置的sigmoid函数可以让精度问题被缩小(因为将指数运算包含在了内部),以维持算法运行时的稳定性,即是说当数据量变大、数据本身也变大时,BCELoss类产生的结果可能有精度问题。所以,当我们的输出层使用sigmoid函数时,我们就可以使用BCEWithLogitsLoss作为损失函数。
与MSELoss相同,二分类交叉熵的类们也有参数reduction,默认是”mean“,表示求解所有样本平均的损失,也可换为”sum”,要求输出整体的损失。以及,还可以使用选项“none”,表示不对损失结果做任何聚合运算,直接输出每个样本对应的损失矩阵。
criterion2 = nn.BCEWithLogitsLoss(reduction = "mean")
loss1 = criterion2(zhat,y)
print(loss1)
criterion2 = nn.BCEWithLogitsLoss(reduction = "sum")
loss2 = criterion2(zhat,y)
print(loss2)
criterion2 = nn.BCEWithLogitsLoss(reduction = "none")
loss3 = criterion2(zhat,y)
print(loss3)
本篇文章我们介绍了二分类交叉熵损失函数,二分类交叉熵损失可以被推广到多分类上,但在实际处理时,二分类与多分类却有一些关键的区别,下次讲多分类交叉熵损失。
3856

被折叠的 条评论
为什么被折叠?



