在机器学习与深度学习领域,损失函数是连接模型预测与真实标签的核心桥梁,它量化了模型预测结果与实际值之间的偏差,是模型训练过程中参数更新的“导航仪”。无论是简单的线性回归、逻辑回归,还是复杂的卷积神经网络(CNN)、循环神经网络(RNN),抑或是当下热门的Transformer模型,都离不开损失函数的指引。正确选择和理解损失函数,直接决定了模型的训练效果、收敛速度乃至最终的任务性能。
本文将系统梳理损失函数的基础理论,从损失函数的定义、核心作用出发,详细划分损失函数的类别,针对每类典型损失函数深入剖析其数学原理、适用场景,并结合Python代码实现案例,帮助读者全面掌握损失函数的应用逻辑。同时,本文还将探讨损失函数选择的关键因素、常见问题及解决方案,为实际模型开发中的损失函数选型提供指导。全文约6000字,兼顾理论深度与实践可行性,适合机器学习初学者、进阶开发者及相关领域研究者阅读。
损失函数(Loss Function),也称为代价函数(Cost Function)或目标函数(Objective Function),是一个衡量模型预测值 \( \hat{y} \) 与真实标签 \( y \) 之间差异的函数,通常用 \( L(y, \hat{y}) \) 表示。其本质是一个映射关系:将模型预测结果与真实值的组合,映射到一个非负实数(损失值),损失值越小,代表模型预测越接近真实情况;反之,损失值越大,说明模型预测与真实情况的偏差越大。
需要注意的是,在实际应用中,“损失函数”与“代价函数”有时会被混用,但严格来说二者存在细微区别:损失函数通常针对单个样本的预测偏差(如单个样本的预测值与真实值的差异),而代价函数则是整个训练数据集上所有样本损失值的平均值或总和,用于衡量模型在整个训练集上的整体性能。例如,对于训练集 \( D = \{(x_1,y_1), (x_2,y_2), ..., (x_n,y_n)\} \),单个样本的损失为 \( L(y_i, \hat{y}_i) \),则代价函数 \( J(\theta) \) 可表示为:
其中 \( \theta \) 是模型的参数,\( \hat{y}_i(\theta, x_i) \) 表示模型在参数 \( \theta \) 下对输入 \( x_i \) 的预测值。而目标函数则是更广义的概念,除了包含代价函数外,还可能加入正则化项(如L1、L2正则化),用于防止模型过拟合,引导模型参数朝着更优的方向收敛。
损失函数在机器学习模型训练中扮演着不可或缺的角色,其核心作用主要体现在以下三个方面:
一个优秀的损失函数应具备以下几个关键特性,以确保模型训练的高效性和稳定性:
根据机器学习任务的类型,损失函数可分为三大类:回归任务损失函数、分类任务损失函数和生成任务损失函数。其中,回归任务和分类任务是最基础、最常见的两大任务类型,对应的损失函数应用范围最广;生成任务损失函数则主要用于生成模型(如GAN、VAE),目标是让模型生成的样本尽可能接近真实样本分布。
本节将重点讲解回归任务和分类任务的典型损失函数,同时简要介绍生成任务的核心损失函数,每个损失函数均包含原理分析、适用场景及代码实现。
回归任务的目标是预测一个连续的数值(如房价、股票价格、温度等),因此回归任务的损失函数需能够衡量两个连续值(预测值与真实值)之间的差异。常见的回归损失函数包括均方误差(MSE)、平均绝对误差(MAE)、均方根误差(RMSE)、平均绝对百分比误差(MAPE)、Huber损失、Log-Cosh损失等。
均方误差是回归任务中最常用的损失函数之一,其计算方式为预测值与真实值之差的平方和的平均值。数学公式如下:
其中,\( n \) 为样本数量,\( y_i \) 为第 \( i \) 个样本的真实值,\( \hat{y}_i \) 为第 \( i \) 个样本的预测值。
MSE的特点是对误差进行平方运算,因此会放大较大误差(异常值)的影响。例如,若某个样本的误差为10,其平方后为100,远大于误差为1的样本(平方后为1),这使得MSE对异常值非常敏感。这种特性在某些场景下是不利的(如数据中存在较多噪声或异常值时),但在数据相对干净、需要重点关注大误差样本的场景下则较为适用。
从可微性角度看,MSE是连续可微的函数,其梯度计算简单,便于使用梯度下降等优化算法进行参数更新。对模型参数 \( \theta \) 的梯度为:
MSE适用于数据分布较为均匀、无明显异常值的回归任务,例如:
以下是使用Python实现MSE的两种方式:手动实现和使用PyTorch框架实现(适用于深度学习模型训练)。
import numpy as np
import torch
import torch.nn as nn
# 1. 手动实现MSE(适用于简单回归模型)
def mean_squared_error(y_true, y_pred):
"""
计算均方误差(MSE)
参数:
y_true: 真实值,numpy数组或列表
y_pred: 预测值,numpy数组或列表
返回:
mse: 均方误差值
"""
y_true = np.array(y_true)
y_pred = np.array(y_pred)
if y_true.shape != y_pred.shape:
raise ValueError("真实值与预测值的形状必须一致")
n = len(y_true)
mse = np.sum((y_true - y_pred) ** 2) / n
return mse
# 测试手动实现的MSE
y_true = [1.2, 2.3, 3.4, 4.5]
y_pred = [1.1, 2.4, 3.3, 4.6]
mse_manual = mean_squared_error(y_true, y_pred)
print(f"手动实现MSE: {mse_manual:.4f}") # 输出:0.0100
# 2. PyTorch实现MSE(适用于深度学习模型)
# 定义MSE损失函数
mse_loss = nn.MSELoss()
# 构造真实值和预测值(PyTorch张量)
y_true_tensor = torch.tensor(y_true, dtype=torch.float32).reshape(-1, 1) # 形状:(4, 1)
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32).reshape(-1, 1) # 形状:(4, 1)
# 计算MSE
mse_torch = mse_loss(y_pred_tensor, y_true_tensor)
print(f"PyTorch实现MSE: {mse_torch.item():.4f}") # 输出:0.0100
平均绝对误差是预测值与真实值之差的绝对值的平均值,数学公式如下:
与MSE不同,MAE采用绝对值来衡量误差,不会对大误差进行平方放大,因此对异常值的敏感度更低,鲁棒性更强。例如,误差为10的样本,其绝对值为10,远小于MSE中的100,这使得MAE在数据中存在较多异常值或噪声的场景下表现更优。
从可微性角度看,MAE在 \( y_i = \hat{y}_i \) 处不可微(绝对值函数在0点的导数不存在),这会给梯度下降优化带来一定挑战。为了解决这一问题,在实际应用中通常会采用次梯度(Subgradient)的方法,将0点的导数定义为0到1之间的任意值(通常取0),以保证优化过程的顺利进行。MAE对模型参数 \( \theta \) 的次梯度为:
其中 \( \text{sign}(\cdot) \) 为符号函数,当 \( x > 0 \) 时返回1,当 \( x < 0 \) 时返回-1,当 \( x = 0 \) 时返回0。
MAE适用于数据中存在异常值、对大误差不敏感的回归任务,例如:
import numpy as np
import torch
import torch.nn as nn
# 1. 手动实现MAE
def mean_absolute_error(y_true, y_pred):
"""
计算平均绝对误差(MAE)
参数:
y_true: 真实值,numpy数组或列表
y_pred: 预测值,numpy数组或列表
返回:
mae: 平均绝对误差值
"""
y_true = np.array(y_true)
y_pred = np.array(y_pred)
if y_true.shape != y_pred.shape:
raise ValueError("真实值与预测值的形状必须一致")
n = len(y_true)
mae = np.sum(np.abs(y_true - y_pred)) / n
return mae
# 测试手动实现的MAE
y_true = [1.2, 2.3, 3.4, 4.5, 100.0] # 加入异常值100.0
y_pred = [1.1, 2.4, 3.3, 4.6, 90.0]
mae_manual = mean_absolute_error(y_true, y_pred)
print(f"手动实现MAE: {mae_manual:.4f}") # 输出:2.1800
# 对比MSE在异常值下的表现
def mean_squared_error(y_true, y_pred):
y_true = np.array(y_true)
y_pred = np.array(y_pred)
n = len(y_true)
return np.sum((y_true - y_pred) ** 2) / n
mse_manual = mean_squared_error(y_true, y_pred)
print(f"异常值下MSE: {mse_manual:.4f}") # 输出:20.0180,远大于MAE
# 2. PyTorch实现MAE
mae_loss = nn.L1Loss() # PyTorch中L1Loss即为MAE
y_true_tensor = torch.tensor(y_true, dtype=torch.float32).reshape(-1, 1)
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32).reshape(-1, 1)
mae_torch = mae_loss(y_pred_tensor, y_true_tensor)
print(f"PyTorch实现MAE: {mae_torch.item():.4f}") # 输出:2.1800
均方根误差是均方误差的平方根,数学公式如下:
RMSE的本质是对MSE进行了开方运算,其核心作用是将损失值的单位转换为与原始数据相同的单位,使得损失值更具可解释性。例如,在预测房价的任务中,MSE的单位是“平方元”,而RMSE的单位是“元”,可以直接对应到房价的实际偏差范围。
与MSE类似,RMSE对异常值仍然较为敏感,因为它继承了MSE的平方运算特性。在优化过程中,RMSE的梯度计算与MSE类似,只是多了一个开方后的系数:
RMSE适用于需要损失值与原始数据单位一致、便于解释的回归任务,同时要求数据中无明显异常值,例如:
import numpy as np
import torch
import torch.nn as nn
# 1. 手动实现RMSE
def root_mean_squared_error(y_true, y_pred):
"""
计算均方根误差(RMSE)
参数:
y_true: 真实值,numpy数组或列表
y_pred: 预测值,numpy数组或列表
返回:
rmse: 均方根误差值
"""
y_true = np.array(y_true)
y_pred = np.array(y_pred)
if y_true.shape != y_pred.shape:
raise ValueError("真实值与预测值的形状必须一致")
n = len(y_true)
mse = np.sum((y_true - y_pred) ** 2) / n
rmse = np.sqrt(mse)
return rmse
# 测试手动实现的RMSE
y_true = [5000.0, 6000.0, 7000.0, 8000.0] # 人均收入数据(单位:元)
y_pred = [4900.0, 6100.0, 6900.0, 8200.0]
rmse_manual = root_mean_squared_error(y_true, y_pred)
print(f"手动实现RMSE: {rmse_manual:.2f} 元") # 输出:111.80 元,单位与原始数据一致
# 2. PyTorch实现RMSE(PyTorch无直接的RMSE函数,可通过MSE开方实现)
mse_loss = nn.MSELoss()
y_true_tensor = torch.tensor(y_true, dtype=torch.float32).reshape(-1, 1)
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32).reshape(-1, 1)
mse_torch = mse_loss(y_pred_tensor, y_true_tensor)
rmse_torch = torch.sqrt(mse_torch)
print(f"PyTorch实现RMSE: {rmse_torch.item():.2f} 元") # 输出:111.80 元
平均绝对百分比误差是预测值与真实值之差的绝对值占真实值的百分比的平均值,数学公式如下:
MAPE的核心特点是将误差标准化为百分比形式,使得损失值不受原始数据单位的影响,便于跨数据集、跨任务比较模型性能。例如,预测房价(万元级)和预测日用品价格(元级)的MAPE可以直接对比,而MSE或MAE则因单位不同无法直接比较。
MAPE的局限性在于:当真实值 \( y_i = 0 \) 时,分母为0,会导致MAPE无意义;当真实值 \( y_i \) 较小时,百分比误差会被放大,导致MAPE对小值样本敏感。因此,MAPE仅适用于真实值不为0且分布较为均匀的回归任务。
MAPE适用于需要衡量相对误差、跨任务比较模型性能的回归任务,例如:
import numpy as np
import torch
# 1. 手动实现MAPE
def mean_absolute_percentage_error(y_true, y_pred):
"""
计算平均绝对百分比误差(MAPE)
参数:
y_true: 真实值,numpy数组或列表
y_pred: 预测值,numpy数组或列表
返回:
mape: 平均绝对百分比误差(%)
"""
y_true = np.array(y_true)
y_pred = np.array(y_pred)
if y_true.shape != y_pred.shape:
raise ValueError("真实值与预测值的形状必须一致")
if np.any(y_true == 0):
raise ValueError("真实值中存在0,无法计算MAPE")
n = len(y_true)
mape = (np.sum(np.abs((y_true - y_pred) / y_true)) / n) * 100
return mape
# 测试手动实现的MAPE
y_true_car = [10000, 12000, 15000] # 汽车销量(辆)
y_pred_car = [9500, 12500, 14500]
mape_car = mean_absolute_percentage_error(y_true_car, y_pred_car)
print(f"汽车销量MAPE: {mape_car:.2f}%") # 输出:3.56%
y_true_phone = [50000, 60000, 70000] # 手机销量(部)
y_pred_phone = [48000, 62000, 69000]
mape_phone = mean_absolute_percentage_error(y_true_phone, y_pred_phone)
print(f"手机销量MAPE: {mape_phone:.2f}%") # 输出:2.22%,可直接与汽车销量MAPE对比
# 2. PyTorch实现MAPE
def torch_mape(y_true, y_pred):
"""
PyTorch版本的MAPE
参数:
y_true: 真实值,PyTorch张量
y_pred: 预测值,PyTorch张量
返回:
mape: 平均绝对百分比误差(%)
"""
if torch.any(y_true == 0):
raise ValueError("真实值中存在0,无法计算MAPE")
mape = (torch.mean(torch.abs((y_true - y_pred) / y_true)) * 100)
return mape
y_true_tensor = torch.tensor(y_true_car, dtype=torch.float32).reshape(-1, 1)
y_pred_tensor = torch.tensor(y_pred_car, dtype=torch.float32).reshape(-1, 1)
mape_torch = torch_mape(y_true_tensor, y_pred_tensor)
print(f"PyTorch实现汽车销量MAPE: {mape_torch.item():.2f}%") # 输出:3.56%Huber损失是由Peter Huber提出的一种鲁棒损失函数,其核心思想是在误差较小时采用MSE(保证优化的平滑性),在误差较大时采用MAE(降低对异常值的敏感度),通过一个阈值 \( \delta \) 来区分“小误差”和“大误差”。数学公式如下:
其中 \( \delta \) 是一个超参数,通常取值为1.0(可根据数据情况调整)。当误差的绝对值小于等于 \( \delta \) 时,Huber损失与MSE一致,具有良好的可微性和平滑的梯度;当误差的绝对值大于 \( \delta \) 时,Huber损失与MAE一致,梯度为常数 \( \delta \) 或 \( -\delta \),不会因异常值导致梯度爆炸。
Huber损失的梯度计算如下:
Huber损失结合了MSE和MAE的优点:既能够在数据干净时保证较快的收敛速度(得益于MSE的平滑梯度),又能够在数据存在异常值时保持较好的鲁棒性(得益于MAE的线性特性)。
Huber损失适用于数据中存在少量异常值、需要平衡收敛速度和鲁棒性的回归任务,例如:
import numpy as np
import torch
import torch.nn as nn
# 1. 手动实现Huber损失
def huber_loss(y_true, y_pred, delta=1.0):
"""
计算Huber损失
参数:
y_true: 真实值,numpy数组或列表
y_pred: 预测值,numpy数组或列表
delta: 阈值,默认1.0
返回:
huber: Huber损失值
"""
y_true = np.array(y_true)
y_pred = np.array(y_pred)
if y_true.shape != y_pred.shape:
raise ValueError("真实值与预测值的形状必须一致")
error = y_true - y_pred
abs_error = np.abs(error)
# 分情况计算损失
huber = np.where(abs_error <= delta, 0.5 * error ** 2, delta * abs_error - 0.5 * delta ** 2)
return np.mean(huber)
# 测试手动实现的Huber损失
y_true = [1.2, 2.3, 3.4, 4.5, 100.0] # 含异常值
y_pred = [1.1, 2.4, 3.3, 4.6, 90.0]
huber_delta1 = huber_loss(y_true, y_pred, delta=1.0)
huber_delta5 = huber_loss(y_true, y_pred, delta=5.0)
print(f"Huber损失(delta=1.0): {huber_delta1:.4f}") # 输出:5.0050
print(f"Huber损失(delta=5.0): {huber_delta5:.4f}") # 输出:5.0180
# 对比MSE和MAE
mse = np.mean((np.array(y_true) - np.array(y_pred)) ** 2)
mae = np.mean(np.abs(np.array(y_true) - np.array(y_pred)))
print(f"MSE: {mse:.4f}") # 输出:20.0180
print(f"MAE: {mae:.4f}") # 输出:2.1800,Huber损失介于两者之间
# 2. PyTorch实现Huber损失
huber_loss_torch = nn.HuberLoss(delta=1.0) # PyTorch 1.9.0及以上版本支持
y_true_tensor = torch.tensor(y_true, dtype=torch.float32).reshape(-1, 1)
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32).reshape(-1, 1)
huber_torch = huber_loss_torch(y_pred_tensor, y_true_tensor)
print(f"PyTorch实现Huber损失(delta=1.0): {huber_torch.item():.4f}") # 输出:5.0050Log-Cosh损失是另一种鲁棒的回归损失函数,其数学表达式为预测值与真实值之差的双曲余弦函数的对数,公式如下:
其中 \( \cosh(x) = \frac{e^x + e^{-x}}{2} \) 是双曲余弦函数。Log-Cosh损失的核心特点是:在误差较小时,其近似于MSE;在误差较大时,其近似于MAE,同时保持了整个定义域内的光滑性(可微性)。
具体来说,当 \( x \) 较小时,\( \cosh(x) \approx 1 + \frac{x^2}{2} \),因此 \( \log(\cosh(x)) \approx \log(1 + \frac{x^2}{2}) \approx \frac{x^2}{2} \)(与MSE的形式一致);当 \( x \) 较大时,\( \cosh(x) \approx \frac{e^{|x|}}{2} \),因此 \( \log(\cosh(x)) \approx |x| - \log(2) \)(与MAE的形式一致,仅相差一个常数项,不影响优化方向)。
与Huber损失相比,Log-Cosh损失的优势在于其完全光滑,没有Huber损失中的分段点,梯度变化更平缓,有助于优化算法的稳定收敛。Log-Cosh损失对模型参数 \( \theta \) 的梯度为:
其中 \( \tanh(x) \) 是双曲正切函数,其取值范围为(-1, 1),因此梯度不会像MSE那样因异常值而过大,保证了优化的稳定性。
Log-Cosh损失适用于数据中存在异常值、需要光滑损失函数以保证优化稳定的回归任务,例如:
import numpy as np
import torch
import torch.nn as nn
# 1. 手动实现Log-Cosh损失
def log_cosh_loss(y_true, y_pred):
"""
计算Log-Cosh损失
参数:
y_true: 真实值,numpy数组或列表
y_pred: 预测值,numpy数组或列表
返回:
log_cosh: Log-Cosh损失值
"""
y_true = np.array(y_true)
y_pred = np.array(y_pred)
if y_true.shape != y_pred.shape:
raise ValueError("真实值与预测值的形状必须一致")
error = y_true - y_pred
log_cosh = np.log(np.cosh(error))
return np.mean(log_cosh)
# 测试手动实现的Log-Cosh损失
y_true = [1.2, 2.3, 3.4, 4.5, 100.0] # 含异常值
y_pred = [1.1, 2.4, 3.3, 4.6, 90.0]
log_cosh = log_cosh_loss(y_true, y_pred)
print(f"Log-Cosh损失: {log_cosh:.4f}") # 输出:4.5051
# 对比Huber损失(delta=1.0)
huber = huber_loss(y_true, y_pred, delta=1.0)
print(f"Huber损失(delta=1.0): {huber:.4f}") # 输出:5.0050,Log-Cosh损失略小
# 2. PyTorch实现Log-Cosh损失
class LogCoshLoss(nn.Module):
"""PyTorch自定义Log-Cosh损失函数"""
def __init__(self):
super(LogCoshLoss, self).__init__()
def forward(self, y_pred, y_true):
error = y_true - y_pred
return torch.mean(torch.log(torch.cosh(error)))
# 初始化损失函数并计算
log_cosh_loss_torch = LogCoshLoss()
y_true_tensor = torch.tensor(y_true, dtype=torch.float32).reshape(-1, 1)
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32).reshape(-1, 1)
log_cosh_torch = log_cosh_loss_torch(y_pred_tensor, y_true_tensor)
print(f"PyTorch实现Log-Cosh损失: {log_cosh_torch.item():.4f}") # 输出:4.5051分类任务的目标是预测样本所属的离散类别(如二分类中的“正/负”、多分类中的“猫/狗/鸟”),因此分类任务的损失函数需能够衡量模型预测的类别概率分布与真实类别分布之间的差异。常见的分类损失函数包括交叉熵损失(Cross-Entropy Loss)、二元交叉熵损失(Binary Cross-Entropy Loss)、负对数似然损失(Negative Log-Likelihood Loss, NLL Loss)、Focal Loss等。
在分类任务中,模型的输出通常是未归一化的得分(Logits),需要通过Softmax函数(多分类)或Sigmoid函数(二分类)转换为概率分布,再与真实类别标签(通常采用独热编码One-Hot Encoding)计算损失。
二元交叉熵损失适用于二分类任务(样本仅有两个可能的类别,如“阳性/阴性”“垃圾邮件/正常邮件”),其核心是衡量模型预测的正类概率与真实标签之间的差异。
对于二分类任务,模型的输出通常通过Sigmoid函数转换为正类的概率 \( \hat{p} = \sigma(z) = \frac{1}{1 + e^{-z}} \)(其中 \( z \) 是模型的Logits输出),真实标签 \( y \in \{0, 1\} \)(0表示负类,1表示正类)。二元交叉熵损失的数学公式如下:
其中 \( n \) 为样本数量,\( y_i \) 为第 \( i \) 个样本的真实标签,\( \hat{p}_i \) 为第 \( i \) 个样本预测为正类的概率。
BCE损失的直观含义是:当真实标签为1时,损失项为 \( -\log(\hat{p}_i) \),此时 \( \hat{p}_i \) 越接近1,损失越小;当真实标签为0时,损失项为 \( -\log(1 - \hat{p}_i) \),此时 \( \hat{p}_i \) 越接近0,损失越小。若模型预测概率与真实标签偏差较大(如真实标签为1但预测概率为0.1),则损失值会显著增大。
为了避免对数运算中出现 \( \log(0) \) 的情况(导致损失值为无穷大),实际实现中通常会在概率值上添加一个微小的epsilon(如1e-7),将概率值限制在 \( [\epsilon, 1 - \epsilon] \) 范围内。
BCE损失对模型参数 \( \theta \) 的梯度为:
由于 \( \frac{\partial \hat{p}_i}{\partial z_i} = \hat{p}_i (1 - \hat{p}_i) \)(Sigmoid函数的导数特性),梯度可简化为:
简化后的梯度计算简单,便于优化算法的实现。
BCE损失适用于所有二分类任务,例如:
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
# 1. 手动实现BCE损失(含Sigmoid转换)
def binary_cross_entropy_loss(y_true, y_logits, epsilon=1e-7):
"""
计算二元交叉熵损失(BCE)
参数:
y_true: 真实标签,numpy数组或列表,取值为0或1
y_logits: 模型Logits输出(未经过Sigmoid),numpy数组或列表
epsilon: 防止log(0)的微小值,默认1e-7
返回:
bce: 二元交叉熵损失值
"""
y_true = np.array(y_true)
y_logits = np.array(y_logits)
if y_true.shape != y_logits.shape:
raise ValueError("真实标签与Logits的形状必须一致")
# Sigmoid转换为概率
y_pred = 1 / (1 + np.exp(-y_logits))
# 限制概率范围
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
# 计算BCE损失
bce = -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
return bce
# 测试手动实现的BCE损失
y_true = [1, 0, 1, 0, 1] # 二分类真实标签
y_logits = [2.3, -1.2, 3.1, 0.5, 1.8] # 模型Logits输出
bce_manual = binary_cross_entropy_loss(y_true, y_logits)
print(f"手动实现BCE损失: {bce_manual:.4f}") # 输出:0.1562
# 2. PyTorch实现BCE损失(两种方式:带Sigmoid和不带Sigmoid)
# 方式1:使用BCEWithLogitsLoss(直接输入Logits,内置Sigmoid,数值更稳定)
bce_with_logits_loss = nn.BCEWithLogitsLoss()
y_true_tensor = torch.tensor(y_true, dtype=torch.float32).reshape(-1, 1)
y_logits_tensor = torch.tensor(y_logits, dtype=torch.float32).reshape(-1, 1)
bce_torch1 = bce_with_logits_loss(y_logits_tensor, y_true_tensor)
print(f"PyTorch BCEWithLogitsLoss: {bce_torch1.item():.4f}") # 输出:0.1562
# 方式2:先Sigmoid再用BCELoss(需注意数值稳定性)
sigmoid = nn.Sigmoid()
y_pred_tensor = sigmoid(y_logits_tensor)
bce_loss = nn.BCELoss()
bce_torch2 = bce_loss(y_pred_tensor, y_true_tensor)
print(f"PyTorch Sigmoid + BCELoss: {bce_torch2.item():.4f}") # 输出:0.1562
交叉熵损失适用于多分类任务(样本有两个以上的可能类别,且每个样本仅属于一个类别),其核心是衡量模型预测的类别概率分布与真实类别分布(独热编码)之间的差异。
对于多分类任务,模型的输出通常是一个维度为类别数 \( C \) 的Logits向量,需要通过Softmax函数转换为概率分布 \( \hat{p} = \text{Softmax}(z) \),其中 \( \hat{p}_j = \frac{e^{z_j}}{\sum_{k=1}^{C} e^{z_k}} \)(\( z_j \) 是Logits向量的第 \( j \) 个元素),满足 \( \sum_{j=1}^{C} \hat{p}_j = 1 \)。
真实类别标签通常采用独热编码表示:若样本属于第 \( k \) 类,则真实标签向量 \( y \) 的第 \( k \) 个元素为1,其余元素为0(即 \( y_j = 1 \) 当且仅当 \( j = k \),否则 \( y_j = 0 \))。交叉熵损失的数学公式如下:
其中 \( n \) 为样本数量,\( C \) 为类别数,\( y_{i,j} \) 为第 \( i \) 个样本真实标签向量的第 \( j \) 个元素,\( \hat{p}_{i,j} \) 为第 \( i \) 个样本预测为第 \( j \) 类的概率。
由于真实标签是独热编码,对于每个样本 \( i \),只有当 \( j \) 为真实类别时 \( y_{i,j} = 1 \),其余项均为0,因此交叉熵损失可简化为:
其中 \( k_i \) 是第 \( i \) 个样本的真实类别。这一简化表明,交叉熵损失本质上是对每个样本真实类别的预测概率取负对数,预测概率越接近1,损失越小;反之,损失越大。
与BCE损失类似,为了避免 \( \log(0) \) 的情况,实际实现中会对预测概率添加微小的epsilon。交叉熵损失的梯度计算利用了Softmax函数的导数特性,最终简化后的梯度为:
梯度计算简单,且数值稳定性较好。
交叉熵损失适用于所有单标签多分类任务(每个样本仅属于一个类别),例如