损失函数是模型训练和优化的核心度量,其本质是为梯度下降算法提供明确的优化目标 —— 通过计算预测值与真实值的差异,损失函数的梯度(导数)为模型参数更新指明方向(梯度反方向为损失下降最快路径)。不同损失函数的数学形态(如交叉熵的平滑曲线、MSE 的抛物线、MAE 的 V 型结构)决定了梯度特性,进而影响优化效率:线性梯度推动快速收敛,常数梯度导致后期优化放缓,而动态权重设计(如焦点损失)则能自适应调整优化重点,这些特性共同塑造了模型学习的行为模式。
梯度下降介绍:网页连接
一、分类任务的损失函数
1.1 交叉熵损失(Cross-Entropy Loss)
数学公式:
- 二分类:
L = − [ y ⋅ log p ^ + ( 1 − y ) ⋅ log ( 1 − p ^ ) ] L = -[y \cdot \log\hat{p} + (1-y) \cdot \log(1-\hat{p})] L=−[y⋅logp^+(1−y)⋅log(1−p^)] - 多分类(Softmax交叉熵):
L = − ∑ k = 1 K y k ⋅ log ( e z k ∑ i = 1 K e z i ) L = -\sum_{k=1}^K y_k \cdot \log\left(\frac{e^{z_k}}{\sum_{i=1}^K e^{z_i}}\right) L=−k=1∑Kyk⋅log(∑i=1Keziezk)
其中 z k z_k zk为模型对第 k k k类的原始输出, y k y_k yk为one-hot标签。
数学原理:
- 信息论基础:交叉熵 H ( p , q ) = − ∑ p ( x ) log q ( x ) H(p,q) = -\sum p(x)\log q(x) H(p,q)=−∑p(x)logq(x)衡量两个概率分布 p p p(真实)与 q q q(预测)的差异,当 p = q p=q p=q时交叉熵等于信息熵 H ( p ) H(p) H(p),差异越大交叉熵越大。
- 最大似然等价性:假设样本服从伯努利分布(二分类)或多项分布(多分类),最大化似然函数等价于最小化交叉熵。以二分类为例,似然函数为:
L ( θ ) = ∏ p ^ y ( 1 − p ^ ) 1 − y L(\theta) = \prod \hat{p}^y(1-\hat{p})^{1-y} L(θ)=∏p^y(1−p^)1−y
取对数后:
log L ( θ ) = ∑ [ y log p ^ + ( 1 − y ) log ( 1 − p ^ ) ] = − n ⋅ L C E \log L(\theta) = \sum [y\log\hat{p} + (1-y)\log(1-\hat{p})] = -n \cdot L_{CE} logL(θ)=∑[ylogp^+(1−y)log(1−p^)]=−n⋅LCE
因此最小化交叉熵等价于最大化对数似然。
数学图像:
绘图代码:
import numpy as np # 导入 NumPy 库,用于数值计算(如数组、矩阵运算等)
import matplotlib.pyplot as plt # 导入 Matplotlib 的 pyplot 模块,用于绘图
from matplotlib.ticker import FormatStrFormatter # 导入格式化坐标轴刻度的工具
# 设置中文字体
plt.rcParams["font.family"] = ["SimHei"] # 设置默认字体为黑体,以支持中文显示
plt.rcParams["axes.unicode_minus"] = False # 禁用 Unicode 负号显示,使用正常减号 '-' 显示负数
# 定义二分类交叉熵损失函数
def cross_entropy_loss(p_hat, y):
"""
计算二分类交叉熵损失值。
参数:
p_hat (float): 模型预测的概率值,范围在 [0,1]
y (int): 真实标签,取值为 0 或 1
返回:
float: 交叉熵损失值
"""
epsilon = 1e-10 # 防止对数输入为 0
p_hat = np.clip(p_hat, epsilon, 1 - epsilon) # 将概率限制在 [epsilon, 1 - epsilon] 范围内
return - (y * np.log(p_hat) + (1 - y) * np.log(1 - p_hat)) # 二分类交叉熵公式
# 计算交叉熵损失的导数
def cross_entropy_derivative(p_hat, y):
"""
计算二分类交叉熵损失对预测概率 p_hat 的导数。
参数:
p_hat (float): 模型预测的概率值
y (int): 真实标签,0 或 1
返回:
float: 损失对 p_hat 的导数值
"""
return p_hat - y # 二分类交叉熵的导数形式
# Softmax 函数
def softmax(z):
"""
计算 Softmax 函数值,将 logits 转换为概率分布。
参数:
z (np.ndarray): 输入的一维数组,表示各类别的 logit 值
返回:
np.ndarray: 各类别对应的概率分布
"""
exp_z = np.exp(z - np.max(z)) # 减去最大值避免指数爆炸
return exp_z / exp_z.sum() # 归一化得到概率分布
# 多分类交叉熵损失函数
def categorical_cross_entropy(y_true, y_pred):
"""
计算多分类交叉熵损失值。
参数:
y_true (np.ndarray): one-hot 编码的真实标签向量
y_pred (np.ndarray): 模型预测的概率向量(Softmax 输出)
返回:
float: 多分类交叉熵损失值
"""
epsilon = 1e-10
y_pred = np.clip(y_pred, epsilon, 1 - epsilon) # 防止 log(0)
return -np.sum(y_true * np.log(y_pred)) # 多分类交叉熵公式
# 多分类交叉熵损失的导数
def categorical_cross_entropy_derivative(y_true, y_pred):
"""
计算多分类交叉熵损失对模型输出(logits)的导数。
参数:
y_true (np.ndarray): one-hot 编码的真实标签向量
y_pred (np.ndarray): 模型预测的概率向量(Softmax 输出)
返回:
np.ndarray: 损失对 logits 的导数(梯度)
"""
return y_pred - y_true # 多分类交叉熵导数形式
# 生成预测概率数据
p_hat = np.linspace(0.01, 0.99, 1000) # 生成从 0.01 到 0.99 的 1000 个预测概率值
# 计算二分类损失和导数
loss_y0 = cross_entropy_loss(p_hat, y=0) # y=0 时的损失值
loss_y1 = cross_entropy_loss(p_hat, y=1) # y=1 时的损失值
derivative_y0 = cross_entropy_derivative(p_hat, y=0) # y=0 时的导数值
derivative_y1 = cross_entropy_derivative(p_hat, y=1) # y=1 时的导数值
# 多分类损失和导数计算(固定真实标签为 [1, 0, 0])
z_values = np.linspace(-5, 5, 1000) # 生成从 -5 到 5 的 1000 个 logit 值
cce_losses = [] # 存储每个 logit 对应的多分类交叉熵损失
cce_derivatives = [] # 存储每个 logit 对应的导数值
for z in z_values:
logits = np.array([z, 0.0, 0.0]) # 固定其他两个类别的 logit 为 0
probs = softmax(logits) # 使用 Softmax 转换为概率分布
loss = categorical_cross_entropy(y_true=np.array([1, 0, 0]), y_pred=probs) # 计算损失
grad = categorical_cross_entropy_derivative(y_true=np.array([1, 0, 0]), y_pred=probs) # 计算导数
cce_losses.append(loss) # 添加到损失列表
cce_derivatives.append(grad[0]) # 只取第一个 logit 对应的梯度值添加到导数列表
# 创建图像(新增一列用于绘制多分类导数)
fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6)) # 创建第一个图形窗口,包含两个子图
# 绘制二分类损失图像
ax1.plot(p_hat, loss_y0, label='y=0时的损失', color='#5DA5DA', linewidth=2) # 绘制 y=0 的损失曲线
ax1.plot(p_hat, loss_y1, label='y=1时的损失', color='#FAA43A', linewidth=2) # 绘制 y=1 的损失曲线
ax1.set_xlabel(r'预测概率 $\hat{p}$', fontsize=12) # 设置 x 轴标签
ax1.set_ylabel('损失值 L', fontsize=12) # 设置 y 轴标签
ax1.set_title('二分类交叉熵损失函数图像', fontsize=14) # 设置图像标题
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线
ax1.xaxis.set_major_formatter(FormatStrFormatter('%.1f')) # 格式化 x 轴刻度为一位小数
# 绘制二分类导数图像
ax2.plot(p_hat, derivative_y0, label='y=0时的导数', color='#5DA5DA', linewidth=2) # 绘制 y=0 的导数曲线
ax2.plot(p_hat, derivative_y1, label='y=1时的导数', color='#FAA43A', linewidth=2) # 绘制 y=1 的导数曲线
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 绘制 y=0 的参考线
ax2.set_xlabel(r'预测概率 $\hat{p}$', fontsize=12) # 设置 x 轴标签
ax2.set_ylabel(r'导数 $\partial L/\partial\hat{p}$', fontsize=12) # 设置 y 轴标签
ax2.set_title('二分类交叉熵损失函数的导数图像', fontsize=14) # 设置图像标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
ax2.xaxis.set_major_formatter(FormatStrFormatter('%.1f')) # 格式化 x 轴刻度为一位小数
# 创建第二个图形窗口,包含两个子图
fig2, (ax3, ax4) = plt.subplots(1, 2, figsize=(14, 6))
# 绘制多分类交叉熵损失图像
ax3.plot(z_values, cce_losses, label='多分类交叉熵损失', color='#60BD68', linewidth=2) # 绘制损失曲线
ax3.set_xlabel(r'logit $z_1$', fontsize=12) # 设置 x 轴标签
ax3.set_ylabel('损失值 L', fontsize=12) # 设置 y 轴标签
ax3.set_title('多分类交叉熵损失函数图像', fontsize=14) # 设置图像标题
ax3.legend(fontsize=12) # 添加图例
ax3.grid(True, linestyle='--', alpha=0.7) # 添加网格线
ax3.xaxis.set_major_formatter(FormatStrFormatter('%.1f')) # 格式化 x 轴刻度为一位小数
# 绘制多分类交叉熵损失的导数图像
ax4.plot(z_values, cce_derivatives, label='多分类损失导数', color='#F15854', linewidth=2) # 绘制导数曲线
ax4.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 绘制 y=0 的参考线
ax4.set_xlabel(r'logit $z_1$', fontsize=12) # 设置 x 轴标签
ax4.set_ylabel(r'导数 $\partial L/\partial z_1$', fontsize=12) # 设置 y 轴标签
ax4.set_title('多分类交叉熵损失函数的导数图像', fontsize=14) # 设置图像标题
ax4.legend(fontsize=12) # 添加图例
ax4.grid(True, linestyle='--', alpha=0.7) # 添加网格线
ax4.xaxis.set_major_formatter(FormatStrFormatter('%.1f')) # 格式化 x 轴刻度为一位小数
# 调整布局
plt.tight_layout() # 自动调整子图参数,防止重叠
plt.show() # 显示所有图形
1.2 Hinge损失(Hinge Loss)
数学公式:
-
二分类:
L = max ( 0 , 1 − y ⋅ y ^ ) L = \max(0, 1 - y \cdot \hat{y}) L=max(0,1−y⋅y^)
其中 y ∈ { − 1 , 1 } y \in \{-1, 1\} y∈{−1,1}, y ^ \hat{y} y^为模型输出分数。 -
多分类扩展(One-vs-Rest):
L = max ( 0 , 1 + max k ≠ y y ^ k − y ^ y ) L = \max(0, 1 + \max_{k\neq y}\hat{y}_k - \hat{y}_y) L=max(0,1+k=ymaxy^k−y^y)
数学原理:
- 几何解释:Hinge损失追求"分类间隔"最大化,当样本分类正确且间隔 y ⋅ y ^ ≥ 1 y \cdot \hat{y} \geq 1 y⋅y^≥1时损失为0,否则惩罚大小为 1 − y ⋅ y ^ 1 - y \cdot \hat{y} 1−y⋅y^。
- 优化目标:对应SVM的优化问题:
min w , b 1 2 ∥ w ∥ 2 + C ∑ max ( 0 , 1 − y i ( w T x i + b ) ) \min_{w,b} \frac{1}{2}\|w\|^2 + C\sum \max(0, 1 - y_i(w^Tx_i + b)) w,bmin21∥w∥2+C∑max(0,1−yi(wTxi+b))
其中第一项为L2正则化,第二项为Hinge损失, C C C控制正则化强度。
数学图像:
绘图代码:
import numpy as np # 导入NumPy库,用于高效的数值计算(如数组操作、数学函数等)
import matplotlib.pyplot as plt # 导入Matplotlib绘图库,用于创建各种图表
from matplotlib.ticker import FormatStrFormatter # 导入格式化坐标轴刻度的工具
# 设置中文字体,确保图表中的中文能正常显示
plt.rcParams["font.family"] = ["SimHei"] # 设置默认字体为黑体
plt.rcParams["axes.unicode_minus"] = False # 确保负号能正确显示
# 二分类Hinge损失函数定义
def hinge_loss_binary(y_true, y_pred):
"""
计算二分类Hinge损失。
参数:
y_true (int): 真实标签,取值为-1或1
y_pred (float): 模型预测分数(未经过sigmoid等激活函数处理)
返回:
float: Hinge损失值
"""
# Hinge损失公式:L = max(0, 1 - y_true * y_pred)
return np.maximum(0, 1 - y_true * y_pred)
def hinge_loss_multiclass(y_true_index, y_pred_logits):
"""
计算多分类Hinge损失(采用One-vs-Rest策略)。
参数:
y_true_index (int): 真实类别的索引(从0开始)
y_pred_logits (np.ndarray): 模型输出的logits向量(未经过softmax处理)
返回:
float: 多分类Hinge损失值
"""
# 删除真实类别对应的logit,获取其余类别的最大logit值
max_wrong = np.max(np.delete(y_pred_logits, y_true_index))
# 多分类Hinge损失公式:L = max(0, 1 + max_{j≠y_true} z_j - z_{y_true})
return np.maximum(0, 1 + max_wrong - y_pred_logits[y_true_index])
# 计算Hinge损失的导数(梯度)
def hinge_loss_binary_derivative(y_true, y_pred):
"""
计算二分类Hinge损失对模型输出的导数。
参数:
y_true (int): 真实标签,取值为-1或1
y_pred (float): 模型预测分数
返回:
float: 导数值
"""
# 当y_true * y_pred >= 1时,损失为0,导数也为0
# 当y_true * y_pred < 1时,损失为1 - y_true * y_pred,导数为-y_true
if y_true * y_pred >= 1:
return 0
else:
return -y_true
def hinge_loss_multiclass_derivative(y_true_index, y_pred_logits, delta=1e-5):
"""
计算多分类Hinge损失对logit的导数(使用数值微分法近似)。
参数:
y_true_index (int): 真实类别的索引
y_pred_logits (np.ndarray): 模型输出的logits向量
delta (float): 用于数值微分的微小扰动值
返回:
float: 第一个logit的导数值(固定其他logit为0)
"""
# 计算原始logits的损失值
loss_base = hinge_loss_multiclass(y_true_index, y_pred_logits)
# 对第一个logit添加微小扰动
logits_perturbed = y_pred_logits.copy()
logits_perturbed[y_true_index] += delta
# 计算扰动后的损失值
loss_perturbed = hinge_loss_multiclass(y_true_index, logits_perturbed)
# 使用中心差分法近似导数:(f(x+h) - f(x)) / h
return (loss_perturbed - loss_base) / delta
# 生成二分类Hinge损失可视化所需的数据
y_pred_binary = np.linspace(-3, 3, 1000) # 生成从-3到3的1000个等间距点作为模型输出值
# 计算y_true=1时的Hinge损失
loss_binary_1 = [hinge_loss_binary(1, y) for y in y_pred_binary]
# 计算y_true=-1时的Hinge损失
loss_binary_neg1 = [hinge_loss_binary(-1, y) for y in y_pred_binary]
# 计算y_true=1时的导数
grad_binary_1 = [hinge_loss_binary_derivative(1, y) for y in y_pred_binary]
# 计算y_true=-1时的导数
grad_binary_neg1 = [hinge_loss_binary_derivative(-1, y) for y in y_pred_binary]
# 生成多分类Hinge损失可视化所需的数据(固定其他两个logit为0)
y_pred_logits_multi = np.linspace(-5, 5, 1000) # 生成从-5到5的1000个等间距点作为第一个logit的值
loss_multiclass = [] # 存储多分类Hinge损失值
grad_multiclass = [] # 存储多分类Hinge损失的导数值
# 遍历每个logit值,计算对应的损失和导数
for z in y_pred_logits_multi:
logits = np.array([z, 0.0, 0.0]) # 固定其余两个logit为0,只改变第一个logit的值
loss = hinge_loss_multiclass(0, logits) # 计算多分类Hinge损失
grad = hinge_loss_multiclass_derivative(0, logits) # 计算导数
loss_multiclass.append(loss)
grad_multiclass.append(grad)
# 创建二分类Hinge损失和导数的图像
fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6)) # 创建包含两个子图的图像
# 绘制二分类Hinge损失曲线
ax1.plot(y_pred_binary, loss_binary_1, label='y=+1时的损失', color='#5DA5DA', linewidth=2)
ax1.plot(y_pred_binary, loss_binary_neg1, label='y=-1时的损失', color='#FAA43A', linewidth=2)
ax1.axvline(x=1, color='g', linestyle='--', alpha=0.5, label='间隔边界') # 标记间隔边界
ax1.axvline(x=-1, color='g', linestyle='--', alpha=0.5) # 标记间隔边界
ax1.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签
ax1.set_ylabel('Hinge Loss值', fontsize=12) # 设置y轴标签
ax1.set_title('二分类Hinge损失函数图像', fontsize=14) # 设置标题
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# 绘制二分类Hinge损失的导数曲线
ax2.plot(y_pred_binary, grad_binary_1, label='y=+1时的导数', color='#5DA5DA', linewidth=2)
ax2.plot(y_pred_binary, grad_binary_neg1, label='y=-1时的导数', color='#FAA43A', linewidth=2)
ax2.axvline(x=1, color='g', linestyle='--', alpha=0.5) # 标记间隔边界
ax2.axvline(x=-1, color='g', linestyle='--', alpha=0.5) # 标记间隔边界
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 标记y=0水平线
ax2.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签
ax2.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12) # 设置y轴标签
ax2.set_title('二分类Hinge损失函数的导数图像', fontsize=14) # 设置标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
plt.tight_layout() # 自动调整子图布局
plt.show() # 显示图像
# 创建多分类Hinge损失和导数的图像
fig2, (ax3, ax4) = plt.subplots(1, 2, figsize=(14, 6)) # 创建包含两个子图的图像
# 绘制多分类Hinge损失曲线
ax3.plot(y_pred_logits_multi, loss_multiclass, label='多分类Hinge损失', color='#60BD68', linewidth=2)
ax3.axvline(x=1, color='g', linestyle='--', alpha=0.5, label='正确分类且间隔充足') # 标记间隔边界
ax3.set_xlabel(r'logit $z_1$', fontsize=12) # 设置x轴标签
ax3.set_ylabel('Hinge Loss值', fontsize=12) # 设置y轴标签
ax3.set_title('多分类Hinge损失函数图像', fontsize=14) # 设置标题
ax3.legend(fontsize=12) # 添加图例
ax3.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# 绘制多分类Hinge损失的导数曲线
ax4.plot(y_pred_logits_multi, grad_multiclass, label='多分类Hinge导数', color='#F15854', linewidth=2)
ax4.axvline(x=1, color='g', linestyle='--', alpha=0.5) # 标记间隔边界
ax4.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 标记y=0水平线
ax4.set_xlabel(r'logit $z_1$', fontsize=12) # 设置x轴标签
ax4.set_ylabel(r'导数 $\partial L/\partial z_1$', fontsize=12) # 设置y轴标签
ax4.set_title('多分类Hinge损失函数的导数图像', fontsize=14) # 设置标题
ax4.legend(fontsize=12) # 添加图例
ax4.grid(True, linestyle='--', alpha=0.7) # 添加网格线
plt.tight_layout() # 自动调整子图布局
plt.show() # 显示图像
1.3 0-1损失(0-1 Loss)
数学公式:
L
=
{
0
,
if
y
^
=
y
1
,
otherwise
L = \begin{cases} 0, & \text{if } \hat{y} = y \\ 1, & \text{otherwise} \end{cases}
L={0,1,if y^=yotherwise
其中,
y
i
y_i
yi 是真实值,
y
^
i
\hat{y}_i
y^i 是预测值。
数学原理:
- 最直接的分类误差度量,对应分类准确率的相反数:
Accuracy = 1 − 1 n ∑ L i \text{Accuracy} = 1 - \frac{1}{n}\sum L_i Accuracy=1−n1∑Li - 不可导性:在 y ^ = y \hat{y}=y y^=y处梯度不存在,导致无法用梯度下降优化,因此实际中常用交叉熵或Hinge损失作为代理损失函数。
注意:
- 为什么不可导的0-1损失仍能作为损失函数?
0-1损失是分类任务的本质性损失函数,直接对应分类准确率的数学定义:
- 当预测类别与真实类别一致时损失为0,否则为1,完美契合分类任务的终极目标。
- 例如在二分类中,0-1损失为0表示预测完全正确,为1表示预测错误,没有中间状态。
- 0-1 损失就像一把 “终极标尺”,直接度量分类任务的最终目标,但由于其 “非黑即白” 的特性,无法指导模型优化过程。而交叉熵、Hinge 损失等可导函数,如同标尺的 “放大镜”,通过平滑的梯度信号引导模型逐步逼近 0-1 损失的最优解,形成 “优化 - 评估” 的完整闭环。
数学图像:
绘图代码:
import numpy as np # 导入NumPy库,用于数值计算(如数组操作、数学函数等)
import matplotlib.pyplot as plt # 导入Matplotlib绘图库,用于创建可视化图表
from matplotlib.ticker import FormatStrFormatter # 导入坐标轴刻度格式化工具
# 设置中文字体显示(确保图表中的中文能正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 设置默认字体为黑体
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题
# 定义0-1损失函数(分类任务的理论理想损失)
def zero_one_loss(y_true, y_pred):
"""
计算二分类0-1损失(即分类错误率)
参数:
y_true (int): 真实标签(取值为-1或1)
y_pred (float): 模型输出的原始分数(未经过激活函数处理)
返回:
int: 分类正确返回0,错误返回1
"""
# 核心逻辑:通过符号函数判断预测方向是否正确
return 0 if (np.sign(y_pred) == y_true) else 1
def zero_one_loss_derivative(y_true, y_pred):
"""
计算0-1损失的导数(理论上不可导,此处返回0作为示意)
参数:
y_true (int): 真实标签
y_pred (float): 模型输出分数
返回:
float: 恒定返回0(表示无法通过梯度下降优化)
"""
return 0.0 # 0-1损失在数学上不可导,故导数恒为0
# 生成模型输出数据(范围[-3, 3],共1000个采样点)
y_pred_binary = np.linspace(-3, 3, 1000)
# 计算不同真实标签下的0-1损失值
loss_01_1 = [zero_one_loss(1, y) for y in y_pred_binary] # 真实标签为1时的损失
loss_01_neg1 = [zero_one_loss(-1, y) for y in y_pred_binary] # 真实标签为-1时的损失
# 计算导数(均为0)
grad_01_1 = [zero_one_loss_derivative(1, y) for y in y_pred_binary]
grad_01_neg1 = [zero_one_loss_derivative(-1, y) for y in y_pred_binary]
# 创建可视化图像(1行2列子图布局,总尺寸14x6英寸)
fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# 绘制0-1损失函数曲线(使用step函数绘制阶梯图,符合离散损失特性)
ax1.step(y_pred_binary, loss_01_1, where='mid', label='y=+1时的损失', color='#5DA5DA', linewidth=2)
ax1.step(y_pred_binary, loss_01_neg1, where='mid', label='y=-1时的损失', color='#FAA43A', linewidth=2)
ax1.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='分类边界') # 标记分类决策边界
ax1.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签(使用LaTeX格式)
ax1.set_ylabel('0-1 Loss值', fontsize=12) # 设置y轴标签
ax1.set_title('二分类0-1损失函数图像', fontsize=14) # 设置图表标题
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# 绘制0-1损失的导数曲线(恒为0)
ax2.plot(y_pred_binary, grad_01_1, label='y=+1时的导数', color='#5DA5DA', linewidth=2)
ax2.plot(y_pred_binary, grad_01_neg1, label='y=-1时的导数', color='#FAA43A', linewidth=2)
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 标记y=0水平线
ax2.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='分类边界') # 标记分类边界
ax2.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签
ax2.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12) # 设置y轴标签
ax2.set_title('二分类0-1损失函数的导数图像(0-1 Loss不可导,故为0)', fontsize=14) # 设置标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
plt.tight_layout() # 自动调整子图布局
plt.show() # 显示图像
二、回归任务的损失函数
2.1 均方误差(MSE - Mean Squared Error)
数学公式:
M
S
E
=
1
n
∑
i
=
1
n
(
y
i
−
y
^
i
)
2
MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2
MSE=n1i=1∑n(yi−y^i)2
其中,
y
i
y_i
yi 是真实值,
y
^
i
\hat{y}_i
y^i 是预测值。
数学原理:
- 最小二乘估计:假设预测误差服从高斯分布,根据最大似然估计,最优参数应使MSE最小。对于线性模型
y
=
w
T
x
+
b
+
ϵ
y = w^Tx + b + \epsilon
y=wTx+b+ϵ,其中
ϵ
∼
N
(
0
,
σ
2
)
\epsilon \sim \mathcal{N}(0, \sigma^2)
ϵ∼N(0,σ2),似然函数为:
L ( w , b ) = ∏ 1 2 π σ e − ( y i − w T x i − b ) 2 2 σ 2 L(w,b) = \prod \frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(y_i - w^Tx_i - b)^2}{2\sigma^2}} L(w,b)=∏2πσ1e−2σ2(yi−wTxi−b)2
最大化似然等价于最小化 ∑ ( y i − w T x i − b ) 2 \sum (y_i - w^Tx_i - b)^2 ∑(yi−wTxi−b)2,即MSE的核心部分。 - 梯度特性:对参数
w
w
w的梯度为:
∇ w M S E = 2 n ∑ ( y ^ i − y i ) x i \nabla_w MSE = \frac{2}{n}\sum (\hat{y}_i - y_i)x_i ∇wMSE=n2∑(y^i−yi)xi
便于梯度下降优化。
数学图像:
绘图代码:
import numpy as np # 导入NumPy库,用于进行数值计算(如数组生成、数学运算等)
import matplotlib.pyplot as plt # 导入Matplotlib的pyplot模块,用于创建可视化图表
from matplotlib.ticker import FormatStrFormatter # 导入坐标轴刻度格式化工具,用于自定义刻度显示格式
# 设置中文字体显示(确保图表中的中文能正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 将默认字体设置为黑体,支持中文显示
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常问题,确保负数正确显示
# 定义均方误差(MSE)损失函数
def mse_loss(y_true, y_pred):
"""
计算单个样本的均方误差损失(Mean Squared Error Loss)
参数:
y_true (float): 样本的真实值(任意实数)
y_pred (float): 模型对该样本的预测值
返回:
float: 均方误差损失值,计算公式为 (真实值-预测值)的平方
"""
return (y_true - y_pred) ** 2 # 均方误差公式:L = (y_true - y_pred)²
def mse_derivative(y_true, y_pred):
"""
计算均方误差损失对预测值的导数(用于梯度下降优化)
参数:
y_true (float): 样本的真实值
y_pred (float): 模型对该样本的预测值
返回:
float: 导数值,计算公式为 2*(预测值-真实值)
"""
return 2 * (y_pred - y_true) # 导数推导:dL/dy_pred = 2*(y_pred - y_true)
# 生成模型预测值数据(用于可视化)
y_pred_binary = np.linspace(-3, 3, 1000) # 在[-3, 3]区间生成1000个等间距的预测值点
# 固定真实值为y=0,计算对应的MSE损失和导数
loss_mse_0 = [mse_loss(0, y) for y in y_pred_binary] # 真实值y=0时的MSE损失
grad_mse_0 = [mse_derivative(0, y) for y in y_pred_binary] # 真实值y=0时的导数
# 创建可视化图像(1行2列子图布局,总尺寸14英寸宽、6英寸高)
fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# --------------------- 左侧子图:MSE损失函数图像 ---------------------
ax1.plot(y_pred_binary, loss_mse_0, label='y=0时的损失', color='#5DA5DA', linewidth=2) # 绘制损失曲线
ax1.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='最小损失点 y=0') # 标记最小损失位置
ax1.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签(使用LaTeX格式渲染数学符号)
ax1.set_ylabel('MSE损失值', fontsize=12) # 设置y轴标签
ax1.set_title('均方误差(MSE)损失函数图像(y=0)', fontsize=14) # 设置图表标题
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线(虚线,透明度70%)
# --------------------- 右侧子图:MSE导数图像 ---------------------
ax2.plot(y_pred_binary, grad_mse_0, label='y=0时的导数', color='#FAA43A', linewidth=2) # 绘制导数曲线
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 标记y=0水平线(导数为0的位置)
ax2.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='最小损失点 y=0') # 标记最小损失位置
ax2.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签
ax2.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12) # 设置y轴标签(导数符号使用LaTeX)
ax2.set_title('均方误差(MSE)损失函数的导数图像(y=0)', fontsize=14) # 设置图表标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
plt.tight_layout() # 自动调整子图布局,避免标签重叠
plt.show() # 显示图像
2.2 平均绝对误差(MAE - Mean Absolute Error)
数学公式:
M
A
E
=
1
n
∑
i
=
1
n
∣
y
i
−
y
^
i
∣
MAE = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i|
MAE=n1i=1∑n∣yi−y^i∣
其中,
y
i
y_i
yi 是真实值,
y
^
i
\hat{y}_i
y^i 是预测值。
数学原理:
- 鲁棒性来源:假设误差服从拉普拉斯分布 p ( ϵ ) = 1 2 σ e − ∣ ϵ ∣ σ p(\epsilon) = \frac{1}{2\sigma}e^{-\frac{|\epsilon|}{\sigma}} p(ϵ)=2σ1e−σ∣ϵ∣,最大似然估计对应最小化MAE。拉普拉斯分布比高斯分布有更厚的尾部,因此对异常值不敏感。
- 梯度特性:在 y i ≠ y ^ i y_i \neq \hat{y}_i yi=y^i时梯度为 ± 1 \pm1 ±1,在 y i = y ^ i y_i = \hat{y}_i yi=y^i处不可导,实际中常用平滑近似(如Huber损失)。
数学图像:
import numpy as np # 导入NumPy库,用于高效处理数值数组和数学运算
import matplotlib.pyplot as plt # 导入Matplotlib绘图库,用于创建可视化图表
from matplotlib.ticker import FormatStrFormatter # 导入坐标轴刻度格式化工具,用于自定义刻度显示格式
# 设置中文字体显示(确保图表中的中文能正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 将默认字体设置为黑体,支持中文显示
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常问题,确保负数正确显示
# 平均绝对误差(MAE)函数定义
def mae_loss(y_true, y_pred):
"""
计算单个样本的平均绝对误差损失(Mean Absolute Error Loss)
参数:
y_true (float): 样本的真实值(任意实数)
y_pred (float): 模型对该样本的预测值
返回:
float: 平均绝对误差损失值,计算公式为 |真实值-预测值|
"""
return np.abs(y_true - y_pred) # MAE公式:L = |y_true - y_pred|,绝对误差避免正负误差抵消
def mae_derivative(y_true, y_pred):
"""
计算MAE损失对预测值的导数(处理不可导点的特殊情况)
参数:
y_true (float): 样本的真实值
y_pred (float): 模型对该样本的预测值
返回:
float: 导数值(分段函数)
- 当预测值>真实值时,导数为1(推动预测值减小)
- 当预测值<真实值时,导数为-1(推动预测值增大)
- 当预测值=真实值时,导数为0(不可导点的平滑处理)
"""
if y_pred > y_true:
return 1.0 # 预测值偏大时,导数为正,指示参数更新应减小预测值
elif y_pred < y_true:
return -1.0 # 预测值偏小时,导数为负,指示参数更新应增大预测值
else:
return 0.0 # 在y_pred=y_true处(不可导点),人为设定导数为0(实际不可导)
# 生成模型预测值数据(在[-3, 3]区间生成1000个等间距点,覆盖正负预测场景)
y_pred_binary = np.linspace(-3, 3, 1000)
# 固定真实值为y=0,计算对应的MAE损失和导数(聚焦于零点附近的损失特性)
loss_mae_0 = [mae_loss(0, y) for y in y_pred_binary] # 真实值y=0时的MAE损失
grad_mae_0 = [mae_derivative(0, y) for y in y_pred_binary] # 真实值y=0时的导数
# 创建可视化图像(1行2列子图布局,总尺寸14英寸宽、6英寸高)
fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# --------------------- 左侧子图:MAE损失函数图像 ---------------------
ax1.plot(y_pred_binary, loss_mae_0, label='y=0时的损失', color='#5DA5DA', linewidth=2) # 绘制MAE曲线
ax1.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='最小损失点 y=0') # 标记损失最小值位置
ax1.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签(使用LaTeX格式渲染数学符号)
ax1.set_ylabel('MAE损失值', fontsize=12) # 设置y轴标签
ax1.set_title('平均绝对误差(MAE)损失函数图像(y=0)', fontsize=14) # 设置图表标题
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线(虚线,透明度70%)
# --------------------- 右侧子图:MAE导数图像 ---------------------
ax2.plot(y_pred_binary, grad_mae_0, label='y=0时的导数', color='#FAA43A', linewidth=2) # 绘制导数曲线
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 标记y=0水平线(导数为0的理论位置)
ax2.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='不可导点 y=0') # 标记不可导点
ax2.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签
ax2.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12) # 设置y轴标签(导数符号使用LaTeX)
ax2.set_title('平均绝对误差(MAE)损失函数的导数图像(y=0)', fontsize=14) # 设置图表标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
plt.tight_layout() # 自动调整子图布局,避免标签重叠
plt.show() # 显示图像
2.3 根均方误差(RMSE - Root Mean Squared Error)
数学公式:
R
M
S
E
=
M
S
E
=
1
n
∑
i
=
1
n
(
y
i
−
y
^
i
)
2
RMSE = \sqrt{MSE} = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2}
RMSE=MSE=n1i=1∑n(yi−y^i)2
其中,
y
i
y_i
yi 是真实值,
y
^
i
\hat{y}_i
y^i 是预测值。
数学原理:
- 量纲一致性:与目标变量 y y y具有相同量纲,例如 y y y为房价(万元)时,RMSE直接表示平均误差万元数,而MSE的量纲为万元²,解释性较差。
- 几何意义:RMSE等价于预测值与真实值的欧氏距离均值,反映模型预测的整体偏差程度,既保留了MSE对异常值的敏感,又不会使数值过大。
数学图像:
在单个样本(只有一个y)的情况下RMSE和MAE的图像一致
在两个样本(
y
1
=
0
,
y
2
=
2
y_1=0,y_2=2
y1=0,y2=2)情况下有区别
import numpy as np # 导入NumPy库,用于数值计算(数组操作、数学函数等)
import matplotlib.pyplot as plt # 导入Matplotlib绘图库,用于创建可视化图表
from matplotlib.ticker import FormatStrFormatter # 导入坐标轴刻度格式化工具,用于自定义刻度显示
# 设置中文字体显示(确保图表中的中文能正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 将默认字体设置为黑体,支持中文显示
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常问题,确保负数正确显示
# 根均方误差(RMSE)相关函数定义
def rmse_loss(y_true, y_pred):
"""
计算单样本的RMSE损失(注:此处简化为绝对误差,实际RMSE应为平方误差的平方根)
参数:
y_true (float): 真实值
y_pred (float): 预测值
返回:
float: 简化后的RMSE损失(此处等价于绝对误差)
"""
return np.abs(y_true - y_pred) # 注:严格来说单样本RMSE应为sqrt((y_true-y_pred)^2),但此处为对比MAE简化
def rmse_derivative(y_true, y_pred):
"""
计算RMSE损失的导数(简化为符号函数,忽略平方根导数)
参数:
y_true (float): 真实值
y_pred (float): 预测值
返回:
float: 导数符号(+1/-1/0),未考虑平方根导数
"""
if y_pred > y_true:
return 1.0 # 预测值偏大时,导数为正
elif y_pred < y_true:
return -1.0 # 预测值偏小时,导数为负
else:
return 0.0 # 预测正确时导数为0(不可导点处理)
# 平均绝对误差(MAE)与多样本RMSE函数
def mae_loss(y_true, y_pred):
"""
计算多样本的MAE损失(所有样本使用同一预测值)
参数:
y_true (np.ndarray): 真实值数组
y_pred (float): 统一预测值
返回:
float: 平均绝对误差
"""
return np.mean(np.abs(y_true - y_pred)) # 计算绝对误差的平均值
def rmse_loss_multi(y_true, y_pred):
"""
计算多样本的RMSE损失(所有样本使用同一预测值)
参数:
y_true (np.ndarray): 真实值数组
y_pred (float): 统一预测值
返回:
float: 均方根误差
"""
return np.sqrt(np.mean((y_true - y_pred) ** 2)) # 计算平方误差的均值再开根号
# 数值微分法计算导数(通用方法)
def loss_derivative(loss_func, y_true, y_pred, delta=1e-5):
"""
使用数值微分法近似计算损失函数的导数
参数:
loss_func: 损失函数对象
y_true: 真实值(数组或标量)
y_pred: 预测值
delta: 扰动值(用于差分计算)
返回:
float: 导数近似值
"""
loss_base = loss_func(y_true, y_pred) # 原始损失值
loss_perturbed = loss_func(y_true, y_pred + delta) # 扰动后的损失值
return (loss_perturbed - loss_base) / delta # 中心差分公式近似导数
# --------------------- 数据准备:单样本RMSE ---------------------
y_pred_binary = np.linspace(-3, 3, 1000) # 生成[-3,3]区间的1000个预测值点
loss_rmse_0 = [rmse_loss(0, y) for y in y_pred_binary] # 真实值y=0时的RMSE损失(简化为绝对误差)
grad_rmse_0 = [rmse_derivative(0, y) for y in y_pred_binary] # 对应的导数
# --------------------- 数据准备:双样本MAE与RMSE ---------------------
y_pred_range = np.linspace(-3, 3, 1000) # 生成预测值范围
y_true = np.array([0, 2]) # 定义两个样本的真实值[0, 2]
# 计算多样本损失
mae_losses = [mae_loss(y_true, y) for y in y_pred_range] # 双样本MAE损失
rmse_losses = [rmse_loss_multi(y_true, y) for y in y_pred_range] # 双样本RMSE损失
# 计算多样本导数(使用数值微分法)
mae_grads = [loss_derivative(mae_loss, y_true, y) for y in y_pred_range] # MAE导数
rmse_grads = [loss_derivative(rmse_loss_multi, y_true, y) for y in y_pred_range] # RMSE导数
# --------------------- 图像1:单样本RMSE损失与导数 ---------------------
fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6)) # 创建1行2列子图
# 左侧子图:单样本RMSE损失曲线
ax1.plot(y_pred_binary, loss_rmse_0, label='y=0时的损失', color='#5DA5DA', linewidth=2)
ax1.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='最小损失点') # 标记最优预测点
ax1.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12) # 设置x轴标签(LaTeX格式)
ax1.set_ylabel('RMSE损失值', fontsize=12)
ax1.set_title('根均方误差(RMSE)损失函数图像(单样本)', fontsize=14)
ax1.legend(fontsize=12)
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# 右侧子图:单样本RMSE导数曲线
ax2.plot(y_pred_binary, grad_rmse_0, label='y=0时的导数', color='#FAA43A', linewidth=2)
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 标记导数零点
ax2.axvline(x=0, color='g', linestyle='--', alpha=0.5, label='最小损失点')
ax2.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12)
ax2.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12)
ax2.set_title('RMSE损失函数的导数图像(单样本)', fontsize=14)
ax2.legend(fontsize=12)
ax2.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout() # 自动调整布局
plt.show() # 显示图像
# --------------------- 图像2:双样本MAE与RMSE对比 ---------------------
fig2, ((ax3, ax4), (ax5, ax6)) = plt.subplots(2, 2, figsize=(16, 10)) # 创建2x2子图
# 左上子图:双样本MAE损失曲线
ax3.plot(y_pred_range, mae_losses, label='MAE', color='#5DA5DA', linewidth=2)
ax3.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12)
ax3.set_ylabel('损失值 L', fontsize=12)
ax3.set_title('平均绝对误差(MAE)损失函数图像(双样本)', fontsize=14)
ax3.legend(fontsize=12)
ax3.grid(True, linestyle='--', alpha=0.7)
# 右上子图:双样本RMSE损失曲线
ax4.plot(y_pred_range, rmse_losses, label='RMSE', color='#FAA43A', linewidth=2)
ax4.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12)
ax4.set_ylabel('损失值 L', fontsize=12)
ax4.set_title('根均方误差(RMSE)损失函数图像(双样本)', fontsize=14)
ax4.legend(fontsize=12)
ax4.grid(True, linestyle='--', alpha=0.7)
# 左下子图:双样本MAE导数曲线
ax5.plot(y_pred_range, mae_grads, label='MAE导数', color='#5DA5DA', linewidth=2)
ax5.axhline(y=0, color='k', linestyle='--', alpha=0.5) # 标记导数零点
ax5.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12)
ax5.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12)
ax5.set_title('MAE损失函数的导数图像(双样本)', fontsize=14)
ax5.legend(fontsize=12)
ax5.grid(True, linestyle='--', alpha=0.7)
# 右下子图:双样本RMSE导数曲线
ax6.plot(y_pred_range, rmse_grads, label='RMSE导数', color='#FAA43A', linewidth=2)
ax6.axhline(y=0, color='k', linestyle='--', alpha=0.5)
ax6.set_xlabel(r'模型输出 $\hat{y}$', fontsize=12)
ax6.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12)
ax6.set_title('RMSE损失函数的导数图像(双样本)', fontsize=14)
ax6.legend(fontsize=12)
ax6.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout() # 自动调整布局
plt.show() # 显示图像
2.4 最小二乘法(Least Squares Method)
数学公式:
min
∑
i
=
1
n
(
y
i
−
y
^
i
)
2
\min \sum_{i=1}^{n} (y_i - \hat{y}_i)^2
mini=1∑n(yi−y^i)2
其中,
y
i
y_i
yi 是真实值,
y
^
i
\hat{y}_i
y^i 是预测值。
数学原理:
- 与MSE的关系:MSE最小化MSE等价于使用最小二乘法。因此,最小二乘法可以看作是MSE损失函数的优化过程。
- **几何解释:在高维空间中,最小二乘法等价于寻找一个超平面,使得所有数据点到该超平面的垂直距离平方和最小。这个超平面就是模型的最优解。
- 统计特性:当误差服从正态分布时,最小二乘法的解等价于极大似然估计(MLE),具有无偏性和最小方差性。
- 解析解:对于线性回归模型 (y = X\theta + \epsilon),参数的最优解可通过正规方程直接计算:
( θ ^ = ( X T X ) − 1 X T y ) (\hat{\theta} = (X^T X)^{-1} X^T y) (θ^=(XTX)−1XTy)
其中 X X X 是设计矩阵, y y y 是目标向量。
三、深度学习特有的损失函数解析
3.1 焦点损失(Focal Loss)
数学公式:
L
=
−
∑
k
=
1
K
y
k
⋅
(
1
−
p
^
k
)
γ
⋅
log
p
^
k
L = - \sum_{k=1}^K y_k \cdot (1-\hat{p}_k)^\gamma \cdot \log\hat{p}_k
L=−k=1∑Kyk⋅(1−p^k)γ⋅logp^k
其中
γ
≥
0
\gamma \geq 0
γ≥0为聚焦参数,通常取2;
p
^
k
\hat{p}_k
p^k为第
k
k
k类的预测概率。
数学原理:
-
类别不平衡解决方案:通过 ( 1 − p ^ k ) γ (1-\hat{p}_k)^\gamma (1−p^k)γ降低易分类样本的权重:
- 当 p ^ k ≈ 1 \hat{p}_k \approx 1 p^k≈1(易分类样本),权重 ( 1 − p ^ k ) γ ≈ 0 (1-\hat{p}_k)^\gamma \approx 0 (1−p^k)γ≈0;
- 当 p ^ k ≈ 0 \hat{p}_k \approx 0 p^k≈0(难分类样本),权重 ( 1 − p ^ k ) γ ≈ 1 (1-\hat{p}_k)^\gamma \approx 1 (1−p^k)γ≈1。
-
α平衡变种:
L = − ∑ α k ⋅ y k ⋅ ( 1 − p ^ k ) γ ⋅ log p ^ k L = - \sum \alpha_k \cdot y_k \cdot (1-\hat{p}_k)^\gamma \cdot \log\hat{p}_k L=−∑αk⋅yk⋅(1−p^k)γ⋅logp^k
其中 α k \alpha_k αk为类别权重,缓解长尾分布问题。焦点损失就像给模型装了一个 “自动过滤系统”:对胸有成竹的简单题(易分类样本)减少关注,对犹豫不决的难题(难分类样本)集中火力,最终让模型在各类别上都能考出高分。
数学图像:
绘图代码:
import numpy as np # 导入NumPy库,用于数值计算(如数组操作、数学函数等)
import matplotlib.pyplot as plt # 导入Matplotlib库,用于创建可视化图表
# 设置中文字体显示(确保图表中的中文能正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 将默认字体设置为黑体,支持中文显示
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常问题,确保负数正确显示
def focal_loss(y_true, p_pred, gamma=2, alpha=0.25):
"""
计算二分类场景下的Focal Loss(焦点损失)
参数:
y_true (int): 真实标签(0或1)
p_pred (float): 模型预测的正类概率值(范围[0,1])
gamma (float): 聚焦参数,控制易分类样本的权重衰减速率(默认2)
alpha (float): 类别平衡参数,缓解正负样本数量不平衡(默认0.25,正样本权重)
返回:
float: Focal Loss损失值
"""
epsilon = 1e-7 # 防止log(0)的极小值
p_pred = np.clip(p_pred, epsilon, 1 - epsilon) # 裁剪概率值到[epsilon, 1-epsilon]区间
if y_true == 1:
# 正样本(y=1)的损失计算:-α(1-p)^γ log(p)
return -alpha * ((1 - p_pred) ** gamma) * np.log(p_pred)
else:
# 负样本(y=0)的损失计算:-(1-α)p^γ log(1-p)
return -(1 - alpha) * (p_pred ** gamma) * np.log(1 - p_pred)
def loss_derivative(loss_func, y_true, p_pred, delta=1e-6):
"""
使用中心差分法近似计算损失函数的导数(数值微分)
参数:
loss_func: 损失函数对象(如focal_loss)
y_true (int): 真实标签
p_pred (float): 预测概率
delta (float): 用于差分计算的微小扰动值
返回:
float: 损失函数在p_pred处的导数值
"""
# 中心差分公式:f'(x) ≈ [f(x+δ) - f(x-δ)] / (2δ)
return (loss_func(y_true, p_pred + delta) - loss_func(y_true, p_pred - delta)) / (2 * delta)
# 生成预测概率数据(范围[0.001, 0.999],避免接近0或1导致log溢出)
p_range = np.linspace(0.001, 0.999, 500)
# 固定Focal Loss超参数
gamma = 2 # 聚焦强度参数
alpha = 0.25 # 正样本权重参数
# 创建简化版Focal Loss函数(固定gamma和alpha,仅保留y_true和p_pred参数)
focal_loss_fixed = lambda y_true, p_pred: focal_loss(y_true, p_pred, gamma=gamma, alpha=alpha)
# 计算不同预测概率下的损失值
loss_positive = [focal_loss_fixed(1, p) for p in p_range] # 真实标签为1时的损失
loss_negative = [focal_loss_fixed(0, p) for p in p_range] # 真实标签为0时的损失
# 计算损失函数的导数值(使用数值微分法)
grad_positive = [loss_derivative(focal_loss_fixed, 1, p) for p in p_range] # 正样本损失的导数
grad_negative = [loss_derivative(focal_loss_fixed, 0, p) for p in p_range] # 负样本损失的导数
# 创建包含两个子图的图像(上下排列,共享x轴)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
# --------------------- 上侧子图:Focal Loss损失曲线 ---------------------
ax1.plot(p_range, loss_positive, label='真实标签 y=1', color='#5DA5DA', linewidth=2) # 正样本损失曲线
ax1.plot(p_range, loss_negative, label='真实标签 y=0', color='#FAA43A', linewidth=2) # 负样本损失曲线
ax1.axvline(x=0.5, color='g', linestyle='--', alpha=0.5, label=r'决策边界 $\hat{p}=0.5$') # 标记决策边界
ax1.set_ylabel('Focal Loss 值', fontsize=12) # 设置y轴标签
ax1.set_title(rf'二分类 Focal Loss($\gamma={gamma}, \alpha={alpha}$)', fontsize=14) # 设置标题(包含参数)
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# --------------------- 下侧子图:Focal Loss导数曲线 ---------------------
ax2.plot(p_range, grad_positive, label='y=1 时的导数', color='#5DA5DA', linewidth=2) # 正样本导数曲线
ax2.plot(p_range, grad_negative, label='y=0 时的导数', color='#FAA43A', linewidth=2) # 负样本导数曲线
ax2.axvline(x=0.5, color='g', linestyle='--', alpha=0.5) # 标记决策边界
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5, label='零导数线') # 标记导数为0的水平线
ax2.set_xlabel(r'模型预测概率 $\hat{p}$', fontsize=12) # 设置x轴标签(使用LaTeX格式)
ax2.set_ylabel(r'导数 $\partial L/\partial \hat{p}$', fontsize=12) # 设置y轴标签(导数符号)
ax2.set_title('Focal Loss 关于预测概率的导数', fontsize=14) # 设置标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# 自动调整图像布局并显示
plt.tight_layout() # 自动调整子图布局,避免标签重叠
plt.show() # 显示图像
3.2 Dice损失(Dice Loss)
数学公式:
L
=
1
−
2
∑
i
=
1
n
y
i
y
^
i
+
ϵ
∑
i
=
1
n
y
i
+
∑
i
=
1
n
y
^
i
+
ϵ
L = 1 - \frac{2\sum_{i=1}^n y_i\hat{y}_i + \epsilon}{\sum_{i=1}^n y_i + \sum_{i=1}^n \hat{y}_i + \epsilon}
L=1−∑i=1nyi+∑i=1ny^i+ϵ2∑i=1nyiy^i+ϵ
其中
y
i
y_i
yi 为二值化真实标签,
y
^
i
\hat{y}_i
y^i 为预测概率(通常经sigmoid后阈值化),
ϵ
\epsilon
ϵ 为平滑项(避免分母为0)。
数学原理:
- Dice系数:衡量两个集合的重叠程度,公式为:
Dice = 2 ∣ A ∩ B ∣ ∣ A ∣ + ∣ B ∣ \text{Dice} = \frac{2|A \cap B|}{|A| + |B|} Dice=∣A∣+∣B∣2∣A∩B∣
在语义分割中, A A A 为真实目标区域, B B B 为预测目标区域,Dice系数越大表示重叠度越高。 - 梯度特性:对
y
^
i
\hat{y}_i
y^i 的梯度为:
∇ y ^ i L = 2 ( 1 − Dice ) ( y i − y ^ i ) ( ∑ y i + ∑ y ^ i + ϵ ) 2 \nabla_{\hat{y}_i} L = \frac{2(1-\text{Dice})(y_i - \hat{y}_i)}{(\sum y_i + \sum \hat{y}_i + \epsilon)^2} ∇y^iL=(∑yi+∑y^i+ϵ)22(1−Dice)(yi−y^i)
当预测与真实差异大时梯度更大,加速收敛。
Dice Loss 就像一个 “专注小目标的裁判”,只盯着模型有没有精准找到少量的目标区域(如肿瘤、细胞),不管背景多大,通过计算预测与真实的重叠率来打分,错得越远惩罚越重,特别适合解决 “大海捞针” 式的分割问题。
数学图像:
绘图代码:
import numpy as np # 导入NumPy库,用于数值计算(数组操作、数学运算等)
import matplotlib.pyplot as plt # 导入Matplotlib库,用于创建可视化图表
# 设置中文字体显示(确保图表中的中文能正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 将默认字体设置为黑体,支持中文显示
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常问题,确保负数正确显示
# 定义Dice Loss损失函数
def dice_loss(y_true, y_pred, epsilon=1e-7):
"""
计算Dice Loss(适用于二分类语义分割任务的损失函数)
参数:
y_true (np.ndarray): 真实标签数组(二值化,0表示背景,1表示前景)
y_pred (float): 模型预测的概率值(将其扩展为与y_true同形状的数组)
epsilon (float): 平滑项,防止分母为0的数值问题
返回:
float: Dice Loss值,计算公式为1 - Dice系数
"""
# 将单一预测概率扩展为与真实标签同形状的数组(假设所有像素预测概率相同)
y_pred_array = np.full_like(y_true, y_pred)
# 计算交集部分:2*真实前景与预测前景的重叠区域 + 平滑项
intersection = 2 * np.sum(y_true * y_pred_array) + epsilon
# 计算分母:真实前景像素数 + 预测前景像素数 + 平滑项
denominator = np.sum(y_true) + np.sum(y_pred_array) + epsilon
# Dice系数 = 2*交集/(真实+预测),损失为1 - Dice系数
return 1 - intersection / denominator
# 定义损失函数导数计算函数
def loss_derivative(loss_func, y_true, p_pred, delta=1e-6):
"""
使用中心差分法近似计算损失函数的导数(数值微分)
参数:
loss_func: 损失函数对象(如dice_loss)
y_true (np.ndarray): 真实标签数组
p_pred (float): 预测概率
delta (float): 用于差分计算的微小扰动值
返回:
float: 损失函数在p_pred处的导数值
"""
# 中心差分公式:f'(x) ≈ [f(x+δ) - f(x-δ)] / (2δ),比单边差分更精确
return (loss_func(y_true, p_pred + delta) - loss_func(y_true, p_pred - delta)) / (2 * delta)
# 固定真实标签(示例:5个像素中3个前景(1)、2个背景(0))
y_true = np.array([1, 1, 1, 0, 0])
# 生成预测概率数据(范围[0,1],共500个点,覆盖所有可能的预测情况)
p_range = np.linspace(0, 1, 500)
# 计算不同预测概率下的Dice Loss值
loss_dice = [dice_loss(y_true, p) for p in p_range]
# 计算Dice Loss关于预测概率的导数值(使用数值微分法)
grad_dice = [loss_derivative(dice_loss, y_true, p) for p in p_range]
# 创建包含两个子图的图像(上下排列,共享x轴以便对比)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
# --------------------- 上侧子图:Dice Loss损失曲线 ---------------------
ax1.plot(p_range, loss_dice, label='Dice Loss', color='#5DA5DA', linewidth=2) # 绘制损失曲线
# 标记真实标签的平均值(前景像素占比60%),作为预测的理论最优值
ax1.axvline(x=y_true.mean(), color='g', linestyle='--', alpha=0.5, label=r'真实平均 $\bar{y}$')
ax1.set_ylabel('Loss值', fontsize=12) # 设置y轴标签
ax1.set_title('Dice Loss关于预测概率的变化曲线', fontsize=14) # 设置标题
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# --------------------- 下侧子图:Dice Loss导数曲线 ---------------------
ax2.plot(p_range, grad_dice, label='导数', color='#FAA43A', linewidth=2) # 绘制导数曲线
ax2.axvline(x=y_true.mean(), color='g', linestyle='--', alpha=0.5, label=r'真实平均 $\bar{y}$') # 标记理论最优点
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5, label='零导数线') # 标记导数为0的位置
ax2.set_xlabel(r'模型预测概率 $\hat{y}$', fontsize=12) # 设置x轴标签(使用LaTeX格式)
ax2.set_ylabel(r'导数 $\partial L/\partial \hat{y}$', fontsize=12) # 设置y轴标签(导数符号)
ax2.set_title('Dice Loss关于预测概率的导数图像', fontsize=14) # 设置标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# 自动调整图像布局并显示
plt.tight_layout() # 自动调整子图布局,避免标签重叠
plt.show() # 显示图像
3.3 三元组损失(Triplet Loss)
数学公式:
L
=
m
a
x
(
0
,
d
(
x
a
,
x
p
)
−
d
(
x
a
,
x
n
)
+
m
a
r
g
i
n
)
L = max(0, d(x^a, x^p) - d(x^a, x^n) + margin)
L=max(0,d(xa,xp)−d(xa,xn)+margin)
其中
x
a
x^a
xa 为锚样本(Anchor),
x
p
x^p
xp 为正样本(Positive,同类别),
x
n
x^n
xn 为负样本(Negative,不同类别),
d
(
⋅
)
d(\cdot)
d(⋅) 为距离函数(如欧氏距离或余弦距离,在公式中即为
x
a
x^a
xa 和
x
p
x^p
xp 之间的距离),
m
a
r
g
i
n
margin
margin 为间隔参数。
数学原理:
- 三元组约束:要求锚样本与正样本的距离小于与负样本的距离至少
m
a
r
g
i
n
margin
margin:
d ( x a , x p ) + m a r g i n < d ( x a , x n ) d(x^a, x^p) + margin < d(x^a, x^n) d(xa,xp)+margin<d(xa,xn)
若不满足则产生损失,推动同类样本聚集、异类样本分离。 - 特征嵌入学习:常用于人脸识别、推荐系统等需要学习样本相似度的场景,使嵌入空间中同类样本的距离小于异类样本。
三元组损失就像一个社交礼仪裁判,强制要求模型把同类样本(自己人)的距离缩得比异类样本(陌生人)近至少一个安全距离,否则就扣分,最终让模型学会用特征空间的距离来区分 “自己人” 和 “陌生人”。
数学图像:
绘图代码:
import numpy as np # 导入NumPy库,用于数值计算(如数组操作、数学运算等)
import matplotlib.pyplot as plt # 导入Matplotlib库,用于创建可视化图表
# 设置中文字体和负号显示(确保图表中的中文和负号正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 将默认字体设置为黑体,支持中文显示
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常问题,确保负数正确显示
# --------------------- Triplet Loss函数定义 ---------------------
def triplet_loss(d_ap, d_an, margin=1.0):
"""
计算三元组损失(Triplet Loss),用于特征嵌入学习
参数:
d_ap (float): 锚点(Anchor)与正样本(Positive)之间的距离(同类样本)
d_an (float): 锚点与负样本(Negative)之间的距离(异类样本)
margin (float): 间隔参数,控制同类与异类样本的距离边界
返回:
float: Triplet Loss值,公式为 max(0, d_ap - d_an + margin)
"""
# 核心公式:当d_an <= d_ap + margin时,产生损失;否则损失为0
# 目标:迫使d_an > d_ap + margin(负样本距离大于正样本距离+间隔)
return np.maximum(0, d_ap - d_an + margin)
# --------------------- 数值微分函数(计算导数) ---------------------
def loss_derivative(loss_func, d_ap, d_an, margin=1.0, delta=1e-6):
"""
使用中心差分法近似计算损失函数关于d_an的导数
参数:
loss_func: 损失函数对象(如triplet_loss)
d_ap (float): 锚点与正样本的距离(固定值)
d_an (float): 锚点与负样本的距离(变量)
margin (float): 间隔参数
delta (float): 用于微分的微小扰动值(避免除以0)
返回:
float: 导数近似值,公式为 [f(d_an+δ) - f(d_an-δ)] / (2δ)
"""
# 中心差分法比单边差分更精确,能减少截断误差
return (loss_func(d_ap, d_an + delta, margin) - loss_func(d_ap, d_an - delta, margin)) / (2 * delta)
# --------------------- 数据准备与计算 ---------------------
d_ap = 0.5 # 固定锚点与正样本的距离为0.5(假设同类样本距离较近)
d_an_range = np.linspace(0, 3, 500) # 负样本距离范围[0,3],生成500个等间距点
margin = 1.0 # 设置间隔参数为1.0
# 计算不同负样本距离下的损失值
loss_values = [triplet_loss(d_ap, d_an, margin) for d_an in d_an_range]
# 计算损失函数关于d_an的导数值(使用数值微分)
grad_values = [loss_derivative(triplet_loss, d_ap, d_an, margin) for d_an in d_an_range]
# --------------------- 可视化绘制 ---------------------
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True) # 创建上下排列的两个子图
# --------------------- 上侧子图:Triplet Loss曲线 ---------------------
ax1.plot(d_an_range, loss_values, label='Triplet Loss', color='#5DA5DA', linewidth=2)
# 标记关键位置:d_an = d_ap + margin(损失为0的临界点)
ax1.axvline(x=d_ap + margin, color='g', linestyle='--', alpha=0.5, label=r'$d_{an} = d_{ap} + \text{margin}$')
ax1.set_ylabel('Loss值', fontsize=12) # 设置y轴标签
ax1.set_title('Triplet Loss随负样本距离变化的图像', fontsize=14) # 设置标题
ax1.legend(fontsize=12) # 添加图例
ax1.grid(True, linestyle='--', alpha=0.7) # 添加网格线
# --------------------- 下侧子图:导数曲线 ---------------------
ax2.plot(d_an_range, grad_values, label='导数', color='#FAA43A', linewidth=2)
ax2.axvline(x=d_ap + margin, color='g', linestyle='--', alpha=0.5, label=r'$d_{an} = d_{ap} + \text{margin}$')
ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5, label='零导数线') # 标记导数为0的水平线
ax2.set_xlabel(r'锚点与负样本距离 $d_{an}$', fontsize=12) # 设置x轴标签(使用LaTeX格式)
ax2.set_ylabel(r'导数 $\partial L/\partial d_{an}$', fontsize=12) # 设置y轴标签(导数符号)
ax2.set_title('Triplet Loss关于负样本距离的导数图像', fontsize=14) # 设置标题
ax2.legend(fontsize=12) # 添加图例
ax2.grid(True, linestyle='--', alpha=0.7) # 添加网格线
plt.tight_layout() # 自动调整布局
plt.show() # 显示图像
四、损失函数梯度特性对比
损失函数 | 梯度公式(以二分类为例) | 梯度特点 | 优化难度 |
---|---|---|---|
交叉熵损失 | ∂ L ∂ p ^ = p ^ − y \frac{\partial L}{\partial \hat{p}} = \hat{p} - y ∂p^∂L=p^−y | 线性梯度,随误差增大而稳定增长 | 低 |
Hinge损失 | ∂ L ∂ y ^ = − y \frac{\partial L}{\partial \hat{y}} = -y ∂y^∂L=−y(当 y y ^ < 1 y\hat{y} < 1 yy^<1) | 常数梯度,误差足够大时梯度为0 | 中 |
MSE | ∂ L ∂ y ^ = 2 ( y ^ − y ) \frac{\partial L}{\partial \hat{y}} = 2(\hat{y} - y) ∂y^∂L=2(y^−y) | 梯度随误差线性增长,可能导致梯度爆炸 | 低 |
MAE | ∂ L ∂ y ^ = sign ( y − y ^ ) \frac{\partial L}{\partial \hat{y}} = \text{sign}(y - \hat{y}) ∂y^∂L=sign(y−y^) | 梯度恒定为±1,优化初期收敛快,后期慢 | 高 |
焦点损失 | ∂ L ∂ p ^ = α ( 1 − p ^ ) γ ( p ^ − y ) \frac{\partial L}{\partial \hat{p}} = \alpha(1-\hat{p})^\gamma(\hat{p} - y) ∂p^∂L=α(1−p^)γ(p^−y) | 动态调整梯度权重,难分类样本梯度更大 | 中 |
五、数学原理指导下的损失函数选择
5.1 基于概率假设的选择
- 高斯分布假设:选MSE(回归)或交叉熵(分类,配合sigmoid/Softmax)
- 拉普拉斯分布假设:选MAE(回归)或Huber损失(兼顾MSE与MAE)
5.2 基于优化理论的选择
- 凸优化需求:线性模型选MSE/交叉熵/Hinge(凸损失)
- 非凸优化容忍:深度学习选焦点损失/三元组损失(非凸但表达能力强)
5.3 基于几何意义的选择
- 距离度量:回归选MSE(欧氏距离)、MAE(曼哈顿距离)
- 区域重叠度量:语义分割选Dice损失(集合重叠度)
六、总结
损失函数的数学公式与原理揭示了其内在优化逻辑:交叉熵源于信息论与最大似然估计,Hinge损失基于几何间隔最大化,MSE/MAE对应不同噪声分布假设,而深度学习特有的损失函数则针对具体任务(如类别不平衡、特征嵌入)设计。理解这些数学基础,能帮助研究者更科学地选择损失函数,甚至根据任务特性定制新的损失函数。在实际应用中,建议结合数据分布假设、模型优化特性及任务几何意义综合决策,必要时可通过加权组合(如MSE+交叉熵)平衡多重优化目标。