人工智能概念之三:常见的损失函数(交叉熵损失、Hinge损失、0-1损失、MSE、MAE、RMSE、最小二乘法、焦点损失、Dice损失、三元组损失)

损失函数是模型训练和优化的核心度量,其本质是为梯度下降算法提供明确的优化目标 —— 通过计算预测值与真实值的差异,损失函数的梯度(导数)为模型参数更新指明方向(梯度反方向为损失下降最快路径)。不同损失函数的数学形态(如交叉熵的平滑曲线、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=[ylogp^+(1y)log(1p^)]
  • 多分类(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=1Kyklog(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(1p^)1y
    取对数后:
    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^+(1y)log(1p^)]=nLCE
    因此最小化交叉熵等价于最大化对数似然。

数学图像
二分类损失函数图像
在这里插入图片描述
绘图代码:

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,1yy^)
    其中 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^ky^y)

数学原理

  • 几何解释:Hinge损失追求"分类间隔"最大化,当样本分类正确且间隔 y ⋅ y ^ ≥ 1 y \cdot \hat{y} \geq 1 yy^1时损失为0,否则惩罚大小为 1 − y ⋅ y ^ 1 - y \cdot \hat{y} 1yy^
  • 优化目标:对应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,bmin21w2+Cmax(0,1yi(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=1n1Li
  • 不可导性:在 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=1n(yiy^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π σ1e2σ2(yiwTxib)2
    最大化似然等价于最小化 ∑ ( y i − w T x i − b ) 2 \sum (y_i - w^Tx_i - b)^2 (yiwTxib)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^iyi)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=1nyiy^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=1n(yiy^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=0y2=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=1n(yiy^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=1Kyk(1p^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 (1p^k)γ降低易分类样本的权重:

    • p ^ k ≈ 1 \hat{p}_k \approx 1 p^k1(易分类样本),权重 ( 1 − p ^ k ) γ ≈ 0 (1-\hat{p}_k)^\gamma \approx 0 (1p^k)γ0
    • p ^ k ≈ 0 \hat{p}_k \approx 0 p^k0(难分类样本),权重 ( 1 − p ^ k ) γ ≈ 1 (1-\hat{p}_k)^\gamma \approx 1 (1p^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=αkyk(1p^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=1i=1nyi+i=1ny^i+ϵ2i=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+B2∣AB
    在语义分割中, 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(1Dice)(yiy^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(yy^)梯度恒定为±1,优化初期收敛快,后期慢
焦点损失 ∂ L ∂ p ^ = α ( 1 − p ^ ) γ ( p ^ − y ) \frac{\partial L}{\partial \hat{p}} = \alpha(1-\hat{p})^\gamma(\hat{p} - y) p^L=α(1p^)γ(p^y)动态调整梯度权重,难分类样本梯度更大

五、数学原理指导下的损失函数选择

5.1 基于概率假设的选择

  • 高斯分布假设:选MSE(回归)或交叉熵(分类,配合sigmoid/Softmax)
  • 拉普拉斯分布假设:选MAE(回归)或Huber损失(兼顾MSE与MAE)

5.2 基于优化理论的选择

  • 凸优化需求:线性模型选MSE/交叉熵/Hinge(凸损失)
  • 非凸优化容忍:深度学习选焦点损失/三元组损失(非凸但表达能力强)

5.3 基于几何意义的选择

  • 距离度量:回归选MSE(欧氏距离)、MAE(曼哈顿距离)
  • 区域重叠度量:语义分割选Dice损失(集合重叠度)

六、总结

损失函数的数学公式与原理揭示了其内在优化逻辑:交叉熵源于信息论与最大似然估计,Hinge损失基于几何间隔最大化,MSE/MAE对应不同噪声分布假设,而深度学习特有的损失函数则针对具体任务(如类别不平衡、特征嵌入)设计。理解这些数学基础,能帮助研究者更科学地选择损失函数,甚至根据任务特性定制新的损失函数。在实际应用中,建议结合数据分布假设、模型优化特性及任务几何意义综合决策,必要时可通过加权组合(如MSE+交叉熵)平衡多重优化目标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值