DINO:2022.3_SOTA_COCO_DETR

DINO是一种基于DETR-like架构的目标检测模型,它解决了传统DETR模型收敛速度慢和query意义模糊的问题。通过引入对比去噪训练、混合query选择方法及向前看两次的策略,DINO不仅提高了模型的精度,还增强了模型对于大型backbone和大规模数据集的适用性。

通过使用一个对比的方式为了去噪训练,一个混合的query选择方法为了anchor初始化,和一个向前看两次的方案为了box预测。

DINO在模型大小和数据大小上表现出良好的可扩展性,用SwinL backbone在Object365大数据集预训练,达到了COCO val2017 63.2AP 和 test-dev 63.3AP。SOTA in 2022.3

Introduction

DETR的主要问题是收敛慢query的意义是不明确的。

目前在最好的检测器当前还是经典的检测器,Dyhead、HTC等。为什么?                                       1.以前的DETR-like模型不如改进的经典检测器;比较经典的研究很多年被很好的优化。                 2.DETR-like的模型的可扩展性还没有被很好的研究,还没有人将其扩展到大backbone和大体量数据集上。                                                                                                                                            本文解决了这两个担忧。

DINO模型在decoder显示制定了query作为动态anchor box(4D锚框坐标表征)并通过decoder多层一步一步精细化他们,像DAB-DETR;在decoder各个层在groud-truth label和box中加入噪声去帮助训练中二分图匹配的稳定,像DN-DETR;还采用了deformable attention为了提高效率。另外,提出了三个新方法。

1.为了提高一对一匹配,提出了对比去噪训练,通过在同一时间加入同一个grond-truth的正、负样本。在给同一个ground-truth box加入两个不同噪声后,我们标记有小噪声的box作为正样本,其他就为负样本。对比去噪训练可以帮助模型避免同一目标的多次输出。                                                2.query的动态锚盒指定将DETR-like的模型与经典的两阶段模型联系起来。因此,我们提出了一种混合的query选择方法,帮助更好的初始化query。我们从编码器的输出中选择初始锚框作为位置查询。然而,我们让内容查询像以前一样是可以学习的,从而鼓励第一个解码层将重点放在空间先验上。                                                                                                                                                    3.为了利用来自上一层的精细化的box信息去帮助优化相邻的早期层的参数,我们提出一种新的两次前瞻方案,利用后层的梯度来修正更新后的参数。

Method

 

<think>我们面对的是一个关于如何加载PyTorch checkpoint的问题。用户提供了保存checkpoint的代码片段,我们需要根据这个保存格式来编写加载代码。 关键点分析: 1. 保存的checkpoint是一个字典(save_dict),包含以下键: - &#39;student&#39;: 学生模型的状态字典 - &#39;teacher&#39;: 教师模型的状态字典(如果有的话) - &#39;optimizer&#39;: 优化器的状态字典 - &#39;epoch&#39;: 下一个epoch的序号(当前训练结束的epoch+1) - &#39;args&#39;: 训练时使用的参数 - &#39;dino_loss&#39;: 损失函数的状态字典(如果有的话) - &#39;fp16_scaler&#39; (可选): 如果使用了混合精度训练,则包含fp16_scaler的状态 2. 保存时使用了`utils.save_on_master`函数,这个函数通常在分布式训练环境下使用,它会确保只在主进程上保存。但是加载时,我们通常直接使用`torch.load`。 3. 加载checkpoint的步骤: a. 加载保存的字典 b. 分别将保存的状态字典加载到对应的模型、优化器等对象中 c. 注意恢复训练时需要将模型设置为训练状态,以及恢复其他状态(如epoch、学习率调度等) 4. 注意:由于保存时可能使用了多个GPU(DataParallel/DistributedDataParallel),而加载时可能环境不同,因此加载模型状态时需要注意处理模块名前缀(如"module.")。 5. 另外,如果使用了混合精度训练,需要恢复fp16_scaler的状态。 根据以上分析,我们可以编写如下加载代码: 步骤: 1. 使用torch.load加载checkpoint文件 2. 分别取出各个部分的状态字典 3. 加载模型状态:由于保存的模型可能是分布式训练得到的,所以需要使用strip_prefix参数去掉多余的"module."前缀,或者使用module属性(如果当前模型是DataParallel包装的则不需要)。这里我们可以使用更通用的方法:在加载之前先对状态字典的key进行处理,去掉"module."前缀(如果当前不是分布式环境,但保存的模型是多GPU训练的,那么状态字典的key会有"module."前缀,而当前模型没有,因此需要去掉)。 有两种常见的方法处理多GPU训练保存的模型加载到单GPU的情况: - 方法1:在保存之前,使用model.module.state_dict()保存,这样保存的状态字典就没有"module."前缀。但用户提供的保存代码直接使用了student.state_dict()和teacher.state_dict(),如果模型是DataParallel或DistributedDataParallel包装的,那么保存的状态字典就会带有"module."前缀。 - 方法2:在加载时,如果当前环境是单GPU,而保存的模型是多GPU训练的,那么我们就需要手动去掉"module."前缀。 因此,我们可以编写一个函数来去除状态字典中的前缀: 另外,注意设备映射:如果保存的模型是在GPU上,而现在要在CPU上加载,需要设置map_location。 示例代码: 首先,我们定义一个去除前缀的函数: ```python def remove_prefix(state_dict, prefix="module."): """去除状态字典中键名的指定前缀""" # 复制一份状态字典的键 keys = list(state_dict.keys()) for key in keys: if key.startswith(prefix): new_key = key[len(prefix):] state_dict[new_key] = state_dict.pop(key) return state_dict ``` 然后,加载checkpoint并恢复状态: ```python def load_checkpoint(path, student, teacher, optimizer, dino_loss, fp16_scaler=None, device=&#39;cuda&#39;): # 加载保存的checkpoint,映射到当前设备 checkpoint = torch.load(path, map_location=device) # 加载学生模型 student_state_dict = checkpoint[&#39;student&#39;] # 去除可能存在的"module."前缀 student_state_dict = remove_prefix(student_state_dict, prefix="module.") # 加载状态字典 student.load_state_dict(student_state_dict) # 加载教师模型(如果有) if teacher is not None: teacher_state_dict = checkpoint[&#39;teacher&#39;] teacher_state_dict = remove_prefix(teacher_state_dict, prefix="module.") teacher.load_state_dict(teacher_state_dict) # 加载优化器 optimizer.load_state_dict(checkpoint[&#39;optimizer&#39;]) # 加载dino_loss的状态(如果有) if dino_loss is not None and &#39;dino_loss&#39; in checkpoint: dino_loss.load_state_dict(checkpoint[&#39;dino_loss&#39;]) # 加载fp16_scaler的状态(如果有) if fp16_scaler is not None and &#39;fp16_scaler&#39; in checkpoint: fp16_scaler.load_state_dict(checkpoint[&#39;fp16_scaler&#39;]) # 返回epoch,因为保存的是下一个epoch,所以这里返回的epoch就是接下来要训练的epoch start_epoch = checkpoint[&#39;epoch&#39;] # 返回其他需要的信息,比如参数args args = checkpoint[&#39;args&#39;] return start_epoch, args ``` 注意:上面的remove_prefix函数会修改状态字典,去掉所有以"module."开头的键的前缀。这样,如果模型是使用多GPU训练保存的,而在单GPU环境下加载,就可以兼容。 另外,如果当前环境是多GPU,而保存的模型也是多GPU(即状态字典有"module."前缀),那么当前模型(被DataParallel或DistributedDataParallel包装)的状态字典也需要有"module."前缀,所以直接加载即可。但是,为了统一处理,我们在加载前都去掉了前缀,这样即使当前模型没有被包装,也可以加载(因为去掉前缀后就是单GPU的键)。如果当前模型是被包装的,那么它的状态字典的键有"module."前缀,而我们去掉了保存的状态字典的前缀,这样就会不匹配。因此,我们应当确保在加载时,模型的状态和保存时的状态一致(即都是包装的或都是未包装的)。如果保存时是包装的(多GPU),而现在我们在单GPU上加载,那么我们需要将模型从包装中取出(即使用model.module),然后再加载。但实际上,我们通常这样处理: 更通用的做法是先保存没有包装的模型(即如果是多GPU,则保存model.module.state_dict())。但用户提供的保存代码没有做这个处理,所以我们采用加载时去掉前缀的方法,并且要求加载时传入的student和teacher模型是单GPU的模型(即没有用DataParallel包装)。如果当前是单GPU训练,那么就直接用;如果是多GPU训练,则应该在加载之前将模型用DataParallel包装,然后加载时去掉前缀,这样状态字典的键就与包装后的模型(每个键都有"module.")不一致了。所以,上面的方法适用于在单GPU环境下加载多GPU训练的模型,或者模型在保存和加载时都是单GPU的。 为了解决多环境的问题,另一种做法是:在保存时,如果是多GPU训练,则使用model.module.state_dict()来保存,这样保存的状态字典就没有"module."前缀。但用户提供的保存代码没有这样做,所以我们在加载时做处理。 因此,我们建议:在加载checkpoint时,确保模型处于与保存时相同的并行状态,或者更灵活地,我们在加载时根据当前模型的键是否有"module."前缀来决定是否去除保存的状态字典的前缀。但为了简化,我们这里假设加载时传入的模型是单GPU模型(即没有用DataParallel包装),所以无论保存时是否是多GPU,我们都去掉前缀。 如果加载时传入的模型是包装过的(即多GPU),那么状态字典的键有"module."前缀,而我们去掉保存的状态字典的前缀后,键就没有前缀了,这样就会导致键不匹配。因此,如果我们要在多个环境下灵活加载,需要更复杂的逻辑。 这里提供一个更健壮的方法: ```python def load_state_dict(model, state_dict): # 当前模型的键是否包含"module."前缀(即是否是多GPU包装) is_model_wrapped = any(k.startswith(&#39;module.&#39;) for k in model.state_dict().keys()) # 保存的状态字典的键是否包含"module."前缀 is_state_wrapped = any(k.startswith(&#39;module.&#39;) for k in state_dict.keys()) # 情况1:当前模型是多GPU包装,而保存的状态字典没有前缀 -> 我们需要给保存的状态字典加上前缀 if is_model_wrapped and not is_state_wrapped: # 给每个键加上"module." new_state_dict = {} for k, v in state_dict.items(): new_key = &#39;module.&#39; + k new_state_dict[new_key] = v state_dict = new_state_dict # 情况2:当前模型没有包装,而保存的状态字典有前缀 -> 我们需要去掉前缀 elif not is_model_wrapped and is_state_wrapped: new_state_dict = {} for k, v in state_dict.items(): if k.startswith(&#39;module.&#39;): new_key = k[len(&#39;module.&#39;):] new_state_dict[new_key] = v else: new_state_dict[k] = v state_dict = new_state_dict # 其他情况:两者都有前缀或都没有,则直接加载 # 加载 model.load_state_dict(state_dict) ``` 然后,在加载学生模型时,我们使用这个函数: ```python load_state_dict(student, checkpoint[&#39;student&#39;]) ``` 同样,教师模型也这样处理。 考虑到代码的简洁性,我们可以选择这种智能匹配前缀的方式。但为了清晰,我们在下面的代码中使用简单方法(假设加载时传入的模型与保存时的模型在并行状态上一致)。如果用户遇到键不匹配的问题,可以自行调整。 根据用户提供的保存代码,我们假设保存时可能使用了多GPU,所以状态字典可能有"module."前缀。因此,我们在加载代码中提供一个可选参数来处理: 最后,我们给出一个完整的加载函数,它能够处理单机/分布式训练下的checkpoint加载,并恢复模型、优化器、损失函数、fp16_scaler以及epoch和args。 注意:dino_loss和fp16_scaler可能是None,所以需要判断。 代码实现: 由于用户提供了保存代码,我们可以假设在保存时包含了这些键,所以加载时直接使用。但为了安全,我们使用条件判断。 下面是一个完整的示例: </think>### 如何加载PyTorch Checkpoint 根据提供的代码片段,加载Checkpoint需要处理多个关键组件:学生模型、教师模型、优化器、训练轮次、参数配置以及损失函数状态。以下是完整的加载方案: #### 1. 基础加载代码框架 ```python import torch import os def load_checkpoint(checkpoint_path, student, teacher, optimizer, dino_loss, fp16_scaler=None): # 加载checkpoint文件 checkpoint = torch.load(checkpoint_path, map_location=&#39;cpu&#39;) # 加载学生模型 student.load_state_dict(checkpoint[&#39;student&#39;]) # 加载教师模型 if &#39;teacher&#39; in checkpoint and teacher is not None: teacher.load_state_dict(checkpoint[&#39;teacher&#39;]) # 加载优化器状态 optimizer.load_state_dict(checkpoint[&#39;optimizer&#39;]) # 加载损失函数状态 if &#39;dino_loss&#39; in checkpoint and dino_loss is not None: dino_loss.load_state_dict(checkpoint[&#39;dino_loss&#39;]) # 加载混合精度训练状态 if &#39;fp16_scaler&#39; in checkpoint and fp16_scaler is not None: fp16_scaler.load_state_dict(checkpoint[&#39;fp16_scaler&#39;]) # 返回训练状态信息 return { &#39;epoch&#39;: checkpoint.get(&#39;epoch&#39;, 0), &#39;args&#39;: checkpoint.get(&#39;args&#39;, None), &#39;step&#39;: checkpoint.get(&#39;step&#39;, 0) # 如果保存了训练步数 } ``` #### 2. 实际使用示例 ```python # 初始化模型和组件 student = YourStudentModel() teacher = YourTeacherModel() optimizer = torch.optim.Adam(student.parameters()) dino_loss = DINOLoss() # 你的损失函数实现 fp16_scaler = torch.cuda.amp.GradScaler() # 如果使用混合精度 # 加载checkpoint checkpoint_path = os.path.join(args.output_dir, &#39;checkpoint.pth&#39;) training_state = load_checkpoint( checkpoint_path, student, teacher, optimizer, dino_loss, fp16_scaler ) # 恢复训练状态 start_epoch = training_state[&#39;epoch&#39;] current_args = training_state[&#39;args&#39;] or args # 优先使用保存的参数 print(f"从轮次 {start_epoch} 恢复训练") ``` #### 3. 关键注意事项 1. **设备映射**: ```python # 如果需要在GPU上加载 checkpoint = torch.load(path, map_location=&#39;cuda:0&#39;) ``` 2. **多GPU训练兼容性**: ```python # 处理DataParallel/DistributedDataParallel保存的前缀 if &#39;module.&#39; in list(checkpoint[&#39;student&#39;].keys())[0]: student_state = {k.replace(&#39;module.&#39;, &#39;&#39;): v for k, v in checkpoint[&#39;student&#39;].items()} student.load_state_dict(student_state) ``` 3. **版本兼容处理**: ```python # 处理PyTorch版本差异 if &#39;fp16_scaler&#39; in checkpoint and checkpoint[&#39;fp16_scaler&#39;] is not None: # PyTorch 1.6+使用新格式 if &#39;growth_tracker&#39; in checkpoint[&#39;fp16_scaler&#39;]: fp16_scaler.load_state_dict(checkpoint[&#39;fp16_scaler&#39;]) ``` 4. **部分恢复策略**: ```python # 选择性加载部分组件 if load_model: student.load_state_dict(checkpoint[&#39;student&#39;]) if load_optimizer: optimizer.load_state_dict(checkpoint[&#39;optimizer&#39;]) ``` #### 4. 完整恢复训练流程 ```mermaid graph LR A[初始化模型/优化器] --> B[检查Checkpoint存在] B -- 存在 --> C[加载Checkpoint] C --> D[恢复模型状态] C --> E[恢复优化器状态] C --> F[恢复损失函数状态] C --> G[恢复训练参数] B -- 不存在 --> H[开始新训练] D --> I[设置训练起始epoch] I --> J[继续训练循环] ``` ### 关键要点总结 1. 使用`map_location`参数确保设备兼容性 2. 处理多GPU训练产生的`module.`前缀 3. 按需加载组件可实现灵活恢复策略 4. AMP混合精度状态需与PyTorch版本匹配 5. 建议保存训练步数(`step`)以实现更精确恢复[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值