ESC
输入关键词搜索文章
目录

损失函数

是什么

交叉熵 transclusion

\( L = -\sum y_i \log(\hat{y}_i) \)

交叉熵(Cross-Entropy)是信息论中的概念,用于衡量两个概率分布之间的差异。在机器学习中,常用于分类任务的损失函数。

定义: 对于真实分布 \( P \) 和预测分布 \( Q \),交叉熵定义为:

$$H(P, Q) = -\sum_{i} P(i) \log Q(i)$$

特点

应用

MSE

均方误差

数学表示

\( \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 \)

为什么线性回归使用 MSE 作为评估指标?

线性回归使用 MSE(均方误差)作为评估指标的主要原因:

  1. 数学性质优良

    • MSE 是光滑可导的凸函数,便于梯度下降等优化算法求解
    • 对异常值更敏感,能更好反映预测误差的严重程度
  2. 统计意义明确

    • 最小化 MSE 等价于寻找条件期望 E[Y|X],具有最优无偏估计的性质
    • 与高斯噪声假设一致:当误差服从正态分布时,MSE 给出最大似然估计
  3. 计算简便

    • 平方运算使正负误差不会相互抵消
    • 公式简单,易于理解和实现
  4. 实际考虑

    • 大误差惩罚更重,符合实际应用中对小误差容忍、大误差重视的需求

虽然 MSE 对异常值敏感,但在数据质量较好时,其理论性质和优化特性使其成为线性回归的理想评估指标。

L1 损失

平均绝对误差

L1 损失是另一种常见的回归损失函数,计算预测值与真实值之间的绝对差平均值。

/定义/:

$$\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|$$

/特点/:

/应用/:

DISTS

DISTS 是一种用于图像质量评估的感知损失函数,结合了结构相似性和纹理相似性。

/定义/: DISTS 通过深度特征提取器(如 VGG)计算多尺度特征之间的相似性,结合了结构信息和纹理信息。其公式为:

$$\text{DISTS} = \alpha \cdot \mathcal{L}_{\text{structure}} + \beta \cdot \mathcal{L}_{\text{texture}}$$
其中,\( \alpha \)\( \beta \) 是权重系数,\( \mathcal{L}_{\text{structure}} \)\( \mathcal{L}_{\text{texture}} \) 分别表示结构和纹理损失。

/特点/:

/应用/:

import torch
import torch.nn.functional as F

def cross_entropy_loss(logits, targets):
    """计算交叉熵损失"""
    return F.cross_entropy(logits, targets)

def mse_loss(predictions, targets):
    """计算均方误差损失"""
    return F.mse_loss(predictions, targets)

def mae_loss(predictions, targets):
    """计算L1损失"""
    return F.l1_loss(predictions, targets)

# 示例使用
logits = torch.randn(4, 10)  # 4个样本,10个类别
targets = torch.tensor([1, 3, 5, 7])
print(f"Cross Entropy Loss: {cross_entropy_loss(logits, targets):.4f}")

predictions = torch.randn(4, 1)
targets = torch.randn(4, 1)
print(f"MSE Loss: {mse_loss(predictions, targets):.4f}")
print(f"MAE Loss: {mae_loss(predictions, targets):.4f}")

DICE Loss

Dice Loss 是一种常用于图像分割任务的损失函数, 基于 Dice 系数(Sørensen–Dice 系数)构建,用于衡量两个样本集合的相似度。

/定义/: Dice 系数定义为:

$$\text {Dice} = \frac{2|A \cap B|}{|A| + |B|}$$
其中 \( A \)\( B \) 分别表示预测分割区域和真实分割区域。Dice Loss 则定义为:
$$L_{\text{Dice}} = 1 - \text{Dice} = 1 - \frac{2 \sum_{i} p_i g_i + \epsilon}{\sum_{i} p_i + \sum_{i} g_i + \epsilon}$$
这里 \( p_i \) 是第 \( i \) 个像素的预测概率,\( g_i \) 是真实标签(0 或 1),\( \epsilon \) 是一个小的平滑常数,避免分母为零。

/特点/:

/应用/:

import torch
import torch.nn.functional as F

def dice_loss(predictions, targets, smooth=1e-6):
    """计算 Dice Loss

    Args:
        predictions: 预测概率,形状 [N, C, H, W]
        targets: 真实标签,形状 [N, C, H, W]
        smooth: 平滑常数
    """
    # 展平预测和标签
    predictions = predictions.contiguous().view(predictions.shape[0], -1)
    targets = targets.contiguous().view(targets.shape[0], -1)

    # 计算交集和并集
    intersection = (predictions * targets).sum(dim=1)
    union = predictions.sum(dim=1) + targets.sum(dim=1)

    # 计算 Dice 系数和损失
    dice = (2. * intersection + smooth) / (union + smooth)
    return 1 - dice.mean()

# 示例使用
predictions = torch.sigmoid(torch.randn(2, 1, 128, 128))  # 二值分割预测
targets = torch.randint(0, 2, (2, 1, 128, 128)).float()  # 二值标签
print(f"Dice Loss: {dice_loss(predictions, targets):.4f}")

SSIM

SSIM(结构相似性指数)是一种基于人眼感知的图像质量评估指标,比 PSNR 更符合主观视觉感受。

测量恢复图像和干净图像之间的相似度

/公式/:

$$\text{SSIM}(x, y) = \frac{(2\mu_x\mu_y + C_1)(2\sigma_{xy} + C_2)}{(\mu_x^2 + \mu_y^2 + C_1)(\sigma_x^2 + \sigma_y^2 + C_2)}$$
其中:

/特点/:

结构相似性指数(Structural Similarity Index Measure, SSIM)是一种用于衡量两幅图 像感知质量的指标,它考虑了亮度、对比度和结构信息的相似性。

更通用的:

SSIM 的计算公式为:

$$\text{SSIM}(x, y) = [l(x, y)]^{\alpha} \cdot [c(x, y)]^{\beta} \cdot [s(x, y)]^{\gamma}$$
其中:

通常取 \( \alpha = \beta = \gamma = 1 \),且 \( C_3 = C_2/2 \),此时简化为:

$$\text{SSIM}(x, y) = \frac{(2\mu_x\mu_y + C_1)(2\sigma_{xy} + C_2)}{(\mu_x^2 + \mu_y^2 + C_1)(\sigma_x^2 + \sigma_y^2 + C_2)}$$

LPIPS

Learned Perceptual Image Patch Similar ity (LPIPS) 是一种基于深度学习的图像相似 性度量方法,也称为"感知损失"。它通过预训练的深度神经网络来度量图像之间的感知相似 性。

定义

LPIPS 使用预训练的卷积神经网络(如 VGG、AlexNet 等)提取图像特征,然后在特征空间计算图像块之间的相似性。其公式为:

$$d(x, x_0 ) = \sum_l \frac{1}{H_l W_l} \sum_{h,w} \| w_l \odot (\hat{y}_{hw}^l - \hat{y}_{0hw}^l) \|_2^2$$
其中:

/特点/:

/应用/:

import torch
import torch.nn as nn
import lpips

def lpips_loss(image1, image2, net='vgg'):
    """计算 LPIPS 损失

    Args:
        image1: 图像1,形状 [N, C, H, W],值范围 [-1, 1] 或 [0,
1]
        image2: 图像2,形状与 image1 相同
        net: 使用的网络 backbone ('alex', 'vgg', 'squeeze')
    """
    loss_fn = lpips.LPIPS(net=net)
    return loss_fn(image1, image2)

# 示例使用
# 注意:LPIPS 要求输入图像在 [-1, 1] 或 [0, 1] 范围内
image1 = torch.rand(2, 3, 224, 224) * 2 - 1  # 范围 [-1, 1]
image2 = torch.rand(2, 3, 224, 224) * 2 - 1
print(f"LPIPS Loss: {lp
ips_loss(image1, image2):.4f}")

与其他损失函数的比较

与 MSE 的比较

与 SSIM 的比较

适用场景

torch 中的损失函数

使用 pytorch 模块进行模型训练时, loss 是怎么得到、怎么使用的? forward 和 backward 怎么使用?如何与 optimizer 联动?

在 PyTorch 中,损失函数、前向传播、反向传播和优化器共同构成了模型训练的核心循环。以下是它们如何协同工作的详细解释。

核心概念与流程

  1. 前向传播 (Forward Pass): 模型根据输入数据计算预测输出。
  2. 计算损失 (Compute Loss): 使用损失函数比较预测输出与真实标签,得到一个标量损失值。
  3. 反向传播 (Backward Pass): 自动计算损失相对于模型所有可训练参数的梯度。
  4. 参数更新 (Parameter Update): 优化器利用计算出的梯度来更新模型参数,以最小化损失。

详细步骤与代码示例

以下是一个完整的训练步骤示例,展示了各组件如何联动。

import torch
import torch.nn as nn
import torch.optim as optim

# 1. 定义简单的模型、损失函数和优化器
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.linear = nn.Linear(10, 1)  # 一个简单的线性层

    def forward(self, x):
        return self.linear(x)

model = SimpleModel()
criterion = nn.MSELoss()  # 均方误差损失函数
optimizer = optim.SGD(model.parameters(), lr=0.01)  # 随机梯度下降优化器

# 2. 模拟一批训练数据
batch_size = 4
inputs = torch.randn(batch_size, 10)  # 输入: (batch_size, input_features)
labels = torch.randn(batch_size, 1)   # 真实标签

# 3. 训练循环中的一个迭代步骤
# 步骤 A: 前向传播
outputs = model(inputs)  # 调用 model.forward(inputs)
print(f"模型输出形状: {outputs.shape}")
print(f"真实标签形状: {labels.shape}")

# 步骤 B: 计算损失
loss = criterion(outputs, labels)
print(f"损失值 (标量): {loss.item()}")

# 步骤 C: 反向传播
# 在计算梯度前,必须将优化器中存储的上一次迭代的梯度清零。
# 因为 PyTorch 默认会累积梯度 (.grad 属性会累加)。
optimizer.zero_grad()

# 调用 loss.backward() 计算损失相对于模型参数的梯度。
# 梯度被存储在模型每个参数的 .grad 属性中。
loss.backward()
print(f"线性层权重 w 的梯度形状: {model.linear.weight.grad.shape}")
print(f"线性层偏置 b 的梯度形状: {model.linear.bias.grad.shape}")

# 步骤 D: 参数更新
# 优化器根据参数的 .grad 属性和其自身的更新规则(如 SGD 的 w = w - lr * grad)来更新参数。
optimizer.step()
print("参数已由优化器更新。")

# 验证参数已更新(例如,检查权重是否变化)
old_weight = model.linear.weight.data.clone()
# 再进行一次前向-反向-更新循环
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
new_weight = model.linear.weight.data
print(f"权重在更新后是否变化: {not torch.allclose(old_weight, new_weight)}")

关键组件详解

损失函数 (Loss Function)

前向传播 (Forward Pass)

反向传播 (Backward Pass) 与 loss.backward()

优化器 (Optimizer)

完整的训练循环模板

以下是一个更通用的训练循环模板,通常嵌套在 epoch 循环中。

def train_one_epoch(model, dataloader, criterion, optimizer, device):
    model.train()  # 将模型设置为训练模式(影响 Dropout, BatchNorm 等层)
    running_loss = 0.0

    for batch_idx, (inputs, labels) in enumerate(dataloader):
        # 将数据移至指定设备(如 GPU)
        inputs, labels = inputs.to(device), labels.to(device)

        # 1. 前向传播
        outputs = model(inputs)

        # 2. 计算损失
        loss = criterion(outputs, labels)

        # 3. 反向传播
        optimizer.zero_grad()
        loss.backward()

        # (可选)梯度裁剪,防止梯度爆炸
        # torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

        # 4. 参数更新
        optimizer.step()

        # 记录损失
        running_loss += loss.item()

    average_loss = running_loss / len(dataloader)
    return average_loss

实例

def train_one_epoch(model, model_without_ddp, data_loader, optimizer, device, epoch, log_writer=None, args=None):
    model.train(True)
    metric_logger = misc.MetricLogger(delimiter="  ")
    metric_logger.add_meter('lr', misc.SmoothedValue(window_size=1, fmt='{value:.6f}'))
    header = 'Epoch: [{}]'.format(epoch)
    print_freq = 20

    optimizer.zero_grad()

    if log_writer is not None:
        print('log_dir: {}'.format(log_writer.log_dir))

    for data_iter_step, (x, labels) in enumerate(metric_logger.log_every(data_loader, print_freq, header)):
        # per iteration (instead of per epoch) lr scheduler
        lr_sched.adjust_learning_rate(optimizer, data_iter_step / len(data_loader) + epoch, args)

        # normalize image to [-1, 1]
        x = x.to(device, non_blocking=True).to(torch.float32).div_(255)
        x = x * 2.0 - 1.0
        labels = labels.to(device, non_blocking=True)

        with torch.amp.autocast('cuda', dtype=torch.bfloat16):
            loss = model(x, labels)

        loss_value = loss.item()
        if not math.isfinite(loss_value):
            print("Loss is {}, stopping training".format(loss_value))
            sys.exit(1)

        # Zero the gradients before backward to prevent accumulation from previous iterations.
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # Synchronize the gradients to ensure they are correctly computed.
        torch.cuda.synchronize()

        # Update the EMA model parameters.
        model_without_ddp.update_ema()

        metric_logger.update(loss=loss_value)
        lr = optimizer.param_groups[0]["lr"]
        metric_logger.update(lr=lr)

        # Reduce the loss value across all processes to get the average loss.
        loss_value_reduce = misc.all_reduce_mean(loss_value)

        if log_writer is not None:
            # Use epoch_1000x as the x-axis in TensorBoard to calibrate curves.
            # Log the loss and learning rate to TensorBoard.
            epoch_1000x = int((data_iter_step / len(data_loader) + epoch) * 1000)
            if data_iter_step % args.log_freq == 0:
                log_writer.add_scalar('train_loss', loss_value_reduce, epoch_1000x)
                log_writer.add_scalar('lr', lr, epoch_1000x)

数学原理简述

对于一个模型参数 \(\theta\),损失函数 \(L\),学习率 \(\eta\),一次梯度下降更新为:

$$\theta_{\text{new}} = \theta_{\text{old}} - \eta \nabla_{\theta} L(\theta_{\text{old}})$$

其中 \(\nabla_{\theta} L\) 就是通过 loss.backward() 计算得到的梯度,optimizer.step() 负责执行上述更新操作(对于 SGD 而言,更复杂的优化器如 Adam 会有自适应学习率和动量项)。

总结