loss_tal.py
utils\loss_tal.py
目录
[2.def smooth_BCE(eps=0.1):](#2.def smooth_BCE(eps=0.1):)
[3.class VarifocalLoss(nn.Module):](#3.class VarifocalLoss(nn.Module):)
[4.class FocalLoss(nn.Module):](#4.class FocalLoss(nn.Module):)
[5.class BboxLoss(nn.Module):](#5.class BboxLoss(nn.Module):)
[6.class ComputeLoss:](#6.class ComputeLoss:)
1.所需的库和模块
python
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
from utils.general import xywh2xyxy
from utils.metrics import bbox_iou
from utils.tal.anchor_generator import dist2bbox, make_anchors, bbox2dist
from utils.tal.assigner import TaskAlignedAssigner
from utils.torch_utils import de_parallel
2.def smooth_BCE(eps=0.1):
python
# 这段代码定义了一个名为 smooth_BCE 的函数,它用于计算平滑的二元交叉熵(Binary Cross-Entropy, BCE)损失函数的两个参数。在机器学习中,二元交叉熵损失函数常用于二分类问题,它衡量的是模型预测的概率分布与真实标签之间的差异。
# 这行代码定义了一个名为 smooth_BCE 的函数,它接受一个参数。
# eps :默认值为 0.1 。 eps 是一个很小的正数,用于平滑损失函数,防止在计算过程中出现数值不稳定的问题,比如除以零。
def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
# return positive, negative label smoothing BCE targets
# 在原始的二元交叉熵损失函数中,正例的标签是 1 ,负例的标签是 0 。但是,为了避免在概率接近 0 或 1 时的数值不稳定问题,通常会对标签进行平滑处理,使得标签值介于 0 和 1 之间。这样做可以提高模型的泛化能力,避免在训练过程中出现过拟合。
# 1.0 - 0.5 * eps :这是平滑后的正例标签值。由于 eps 是一个很小的正数,所以这个值会非常接近 1 ,但不是完全等于 1 。
# 0.5 * eps :这是平滑后的负例标签值。同样,由于 eps 很小,这个值也会非常接近 0 ,但不是完全等于 0 。
return 1.0 - 0.5 * eps, 0.5 * eps
# smooth_BCE 函数是一个辅助函数,用于生成平滑二元交叉熵损失函数的两个参数。通过引入一个小的正数 eps ,我们可以避免在预测值接近 0 或 1 时梯度消失的问题,从而提高模型的训练稳定性。函数返回的两个值分别对应于平滑后的损失函数中的两个关键点,即当预测值为 0 和 1 时的损失值。
3.class VarifocalLoss(nn.Module):
python
# 这段代码定义了一个名为 VarifocalLoss 的类,它是一个 PyTorch 模块,用于计算变分焦点损失(Varifocal Loss)。这种损失函数通常用于目标检测任务中,特别是在类别不平衡的情况下。
# 这行代码定义了一个名为 VarifocalLoss 的类,它继承自 nn.Module ,这是所有PyTorch模块的基类。
class VarifocalLoss(nn.Module):
# Varifocal loss by Zhang et al. https://arxiv.org/abs/2008.13367
# 这是 VarifocalLoss 类的构造函数。
def __init__(self):
# 调用了父类 nn.Module 的构造函数,这是初始化模块时的标准做法。
super().__init__()
# 这是 VarifocalLoss 类的前向传播函数,它接受五个参数。
# 1.pred_score :预测分数。
# 2.gt_score :真实分数。
# 3.label :标签。
# 4.alpha :平衡因子,默认值为0.75。
# 5.gamma :调节因子,默认值为2.0。
def forward(self, pred_score, gt_score, label, alpha=0.75, gamma=2.0):
# 计算了每个样本的权重。权重是基于 预测分数 、 真实分数 、 标签 、 alpha 和 gamma 参数计算得出的。 pred_score.sigmoid() 将预测分数通过 sigmoid 函数转换为概率值, pow(gamma) 则对这些概率值进行幂次调整,以增强模型对难样本的关注。 (1 - label) 和 label 分别对应于负样本和正样本的权重调整。
weight = alpha * pred_score.sigmoid().pow(gamma) * (1 - label) + gt_score * label
# 使用了 PyTorch 的自动混合精度(AMP)上下文管理器 autocast 。在这里,它被设置为 enabled=False ,意味着在该上下文中,所有的操作都将使用全精度浮点数进行计算,而不是混合精度。这通常用于避免在损失计算中出现精度问题。
with torch.cuda.amp.autocast(enabled=False):
# 计算了变分焦点损失。首先,使用 F.binary_cross_entropy_with_logits 函数计算二元交叉熵损失,其中 pred_score 和 gt_score 都被转换为浮点数。 reduction="none" 参数表示不对损失进行求和或平均,而是返回每个样本的损失。然后,这些损失值乘以之前计算的权重,并对所有样本的加权损失求和。
loss = (F.binary_cross_entropy_with_logits(pred_score.float(), gt_score.float(),
reduction="none") * weight).sum()
# 返回计算出的损失值。
return loss
# VarifocalLoss 类实现了变分焦点损失函数,该函数通过调整权重来增强模型对难样本的关注,特别是在类别不平衡的情况下。这个损失函数可以提高目标检测模型在处理不平衡数据时的性能。
4.class FocalLoss(nn.Module):
python
# 这段代码定义了一个名为 FocalLoss 的类,它是一个包装类,用于将焦点损失(Focal Loss)应用于现有的二元交叉熵损失函数。焦点损失是一种解决类别不平衡问题的方法,特别适用于目标检测任务。
# 这行代码定义了一个名为 FocalLoss 的类,它继承自 nn.Module ,这是所有PyTorch模块的基类。
class FocalLoss(nn.Module):
# Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
# 这是 FocalLoss 类的构造函数,它接受三个参数。
# 1.loss_fcn :一个损失函数,通常是 nn.BCEWithLogitsLoss() 。
# 2.gamma :调节因子,默认值为1.5。
# 3.alpha :平衡因子,默认值为0.25。
def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
# 调用了父类 nn.Module 的构造函数,这是初始化模块时的标准做法。
super().__init__()
# 将传入的损失函数赋值给 self.loss_fcn 属性。
self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
# 将传入的 gamma 参数赋值给 self.gamma 属性。
self.gamma = gamma
# 将传入的 alpha 参数赋值给 self.alpha 属性。
self.alpha = alpha
# 将传入损失函数的 reduction 属性值赋值给 self.reduction 属性。
self.reduction = loss_fcn.reduction
# 将传入损失函数的 reduction 属性设置为 "none" ,这是为了在计算焦点损失时不对损失进行求和或平均,而是对每个元素单独应用焦点损失。
self.loss_fcn.reduction = "none" # required to apply FL to each element
# 这是 FocalLoss 类的前向传播函数,它接受两个参数。
# 1.pred :预测值。
# 2.true :真实值。
def forward(self, pred, true):
# 使用传入的损失函数计算原始的二元交叉熵损失。
loss = self.loss_fcn(pred, true)
# p_t = torch.exp(-loss)
# loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
# TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
# 将预测值通过 sigmoid 函数转换为概率值。
pred_prob = torch.sigmoid(pred) # prob from logits
# 计算 p_t ,它是一个介于0和1之间的值,表示预测概率和真实标签之间的匹配程度。
p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
# 计算 alpha_factor ,它是一个根据真实标签调整的 alpha 因子。
alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
# 计算 modulating_factor ,它是一个调节因子,用于增强模型对难样本的关注。
modulating_factor = (1.0 - p_t) ** self.gamma
# 将原始损失乘以 alpha_factor 和 modulating_factor ,得到焦点损失。
loss *= alpha_factor * modulating_factor
# 检查 self.reduction 属性是否为 "mean" ,如果是,则返回损失的平均值。
if self.reduction == "mean":
return loss.mean()
# 检查 self.reduction 属性是否为 "sum" ,如果是,则返回损失的总和。
elif self.reduction == "sum":
return loss.sum()
# 处理 self.reduction 属性为 "none" 的情况,返回未经求和或平均的损失。
else: # 'none'
return loss
# FocalLoss 类实现了焦点损失,它通过调整损失函数来增强模型对难样本的关注,特别是在类别不平衡的情况下。这个损失函数可以提高模型在处理不平衡数据时的性能。通过设置 reduction 属性,用户可以控制损失的聚合方式。
5.class BboxLoss(nn.Module):
python
# 这段代码定义了一个名为 BboxLoss 的类,它是一个 PyTorch 模块,用于计算目标检测任务中的边界框损失。这个损失函数包括两个部分 :IoU损失和DFLoss(Distance Focal Loss)。
# 这行代码定义了一个名为 BboxLoss 的类,它继承自 nn.Module ,这是所有PyTorch模块的基类。
class BboxLoss(nn.Module):
# 这是 BboxLoss 类的构造函数,它接受两个参数。
# 1.reg_max :用于DFLoss的最大值。
# 2.use_dfl :一个布尔值,表示是否使用DFLoss,默认为False。
def __init__(self, reg_max, use_dfl=False):
# 调用了父类 nn.Module 的构造函数,这是初始化模块时的标准做法。
super().__init__()
# 将传入的 reg_max 参数赋值给 self.reg_max 属性。
self.reg_max = reg_max
# 将传入的 use_dfl 参数赋值给 self.use_dfl 属性。
self.use_dfl = use_dfl
# 这是 BboxLoss 类的前向传播函数,它接受七个参数。
# 1.pred_dist :预测的距离分布。
# 2.pred_bboxes :预测的边界框。
# 3.anchor_points :锚点。
# 4.target_bboxes :目标边界框。
# 5.target_scores :目标分数。
# 6.target_scores_sum :目标分数总和。
# 7.fg_mask :前景掩码。
def forward(self, pred_dist, pred_bboxes, anchor_points, target_bboxes, target_scores, target_scores_sum, fg_mask):
# iou loss
# 将前景掩码扩展到四维,并重复以匹配边界框的维度。
bbox_mask = fg_mask.unsqueeze(-1).repeat([1, 1, 4]) # (b, h*w, 4)
# 使用掩码选择预测的边界框,并将其重塑为二维。
pred_bboxes_pos = torch.masked_select(pred_bboxes, bbox_mask).view(-1, 4)
# 使用掩码选择目标边界框,并将其重塑为二维。
target_bboxes_pos = torch.masked_select(target_bboxes, bbox_mask).view(-1, 4)
# 使用掩码选择目标分数,并将其重塑为二维。
bbox_weight = torch.masked_select(target_scores.sum(-1), fg_mask).unsqueeze(-1)
# 计算预测边界框和目标边界框之间的IoU(交并比)。
iou = bbox_iou(pred_bboxes_pos, target_bboxes_pos, xywh=False, CIoU=True)
# 计算IoU损失。
loss_iou = 1.0 - iou
# 将IoU损失乘以权重。
loss_iou *= bbox_weight
# 计算IoU损失的平均值。
loss_iou = loss_iou.sum() / target_scores_sum
# dfl loss
# 检查是否使用DFLoss。
if self.use_dfl:
# 将前景掩码扩展到DFLoss所需的维度,并重复以匹配距离分布的维度。
dist_mask = fg_mask.unsqueeze(-1).repeat([1, 1, (self.reg_max + 1) * 4])
# 使用掩码选择预测的距离分布,并将其重塑为三维。
pred_dist_pos = torch.masked_select(pred_dist, dist_mask).view(-1, 4, self.reg_max + 1)
# 将目标边界框转换为距离分布。
target_ltrb = bbox2dist(anchor_points, target_bboxes, self.reg_max)
# 使用掩码选择目标距离分布,并将其重塑为二维。
target_ltrb_pos = torch.masked_select(target_ltrb, bbox_mask).view(-1, 4)
# 计算DFLoss。
loss_dfl = self._df_loss(pred_dist_pos, target_ltrb_pos) * bbox_weight
# 计算DFLoss的平均值。
loss_dfl = loss_dfl.sum() / target_scores_sum
# 处理不使用DFLoss的情况。
else:
# 在不使用DFLoss时,将DFLoss设置为0。
loss_dfl = torch.tensor(0.0).to(pred_dist.device)
# 返回IoU损失、DFLoss和IoU值。
return loss_iou, loss_dfl, iou
# 这是 BboxLoss 类的私有方法,用于计算DFLoss。
# 1.pred_dist :预测的距离。
# 2.target :目标距离。
def _df_loss(self, pred_dist, target):
# 将目标距离转换为整数。
target_left = target.to(torch.long)
# 计算目标距离的右侧边界。
target_right = target_left + 1
# 计算左侧边界的权重。
weight_left = target_right.to(torch.float) - target
# 计算右侧边界的权重。
weight_right = 1 - weight_left
# 计算左侧边界的交叉熵损失。
loss_left = F.cross_entropy(pred_dist.view(-1, self.reg_max + 1), target_left.view(-1), reduction="none").view(
target_left.shape) * weight_left
# 计算右侧边界的交叉熵损失。
loss_right = F.cross_entropy(pred_dist.view(-1, self.reg_max + 1), target_right.view(-1),
reduction="none").view(target_left.shape) * weight_right
# 计算DFLoss的平均值。
return (loss_left + loss_right).mean(-1, keepdim=True)
# BboxLoss 类实现了目标检测任务中的边界框损失计算,包括IoU损失和可选的DFLoss。这个损失函数可以帮助模型更准确地预测边界框的位置。通过设置 use_dfl 参数,用户可以控制是否使用DFLoss。
6.class ComputeLoss:
python
# 这段代码定义了一个名为 ComputeLoss 的类,它用于计算目标检测模型的损失。这个类整合了多种损失函数,包括类别损失、边界框损失(IoU损失和DFLoss),并且支持标签平滑和焦点损失。
# 定义了一个名为 ComputeLoss 的类。
class ComputeLoss:
# Compute losses
# 这是 ComputeLoss 类的构造函数,它接受两个参数。
# 1.model :目标检测模型。
# 2.use_dfl :一个布尔值,表示是否使用DFLoss,默认为True。
def __init__(self, model, use_dfl=True):
# 获取模型参数所在的设备(CPU或GPU)。
device = next(model.parameters()).device # get model device
# 获取模型的超参数。
h = model.hyp # hyperparameters
# Define criteria
# 定义二元交叉熵损失函数,用于类别损失计算,其中 pos_weight 是正样本的权重。
BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h["cls_pw"]], device=device), reduction='none')
# Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
# 使用 smooth_BCE 函数计算标签平滑的正负样本目标值。
self.cp, self.cn = smooth_BCE(eps=h.get("label_smoothing", 0.0)) # positive, negative BCE targets
# Focal loss
# 获取焦点损失的 gamma 参数。
g = h["fl_gamma"] # focal loss gamma
# 如果 gamma 大于0,则使用焦点损失。
if g > 0:
# 如果使用焦点损失,则将 BCEcls 包装为焦点损失。
BCEcls = FocalLoss(BCEcls, g)
# 获取模型的最后一层。
m = de_parallel(model).model[-1] # Detect() module
# 定义不同层的损失平衡系数。
self.balance = {3: [4.0, 1.0, 0.4]}.get(m.nl, [4.0, 1.0, 0.25, 0.06, 0.02]) # P3-P7
# 保存类别损失函数。
self.BCEcls = BCEcls
# 保存超参数。
self.hyp = h
# 保存模型的步长。
self.stride = m.stride # model strides
# 保存类别数量。
self.nc = m.nc # number of classes
# 保存模型的层数。
self.nl = m.nl # number of layers
# 保存模型的输出数量。
self.no = m.no
# 保存DFLoss的最大值。
self.reg_max = m.reg_max
# 保存设备信息。
self.device = device
# 创建任务对齐分配器。
self.assigner = TaskAlignedAssigner(topk=int(os.getenv('YOLOM', 10)),
num_classes=self.nc,
alpha=float(os.getenv('YOLOA', 0.5)),
beta=float(os.getenv('YOLOB', 6.0)))
# 创建边界框损失计算器。
self.bbox_loss = BboxLoss(m.reg_max - 1, use_dfl=use_dfl).to(device)
# 创建投影向量。
self.proj = torch.arange(m.reg_max).float().to(device) # / 120.0
# 保存是否使用DFL的标志。
self.use_dfl = use_dfl
# 这段代码是 ComputeLoss 类中的 preprocess 方法,它用于预处理目标数据,将其转换为模型所需的格式。
# 定义 preprocess 方法,它接受四个参数。
# 1.self :类的实例。
# 2.targets :目标数据。
# 3.batch_size :批次大小。
# 4.scale_tensor :用于缩放目标边界框的张量)。
def preprocess(self, targets, batch_size, scale_tensor):
# 检查目标数据是否为空。
if targets.shape[0] == 0:
# 如果目标数据为空,则创建一个形状为 (batch_size, 0, 5) 的零张量,其中 5 表示每个目标的五个属性(通常是类别标签和四个边界框坐标)。
out = torch.zeros(batch_size, 0, 5, device=self.device)
# 如果目标数据不为空,则执行以下操作。
else:
# 提取目标数据中的第一列,即图像索引。
i = targets[:, 0] # image index
# 计算每个图像索引出现的次数。
_, counts = i.unique(return_counts=True)
# 创建一个形状为 (batch_size, max_counts, 5) 的零张量,其中 max_counts 是所有图像索引出现次数的最大值。
out = torch.zeros(batch_size, counts.max(), 5, device=self.device)
# 遍历每个批次。
for j in range(batch_size):
# 对于每个批次,找到与当前批次索引匹配的目标。
matches = i == j
# 计算匹配目标的数量。
n = matches.sum()
# 如果匹配目标的数量大于0,则执行以下操作。
if n:
# 将匹配的目标数据复制到输出张量中。
out[j, :n] = targets[matches, 1:]
# 将输出张量中的边界框坐标从 xywh 格式转换为 xyxy 格式,并乘以 scale_tensor 进行缩放。
out[..., 1:5] = xywh2xyxy(out[..., 1:5].mul_(scale_tensor))
# 返回预处理后的目标数据。
return out
# preprocess 方法将目标数据转换为模型所需的格式,包括将边界框坐标从 xywh 格式转换为 xyxy 格式,并根据图像尺寸进行缩放。如果目标数据为空,则返回一个全零的张量。这个方法确保了目标数据与模型的输入要求相匹配。
# 这段代码是 ComputeLoss 类中的 bbox_decode 方法,它用于将预测的距离分布转换为边界框坐标。
# 定义 bbox_decode 方法,它接受三个参数。
# 1.self :类的实例。
# 2.anchor_points :锚点。
# 3.pred_dist :预测的距离分布。
def bbox_decode(self, anchor_points, pred_dist):
# 检查是否使用 DFLoss(Distance Focal Loss)。
if self.use_dfl:
# 如果使用 DFLoss,获取预测距离分布的形状,分别对应批次大小( b )、锚点数量( a )和通道数( c )。
b, a, c = pred_dist.shape # batch, anchors, channels
# 将预测距离分布重塑为 (batch, anchors, 4, channels // 4) 的形状,并在最后一个维度(通道)上应用 softmax 函数,以获取每个距离分布的概率分布。使用矩阵乘法将 softmax 后的概率分布与投影向量( self.proj )相乘,以得到最终的预测距离。
pred_dist = pred_dist.view(b, a, 4, c // 4).softmax(3).matmul(self.proj.type(pred_dist.dtype))
# pred_dist = pred_dist.view(b, a, c // 4, 4).transpose(2,3).softmax(3).matmul(self.proj.type(pred_dist.dtype))
# pred_dist = (pred_dist.view(b, a, c // 4, 4).softmax(2) * self.proj.type(pred_dist.dtype).view(1, 1, -1, 1)).sum(2)
# 使用 dist2bbox 函数将预测的距离转换为边界框坐标。 xywh=False 参数表示输出的边界框坐标格式不是 xywh (中心点坐标和宽高),而是其他格式,如 ltrb (左上角和右下角坐标)。
return dist2bbox(pred_dist, anchor_points, xywh=False)
# bbox_decode 方法将预测的距离分布转换为边界框坐标。如果使用 DFLoss,它会先对预测距离分布应用 softmax 函数,然后与投影向量进行矩阵乘法,以获取最终的预测距离。最后,它使用 dist2bbox 函数将这些距离转换为边界框坐标。这个方法是目标检测模型中将预测的分布转换为实际边界框的关键步骤。
# 这段代码是 ComputeLoss 类的 __call__ 方法,它是类的主函数,用于计算模型的损失。
# 定义 __call__ 方法,它接受四个参数。
# 1.self :类的实例。
# 2.p :模型的预测输出。
# 3.targets :目标数据。
# 4.img :图像数据,可选。
# 5.epoch :当前训练周期,可选,默认为0。
def __call__(self, p, targets, img=None, epoch=0):
# 初始化一个包含三个元素的零张量,分别用于存储边界框损失、类别损失和DFLoss。
loss = torch.zeros(3, device=self.device) # box, cls, dfl
# 如果 p 是一个元组,则提取第二个元素(特征图);否则,直接使用 p 。
feats = p[1] if isinstance(p, tuple) else p
# 将特征图在第二维度上连接,然后分割成预测的距离分布和预测的分数。
pred_distri, pred_scores = torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split(
(self.reg_max * 4, self.nc), 1) # ❌ ⚠️ 不应该在维度1上进行分割,正确的应该是 在 形状为 (batch_size, self.no, total_elements)张量的 total_elements 维度,即维度2上进行分割。
# 调整预测分数的维度,并使其连续。
pred_scores = pred_scores.permute(0, 2, 1).contiguous()
# 调整预测距离分布的维度,并使其连续。
pred_distri = pred_distri.permute(0, 2, 1).contiguous()
# 获取预测分数的数据类型。
dtype = pred_scores.dtype
# 获取批次大小和网格大小。
batch_size, grid_size = pred_scores.shape[:2]
# 计算图像尺寸,考虑步长。
imgsz = torch.tensor(feats[0].shape[2:], device=self.device, dtype=dtype) * self.stride[0] # image size (h,w)
# 创建锚点和步长张量。
anchor_points, stride_tensor = make_anchors(feats, self.stride, 0.5)
# targets
# 预处理目标数据。
targets = self.preprocess(targets, batch_size, scale_tensor=imgsz[[1, 0, 1, 0]])
# 分割目标数据为类别标签和边界框。
gt_labels, gt_bboxes = targets.split((1, 4), 2) # cls, xyxy
# 创建目标掩码,标记非零边界框。
mask_gt = gt_bboxes.sum(2, keepdim=True).gt_(0)
# pboxes
# 解码预测边界框。
pred_bboxes = self.bbox_decode(anchor_points, pred_distri) # xyxy, (b, h*w, 4)
# 使用分配器分配目标。
target_labels, target_bboxes, target_scores, fg_mask = self.assigner(
pred_scores.detach().sigmoid(),
(pred_bboxes.detach() * stride_tensor).type(gt_bboxes.dtype),
anchor_points * stride_tensor,
gt_labels,
gt_bboxes,
mask_gt)
# 调整目标边界框。
# ⚠️ 从上面self.assigner()得到的 真实边界框 target_bboxes 是在原始图像坐标系下的坐标,将它除以 步长 stride_tensor 得到的是在特征图坐标系下的坐标。上下文中从self.bbox_decode()得到的 预测边界框 pred_bboxes 它也是特征图坐标系下的坐标。
# ⚠️ 这两个坐标将用于下文self.bbox_loss()中计算损失。
# ⚠️ 在计算损失时涉及真实框与预测框的交并比(IoU)的计算。在计算交并比(IoU)时,真实框和预测框的坐标确实需要在同一坐标系下。通常情况下,这些坐标应该是相对于原始图像的绝对坐标,而不是特征图坐标系下的坐标。
# ⚠️ 我认为应该将 target_bboxes /= stride_tensor 代码改为 pred_bboxes *= stride_tensor ,即将 预测边界框 pred_bboxes 从特征图坐标系转换为原始坐标系,与本就是原始坐标系下的 真实边界框 target_bboxes 参与self.bbox_loss()中损失的计算。
target_bboxes /= stride_tensor
# 计算目标分数总和。
target_scores_sum = max(target_scores.sum(), 1)
# cls loss
# loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum # VFL way
# 计算类别损失。
loss[1] = self.BCEcls(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum # BCE
# bbox loss
# 如果存在前景掩码,则计算边界框损失。
if fg_mask.sum():
# 计算边界框损失和DFLoss。
loss[0], loss[2], iou = self.bbox_loss(pred_distri,
pred_bboxes,
anchor_points,
target_bboxes,
target_scores,
target_scores_sum,
fg_mask)
# 调整边界框损失的权重。
loss[0] *= 7.5 # box gain
# 调整类别损失的权重。
loss[1] *= 0.5 # cls gain
# 调整DFLoss的权重。
loss[2] *= 1.5 # dfl gain
# 返回总损失和损失的副本。
return loss.sum() * batch_size, loss.detach() # loss(box, cls, dfl)
# __call__ 方法是 ComputeLoss 类的核心,它整合了类别损失、边界框损失和DFLoss的计算,并根据需要调整损失的权重。这个方法确保了损失的计算与模型的预测输出和目标数据相匹配,并返回最终的损失值。
# ComputeLoss 类整合了目标检测模型的多种损失计算,包括类别损失、边界框损失(IoU损失和DFLoss),并支持标签平滑和焦点损失。这个类提供了一个统一的接口来计算和返回模型的损失。