一、三种常见的回归任务损失函数

损失函数用于量化模型预测值与真实标签的误差,是神经网络反向传播、参数更新的核心依据。

1、L2 Loss  平方损失

公式:

下图中蓝色代表损失函数,黄色代表损失函数梯度,绿色代表似然函数

损失函数:抛物线形态,误差越大损失增长越快,对大误差的惩罚力度呈平方级上升。

梯度函数:随误差线性增大,误差越大梯度越大,大误差下更新步长会急剧增加。

2、L1 Loss 绝对值误差

公式:

损失函数:两条斜率为 ±1 的直线,损失随误差线性增长,惩罚力度恒定。

梯度函数:除原点外梯度恒为 ±1,梯度大小不随误差变化,原点处梯度突变、不可导。

3、Huber Loss

公式:

损失函数:误差较小时为抛物线,误差较大时为直线。

梯度函数:小误差下梯度随误差线性减小(L2 特性),大误差下梯度被截断为恒定值(L1 特性),全程平滑可导。

核心总结

L2 Loss、L1 Loss、Huber Loss 是回归任务的三类经典损失函数,核心差异在于对预测误差的惩罚力度与梯度特性不同,最终在可导性、异常值鲁棒性、收敛速度三个维度形成明确的取舍。

L2 Loss误差小时效果较好,误差较大的少量数据会导致训练不稳定,适用于干净数据情况,如经典线性回归、数据噪声极低的回归任务。

L1 Loss即使误差较大也能平稳的进行更新,对异常值有较好处理,但在接近最优解时会使更新步长过大,适用于数据异常值多、对精度要求低于稳定性的回归任务。

Huber Loss结合了以上两种方式,是通用回归任务的均衡优选方案,工业界应用最广,典型如目标检测的边界框回归。

二、从零实现softmax回归

概念介绍

Softmax回归是单层线性网络,输出维度等于互斥类别数 x。其输出通过 指数归一化 将线性得分 ai 映射为概率分布(和为1),适用于多分类。

以节点x1到节点y1为例,通过参数w11和b1计算得 a1 = w1*x1 + b1

在经过激活函数 y1 = softmax(a1) 

softmax公式:

下图exp(ak) = e的ak次方

分母为所有y的和,所以 y1+y2+y3 = 1,yk可以看作输出类别为类别k的概率,这也正是softmax适合分类任务的原因。

直接按公式计算 Softmax 存在严重的数值溢出风险,通过指数函数和数学运算的特性,将yk分子分母同时除以相同的数,在通过指数函数特性将其放到指数函数中

C‘设为ai中的最大值即可解决问题

代码实现

我们基于 Fashion-MNIST 数据集实现完整的 Softmax 回归,覆盖数据加载、模型定义、损失函数、训练验证全流程。

1. 环境依赖导入

import torch
import torchvision
from torch.utils import data
from torchvision import transforms

2. 数据集加载

使用 Fashion-MNIST 服饰分类数据集:包含 10 类服饰图像,单通道 28×28 像素,训练集 60000 张,测试集 10000 张。

def load_data_fashion_mnist(batch_size, resize=None):
    """加载 Fashion-MNIST 数据集,返回训练集和测试集的DataLoader
    
    Args:
        batch_size: 小批量样本数
        resize: 可选,将图像缩放到指定尺寸
    Returns:
        train_iter: 训练数据迭代器
        test_iter: 测试数据迭代器
    """
    # 定义数据预处理流水线
    trans = [transforms.ToTensor()]  # PIL图像转张量,像素值归一化到[0,1]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)

    # 加载数据集(首次运行会自动下载)
    mnist_train = torchvision.datasets.FashionMNIST(
        root="./data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="./data", train=False, transform=trans, download=True)

    # 构建DataLoader:训练集打乱顺序,测试集不打乱
    return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=2),
            data.DataLoader(mnist_test, batch_size, shuffle=False, num_workers=2))

3. 评估与训练辅助工具

(1)准确率计算函数
def accuracy(y_hat, y):
    """计算批量中预测正确的样本数量
    
    Args:
        y_hat: 预测概率矩阵,形状 (batch_size, num_classes)
        y: 真实类别索引,形状 (batch_size,)
    Returns:
        正确预测的样本总数
    """
    # 多分类场景:取每行概率最大值的索引作为预测类别
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        y_hat = y_hat.argmax(axis=1)
    # 数据类型对齐后比较,统计正确样本数
    cmp = y_hat.type(y.dtype) == y
    return float(cmp.type(y.dtype).sum())
(2)指标累加器

用于遍历批量时累计损失、正确样本数、总样本数等指标,避免频繁创建变量。

class Accumulator:
    """多变量累加器,用于训练/评估时批量统计指标"""
    def __init__(self, n):
        self.data = [0.0] * n  # n个待累加的统计变量

    def add(self, *args):
        """按位置累加对应数值"""
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def reset(self):
        """重置所有累加值为0"""
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        """按索引获取累加值"""
        return self.data[idx]
(3)数据集精度评估
def evaluate_accuracy(net, data_iter):
    """计算模型在指定数据集上的分类准确率
    
    Args:
        net: 模型函数/网络
        data_iter: 数据迭代器
    Returns:
        整体准确率(正确数/总样本数)
    """
    if isinstance(net, torch.nn.Module):
        net.eval()  # 切换评估模式,禁用Dropout、BN等训练专属层
    metric = Accumulator(2)  # 两个统计量:预测正确数、总样本数
    with torch.no_grad():    # 关闭梯度计算,节省显存与算力
        for X, y in data_iter:
            metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

4. Softmax 函数与网络前向传播

(1)数值稳定版 Softmax 实现
def softmax(X):
    """数值稳定的Softmax函数
    
    Args:
        X: 原始得分矩阵,形状 (batch_size, num_classes)
    Returns:
        概率矩阵,每行和为1,形状与输入一致
    """
    # 取每行最大值,保持维度以便广播运算
    X_max = X.max(dim=1, keepdim=True)[0]
    X_stable = X - X_max  # 减去行最大值,避免指数上溢
    X_exp = torch.exp(X_stable)
    partition = X_exp.sum(dim=1, keepdim=True)  # 归一化分母
    return X_exp / partition
(2)Softmax 回归前向传播
def net(X):
    """Softmax回归前向传播逻辑
    
    Args:
        X: 输入图像批次,形状 (batch_size, 1, 28, 28)
    Returns:
        类别概率矩阵,形状 (batch_size, 10)
    """
    # 图像展平:将[batch, 1, 28, 28] reshape为[batch, 784]
    # -1表示自动计算该维度大小,等价于batch_size
    X_flat = X.reshape(-1, W.shape[0])
    # 线性变换:矩阵乘法 + 偏置广播
    linear_output = torch.matmul(X_flat, W) + b
    # 经Softmax输出概率分布
    return softmax(linear_output)

5. 交叉熵损失实现

def cross_entropy(y_hat, y):
    """交叉熵损失函数
    
    Args:
        y_hat: 预测概率矩阵,形状 (batch_size, num_classes)
        y: 真实类别索引,形状 (batch_size,)
    Returns:
        每个样本的损失值,形状 (batch_size,)
    """
    # 高级索引:批量取出每个样本对应真实类别的预测概率
    # range(len(y_hat)) 生成行索引(0到batch_size-1),y为列索引
    # 等价于依次取 y_hat[0][y[0]], y_hat[1][y[1]], ..., y_hat[n-1][y[n-1]]
    return -torch.log(y_hat[range(len(y_hat)), y])

y_hat[range(len(y_hat)), y]可以拆解成 y_hat[a,b], a = range(len(y_hat)), b=y

y_hat: 预测概率矩阵   y: 真实类别索引

假设本次进行三分类任务,batch_size = 3,y_hat.shape = (3,3)

y_hat=[
        [0.2, 0.3, 0.5]
        [0.4, 0.1, 0.5]
        [0.8, 0.1, 0.1]
        ]

第一行[0.2,0.3,0.5]是这一批中第一个数据点分别是第一类,第二类,第三类的概率预测,其他两行同理

y=[2, 2, 0]

第一个数据就是第三类,第二个也是第三类,第三个是第一类

len(y_hat) = 批次 = 3, a=range(len(y_hat))会生成从0到2,同时b=y[a],会依次取出数据点真实标签
所以y_hat[range(len(y_hat)), y] 分别回取出y_hat[0][2] y_hat[1][2] y_hat[2][0], 正是真实标签在预测中的概率
而真实预测的概率越大,损失越小,所以最终返回加了个负号为 -torch.log(y_hat[range(len(y_hat)), y])

6. 完整训练流程

(1)单轮训练(一个 Epoch)
def train_epoch_ch3(net, train_iter, loss, updater):
    """训练模型一个epoch(完整遍历一次训练集)
    
    Args:
        net: 模型
        train_iter: 训练数据迭代器
        loss: 损失函数
        updater: 优化器
    Returns:
        平均训练损失、平均训练准确率
    """
    if isinstance(net, torch.nn.Module):
        net.train()  # 切换训练模式
    metric = Accumulator(3)  # 累计:损失总和、正确样本数、总样本数

    for X, y in train_iter:
        y_hat = net(X)          # 前向传播:计算预测概率
        l = loss(y_hat, y)      # 计算每个样本的损失

        if isinstance(updater, torch.optim.Optimizer):
            # PyTorch内置优化器标准流程
            updater.zero_grad()  # 清零上一轮梯度
            l.mean().backward()  # 损失均值反向传播(数值更稳定)
            updater.step()       # 更新参数
        else:
            # 自定义优化器兼容流程
            l.sum().backward()
            updater(X.shape[0])

        # 累计本轮统计指标
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())

    # 返回本轮平均损失和平均准确率
    return metric[0] / metric[2], metric[1] / metric[2]
(2)全周期训练循环
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
    """完整训练流程,每轮输出训练与测试指标"""
    for epoch in range(num_epochs):
        train_loss, train_acc = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        print(f"epoch {epoch+1:2d} | 训练损失: {train_loss:.4f} | "
              f"训练准确率: {train_acc:.4f} | 测试准确率: {test_acc:.4f}")

7. 预测可视化辅助

def get_fashion_mnist_labels(labels):
    """将类别索引转换为中文文本标签"""
    text_labels = ['T恤', '裤子', '套头衫', '连衣裙', '外套',
                   '凉鞋', '衬衫', '运动鞋', '包', '短靴']
    return [text_labels[int(i)] for i in labels]

def predict_ch3(net, test_iter, n=6):
    """取一批测试样本,输出真实标签与预测标签对比"""
    for X, y in test_iter:
        break  # 只取第一批数据
    trues = get_fashion_mnist_labels(y)
    preds = get_fashion_mnist_labels(net(X).argmax(axis=1))
    print("\n预测示例:")
    print(f"真实标签: {trues[:n]}")
    print(f"预测标签: {preds[:n]}")

8. 主程序:参数初始化与运行

if __name__ == '__main__':
    # ========== 超参数设置 ==========
    batch_size = 32       # 小批量样本数
    num_epochs = 10       # 训练总轮数
    learning_rate = 0.1   # 学习率

    # ========== 加载数据集 ==========
    train_iter, test_iter = load_data_fashion_mnist(batch_size)

    # ========== 模型参数初始化 ==========
    num_inputs = 28 * 28   # 输入特征维度:28*28=784
    num_outputs = 10       # 输出类别数:10类服饰

    # 权重:正态分布初始化,均值0,标准差0.01,开启梯度追踪
    W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
    # 偏置:初始化为0,开启梯度追踪
    b = torch.zeros(num_outputs, requires_grad=True)

    # ========== 定义损失与优化器 ==========
    loss = cross_entropy
    updater = torch.optim.SGD([W, b], lr=learning_rate)

    # ========== 启动训练 ==========
    print("开始训练Softmax回归...")
    train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)

    # ========== 测试预测效果 ==========
    predict_ch3(net, test_iter)

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐