YOLOv11-ultralytics-8.3.67部分代码阅读笔记-ops.py

news/2025/3/1 15:12:51/

ops.py

ultralytics\models\utils\ops.py

目录

ops.py

1.所需的库和模块

2.class HungarianMatcher(nn.Module): 

3.def get_cdn_group(batch, num_classes, num_queries, class_embed, num_dn=100, cls_noise_ratio=0.5, box_noise_scale=1.0, training=False): 


1.所需的库和模块

# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/licenseimport torch
import torch.nn as nn
import torch.nn.functional as F
from scipy.optimize import linear_sum_assignmentfrom ultralytics.utils.metrics import bbox_iou
from ultralytics.utils.ops import xywh2xyxy, xyxy2xywh

2.class HungarianMatcher(nn.Module): 

# 匈牙利算法(Hungarian Algorithm)是一种经典的组合优化算法,用于解决二分图的最大匹配问题或最小成本分配问题。它最初由匈牙利数学家康尼格(Dénes Kőnig)和埃格尔瓦里(Jenő Egerváry)提出,因此得名“匈牙利算法”。该算法在计算机科学、运筹学和经济学等领域有广泛的应用。
# 背景与问题定义 :
# 在许多实际问题中,我们需要将一组元素与另一组元素进行最优匹配。例如 :
# 分配问题 :有若干项任务和若干个工人,每个工人完成每项任务都有一个成本,目标是将任务分配给工人,使得总成本最小。
# 目标检测中的匹配问题 :在目标检测中,需要将预测框与真实框进行匹配,使得匹配的总成本(如分类损失、位置损失等)最小。
# 这些问题可以抽象为一个二分图匹配问题,其中 :
# 一组节点表示“任务”,另一组节点表示“工人”。
# 每个节点对之间有一个成本(或权重)。
# 目标是找到一个匹配方案,使得总成本最小(或总权重最大)。
# 匈牙利算法的基本思想 :
# 匈牙利算法的核心思想是通过增广路径来逐步优化匹配结果。具体步骤如下 :
# 构建成本矩阵 :
# 假设我们有 n 个任务和 n 个工人,每个任务分配给每个工人的成本可以用一个 n×n 的矩阵表示。例如 :
# ┌ 任务n ┬ 工人1 ┬ 工人2 ┬ 工人3 ┐
# ├ 任务1 ┼   4  ┼    2   ┼    3   ┤
# ├ 任务2 ┼   3  ┼    1   ┼    2   ┤
# └ 任务3 ┴   2  ┴    3   ┴    4   ┘
# 初始化匹配 :初始时,每个任务和工人都未匹配。
# 寻找增广路径 :
# 增广路径 :从一个未匹配的任务出发,通过交替的匹配和未匹配边,找到一个可以改善匹配的路径。
# 例如,假设当前匹配为 :
# 任务1 → 工人2
# 任务2 → 工人3
# 任务3 → 未匹配
# 如果存在路径 :任务3 → 工人1 → 任务1 → 工人2,那么可以通过交换匹配关系,将任务3分配给工人1,任务1分配给工人2,从而改善匹配。
# 更新匹配 :通过增广路径重新分配任务和工人,直到无法再找到增广路径为止。
# 优化成本 :匈牙利算法通过行和列的调整(如减去行最小值或列最小值),使得成本矩阵中尽可能多地出现零元素,从而简化匹配过程。
# 算法步骤(简化版) :
# 初始化 :从成本矩阵中减去每行的最小值,再减去每列的最小值。
# 标记未匹配行和列 :标记所有未匹配的行,通过交替标记行和列,找到所有可能的增广路径。
# 调整成本矩阵 :如果无法找到完美匹配,通过调整矩阵中的值,使得更多零元素出现。
# 重复步骤2和3 :直到找到完美匹配。
# 复杂度 :匈牙利算法的时间复杂度为 O(n³),其中 n 是任务或工人的数量。虽然复杂度较高,但在实际应用中,由于其高效性和稳定性,仍然是解决这类问题的常用方法。
# 应用场景 :
# 目标检测 :在目标检测中,匈牙利算法用于将预测框与真实框进行最优匹配,最小化分类损失、位置损失等总成本。
# 任务分配 :在多任务分配问题中,匈牙利算法可以找到成本最低的分配方案。
# 网络流问题 :在图论中,匈牙利算法可以用于求解二分图的最大流问题。
# 总结 :匈牙利算法是一种高效的组合优化算法,用于解决二分图的最大匹配问题或最小成本分配问题。它通过增广路径逐步优化匹配结果,并通过行和列的调整简化计算过程。在目标检测、任务分配和图论等领域有广泛的应用。# 这段代码定义了一个名为 HungarianMatcher 的 PyTorch 模块,用于实现基于匈牙利算法的目标检测匹配机制。它主要用于计算预测框和真实框之间的匹配关系,以便在目标检测任务中优化模型的训练过程。
# 定义了一个名为 HungarianMatcher 的类,继承自 PyTorch 的 nn.Module ,表示这是一个可复用的神经网络模块。
class HungarianMatcher(nn.Module):# 实现 HungarianMatcher 的模块,这是一个可微分模块,用于以端到端方式解决分配问题。# HungarianMatcher 使用考虑分类分数、边界框坐标以及可选的掩码预测的成本函数对预测和地面真实边界框执行最佳分配。# 方法:# forward(pred_bboxes, pred_scores、gt_bboxes、gt_cls、gt_groups、masks=None、gt_mask=None):计算一批预测结果与基本事实之间的分配。# _cost_mask(bs、num_gts、masks=None、gt_mask=None):如果预测了 mask,则计算 mask 成本和 dice 成本。"""A module implementing the HungarianMatcher, which is a differentiable module to solve the assignment problem in anend-to-end fashion.HungarianMatcher performs optimal assignment over the predicted and ground truth bounding boxes using a costfunction that considers classification scores, bounding box coordinates, and optionally, mask predictions.Attributes:cost_gain (dict): Dictionary of cost coefficients: 'class', 'bbox', 'giou', 'mask', and 'dice'.use_fl (bool): Indicates whether to use Focal Loss for the classification cost calculation.with_mask (bool): Indicates whether the model makes mask predictions.num_sample_points (int): The number of sample points used in mask cost calculation.alpha (float): The alpha factor in Focal Loss calculation.gamma (float): The gamma factor in Focal Loss calculation.Methods:forward(pred_bboxes, pred_scores, gt_bboxes, gt_cls, gt_groups, masks=None, gt_mask=None): Computes theassignment between predictions and ground truths for a batch._cost_mask(bs, num_gts, masks=None, gt_mask=None): Computes the mask cost and dice cost if masks are predicted."""# 这段代码是 HungarianMatcher 类的初始化方法 __init__ ,用于定义和初始化该类的属性。# 定义了 HungarianMatcher 类的初始化方法,接收以下参数 :# 1.cost_gain (可选):一个字典,用于定义不同损失项的权重。如果未提供,则会使用默认值。# 2.use_fl (布尔值,默认为 True ):是否使用 Focal Loss(焦点损失)。# 3.with_mask (布尔值,默认为 False ):是否启用掩码匹配功能。# 4.num_sample_points (整数,默认为 12544 ):掩码匹配时的采样点数。# 5.alpha (浮点数,默认为 0.25 ):Focal Loss 中的一个超参数,用于调整正样本的权重。# 6.gamma (浮点数,默认为 2.0 ):Focal Loss 中的另一个超参数,用于调整难分类样本的权重。def __init__(self, cost_gain=None, use_fl=True, with_mask=False, num_sample_points=12544, alpha=0.25, gamma=2.0):# 初始化 HungarianMatcher 模块,以便对预测和地面真实边界框进行最佳分配。"""Initializes a HungarianMatcher module for optimal assignment of predicted and ground truth bounding boxes."""# 调用父类 nn.Module 的初始化方法。这是 PyTorch 中模块的标准初始化方式,确保模块能够正确注册参数和子模块。super().__init__()# 如果用户没有提供 cost_gain 参数,则使用默认值。 cost_gain 是一个字典, 定义了不同损失项的权重 。if cost_gain is None:# "class" :分类损失的权重。# "bbox" :边界框(Bounding Box)L1 损失的权重。# "giou" :广义交并比(GIoU)损失的权重。# "mask" :掩码损失的权重(如果启用掩码匹配)。# "dice" :Dice 损失的权重(如果启用掩码匹配)。cost_gain = {"class": 1, "bbox": 5, "giou": 2, "mask": 1, "dice": 1}# 将 cost_gain 保存为类的属性,以便在后续的前向传播方法中使用。self.cost_gain = cost_gain# 将 use_fl 保存为类的属性,用于决定是否在分类损失中使用 Focal Loss。self.use_fl = use_fl# 将 with_mask 保存为类的属性,用于决定是否启用掩码匹配功能。self.with_mask = with_mask# 将 num_sample_points 保存为类的属性,用于掩码匹配时的采样点数。如果启用掩码匹配,该参数将用于计算掩码损失。self.num_sample_points = num_sample_points# 将 alpha 和 gamma 保存为类的属性。这两个参数是 Focal Loss 的超参数,用于调整损失函数的形状。# alpha :用于调整 正样本的权重 。self.alpha = alpha# gamma :用于调整 难分类样本的权重 。self.gamma = gamma# 这段代码定义了 HungarianMatcher 类的初始化方法,用于设置和保存类的属性。这些属性包括:不同损失项的权重( cost_gain )。是否使用 Focal Loss( use_fl )。是否启用掩码匹配( with_mask )。掩码匹配时的采样点数( num_sample_points )。 Focal Loss 的超参数( alpha 和 gamma )。这些属性在后续的前向传播方法中会被用于计算预测框和真实框之间的匹配成本矩阵,从而实现最优匹配。# 掩码匹配是一种在目标检测任务中用于比较预测掩码(预测的分割结果)和真实掩码(标注的分割结果)的方法。它的目的是衡量预测掩码与真实掩码之间的相似度,并通过某种方式计算它们之间的匹配程度。# 在目标检测和实例分割任务中,除了需要定位目标(通过边界框)和分类目标外,还需要对目标的形状进行精确的分割。掩码匹配就是用于解决这一问题的关键步骤之一。# 具体应用 :# 分割任务 :在实例分割任务中,模型不仅需要输出目标的边界框和类别,还需要输出目标的分割掩码(通常是一个二维的二值图像,表示目标的轮廓)。# 损失计算 :掩码匹配通常用于计算分割掩码的损失函数,例如 Dice 损失 或 IoU 损失。通过比较预测掩码和真实掩码的相似度,可以优化模型的分割性能。# 掩码匹配的常见度量方式 :# Dice 系数 :衡量两个集合的相似度,计算公式为 :# Dice = (2×TP) / (2×TP+FP+FN)# 其中,TP 是真正例,FP 是假正例,FN 是假负例。# IoU(交并比) :计算预测掩码和真实掩码的交集与并集的比值 :# IoU = Intersection / Union# 点匹配 :通过采样点来比较预测掩码和真实掩码之间的相似度。# 什么是“掩码匹配时的采样点数”?# 在掩码匹配过程中,直接比较整个掩码(通常是高分辨率的二维图像)可能会非常耗时,尤其是在处理大规模数据集时。为了提高效率,通常会采用采样点数的方法,即从掩码中随机或均匀采样一定数量的点,然后通过这些点来近似计算掩码之间的相似度。# 具体含义 :# 采样点数 :表示在掩码匹配时,从预测掩码和真实掩码中采样的点的数量。例如,采样点数为 12544,意味着从每个掩码中采样 12544 个点。# 作用 :通过减少需要比较的点的数量,降低计算复杂度,同时保持足够的精度来衡量掩码之间的相似度。# 采样方法 :# 随机采样 :从掩码中随机选择点。# 均匀采样 :按照一定的规则(例如网格采样)从掩码中均匀选择点。# 重要性采样 :根据掩码的某些特征(例如边缘或关键区域)选择点。# 为什么需要采样?# 效率 :直接比较高分辨率掩码的每个像素会非常耗时,采样可以显著减少计算量。# 精度 :在某些情况下,采样点数足够多时,采样方法可以近似地反映掩码之间的整体相似度。# 总结 :掩码匹配是一种用于比较预测掩码和真实掩码相似度的方法,常用于实例分割任务中的损失计算。 采样点数是为了提高掩码匹配效率而采用的一种技术,通过从掩码中采样一定数量的点来近似计算掩码之间的相似度。 在实际应用中,采样点数的选择需要权衡效率和精度,通常根据任务需求和计算资源进行调整。# 这段代码是 HungarianMatcher 类的 forward 方法,用于计算预测框和真实框之间的最优匹配关系。它通过构建一个综合成本矩阵(包含分类损失、边界框损失和 GIoU 损失),并使用匈牙利算法( linear_sum_assignment )找到最优匹配。# 定义了 forward 方法,接收以下输入 :# 1.pred_bboxes :预测框的坐标,形状为 (batch_size, num_queries, 4) 。# 2.pred_scores :预测框的类别得分,形状为 (batch_size, num_queries, num_classes) 。# 3.gt_bboxes :真实框的坐标,形状为 (num_gt, 4) 。# 4.gt_cls :真实框的类别标签,形状为 (num_gt,) 。# 5.gt_groups :每个图像中真实框的数量,形状为 (batch_size,) 。# 6.masks 和 7.gt_mask :掩码相关输入,仅在启用掩码匹配时使用。def forward(self, pred_bboxes, pred_scores, gt_bboxes, gt_cls, gt_groups, masks=None, gt_mask=None):# HungarianMatcher 的前向传递。此函数根据预测和基本事实(分类成本、框之间的 L1 成本和框之间的 GIoU 成本)计算成本,并根据这些成本找到预测和基本事实之间的最佳匹配。# 参数:# pred_bboxes (Tensor):形状为 [batch_size, num_queries, 4] 的预测边界框。# pred_scores (Tensor):形状为 [batch_size, num_queries, num_classes] 的预测分数。# gt_cls (torch.Tensor):形状为 [num_gts, ] 的基本事实类。# gt_bboxes (torch.Tensor):形状为 [num_gts, 4] 的基本事实边界框。# gt_groups (List[int]):长度等于批次大小的列表,包含每个图像的基本事实数量。# mask (Tensor,可选):形状为 [batch_size, num_queries, height, width]。默认为 None。# gt_mask(List[Tensor],可选):地面实况掩码列表,每个掩码的形状为 [num_masks, Height, Width]。默认为 None。# 返回:# (List[Tuple[Tensor, Tensor]]):大小为 batch_size 的列表,每个元素都是一个元组 (index_i, index_j),其中:# - index_i 是所选预测的索引张量(按顺序)# - index_j 是相应所选地面实况目标的索引张量(按顺序)# 对于每个批次元素,它包含:# len(index_i) = len(index_j) = min(num_queries, num_target_boxes)"""Forward pass for HungarianMatcher. This function computes costs based on prediction and ground truth(classification cost, L1 cost between boxes and GIoU cost between boxes) and finds the optimal matching betweenpredictions and ground truth based on these costs.Args:pred_bboxes (Tensor): Predicted bounding boxes with shape [batch_size, num_queries, 4].pred_scores (Tensor): Predicted scores with shape [batch_size, num_queries, num_classes].gt_cls (torch.Tensor): Ground truth classes with shape [num_gts, ].gt_bboxes (torch.Tensor): Ground truth bounding boxes with shape [num_gts, 4].gt_groups (List[int]): List of length equal to batch size, containing the number of ground truths foreach image.masks (Tensor, optional): Predicted masks with shape [batch_size, num_queries, height, width].Defaults to None.gt_mask (List[Tensor], optional): List of ground truth masks, each with shape [num_masks, Height, Width].Defaults to None.Returns:(List[Tuple[Tensor, Tensor]]): A list of size batch_size, each element is a tuple (index_i, index_j), where:- index_i is the tensor of indices of the selected predictions (in order)- index_j is the tensor of indices of the corresponding selected ground truth targets (in order)For each batch element, it holds:len(index_i) = len(index_j) = min(num_queries, num_target_boxes)"""# 获取输入张量的形状。# bs :批量大小(batch size)。# nq :每个图像的预测框数量(queries)。# nc :类别数量。bs, nq, nc = pred_scores.shape# 如果没有真实框( sum(gt_groups) == 0 ),直接返回空匹配结果。这避免了在没有真实框的情况下进行不必要的计算。if sum(gt_groups) == 0:return [(torch.tensor([], dtype=torch.long), torch.tensor([], dtype=torch.long)) for _ in range(bs)]# 这段代码的作用是将预测框的得分和坐标进行展平处理,以便在批量(batch)中统一计算成本矩阵。# We flatten to compute the cost matrices in a batch    我们平铺计算一批成本矩阵。# [batch_size * num_queries, num_classes]# 将 预测得分张量 从形状 (batch_size, num_queries, num_classes) 展平为 (batch_size * num_queries, num_classes) 。# pred_scores 是 模型输出的预测得分 ,形状为 (batch_size, num_queries, num_classes) ,其中 : batch_size 是批量大小。 num_queries 是每个图像的预测框数量。 num_classes 是类别数量。# 使用 .view(-1, nc) 将其展平为二维张量,目的是 将所有图像的预测框得分合并到一个矩阵中 ,便于后续批量计算。# .detach() 是 PyTorch 的操作,用于分离张量,避免影响梯度传播(即不计算这部分操作的梯度)。pred_scores = pred_scores.detach().view(-1, nc)# 根据是否启用 Focal Loss( self.use_fl ),对预测得分进行激活操作。# 如果启用 Focal Loss( self.use_fl = True ),则对预测得分应用 Sigmoid 激活函数。Sigmoid 函数将得分映射到 (0, 1) 区间,表示 每个类别的预测概率 。# 如果不启用 Focal Loss( self.use_fl = False ),则对预测得分应用 Softmax 激活函数。Softmax 函数将 得分归一化为概率分布 ,使得 每一行的和为 1 。# 这一步的目的是将 预测得分 转换为 概率值 ,以便后续计算分类成本。pred_scores = F.sigmoid(pred_scores) if self.use_fl else F.softmax(pred_scores, dim=-1)# [batch_size * num_queries, 4]# 将预测框的坐标张量从形状 (batch_size, num_queries, 4) 展平为 (batch_size * num_queries, 4) 。# pred_bboxes 是 模型输出的预测框坐标 ,形状为 (batch_size, num_queries, 4) ,其中 4 表示每个框的坐标(通常是 (x, y, w, h) 或 (x1, y1, x2, y2) )。# 使用 .view(-1, 4) 将其展平为二维张量,目的是将所有图像的预测框坐标合并到一个矩阵中,便于后续批量计算。# .detach() 同样用于分离张量,避免影响梯度传播。pred_bboxes = pred_bboxes.detach().view(-1, 4)# 这三行代码的主要目的是将预测框的得分和坐标进行展平处理,以便在批量中统一计算成本矩阵。具体步骤如下。使用 .view() 将预测得分和预测框坐标从三维张量展平为二维张量。对预测得分进行激活操作(Sigmoid 或 Softmax),将其转换为概率值。使用 .detach() 分离张量,避免影响梯度传播。这种展平操作是深度学习中常见的处理方式,能够简化后续的计算过程,特别是在批量处理时,可以显著提高效率。# 这段代码的作用是计算分类成本(classification cost),即衡量预测框的类别得分与真实类别之间的匹配程度。分类成本是匈牙利匹配算法中的一个重要组成部分,用于评估预测框与真实框之间的类别相似度。# Compute the classification cost# 从预测得分中提取与真实类别对应的得分。# pred_scores 的形状为 (batch_size * num_queries, num_classes) ,表示 每个预测框对所有类别的得分 。# gt_cls 是一个一维张量,形状为 (num_gt,) ,表示 每个真实框的类别标签 。# 通过 pred_scores[:, gt_cls] ,选择 每个预测框对应的真实类别得分 。这一步确保后续计算的分类成本是针对真实类别进行的。pred_scores = pred_scores[:, gt_cls]# 判断是否使用 Focal Loss(焦点损失)来计算分类成本。 如果 self.use_fl 为 True ,则使用 Focal Loss 的公式计算分类成本。 Focal Loss 是一种改进的分类损失函数,旨在解决类别不平衡问题,特别适用于目标检测任务中正负样本比例悬殊的情况。if self.use_fl:# 计算 负样本的分类成本 。# self.alpha 和 self.gamma 是 Focal Loss 的超参数。 alpha 用于调整正样本的权重。 gamma 用于调整难分类样本的权重。 pred_scores 是预测框对真实类别的得分(概率值)。 -(1 - pred_scores + 1e-8).log() 是负样本的对数损失部分, 1e-8 是为了避免对 0 取对数导致数值不稳定。# neg_cost_class 表示负样本的分类成本,其公式为 :# neg_cost_class = (1-α) · (pred_scores^γ) · log(1-pred_scores+ε) 。neg_cost_class = (1 - self.alpha) * (pred_scores**self.gamma) * (-(1 - pred_scores + 1e-8).log())# 计算 正样本的分类成本 。# -(pred_scores + 1e-8).log() 是正样本的对数损失部分。 pos_cost_class 表示正样本的分类成本,其公式为 :# pos_cost_class = α · (1-pred_scores)^γ · log(pred_scores+ε) 。pos_cost_class = self.alpha * ((1 - pred_scores) ** self.gamma) * (-(pred_scores + 1e-8).log())# 计算 最终的分类成本 。# 在 Focal Loss 中,分类成本是 正样本成本 与 负样本成本 的差值。# 这种设计使得分类成本能够更好地反映预测框与真实框之间的类别匹配程度。cost_class = pos_cost_class - neg_cost_class# 如果不使用 Focal Loss,则直接取 预测得分的负值 作为分类成本。 这种简单的方式假设预测得分越高,分类成本越低,适用于 Softmax 输出的概率值。else:cost_class = -pred_scores# 这段代码实现了分类成本的计算,是匈牙利匹配算法中的一个重要步骤。主要逻辑如下。提取真实类别得分:从预测得分中选择与真实类别对应的得分。计算分类成本:如果启用 Focal Loss,分别计算正样本和负样本的成本,并通过它们的差值得到最终的分类成本。如果不启用 Focal Loss,直接取预测得分的负值作为分类成本。这种设计使得分类成本能够灵活地适应不同的损失函数需求,同时为后续的匹配算法提供了重要的输入。# 这段代码的作用是计算预测框和真实框之间的位置成本(包括 L1 距离成本和 GIoU 成本),并结合分类成本和掩码成本(如果启用)来构建最终的成本矩阵。# Compute the L1 cost between boxes    计算框之间的 L1 成本。# 计算预测框和真实框之间的 L1 距离成本。# pred_bboxes 的形状为 (bs * nq, 4) ,表示 所有预测框的坐标 。# gt_bboxes 的形状为 (num_gt, 4) ,表示 所有真实框的坐标 。# unsqueeze(1) 和 unsqueeze(0) 分别将 pred_bboxes 和 gt_bboxes 增加一个维度,以便进行广播操作。# abs() 计算预测框和真实框坐标差的绝对值。# sum(-1) 将每个框的坐标差的绝对值求和,得到 每个预测框和真实框之间的 L1 距离成本。# 最终得到的 cost_bbox 的形状为 (bs * nq, num_gt) 。cost_bbox = (pred_bboxes.unsqueeze(1) - gt_bboxes.unsqueeze(0)).abs().sum(-1)  # (bs*num_queries, num_gt)# Compute the GIoU cost between boxes, (bs*num_queries, num_gt)    计算框之间的 GIoU 成本,(bs*num_queries, num_gt)。# 计算预测框和真实框之间的 GIoU 成本。# bbox_iou 是一个函数,用于计算预测框和真实框之间的 IoU(交并比)或 GIoU(广义交并比)。# xywh=True 表示输入的框坐标是 (x, y, w, h) 格式。# GIoU=True 表示计算 GIoU,而不是普通的 IoU。# squeeze(-1) 去除最后一个维度,得到每个预测框和真实框之间的 GIoU 值。# 1.0 - bbox_iou 将 GIoU 值转换为 GIoU 成本,因为 GIoU 越接近 1,表示框越匹配,成本越低。cost_giou = 1.0 - bbox_iou(pred_bboxes.unsqueeze(1), gt_bboxes.unsqueeze(0), xywh=True, GIoU=True).squeeze(-1)# Final cost matrix    最终成本矩阵。# 构建最终的成本矩阵。# self.cost_gain 是一个字典,定义了 不同损失项的权重 。# cost_class 、 cost_bbox 和 cost_giou 分别是 分类成本 、 L1 距离成本 和 GIoU 成本 。 通过加权求和,得到最终的成本矩阵 C ,形状为 (bs * nq, num_gt) 。C = (self.cost_gain["class"] * cost_class+ self.cost_gain["bbox"] * cost_bbox+ self.cost_gain["giou"] * cost_giou)# Compute the mask cost and dice cost    计算掩码成本和dice成本。# 如果启用掩码匹配,计算掩码成本并将其加入总成本矩阵。# self.with_mask 是一个布尔值,表示是否启用掩码匹配。# self._cost_mask 是一个方法,用于计算掩码成本。# bs 是批量大小, gt_groups 是每个图像中真实框的数量, masks 和 gt_mask 是掩码相关输入。# 将计算得到的掩码成本加入总成本矩阵 C 。if self.with_mask:C += self._cost_mask(bs, gt_groups, masks, gt_mask)# 这段代码实现了预测框和真实框之间的位置成本计算,并结合分类成本和掩码成本(如果启用)来构建最终的成本矩阵。主要步骤如下。计算 L1 距离成本:通过计算预测框和真实框坐标差的绝对值并求和,得到 L1 距离成本。计算 GIoU 成本:通过计算预测框和真实框之间的 GIoU 值,并将其转换为成本。构建最终成本矩阵:将分类成本、L1 距离成本和 GIoU 成本加权求和,得到最终的成本矩阵。计算掩码成本(如果启用):计算掩码成本并将其加入总成本矩阵。这种综合成本矩阵的构建方式使得匈牙利匹配算法能够全面考虑预测框和真实框之间的类别、位置和掩码匹配程度,从而实现更精确的匹配。# 这段代码的作用是处理成本矩阵中的无效值,然后使用匈牙利算法(线性求和分配)找到最优匹配,并将匹配结果调整为全局索引。# Set invalid values (NaNs and infinities) to 0 (fixes ValueError: matrix contains invalid numeric entries)    将无效值(NaN 和无穷大)设置为 0(修复 ValueError:矩阵包含无效的数字条目)。# 将成本矩阵 C 中的无效值(NaN 和无穷大)替换为 0。# C.isnan() 生成一个与 C 形状相同的布尔矩阵,其中无效值(NaN)为 True 。# C.isinf() 生成一个与 C 形状相同的布尔矩阵,其中无穷大值为 True 。# C[C.isnan() | C.isinf()] 选择 C 中所有无效值的位置,并将它们替换为 0。这一步是必要的,因为无效值会导致后续计算出错。C[C.isnan() | C.isinf()] = 0.0# 将成本矩阵 C 重新调整为 (bs, nq, num_gt) 的形状,并移至 CPU。# C.view(bs, nq, -1) 将 C 从形状 (bs * nq, num_gt) 调整为 (bs, nq, num_gt) ,其中 bs 是批量大小, nq 是每个图像的预测框数量, num_gt 是真实框的数量。 .cpu() 将 C 移至 CPU,因为线性求和分配算法(匈牙利算法)通常在 CPU 上执行。C = C.view(bs, nq, -1).cpu()# 使用线性求和分配算法(匈牙利算法)为 每个图像 计算 预测框 和 真实框 之间的 最优匹配索引 。# C.split(gt_groups, -1) 按照每个图像的 真实框数量 分割成本矩阵 C 。# linear_sum_assignment(c[i]) 对每个分割后的成本矩阵 c[i] 应用 线性求和分配算法 ,找到 最优匹配的 行索引 和 列索引 。 indices 是一个列表,其中每个元素是一个元组,包含 每个图像的最优匹配索引 。indices = [linear_sum_assignment(c[i]) for i, c in enumerate(C.split(gt_groups, -1))]# 计算每个图像中 真实框的累积索引 。# gt_groups 是一个一维张量,表示每个图像中 真实框的数量 。# torch.as_tensor([0, *gt_groups[:-1]]) 在 gt_groups 前面添加一个 0,并移除最后一个元素。# .cumsum_(0) 计算累积和,得到每个图像中 真实框的累积索引 。这一步用于 将局部索引调整为全局索引 。gt_groups = torch.as_tensor([0, *gt_groups[:-1]]).cumsum_(0)  # (idx for queries, idx for gt)# 将匹配结果调整为 全局索引 并返回。# indices 是一个列表,其中每个元素是一个元组,包含 每个图像的最优匹配索引 。# for k, (i, j) in enumerate(indices) 遍历每个图像的最优匹配索引。# torch.tensor(i, dtype=torch.long) 将行索引(预测框索引)转换为 PyTorch 张量。# torch.tensor(j, dtype=torch.long) + gt_groups[k] 将列索引(真实框索引)转换为 PyTorch 张量,并加上累积索引 gt_groups[k] ,得到全局索引。# 返回一个列表,其中每个元素是一个元组,包含每个图像的预测框索引和真实框索引的匹配结果。return [(torch.tensor(i, dtype=torch.long), torch.tensor(j, dtype=torch.long) + gt_groups[k])for k, (i, j) in enumerate(indices)]# 段代码实现了成本矩阵的处理、最优匹配的计算和匹配结果的调整。主要步骤如下。处理无效值:将成本矩阵中的无效值(NaN 和无穷大)替换为 0。调整成本矩阵:将成本矩阵重新调整为 (bs, nq, num_gt) 的形状,并移至 CPU。计算最优匹配:使用线性求和分配算法(匈牙利算法)为每个图像计算预测框和真实框之间的最优匹配索引。调整匹配结果:将匹配结果调整为全局索引并返回。这种设计使得匈牙利匹配算法能够高效地找到最优匹配,并将匹配结果正确地表示为全局索引,为后续的损失计算和模型优化提供支持。# 这段代码定义了 HungarianMatcher 类的 forward 方法,用于计算预测框和真实框之间的最优匹配关系。主要步骤包括。输入处理:将预测框和预测得分展平,并根据是否使用 Focal Loss 进行激活。成本计算:计算分类成本(使用 Focal Loss 或负预测得分)。计算 L1 距离成本(衡量位置差异)。计算 GIoU 成本(衡量形状和大小差异)。如果启用掩码匹配,计算掩码成本。成本矩阵优化:将无效值替换为 0,并调整成本矩阵的形状。匈牙利算法:使用线性求和分配算法找到最优匹配。返回结果:返回每个图像的预测框索引和真实框索引的匹配结果。通过这种方式, HungarianMatcher 能够高效地为每个预测框找到最优的真实框匹配,从而为后续的损失计算和模型优化提供支持。# This function is for future RT-DETR Segment models# def _cost_mask(self, bs, num_gts, masks=None, gt_mask=None):#     assert masks is not None and gt_mask is not None, 'Make sure the input has `mask` and `gt_mask`'#     # all masks share the same set of points for efficient matching#     sample_points = torch.rand([bs, 1, self.num_sample_points, 2])#     sample_points = 2.0 * sample_points - 1.0##     out_mask = F.grid_sample(masks.detach(), sample_points, align_corners=False).squeeze(-2)#     out_mask = out_mask.flatten(0, 1)##     tgt_mask = torch.cat(gt_mask).unsqueeze(1)#     sample_points = torch.cat([a.repeat(b, 1, 1, 1) for a, b in zip(sample_points, num_gts) if b > 0])#     tgt_mask = F.grid_sample(tgt_mask, sample_points, align_corners=False).squeeze([1, 2])##     with torch.amp.autocast("cuda", enabled=False):#         # binary cross entropy cost#         pos_cost_mask = F.binary_cross_entropy_with_logits(out_mask, torch.ones_like(out_mask), reduction='none')#         neg_cost_mask = F.binary_cross_entropy_with_logits(out_mask, torch.zeros_like(out_mask), reduction='none')#         cost_mask = torch.matmul(pos_cost_mask, tgt_mask.T) + torch.matmul(neg_cost_mask, 1 - tgt_mask.T)#         cost_mask /= self.num_sample_points##         # dice cost#         out_mask = F.sigmoid(out_mask)#         numerator = 2 * torch.matmul(out_mask, tgt_mask.T)#         denominator = out_mask.sum(-1, keepdim=True) + tgt_mask.sum(-1).unsqueeze(0)#         cost_dice = 1 - (numerator + 1) / (denominator + 1)##         C = self.cost_gain['mask'] * cost_mask + self.cost_gain['dice'] * cost_dice#     return C

3.def get_cdn_group(batch, num_classes, num_queries, class_embed, num_dn=100, cls_noise_ratio=0.5, box_noise_scale=1.0, training=False): 

# 这段代码定义了一个名为 get_cdn_group 的函数,用于生成对比去噪训练(Contrastive Denoising Training, CDN)所需的正负样本组。它通过对输入的类别标签和边界框坐标添加噪声来生成这些样本,并返回修改后的类别嵌入、边界框、注意力掩码以及元信息。
# 定义了一个函数 get_cdn_group ,接受以下参数 :
# 1.batch :包含 gt_cls (类别标签)、 gt_bboxes (边界框坐标)和 gt_groups (每张图片的 GT 数量)的字典。
# 2.num_classes :类别总数。
# 3.num_queries :查询数量。
# 4.class_embed :类别嵌入向量。
# 5.num_dn :去噪数量,默认为 100。
# 6.cls_noise_ratio :类别标签噪声比例,默认为 0.5。
# 7.box_noise_scale :边界框噪声比例,默认为 1.0。
# 8.training :是否为训练模式,默认为 False 。
def get_cdn_group(batch, num_classes, num_queries, class_embed, num_dn=100, cls_noise_ratio=0.5, box_noise_scale=1.0, training=False
):# 获取对比去噪训练组。此函数使用来自地面实况 (gt) 的正样本和负样本创建一个对比去噪训练组。它将噪声应用于类标签和边界框坐标,并返回修改后的标签、边界框、注意掩码和元信息。# 参数:# batch (dict):一个字典,包含“gt_cls”(形状为 [num_gts, ] 的 torch.Tensor)、“gt_bboxes”(形状为 [num_gts, 4] 的 torch.Tensor)、“gt_groups”(List(int)),它是一个批处理大小长度列表,表示每个图像的 gts 数量。# num_classes (int):类数。# num_queries (int):查询数。# class_embed (torch.Tensor):嵌入权重,用于将类标签映射到嵌入空间。# num_dn (int,可选):去噪次数。默认为 100。# cls_noise_ratio (float,可选):类标签的噪声比。默认为 0.5。# box_noise_scale (float,可选):边界框坐标的噪声比例。默认为 1.0。# training (bool,可选):如果处于训练模式。默认为 False。# 返回:# (Tuple[Optional[Tensor], Optional[Tensor], Optional[Tensor], Optional[Dict]]):用于去噪的修改后的类嵌入、边界框、注意掩码和元信息。如果不处于训练模式或“num_dn”小于或等于 0,则该函数将为元组中的所有元素返回 None。"""Get contrastive denoising training group. This function creates a contrastive denoising training group with positiveand negative samples from the ground truths (gt). It applies noise to the class labels and bounding box coordinates,and returns the modified labels, bounding boxes, attention mask and meta information.Args:batch (dict): A dict that includes 'gt_cls' (torch.Tensor with shape [num_gts, ]), 'gt_bboxes'(torch.Tensor with shape [num_gts, 4]), 'gt_groups' (List(int)) which is a list of batch size lengthindicating the number of gts of each image.num_classes (int): Number of classes.num_queries (int): Number of queries.class_embed (torch.Tensor): Embedding weights to map class labels to embedding space.num_dn (int, optional): Number of denoising. Defaults to 100.cls_noise_ratio (float, optional): Noise ratio for class labels. Defaults to 0.5.box_noise_scale (float, optional): Noise scale for bounding box coordinates. Defaults to 1.0.training (bool, optional): If it's in training mode. Defaults to False.Returns:(Tuple[Optional[Tensor], Optional[Tensor], Optional[Tensor], Optional[Dict]]): The modified class embeddings,bounding boxes, attention mask and meta information for denoising. If not in training mode or 'num_dn'is less than or equal to 0, the function returns None for all elements in the tuple."""# 这段代码是函数 get_cdn_group 的起始部分,主要作用是进行一些初步的检查和判断,以决定是否继续执行后续的去噪训练(Denoising Training)逻辑。# 判断是否需要执行去噪训练。# not training :判断是否处于训练模式。如果 training 参数为 False ,表示当前不在训练模式下,去噪训练逻辑将不会执行。# num_dn <= 0 :判断去噪数量 num_dn 是否小于等于 0。如果 num_dn 为 0 或负数,表示不需要生成去噪样本。if (not training) or num_dn <= 0:#  如果上述条件中的任意一个成立,则直接返回四个 None ,表示不需要进行去噪训练。return None, None, None, None# 从输入的 batch 字典中提取 gt_groups 。 gt_groups 是一个列表,表示每张图片中 GT(真实标注)的数量。例如,如果 gt_groups = [3, 2, 1] ,表示第一张图片有 3 个 GT,第二张图片有 2 个 GT,第三张图片有 1 个 GT。gt_groups = batch["gt_groups"]# 计算所有图片中 GT 的总数。例如, gt_groups = [3, 2, 1] 时, total_num = 3 + 2 + 1 = 6 。total_num = sum(gt_groups)# 计算每张图片中 GT 的最大数量。例如, gt_groups = [3, 2, 1] 时, max_nums = 3 。max_nums = max(gt_groups)# 如果 max_nums == 0 ,表示所有图片中都没有 GT(即每张图片的 GT 数量都为 0)。if max_nums == 0:# 如果没有 GT,则直接返回四个 None ,表示无法进行去噪训练。return None, None, None, None# 这段代码的作用是。判断是否需要执行去噪训练逻辑(是否处于训练模式,以及去噪数量是否大于 0)。提取每张图片的 GT 数量,计算总 GT 数量和最大 GT 数量。如果没有 GT(即最大 GT 数量为 0),则直接返回 None ,表示无法进行去噪训练。这些初步检查确保了只有在训练模式下且有 GT 的情况下,才会继续执行后续的去噪训练逻辑。# 这段代码继续了上文的逻辑,主要目的是确定去噪组的数量,并从输入的 batch 中提取类别标签、边界框和批次索引等信息。# 计算每个 GT 应该分配的去噪组数量 。 num_dn 是总的去噪样本数量, max_nums 是每张图片中 GT 的最大数量。通过整除操作,确定每个 GT 可以分配到多少组去噪样本。# 例如,如果 num_dn = 100 , max_nums = 3 ,则 num_group = 100 // 3 = 33 。num_group = num_dn // max_nums# 如果计算结果 num_group == 0 ,则将其设置为 1。这是因为即使去噪数量不足以分配到每个 GT,至少也需要一个去噪组来保证逻辑的完整性。num_group = 1 if num_group == 0 else num_group# Pad gt to max_num of a batch# 计算 批量大小 ,即当前批次中有多少张图片。 gt_groups 是一个列表,其长度表示图片的数量。bs = len(gt_groups)# 从 batch 字典中提取 类别标签 。 gt_cls 的形状为 (bs*num,) ,其中 num 是每张图片的 GT 数量, bs 是批量大小。这表示所有图片的类别标签被展平为一个一维张量。gt_cls = batch["cls"]  # (bs*num, )# 从 batch 字典中提取 边界框坐标 。 gt_bbox 的形状为 (bs*num, 4) ,表示所有图片的边界框坐标,每个边界框有 4 个坐标值(通常是 (x, y, w, h) 或 (x1, y1, x2, y2) )。gt_bbox = batch["bboxes"]  # bs*num, 4# 从 batch 字典中提取 批次索引 。 b_idx 用于标识每个 GT 所属的图片索引,以便后续操作可以正确区分不同图片的 GT。b_idx = batch["batch_idx"]# 段代码的作用是。计算去噪组数量:根据总的去噪样本数量 num_dn 和每张图片的最大 GT 数量 max_nums ,计算每个 GT 应分配的去噪组数量,并确保至少有一个去噪组。提取批次信息:从输入的 batch 中提取类别标签、边界框坐标和批次索引,这些信息将用于后续的去噪训练逻辑。这些操作为后续生成正负样本和添加噪声等步骤提供了基础数据支持。# 这段代码的核心逻辑是为每个 GT(Ground Truth)生成正样本和负样本,并为后续的去噪训练(Denoising Training)准备数据。# Each group has positive and negative queries.# 生成正负样本。# 将 类别标签 gt_cls 重复 2 * num_group 次。每个 GT 会生成 2 * num_group 个样本,其中一半是正样本,另一半是负样本。因此,重复后的 dn_cls 的形状为 (2*num_group*bs*num,) 。dn_cls = gt_cls.repeat(2 * num_group)  # (2*num_group*bs*num, )# 将 边界框坐标 gt_bbox 重复 2 * num_group 次。边界框坐标是一个二维张量,形状为 (bs*num, 4) ,重复后形状为 (2*num_group*bs*num, 4) 。dn_bbox = gt_bbox.repeat(2 * num_group, 1)  # 2*num_group*bs*num, 4# 将 批次索引 b_idx 重复 2 * num_group 次,并将其展平为一维张量。 b_idx 用于标识每个 GT 所属的图片索引,重复后的 dn_b_idx 的形状为 (2*num_group*bs*num,) 。dn_b_idx = b_idx.repeat(2 * num_group).view(-1)  # (2*num_group*bs*num, )# Positive and negative mask# 定义负样本掩码。# (bs*num*num_group, ), the second total_num*num_group part as negative samples# torch.arange(total_num * num_group, dtype=torch.long, device=gt_bbox.device) :生成一个从 0 到 total_num * num_group - 1 的一维张量,表示正样本的索引范围。# + num_group * total_num :将上述索引范围整体向后移动 num_group * total_num 个位置,生成负样本的索引范围。# 例如,假设 total_num = 6 (总 GT 数量), num_group = 2 (每个 GT 的去噪组数量),则 :# 正样本索引范围为 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] (  total_num * num_group  )。# 负样本索引范围为 [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] ( total_num * num_group + num_group * total_num )。# 这样, neg_idx 包含了所有负样本的索引,用于后续对负样本进行特殊处理(如添加噪声)。neg_idx = torch.arange(total_num * num_group, dtype=torch.long, device=gt_bbox.device) + num_group * total_num# 这段代码的作用是。生成正负样本:通过重复类别标签、边界框坐标和批次索引,为每个 GT 生成正样本和负样本。每个 GT 会生成 2 * num_group 个样本,其中一半是正样本,另一半是负样本。定义正负样本掩码:通过计算负样本的索引范围,为后续的噪声添加和处理提供支持。这些操作为后续的去噪训练逻辑(如添加类别标签噪声和边界框噪声)奠定了基础。# 这段代码的作用是在类别标签中添加噪声,以生成负样本。这是去噪训练(Denoising Training)的一个重要步骤,通过引入噪声来增强模型的鲁棒性。# 判断是否需要添加类别标签噪声。 cls_noise_ratio 是类别标签噪声的比例。如果该值大于 0,则表示需要在类别标签中添加噪声。if cls_noise_ratio > 0:# Half of bbox prob# 生成噪声掩码。生成随机掩码。# torch.rand(dn_cls.shape) :生成一个与 dn_cls 形状相同的随机张量,其值在 [0, 1) 范围内。# < (cls_noise_ratio * 0.5) :将随机值与 cls_noise_ratio * 0.5 进行比较,生成一个布尔掩码 mask 。这个掩码用于标识哪些位置的类别标签需要被替换为噪声。# 例如,如果 cls_noise_ratio = 0.5 ,则 cls_noise_ratio * 0.5 = 0.25 。这意味着大约 25% 的类别标签会被替换为噪声。mask = torch.rand(dn_cls.shape) < (cls_noise_ratio * 0.5)# 获取需要替换噪声的索引。获取索引。# torch.nonzero(mask) :找到布尔掩码 mask 中为 True 的位置,返回一个二维张量,其中每一行表示一个索引。# .squeeze(-1) :将二维张量压缩为一维张量,得到需要替换噪声的索引 idx 。idx = torch.nonzero(mask).squeeze(-1)# Randomly put a new one here# 生成新的随机类别标签。生成随机标签。# torch.randint_like(idx, 0, num_classes) :生成一个与 idx 形状相同的随机整数张量,其值在 [0, num_classes) 范围内。这些随机整数将作为新的类别标签。# dtype=dn_cls.dtype, device=dn_cls.device :确保生成的随机标签与 dn_cls 的数据类型和设备一致。new_label = torch.randint_like(idx, 0, num_classes, dtype=dn_cls.dtype, device=dn_cls.device)# 替换类别标签。替换操作。# 使用索引 idx 将 dn_cls 中对应位置的类别标签替换为新的随机标签 new_label 。这一步完成了类别标签的噪声添加。dn_cls[idx] = new_label# 这段代码的作用是。判断是否需要添加噪声:通过检查 cls_noise_ratio 是否大于 0 来决定是否添加类别标签噪声。生成噪声掩码:通过随机生成布尔掩码来决定哪些类别标签需要被替换。获取需要替换的索引:找到需要添加噪声的类别标签的位置。生成新的随机类别标签:为需要替换的位置生成新的随机类别标签。替换类别标签:将指定位置的类别标签替换为新的随机标签。通过这种方式,代码为去噪训练生成了带有噪声的负样本,从而增强模型对噪声的鲁棒性。# 这段代码的作用是在边界框坐标中添加噪声,以生成负样本。这是去噪训练(Denoising Training)的另一个重要步骤,通过引入噪声来增强模型的鲁棒性。# 判断是否需要添加边界框噪声。逻辑判断。# box_noise_scale 是边界框噪声的规模。如果该值大于 0,则表示需要在边界框坐标中添加噪声。if box_noise_scale > 0:# 转换边界框格式。将边界框从 (x, y, w, h) 格式转换为 (x1, y1, x2, y2) 格式。这种转换是为了方便后续的噪声添加操作。known_bbox = xywh2xyxy(dn_bbox)# 计算噪声范围。# dn_bbox[..., 2:] :提取边界框的宽度和高度。# * 0.5 :将宽度和高度减半,得到噪声的范围。# .repeat(1, 2) :将噪声范围重复两次,以匹配 (x1, y1, x2, y2) 格式的边界框。# * box_noise_scale :将噪声范围乘以 box_noise_scale ,得到最终的噪声范围 diff 。diff = (dn_bbox[..., 2:] * 0.5).repeat(1, 2) * box_noise_scale  # 2*num_group*bs*num, 4# 生成随机符号和随机部分。# 生成随机符号。# torch.randint_like(dn_bbox, 0, 2) :这行代码生成一个与 dn_bbox 形状相同的随机整数张量,其值在 [0, 2) 范围内,即随机值只能是 0 或 1 。# 例如,假设 dn_bbox 的形状是 (10, 4) ,那么生成的随机张量可能是 :# tensor([[0, 1, 0, 1],#         [1, 0, 1, 0],#         [0, 1, 0, 1],#         [1, 0, 1, 0],#         [0, 1, 0, 1],#         [1, 0, 1, 0],#         [0, 1, 0, 1],#         [1, 0, 1, 0],#         [0, 1, 0, 1],#         [1, 0, 1, 0]])# * 2.0 :将上述随机张量中的每个值乘以 2.0 。此时, 0 变为 0.0 , 1 变为 2.0 。# 继续上面的例子,结果为 :# tensor([[0.0, 2.0, 0.0, 2.0],#         [2.0, 0.0, 2.0, 0.0],#         [0.0, 2.0, 0.0, 2.0],#         [2.0, 0.0, 2.0, 0.0],#         [0.0, 2.0, 0.0, 2.0],#         [2.0, 0.0, 2.0, 0.0],#         [0.0, 2.0, 0.0, 2.0],#         [2.0, 0.0, 2.0, 0.0],#         [0.0, 2.0, 0.0, 2.0],#         [2.0, 0.0, 2.0, 0.0]])# - 1.0 :将上述结果中的每个值减去 1.0 。此时, 0.0 变为 -1.0 , 2.0 变为 1.0 。# 继续上面的例子,最终结果为 :# tensor([[-1.0,  1.0, -1.0,  1.0],#         [ 1.0, -1.0,  1.0, -1.0],#         [-1.0,  1.0, -1.0,  1.0],#         [ 1.0, -1.0,  1.0, -1.0],#         [-1.0,  1.0, -1.0,  1.0],#         [ 1.0, -1.0,  1.0, -1.0],#         [-1.0,  1.0, -1.0,  1.0],#         [ 1.0, -1.0,  1.0, -1.0],#         [-1.0,  1.0, -1.0,  1.0],#         [ 1.0, -1.0,  1.0, -1.0]])# 总结 :# 通过 torch.randint_like(dn_bbox, 0, 2) * 2.0 - 1.0 ,以将随机整数 0 和 1 转换为 -1.0 和 1.0 。# 0 转换为 -1.0 : 0 * 2.0 - 1.0 = -1.0 。# 1 转换为 1.0 : 1 * 2.0 - 1.0 = 1.0 。# 这样的操作生成了一个随机符号张量 rand_sign ,其中每个元素的值随机为 -1.0 或 1.0 ,用于后续的噪声添加操作。rand_sign = torch.randint_like(dn_bbox, 0, 2) * 2.0 - 1.0# 生成随机部分。# 生成一个与 dn_bbox 形状相同的随机张量,其值在 [0, 1) 范围内。rand_part = torch.rand_like(dn_bbox)# 将负样本的随机部分增加 1,以确保负样本的噪声更大。rand_part[neg_idx] += 1.0# 将随机部分乘以随机符号,得到最终的随机噪声 rand_part 。rand_part *= rand_sign# 添加噪声并裁剪。# 添加噪声。将随机噪声 rand_part 乘以噪声范围 diff ,然后加到边界框 known_bbox 上,得到带有噪声的边界框。known_bbox += rand_part * diff# 裁剪边界框。 known_bbox.clip_(min=0.0, max=1.0) :将边界框的值裁剪到 [0, 1] 范围内,确保边界框的坐标合法。known_bbox.clip_(min=0.0, max=1.0)# 转换回原始格式。# 转换格式。将带有噪声的边界框从 (x1, y1, x2, y2) 格式转换回 (x, y, w, h) 格式。dn_bbox = xyxy2xywh(known_bbox)# 逆 sigmoid 转换。 torch.logit(dn_bbox, eps=1e-6) :对边界框进行逆 sigmoid 转换,得到最终的带有噪声的边界框 dn_bbox 。dn_bbox = torch.logit(dn_bbox, eps=1e-6)  # inverse sigmoid# 这段代码的作用是。判断是否需要添加噪声:通过检查 box_noise_scale 是否大于 0 来决定是否添加边界框噪声。转换边界框格式:将边界框从 (x, y, w, h) 格式转换为 (x1, y1, x2, y2) 格式。计算噪声范围:根据边界框的宽度和高度计算噪声范围。生成随机符号和随机部分:生成随机符号和随机部分,用于添加噪声。添加噪声并裁剪:将噪声添加到边界框中,并将边界框的值裁剪到合法范围。转换回原始格式:将带有噪声的边界框从 (x1, y1, x2, y2) 格式转换回 (x, y, w, h) 格式,并进行逆 sigmoid 转换。通过这种方式,代码为去噪训练生成了带有噪声的负样本,从而增强模型对噪声的鲁棒性。# 这段代码的作用是将带有噪声的类别标签和边界框嵌入到一个填充后的张量中,以便后续的去噪训练。# 计算 总去噪查询数量 。 max_nums 是每张图片中 GT 的最大数量。 2 * num_group 是每个 GT 的正负样本总数(每个 GT 有 2 * num_group 个样本,其中一半是正样本,另一半是负样本)。 因此, num_dn 表示整个批次中所有去噪样本的总数。num_dn = int(max_nums * 2 * num_group)  # total denoising queries# class_embed = torch.cat([class_embed, torch.zeros([1, class_embed.shape[-1]], device=class_embed.device)])# 在代码 dn_cls_embed = class_embed[dn_cls] 中,得到的 dn_cls_embed 的形状为 (bs*num * 2 * num_group, 256) ,其中的 256 来自于 class_embed 的嵌入维度。以下是详细的解释 :# class_embed 的形状 class_embed 是一个二维张量,其形状通常为 (num_classes + 1, embed_dim) ,其中 :# num_classes 是类别数量。# embed_dim 是嵌入维度,表示每个类别标签的嵌入向量的长度。# +1 是为了额外表示一个“背景”类别或“无目标”类别。# 在许多目标检测模型中,嵌入维度 embed_dim 通常被设置为 256。因此, class_embed 的形状可能是 (num_classes + 1, 256) 。# dn_cls 的形状 :# dn_cls 是一个一维张量,表示带有噪声的类别标签。其形状为 (bs*num * 2 * num_group,) ,其中 :# bs 是批量大小。# num 是每张图片的 GT 数量。# 2 * num_group 是每个 GT 的正负样本总数。# 索引操作 : dn_cls_embed = class_embed[dn_cls] 。# 这行代码通过 dn_cls 中的类别标签索引 class_embed ,获取对应的类别嵌入。# class_embed[dn_cls] 的操作会根据 dn_cls 中的每个类别标签,从 class_embed 中取出对应的嵌入向量。# 因此, dn_cls_embed 的形状为 (bs*num * 2 * num_group, embed_dim) 。# 嵌入维度 256 :# 由于 class_embed 的嵌入维度 embed_dim 是 256,因此 dn_cls_embed 的形状为 (bs*num * 2 * num_group, 256) 。# 总结 :# dn_cls_embed 的形状 (bs*num * 2 * num_group, 256) 中的 256 来自于 class_embed 的嵌入维度。这是因为 class_embed 的每个类别标签的嵌入向量长度为 256,而 dn_cls_embed 是通过索引 class_embed 得到的,因此其嵌入维度保持一致。# 示例 :# 假设 :# num_classes = 80 (COCO 数据集的类别数量)。# embed_dim = 256 。# bs = 4 (批量大小)。# num = 10 (每张图片的 GT 数量)。# num_group = 2 。则 :# class_embed 的形状为 (81, 256) 。# dn_cls 的形状为 (4 * 10 * 2 * 2,) = (160,) 。# dn_cls_embed 的形状为 (160, 256) 。# 嵌入向量(Embedding Vector)是机器学习和深度学习中一个非常重要的概念,尤其是在处理离散数据(如文本、类别标签等)时。嵌入向量的主要作用是将离散的符号或类别标签映射到一个连续的向量空间中,以便模型能够更好地处理这些数据。以下是详细的解释 :# 嵌入向量的定义 :嵌入向量是一种将离散数据(如类别标签、单词等)转换为连续向量的技术。每个离散符号或类别标签都被映射到一个固定维度的向量上,这些向量通常被称为“嵌入向量”。# 嵌入向量的作用 :# 嵌入向量的主要作用是将离散数据转换为模型可以处理的连续形式。具体来说,嵌入向量有以下几个重要功能 :# 连续表示 :将离散的符号或类别标签转换为连续的向量,使得模型可以进行数学运算。# 语义表示 :嵌入向量可以捕捉符号或类别标签之间的语义关系。例如,在自然语言处理中,单词嵌入可以捕捉单词之间的语义相似性。# 降维 :将高维的离散数据映射到低维的向量空间中,减少计算复杂度。# 嵌入向量的形状 :# 嵌入向量通常是一个二维张量,其形状为 (num_classes, embed_dim) ,其中 :# num_classes :表示类别标签的总数(或词汇表的大小)。# embed_dim :表示嵌入向量的维度,即每个类别标签被映射到的向量的长度。# 例如 :如果有 80 个类别标签,嵌入维度为 256,则嵌入向量的形状为 (80, 256) 。# 嵌入向量的使用 :# 嵌入向量通常用于以下场景 :# 目标检测 :将类别标签映射到嵌入向量中,用于类别嵌入(Class Embedding)。# 嵌入向量的初始化 :# 嵌入向量可以通过以下几种方式初始化 :# 随机初始化 :在训练开始时,随机生成嵌入向量,然后通过训练学习到合适的嵌入。# 预训练嵌入 :使用预训练的嵌入向量(如 Word2Vec、GloVe)初始化,这些嵌入向量已经在大规模数据上训练过,能够捕捉到丰富的语义信息。# 嵌入向量的训练 :# 嵌入向量通常作为模型的一部分进行训练。在训练过程中,嵌入向量会根据损失函数进行更新,以更好地捕捉数据的语义信息。# 示例 :# 假设有一个目标检测任务,类别标签为 [0, 1, 2] ,嵌入维度为 256。嵌入向量可以表示为 :# import torch# num_classes = 3  # 类别数量# embed_dim = 256  # 嵌入维度# # 随机初始化嵌入向量# class_embed = torch.randn(num_classes, embed_dim)# # 假设我们有一个类别标签张量# labels = torch.tensor([0, 1, 2, 1, 0])  # 类别标签# # 获取对应的嵌入向量# embeds = class_embed[labels]  # 形状为 (5, 256)# 在这个例子中 :# class_embed 是一个形状为 (3, 256) 的嵌入向量。# labels 是一个包含类别标签的张量。# class_embed[labels] 会根据 labels 中的类别标签索引 class_embed ,得到对应的嵌入向量 embeds ,其形状为 (5, 256) 。# 总结 :嵌入向量是一种将离散数据转换为连续向量的技术,广泛应用于自然语言处理、目标检测等领域。嵌入向量可以捕捉数据的语义信息,使得模型能够更好地处理离散数据。# 嵌入向量的维度(如256)通常是经验值,但它的选择也受到多种因素的影响。以下是一些决定嵌入维度的因素 :# 经验值 :# 常见的嵌入维度 :在许多深度学习模型中,嵌入维度通常被设置为一些常见的值,如64、128、256、512等。这些值在实际应用中被广泛验证,能够取得较好的效果。# 256的常见性 : 256是一个常用的嵌入维度,因为它在计算效率和表示能力之间取得了较好的平衡。它足够大,能够捕捉到丰富的语义信息,同时又不会导致计算资源的过度消耗。# 模型复杂度 :# 表示能力 :嵌入维度越大,模型的表示能力越强,能够捕捉到更复杂的语义信息。但同时,维度越大也会增加模型的复杂度和计算成本。# 计算资源 :较大的嵌入维度需要更多的计算资源和内存。在实际应用中,需要根据硬件资源和任务需求进行权衡。# 任务需求 :# 任务复杂度 :对于简单的任务,较小的嵌入维度可能已经足够。而对于复杂的任务,如自然语言处理中的语言模型或目标检测中的多类别分类,可能需要较大的嵌入维度来捕捉更丰富的语义信息。# 数据规模 :较大的数据集通常需要较大的嵌入维度来充分利用数据中的信息。较小的数据集可能不需要太大的嵌入维度,以避免过拟合。# 实验和验证 :# 超参数调优 :嵌入维度通常被视为一个超参数,需要通过实验和验证来确定最佳值。在实际应用中,通常会尝试多个不同的嵌入维度,通过交叉验证等方法来选择最优的维度。# 性能评估 :最终选择的嵌入维度应该在验证集或测试集上取得最佳性能,同时满足计算资源的限制。# 示例 :# 假设我们有一个目标检测任务,类别数量为80(如COCO数据集),嵌入维度的选择可以参考以下步骤 :# 初步选择 :根据经验,选择一个常见的嵌入维度,如256。# 实验验证 :尝试不同的嵌入维度(如128、256、512),通过交叉验证评估模型性能。# 性能评估 :选择在验证集上性能最佳的嵌入维度,同时考虑计算资源的限制。# 总结 :嵌入向量的维度(如256)通常是经验值,但它的选择需要根据模型复杂度、任务需求、数据规模和计算资源等因素进行综合考虑。通过实验和验证,可以确定最适合当前任务的嵌入维度。# 获取类别嵌入。# class_embed 是类别嵌入权重,用于将类别标签映射到嵌入空间。# dn_cls 是带有噪声的类别标签。# class_embed[dn_cls] :根据 dn_cls 获取对应的类别嵌入,得到的 dn_cls_embed 的形状为 (bs*num * 2 * num_group, 256) 。dn_cls_embed = class_embed[dn_cls]  # bs*num * 2 * num_group, 256# 初始化类别嵌入的填充张量。这些张量被初始化为零张量,后续将用实际的 类别嵌入 和 边界框坐标 填充。# padding_cls 是一个三维张量,形状为 (bs, num_dn, 256) ,用于 存储所有去噪样本的类别嵌入 。padding_cls = torch.zeros(bs, num_dn, dn_cls_embed.shape[-1], device=gt_cls.device)# padding_bbox 是一个三维张量,形状为 (bs, num_dn, 4) ,用于 存储所有去噪样本的边界框坐标 。padding_bbox = torch.zeros(bs, num_dn, 4, device=gt_bbox.device)# 生成索引映射。# torch.tensor(range(num), dtype=torch.long) :为每张图片生成一个从 0 到 num-1 的索引张量,其中 num 是该图片的 GT 数量。# torch.cat(...) :将所有图片的索引张量拼接成一个一维张量 map_indices ,表示所有 GT 的索引。map_indices = torch.cat([torch.tensor(range(num), dtype=torch.long) for num in gt_groups])# 计算正样本索引。# map_indices + max_nums * i :将每个 GT 的索引向后移动 max_nums * i 个位置,得到每个去噪组的正样本索引。# torch.stack(..., dim=0) :将所有去噪组的正样本索引堆叠成一个二维张量 pos_idx ,形状为 (num_group, total_num) 。pos_idx = torch.stack([map_indices + max_nums * i for i in range(num_group)], dim=0)# 更新索引映射。# 将 map_indices 扩展到包含所有去噪组的索引,包括正样本和负样本。每个 GT 的索引被向后移动 max_nums * i 个位置,其中 i 的范围是 [0, 2 * num_group) 。map_indices = torch.cat([map_indices + max_nums * i for i in range(2 * num_group)])# 填充类别嵌入。# 使用 dn_b_idx 和 map_indices 作为索引,将 dn_cls_embed 填充到 padding_cls 中。 dn_b_idx 表示每个样本所属的图片索引, map_indices 表示每个样本在去噪组中的索引。padding_cls[(dn_b_idx, map_indices)] = dn_cls_embed# 填充边界框。使用相同的索引,将 dn_bbox 填充到 padding_bbox 中。padding_bbox[(dn_b_idx, map_indices)] = dn_bbox# 这段代码的作用是。计算总去噪查询数量:根据每张图片的最大 GT 数量和每个 GT 的去噪组数量,计算总去噪样本数量。获取类别嵌入:根据带有噪声的类别标签获取对应的类别嵌入。初始化填充张量:创建用于存储类别嵌入和边界框坐标的填充张量。生成索引映射:为每个 GT 生成索引映射,包括正样本和负样本。填充类别嵌入和边界框:将带有噪声的类别嵌入和边界框坐标填充到对应的填充张量中。这些操作为后续的去噪训练准备了数据,确保所有去噪样本的类别嵌入和边界框坐标都被正确存储和索引。# 这段代码的作用是生成注意力掩码( attn_mask )和去噪训练的元信息( dn_meta ),并返回填充后的类别嵌入、边界框坐标、注意力掩码和元信息。# 计算目标大小。 tgt_size 是 去噪样本数量 num_dn 和 常规查询数量 num_queries 的总和。这表示在去噪训练中,总的目标数量是去噪样本和常规查询的总和。tgt_size = num_dn + num_queries# 初始化注意力掩码。创建一个形状为 (tgt_size, tgt_size) 的二维布尔张量,初始值为 False 。这个张量用于定义 哪些目标之间可以相互“看到”(即可以进行注意力计算) ,哪些不能。attn_mask = torch.zeros([tgt_size, tgt_size], dtype=torch.bool)# Match query cannot see the reconstruct# 设置常规查询与去噪样本之间的掩码。# 将常规查询(索引从 num_dn 到 tgt_size )与去噪样本(索引从 0 到 num_dn )之间的注意力掩码设置为 True 。这意味着常规查询不能“看到”去噪样本。attn_mask[num_dn:, :num_dn] = True# Reconstruct cannot see each other# 设置去噪样本之间的掩码。# 通过循环设置每个去噪组之间的注意力掩码。这样,去噪样本之间不能相互“看到”,以避免信息泄露。# max_nums * 2 * i 和 max_nums * 2 * (i + 1) 用于确定每个去噪组的索引范围。对于每个去噪组 :for i in range(num_group):# 如果是第一个去噪组( i == 0 ),将该组与后续所有去噪组之间的掩码设置为 True 。if i == 0:attn_mask[max_nums * 2 * i : max_nums * 2 * (i + 1), max_nums * 2 * (i + 1) : num_dn] = True# 如果是最后一个去噪组( i == num_group - 1 ),将该组与之前所有去噪组之间的掩码设置为 True 。if i == num_group - 1:attn_mask[max_nums * 2 * i : max_nums * 2 * (i + 1), : max_nums * i * 2] = True# 对于中间的去噪组,将该组与后续和之前的所有去噪组之间的掩码设置为 True 。else:attn_mask[max_nums * 2 * i : max_nums * 2 * (i + 1), max_nums * 2 * (i + 1) : num_dn] = Trueattn_mask[max_nums * 2 * i : max_nums * 2 * (i + 1), : max_nums * 2 * i] = True# 生成去噪训练的元信息。dn_meta = {# 将正样本索引 pos_idx 按照每张图片的 GT 数量分割,并将其转换为 CPU 张量,然后展平为一维张量。"dn_pos_idx": [p.reshape(-1) for p in pos_idx.cpu().split(list(gt_groups), dim=1)],# 记录去噪组的数量。"dn_num_group": num_group,# 记录去噪样本数量和常规查询数量的分割点。"dn_num_split": [num_dn, num_queries],}# 返回结果。将填 充后的类别嵌入 padding_cls 、 边界框坐标 padding_bbox 和 注意力掩码 attn_mask 移动到与 class_embed 相同的设备上。 返回这些张量以及 去噪训练的元信息 dn_meta 。return (padding_cls.to(class_embed.device),padding_bbox.to(class_embed.device),attn_mask.to(class_embed.device),dn_meta,)# 这段代码的作用是。计算目标大小:确定去噪样本和常规查询的总数量。初始化注意力掩码:创建一个布尔张量,用于定义哪些目标之间可以相互“看到”。设置注意力掩码:通过循环设置去噪样本和常规查询之间的掩码,以及去噪样本之间的掩码,以避免信息泄露。生成去噪训练的元信息:记录正样本索引、去噪组数量和去噪样本与常规查询的分割点。返回结果:返回填充后的类别嵌入、边界框坐标、注意力掩码和元信息。这些操作为后续的去噪训练提供了必要的数据和掩码信息。
# get_cdn_group 函数是一个用于生成对比去噪训练(Contrastive Denoising Training)样本组的关键模块。它通过在类别标签和边界框坐标上添加噪声,生成正样本和负样本,并将这些样本嵌入到一个填充后的张量中。同时,该函数还生成了注意力掩码,以确保在训练过程中,正样本和负样本之间不会相互“泄露”信息。此外,它还返回了去噪训练的元信息,包括正样本索引、去噪组数量和样本分割点。这些数据和掩码信息为后续的去噪训练提供了必要的支持,增强了模型对噪声的鲁棒性和泛化能力。


http://www.ppmy.cn/news/1575800.html

相关文章

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(9)

详解&#xff08;9&#xff09; 获取并存储主机名 if (gethostname(hostname, NGX_MAXHOSTNAMELEN) -1) {ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "gethostname() failed");ngx_destroy_pool(pool);return NULL;}/* on Linux gethostname() silently truncat…

软件工程复试专业课-软件生命周期

文章目录 软件过程模型瀑布模型模型图特点优缺点改进后的瀑布模型 快速原型模型模型图优缺点 增量模型&#xff08;迭代-递增模型&#xff09;原型图与瀑布和快速原型的区别优缺点风险更大的增量模型 螺旋模型简介模型图优缺点 喷泉模型模型图优缺点 编码修补模型敏捷过程优缺点…

SenseGlove力反馈数据手套Nova 2:重新定义虚拟现实交互

在虚拟现实&#xff08;VR&#xff09;领域&#xff0c;交互体验的真实感是技术发展的关键。SenseGlove Nova 2 力反馈数据手套通过触觉反馈、力反馈和振动反馈技术&#xff0c;为用户带来了更加沉浸的虚拟体验。 SenseGlove力反馈数据手套Nova 2核心技术特点 触觉反馈技术 S…

【Prometheus】prometheus服务发现与relabel原理解析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

实测四大开源AI视频模型 - 阿里、腾讯、阶跃星辰和智谱,无限生成的Time要来了

终于&#xff0c;视频模型开源卷到新阶段了&#xff01; 前有智谱 CogVideoX v1.5、腾讯混元 HunyuanVideo、阶跃星辰 Step-Video-T2V、这昨天又来了一个阿里全新开源的 Wan2.1。 开源模型这么多&#xff0c;究竟生成效果有什么差异&#xff1f; 我们先来看一组之前大家都熟…

【愚公系列】《鸿蒙原生应用开发从零基础到多实战》004-TypeScript 中的泛型

标题详情作者简介愚公搬代码头衔华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xff0c;阿里云签约作者&#xff0c;腾讯云优秀博主&…

Python毕业设计选题:基于Python的社区爱心养老管理系统设计与实现_django

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 身体健康界面 公共书籍界面 借阅信息界面 归还…

xss-labs搭建及学习

搭建 搭建过程与一般的网站搭建差不多 参考资料 当出现这个界面就是成功了 学习 学习资料 xss概念理解&#xff1a;XSS跨站脚本攻击 xss常见标签&#xff1a;XSS常见触发标签 level1-直接打 这里提示payload长度为4查看一下源码 发现get传参name的值test插入了html里头&am…