📚 课程目录

第4课:损失函数

📖 本课概述

损失函数衡量模型预测与真实标签的差距,是训练优化的目标。选择正确的损失函数对模型性能至关重要。本课深入分析回归和分类中各种损失函数的数学性质与适用场景。

📐 一、回归损失函数

1.1 均方误差 (MSE / L2 Loss)

L_MSE = (1/N) Σᵢ(yᵢ - ŷᵢ)²

导数:∂L/∂ŷ = (2/N)(ŷ - y)

MSE对大误差惩罚更重(平方),因此对异常值敏感。

1.2 平均绝对误差 (MAE / L1 Loss)

L_MAE = (1/N) Σᵢ|yᵢ - ŷᵢ|

导数:∂L/∂ŷ = sign(ŷ - y)

MAE对异常值更鲁棒,但在0点不可导,收敛可能较慢。

1.3 Huber Loss (Smooth L1)

L_δ(r) = { 0.5r², if |r| ≤ δ ; δ(|r| - 0.5δ), if |r| > δ }

其中 r = y - ŷ,通常 δ = 1.0

Huber结合了MSE和MAE的优点:小误差用MSE(平滑),大误差用MAE(鲁棒)。


import torch
import torch.nn as nn
import torch.nn.functional as F
import math

# 常见损失函数演示
print("=== 回归损失函数 ===")

# 模拟数据
pred = torch.tensor([2.5, 0.0, 2.1, 7.8])
target = torch.tensor([3.0, -0.5, 2.0, 8.0])

mse = F.mse_loss(pred, target)
mae = F.l1_loss(pred, target)
huber = F.smooth_l1_loss(pred, target)

print(f"预测值: {pred.tolist()}")
print(f"真实值: {target.tolist()}")
print(f"MSE  Loss: {mse.item():.6f}")
print(f"MAE  Loss: {mae.item():.6f}")
print(f"Huber Loss: {huber.item():.6f}")

# MSE vs MAE 对异常值的敏感性
print("\n=== 异常值敏感性 ===")
pred_clean = torch.tensor([1.0, 2.0, 3.0, 4.0])
target_clean = torch.tensor([1.1, 2.2, 2.8, 4.5])

pred_outlier = torch.tensor([1.0, 2.0, 3.0, 100.0])
target_outlier = torch.tensor([1.1, 2.2, 2.8, 4.5])

print(f"无异常值: MSE={F.mse_loss(pred_clean, target_clean):.4f}, MAE={F.l1_loss(pred_clean, target_clean):.4f}")
print(f"有异常值: MSE={F.mse_loss(pred_outlier, target_outlier):.4f}, MAE={F.l1_loss(pred_outlier, target_outlier):.4f}")
print(f"MSE对异常值放大倍数: {F.mse_loss(pred_outlier, target_outlier)/F.mse_loss(pred_clean, target_clean):.1f}x")
print(f"MAE对异常值放大倍数: {F.l1_loss(pred_outlier, target_outlier)/F.l1_loss(pred_clean, target_clean):.1f}x")
🟢 运行结果 — 回归损失对比 ✅验证通过 === 回归损失函数 === 预测值: [2.5, 0.0, 2.0999999046325684, 7.800000190734863] 真实值: [3.0, -0.5, 2.0, 8.0] MSE Loss: 0.137500 MAE Loss: 0.325000 Huber Loss: 0.068750 === 异常值敏感性 === 无异常值: MSE=0.0850, MAE=0.2500 有异常值: MSE=2280.0850, MAE=24.0000 MSE对异常值放大倍数: 26824.5x MAE对异常值放大倍数: 96.0x

🎯 二、分类损失函数

2.1 交叉熵损失 (Cross-Entropy)

二分类:L_BCE = -[y·log(ŷ) + (1-y)·log(1-ŷ)]

多分类:L_CE = -Σₖ yₖ·log(ŷₖ)

2.2 Focal Loss

L_FL = -αₜ(1-pₜ)^γ · log(pₜ)

γ(聚焦参数)降低易分类样本的损失权重,让模型关注难样本。


import torch
import torch.nn as nn
import torch.nn.functional as F
import math

print("=== 分类损失函数 ===")

# 二分类
logits = torch.tensor([0.8, -0.5, 1.2, -2.0])
labels = torch.tensor([1.0, 0.0, 1.0, 0.0])

bce = F.binary_cross_entropy_with_logits(logits, labels)
print(f"二分类 BCE Loss: {bce.item():.6f}")

# 手动计算BCE
probs = torch.sigmoid(logits)
manual_bce = -(labels * torch.log(probs + 1e-8) + (1-labels) * torch.log(1-probs + 1e-8)).mean()
print(f"手动计算 BCE:    {manual_bce.item():.6f}")

# 多分类
logits_multi = torch.tensor([[2.0, 1.0, 0.1], [0.5, 2.5, 0.3], [0.1, 0.2, 3.0]])
labels_multi = torch.tensor([0, 1, 2])

ce = F.cross_entropy(logits_multi, labels_multi)
print(f"\n多分类 Cross Entropy Loss: {ce.item():.6f}")

# 手动计算
probs_multi = F.softmax(logits_multi, dim=1)
print(f"Softmax概率: {probs_multi.tolist()}")
manual_ce = 0
for i in range(3):
    manual_ce -= torch.log(probs_multi[i, labels_multi[i]] + 1e-8)
manual_ce /= 3
print(f"手动计算 CE: {manual_ce.item():.6f}")

# Focal Loss演示
print("\n=== Focal Loss vs CE Loss ===")
gamma = 2.0
for conf in [0.9, 0.7, 0.5, 0.3]:
    ce_val = -math.log(conf) if conf > 0 else 20.0
    fl_val = (1 - conf)**gamma * ce_val
    print(f"正确类概率={conf:.1f}: CE={ce_val:.4f}, Focal={fl_val:.4f}, 压缩比={fl_val/ce_val:.4f}")
🟢 运行结果 — 分类损失对比 ✅验证通过 === 分类损失函数 === 二分类 BCE Loss: 0.308847 手动计算 BCE: 0.308847 多分类 Cross Entropy Loss: 0.248894 Softmax概率: [[0.6590011715888977, 0.24243298172950745, 0.09856589138507843], [0.10860372334718704, 0.8024790287017822, 0.08891721069812775], [0.0493113249540329, 0.05449745059013367, 0.8961912393569946]] 手动计算 CE: 0.248894 === Focal Loss vs CE Loss === 正确类概率=0.9: CE=0.1054, Focal=0.0011, 压缩比=0.0100 正确类概率=0.7: CE=0.3567, Focal=0.0321, 压缩比=0.0900 正确类概率=0.5: CE=0.6931, Focal=0.1733, 压缩比=0.2500 正确类概率=0.3: CE=1.2040, Focal=0.5899, 压缩比=0.4900

📋 三、损失函数选择指南

任务推荐损失说明
回归(无异常值)MSE平滑梯度,收敛快
回归(有异常值)Huber / MAE对异常值鲁棒
二分类BCE with Logits数值稳定
多分类Cross Entropy标准选择
类别不平衡Focal Loss关注难样本
标签平滑Label Smoothing CE防止过自信

🔬 四、高级损失函数

4.1 Label Smoothing

标准标签: y = [0, 0, 1, 0, ...] (one-hot)

平滑标签: y' = (1-ε)·y + ε/K = [ε/K, ε/K, 1-ε+ε/K, ε/K, ...]

ε通常取0.1,K为类别数

防止模型过度自信,提高泛化能力

4.2 对比损失(Contrastive Loss)

L = (1-y)·0.5·d² + y·0.5·max(0, m-d)²

y=0: 相似样本,拉近距离d

y=1: 不相似样本,推远到至少margin m

用于度量学习和自监督学习

4.3 KL散度

D_KL(P||Q) = Σ P(x)·log(P(x)/Q(x))

衡量两个分布的差异(非对称!)

VAE中用作正则项:迫使学到的分布接近先验

4.4 损失函数设计原则

💡 设计损失函数的核心原则:

📖 五、延伸阅读

💡 推荐资源:

📝 练习

练习1:实现Focal Loss

用PyTorch实现Focal Loss,在不同γ值下对比训练效果。

练习2:Label Smoothing

实现Label Smoothing交叉熵,理解为什么它能提高模型泛化能力。

练习3:自定义损失

设计一个多任务损失函数,同时优化分类和回归目标。

💡 六、实践技巧总结

6.1 损失函数数值稳定性

实际实现中,数值稳定性至关重要:

❌ 不稳定: -log(p) → 当p→0时,log(0)=-∞

✅ 稳定: binary_cross_entropy_with_logits → 内部用log-sum-exp技巧

❌ 不稳定: softmax后接log → 两次近似

✅ 稳定: cross_entropy直接从logits计算 → LogSoftmax+NLLLoss

6.2 多任务损失加权

当同时优化多个损失时,如何平衡权重?

6.3 不平衡数据的损失设计

类别权重: w_c = N / (C × N_c),给少数类更大权重

Focal Loss: 降低易分类样本权重,关注难样本

过采样/欠采样: 直接调整数据分布

🔬 深度拓展:从理论到实践

核心概念网络

理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:

关键洞察与直觉

建立正确的直觉比记住公式更重要:

1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)

2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的

3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构

4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)

与其他课程的关联

本课作为神经网络基础阶段的核心内容,与前后课程紧密关联:

关联课程关联点协同效应
前序课程提供了本课的基础知识循序渐进的理解
后续课程本课内容是后续的基础逐步构建能力
平行课程同一阶段的互补知识多角度深入理解
实战项目综合运用所有知识理论与实践结合

面试常见问题

💡 准备面试时,确保能回答以下问题:

  1. 用简单语言解释本课核心概念
  2. 画出关键算法/结构的流程图
  3. 比较不同方法的优缺点
  4. 举出实际应用场景
  5. 讨论常见误解和注意事项

进阶学习路径

掌握本课内容后,可以通过以下方式继续深入:

🏆

成就解锁:目标工程师

你已经掌握了如何为不同任务选择和设计损失函数。
损失函数定义了学习目标——目标对了,学习才有方向!