DETR纯代码分享(一) main.py

一、导入各种Python库和模块

# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
import argparse
import datetime
import json
import random
import time
from pathlib import Path

import numpy as np
import torch
from torch.utils.data import DataLoader, DistributedSampler

import datasets
import util.misc as utils
from datasets import build_dataset, get_coco_api_from_dataset
from engine import evaluate, train_one_epoch
from models import build_model

这段代码是一个Python程序,用于导入各种Python库和模块,以便在后续的代码中使用它们。下面是对每个导入的库和模块的解释:

  1. import argparse:导入argparse库,用于解析命令行参数。

  2. import datetime:导入datetime库,用于处理日期和时间相关的操作。

  3. import json:导入json库,用于处理JSON格式的数据。

  4. import random:导入random库,用于生成随机数。

  5. import time:导入time库,用于处理时间相关的操作。

  6. from pathlib import Path:从pathlib库中导入Path类,用于处理文件路径。

  7. import numpy as np:导入NumPy库,并将其重命名为np,以用于科学计算和数组操作。

  8. import torch:导入PyTorch库,一个用于深度学习的开源机器学习库。

  9. from torch.utils.data import DataLoader, DistributedSampler:从PyTorch的torch.utils.data模块中导入DataLoader和DistributedSampler类,用于数据加载和分布式训练时的数据采样。

  10. import datasets:导入一个名为datasets的模块,可能是用于处理数据集的自定义代码。

  11. import util.misc as utils:导入一个名为utils的自定义模块,可能包含一些实用工具函数。

  12. from datasets import build_dataset, get_coco_api_from_dataset:从datasets模块中导入build_dataset和get_coco_api_from_dataset函数,用于构建数据集和获取与COCO数据集相关的API。

  13. from engine import evaluate, train_one_epoch:从engine模块中导入evaluate和train_one_epoch函数,这些函数可能用于训练和评估模型。

  14. from models import build_model:从models模块中导入build_model函数,用于构建深度学习模型。

这段代码的主要目的是为了准备在后续的代码中使用这些库、模块和函数,以便进行深度学习任务,如模型训练、数据加载和评估。

二、创建命令行参数解析器

def get_args_parser():
    parser = argparse.ArgumentParser('Set transformer detector', add_help=False)
    parser.add_argument('--lr', default=1e-4, type=float)      #其他参数学习率
    parser.add_argument('--lr_backbone', default=1e-5, type=float)  #backbone参数的学习率
    parser.add_argument('--batch_size', default=2, type=int)
    parser.add_argument('--weight_decay', default=1e-4, type=float)
    parser.add_argument('--epochs', default=300, type=int)
    parser.add_argument('--lr_drop', default=200, type=int)
    parser.add_argument('--clip_max_norm', default=0.1, type=float,
                        help='gradient clipping max norm')

    # Model parameters
    parser.add_argument('--frozen_weights', type=str, default=None,
                        help="Path to the pretrained model. If set, only the mask head will be trained")
    # * Backbone
    parser.add_argument('--backbone', default='resnet50', type=str,
                        help="Name of the convolutional backbone to use")
    parser.add_argument('--dilation', action='store_true',
                        help="If true, we replace stride with dilation in the last convolutional block (DC5)") #backbone中的网络是否使用空洞卷积
    parser.add_argument('--position_embedding', default='sine', type=str, choices=('sine', 'learned'),
                        help="Type of positional embedding to use on top of the image features")   #位置编码两种方式:sin-cos编码(默认),可学习的编码

    # * Transformer
    parser.add_argument('--enc_layers', default=6, type=int,
                        help="Number of encoding layers in the transformer")
    parser.add_argument('--dec_layers', default=6, type=int,
                        help="Number of decoding layers in the transformer")
    parser.add_argument('--dim_feedforward', default=2048, type=int,
                        help="Intermediate size of the feedforward layers in the transformer blocks") #经过backbone后网络的维度
    parser.add_argument('--hidden_dim', default=256, type=int,
                        help="Size of the embeddings (dimension of the transformer)")
    parser.add_argument('--dropout', default=0.1, type=float,
                        help="Dropout applied in the transformer")
    parser.add_argument('--nheads', default=8, type=int,
                        help="Number of attention heads inside the transformer's attentions")
    parser.add_argument('--num_queries', default=100, type=int,
                        help="Number of query slots")  #后边会重点讲一下
    parser.add_argument('--pre_norm', action='store_true')

    # * Segmentation实例分割的内容
    parser.add_argument('--masks', action='store_true',
                        help="Train segmentation head if the flag is provided")#https://www.zhihu.com/question/56692630

    # Loss   #计算每一层decoder的loss并进行汇总,实际当中也没有用,直接用decoder最后一层loss来计算的
    parser.add_argument('--no_aux_loss', dest='aux_loss', action='store_false',   
                        help="Disables auxiliary decoding losses (loss at each layer)")
    # * Matcher 一个是匹配器的loss比重
    parser.add_argument('--set_cost_class', default=1, type=float,
                        help="Class coefficient in the matching cost") #分类的权重占多少
    parser.add_argument('--set_cost_bbox', default=5, type=float,
                        help="L1 box coefficient in the matching cost") #bbox权重占多少
    parser.add_argument('--set_cost_giou', default=2, type=float,
                        help="giou box coefficient in the matching cost") #giou的损失占多少
    # * Loss coefficients 真正loss的比重
    parser.add_argument('--mask_loss_coef', default=1, type=float)
    parser.add_argument('--dice_loss_coef', default=1, type=float)
    parser.add_argument('--bbox_loss_coef', default=5, type=float)
    parser.add_argument('--giou_loss_coef', default=2, type=float)
    parser.add_argument('--eos_coef', default=0.1, type=float,
                        help="Relative classification weight of the no-object class")

    # dataset parameters
    parser.add_argument('--dataset_file', default='coco')
    parser.add_argument('--coco_path', type=str)
    parser.add_argument('--coco_panoptic_path', type=str)
    parser.add_argument('--remove_difficult', action='store_true')

    parser.add_argument('--output_dir', default='',
                        help='path where to save, empty for no saving')
    parser.add_argument('--device', default='cuda',
                        help='device to use for training / testing')
    parser.add_argument('--seed', default=42, type=int)
    parser.add_argument('--resume', default='', help='resume from checkpoint')
    parser.add_argument('--start_epoch', default=0, type=int, metavar='N',
                        help='start epoch')
    parser.add_argument('--eval', action='store_true')
    parser.add_argument('--num_workers', default=2, type=int)

    # distributed training parameters
    parser.add_argument('--world_size', default=1, type=int,
                        help='number of distributed processes')
    parser.add_argument('--dist_url', default='env://', help='url used to set up distributed training')
    return parser
1、创建一个argparse解析器
def get_args_parser():
    parser = argparse.ArgumentParser('Set transformer detector', add_help=False)
    parser.add_argument('--lr', default=1e-4, type=float)      #其他参数学习率
    parser.add_argument('--lr_backbone', default=1e-5, type=float)  #backbone参数的学习率
    parser.add_argument('--batch_size', default=2, type=int)
    parser.add_argument('--weight_decay', default=1e-4, type=float)
    parser.add_argument('--epochs', default=300, type=int)
    parser.add_argument('--lr_drop', default=200, type=int)
    parser.add_argument('--clip_max_norm', default=0.1, type=float,
                        help='gradient clipping max norm')

这是一个函数,用于创建命令行参数解析器(argparse.ArgumentParser),该解析器用于配置一个目标检测模型(Set transformer detector)的训练过程。以下是各个命令行参数的解释:

  • parser = argparse.ArgumentParser('Set transformer detector', add_help=False):创建一个argparse解析器,并指定程序的描述为"Set transformer detector"。add_help=False参数表示禁用内置的帮助信息。

  • parser.add_argument('--lr', default=1e-4, type=float):定义了一个名为lr的命令行参数,用于设置学习率(learning rate),默认值为1e-4,类型为浮点数。

  • parser.add_argument('--lr_backbone', default=1e-5, type=float):定义了一个名为lr_backbone的命令行参数,用于设置backbone部分的学习率,默认值为1e-5,类型为浮点数。

  • parser.add_argument('--batch_size', default=2, type=int):定义了一个名为batch_size的命令行参数,用于设置训练时的批次大小(batch size),默认值为2,类型为整数。

  • parser.add_argument('--weight_decay', default=1e-4, type=float):定义了一个名为weight_decay的命令行参数,用于设置权重衰减(weight decay)的值,即正则化项的强度,默认值为1e-4,类型为浮点数。

  • parser.add_argument('--epochs', default=300, type=int):定义了一个名为epochs的命令行参数,用于设置训练的总轮次数,默认值为300,类型为整数。

  • parser.add_argument('--lr_drop', default=200, type=int):定义了一个名为lr_drop的命令行参数,用于设置学习率的衰减周期,默认值为200,类型为整数。

  • parser.add_argument('--clip_max_norm', default=0.1, type=float, help='gradient clipping max norm'):定义了一个名为clip_max_norm的命令行参数,用于设置梯度裁剪的最大范数值,默认值为0.1,类型为浮点数,并提供了一个帮助信息,解释了梯度裁剪的含义。

这些命令行参数允许用户在运行程序时通过命令行来配置训练过程的不同参数,如学习率、批次大小、正则化项等。

2、模型参数model parameters
    # Model parameters
    parser.add_argument('--frozen_weights', type=str, default=None,
                        help="Path to the pretrained model. If set, only the mask head will be trained")

这段代码是在前面定义的命令行参数解析器(argparse.ArgumentParser)上继续添加一个额外的命令行参数。以下是该参数的解释:

  • parser.add_argument('--frozen_weights', type=str, default=None, help="Path to the pretrained model. If set, only the mask head will be trained")定义了一个名为frozen_weights的命令行参数,用于设置预训练模型的路径这个参数的类型是字符串(str),默认值为None,表示不使用预训练模型。如果设置了该参数的值,那么只有掩码头部(mask head)将被训练,其他部分将被冻结。帮助信息解释了该参数的作用,即指定预训练模型的路径,以决定是否要进行模型微调。
3、模型参数(backbone)
    # * Backbone
    parser.add_argument('--backbone', default='resnet50', type=str,
                        help="Name of the convolutional backbone to use")
    parser.add_argument('--dilation', action='store_true',
                        help="If true, we replace stride with dilation in the last convolutional block (DC5)") #backbone中的网络是否使用空洞卷积
    parser.add_argument('--position_embedding', default='sine', type=str, choices=('sine', 'learned'),
                        help="Type of positional embedding to use on top of the image features")   #位置编码两种方式:sin-cos编码(默认),可学习的编码

这段代码继续在命令行参数解析器中添加了一些与模型的主干(backbone)相关的参数。以下是这些参数的解释:

  • parser.add_argument('--backbone', default='resnet50', type=str, help="Name of the convolutional backbone to use"):定义了一个名为backbone的命令行参数,用于设置要使用的卷积背景(convolutional backbone)的名称,默认为'resnet50'。用户可以通过该参数指定要在模型中使用的卷积神经网络的架构。

  • parser.add_argument('--dilation', action='store_true', help="If true, we replace stride with dilation in the last convolutional block (DC5)")定义了一个名为dilation的命令行参数,它是一个标志参数(flag),当存在该参数时,将启用空洞卷积(dilation convolution)而不是普通卷积。该参数用于控制是否在卷积背景的最后一个卷积块中使用空洞卷积。

  • parser.add_argument('--position_embedding', default='sine', type=str, choices=('sine', 'learned'), help="Type of positional embedding to use on top of the image features"):定义了一个名为position_embedding的命令行参数,用于设置要在图像特征之上使用的位置编码(positional embedding)的类型。它有两个选择:'sine'表示使用正弦/余弦位置编码(默认选项),'learned'表示使用可学习的位置编码。用户可以通过该参数选择不同类型的位置编码方式。

这些参数允许用户在运行程序时灵活地配置模型的背景、位置编码方式以及是否使用空洞卷积等相关设置。

4、Transformer模型参数
    # * Transformer
    parser.add_argument('--enc_layers', default=6, type=int,
                        help="Number of encoding layers in the transformer")
    parser.add_argument('--dec_layers', default=6, type=int,
                        help="Number of decoding layers in the transformer")
    parser.add_argument('--dim_feedforward', default=2048, type=int,
                        help="Intermediate size of the feedforward layers in the transformer blocks") #经过backbone后网络的维度
    parser.add_argument('--hidden_dim', default=256, type=int,
                        help="Size of the embeddings (dimension of the transformer)")
    parser.add_argument('--dropout', default=0.1, type=float,
                        help="Dropout applied in the transformer")
    parser.add_argument('--nheads', default=8, type=int,
                        help="Number of attention heads inside the transformer's attentions")
    parser.add_argument('--num_queries', default=100, type=int,
                        help="Number of query slots")  #后边会重点讲一下
    parser.add_argument('--pre_norm', action='store_true')

这段代码继续在命令行参数解析器中添加了一些与Transformer模型相关的参数。以下是这些参数的解释:

  • parser.add_argument('--enc_layers', default=6, type=int, help="Number of encoding layers in the transformer"):定义了一个名为enc_layers的命令行参数,用于设置Transformer编码器(encoder)中的编码层数,默认值为6。这个参数控制了编码器的深度,影响模型的复杂度。

  • parser.add_argument('--dec_layers', default=6, type=int, help="Number of decoding layers in the transformer"):定义了一个名为dec_layers的命令行参数,用于设置Transformer解码器(decoder)中的解码层数,默认值为6。这个参数控制了解码器的深度,同样影响模型的复杂度。

  • parser.add_argument('--dim_feedforward', default=2048, type=int, help="Intermediate size of the feedforward layers in the transformer blocks"):定义了一个名为dim_feedforward的命令行参数,用于设置Transformer块中的前馈神经网络的中间层的维度,默认值为2048。这个参数控制了Transformer块中的前馈网络的宽度。

  • parser.add_argument('--hidden_dim', default=256, type=int, help="Size of the embeddings (dimension of the transformer)"):定义了一个名为hidden_dim的命令行参数,用于设置Transformer模型中的嵌入(embedding)的维度,默认值为256。这个参数控制了模型内部的向量维度。

  • parser.add_argument('--dropout', default=0.1, type=float, help="Dropout applied in the transformer"):定义了一个名为dropout的命令行参数,用于设置Transformer模型中的dropout率,默认值为0.1。dropout用于模型的正则化,以减少过拟合风险。

  • parser.add_argument('--nheads', default=8, type=int, help="Number of attention heads inside the transformer's attentions"):定义了一个名为nheads的命令行参数,用于设置Transformer模型中的注意力头的数量,默认值为8。这个参数控制了注意力机制的多头数量。

  • parser.add_argument('--num_queries', default=100, type=int, help="Number of query slots"):定义了一个名为num_queries的命令行参数,用于设置查询槽(query slots)的数量,默认值为100。查询槽在目标检测任务中通常用于表示模型对目标的预测。

  • parser.add_argument('--pre_norm', action='store_true'):定义了一个名为pre_norm的命令行参数,它是一个标志参数(flag)。如果存在该参数,将启用预正则化(pre-norm)操作,影响模型的训练方式。

这些参数允许用户在运行程序时灵活地配置Transformer模型的各个方面,包括层数、维度、注意力头数等等。其中,num_queries参数在目标检测任务中具有重要作用,因为它决定了模型能够预测的目标数量。

5、实例分割(Segmentation)
    # * Segmentation实例分割的内容
    parser.add_argument('--masks', action='store_true',
                        help="Train segmentation head if the flag is provided")#https://www.zhihu.com/question/56692630

这段代码添加了一个与实例分割(Segmentation)相关的命令行参数:

  • parser.add_argument('--masks', action='store_true', help="Train segmentation head if the flag is provided"):定义了一个名为masks的命令行参数,它是一个标志参数(flag)。如果用户在运行程序时提供了该标志参数,即输入命令行中包含了--masks,则表示要训练分割头部(segmentation head)。这个参数用于控制是否训练实例分割模型的分割头部,分割头部通常用于将图像中的不同实例(例如,不同的物体)分割出来。

此参数的作用是在训练时决定是否要进行实例分割任务的训练,具体是否训练分割头部。如果用户提供了这个参数,模型将根据标志进行相应的训练配置。

6、Loss设置
    # Loss   #计算每一层decoder的loss并进行汇总,实际当中也没有用,直接用decoder最后一层loss来计算的
    parser.add_argument('--no_aux_loss', dest='aux_loss', action='store_false',   
                        help="Disables auxiliary decoding losses (loss at each layer)")
    # * Matcher 一个是匹配器的loss比重
    parser.add_argument('--set_cost_class', default=1, type=float,
                        help="Class coefficient in the matching cost") #分类的权重占多少
    parser.add_argument('--set_cost_bbox', default=5, type=float,
                        help="L1 box coefficient in the matching cost") #bbox权重占多少
    parser.add_argument('--set_cost_giou', default=2, type=float,
                        help="giou box coefficient in the matching cost") #giou的损失占多少
    # * Loss coefficients 真正loss的比重
    parser.add_argument('--mask_loss_coef', default=1, type=float)
    parser.add_argument('--dice_loss_coef', default=1, type=float)
    parser.add_argument('--bbox_loss_coef', default=5, type=float)
    parser.add_argument('--giou_loss_coef', default=2, type=float)
    parser.add_argument('--eos_coef', default=0.1, type=float,
                        help="Relative classification weight of the no-object class")

这段代码定义了一些与损失函数相关的命令行参数,用于配置模型训练中不同损失项的权重和设置。以下是这些参数的解释:

  • parser.add_argument('--no_aux_loss', dest='aux_loss', action='store_false', help="Disables auxiliary decoding losses (loss at each layer)"):定义了一个名为no_aux_loss的命令行参数,它是一个标志参数(flag)。如果用户提供了这个标志参数,即输入命令行中包含了--no_aux_loss,则会禁用辅助的解码损失(每一层的损失)。默认情况下,这个参数为False,表示允许使用每一层的解码损失。

  • parser.add_argument('--set_cost_class', default=1, type=float, help="Class coefficient in the matching cost"):定义了一个名为set_cost_class的命令行参数,用于设置匹配损失中的类别损失的权重,默认值为1。这个参数用于控制匹配损失中类别损失项的重要性。

  • parser.add_argument('--set_cost_bbox', default=5, type=float, help="L1 box coefficient in the matching cost"):定义了一个名为set_cost_bbox的命令行参数,用于设置匹配损失中边界框(bbox)损失的权重,默认值为5。这个参数用于控制匹配损失中边界框损失项的重要性。

  • parser.add_argument('--set_cost_giou', default=2, type=float, help="giou box coefficient in the matching cost"):定义了一个名为set_cost_giou的命令行参数,用于设置匹配损失中GIoU损失的权重,默认值为2。这个参数用于控制匹配损失中GIoU损失项的重要性。

  • parser.add_argument('--mask_loss_coef', default=1, type=float):定义了一个名为mask_loss_coef的命令行参数,用于设置分割(mask)损失的权重,默认值为1。

  • parser.add_argument('--dice_loss_coef', default=1, type=float):定义了一个名为dice_loss_coef的命令行参数,用于设置Dice损失的权重,默认值为1。

  • parser.add_argument('--bbox_loss_coef', default=5, type=float):定义了一个名为bbox_loss_coef的命令行参数,用于设置边界框(bbox)损失的权重,默认值为5。

  • parser.add_argument('--giou_loss_coef', default=2, type=float):定义了一个名为giou_loss_coef的命令行参数,用于设置GIoU损失的权重,默认值为2。

  • parser.add_argument('--eos_coef', default=0.1, type=float, help="Relative classification weight of the no-object class"):定义了一个名为eos_coef的命令行参数,用于设置非目标类别的相对分类权重,默认值为0.1。这个参数通常用于处理目标检测中的背景类别。

这些参数允许用户在模型训练过程中自定义不同损失项的重要性,以调整模型的训练目标和性能。

7、数据集设置
    # dataset parameters
    parser.add_argument('--dataset_file', default='coco')
    parser.add_argument('--coco_path', type=str)
    parser.add_argument('--coco_panoptic_path', type=str)
    parser.add_argument('--remove_difficult', action='store_true')

    parser.add_argument('--output_dir', default='',
                        help='path where to save, empty for no saving')
    parser.add_argument('--device', default='cuda',
                        help='device to use for training / testing')
    parser.add_argument('--seed', default=42, type=int)
    parser.add_argument('--resume', default='', help='resume from checkpoint')
    parser.add_argument('--start_epoch', default=0, type=int, metavar='N',
                        help='start epoch')
    parser.add_argument('--eval', action='store_true')
    parser.add_argument('--num_workers', default=2, type=int)

这段代码定义了一些与数据集和训练配置相关的命令行参数。以下是这些参数的解释:

  • parser.add_argument('--dataset_file', default='coco'):定义了一个名为dataset_file的命令行参数,用于设置数据集文件的名称,默认为'coco'。这个参数通常用于指定要使用的数据集。

  • parser.add_argument('--coco_path', type=str):定义了一个名为coco_path的命令行参数,用于设置COCO数据集的路径

  • parser.add_argument('--coco_panoptic_path', type=str):定义了一个名为coco_panoptic_path的命令行参数,用于设置COCO Panoptic数据集的路径

  • parser.add_argument('--remove_difficult', action='store_true'):定义了一个名为remove_difficult的命令行参数,它是一个标志参数(flag)。如果用户提供了这个标志参数,即输入命令行中包含了--remove_difficult,则表示在数据加载过程中删除难以处理的样本(例如,难以识别的目标)

  • parser.add_argument('--output_dir', default='', help='path where to save, empty for no saving'):定义了一个名为output_dir的命令行参数,用于设置模型训练过程中保存结果的目录路径,默认为空字符串,表示不保存结果。

  • parser.add_argument('--device', default='cuda', help='device to use for training / testing'):定义了一个名为device的命令行参数,用于设置训练和测试时使用的计算设备,默认为'cuda',表示使用GPU进行计算。可以将其设置为'cpu'以在CPU上运行。

  • parser.add_argument('--seed', default=42, type=int):定义了一个名为seed的命令行参数,用于设置随机种子,以确保实验的可重复性,默认值为42。

  • parser.add_argument('--resume', default='', help='resume from checkpoint'):定义了一个名为resume的命令行参数,用于指定从哪个检查点(checkpoint)恢复训练。

  • parser.add_argument('--start_epoch', default=0, type=int, metavar='N', help='start epoch'):定义了一个名为start_epoch的命令行参数,用于设置训练的起始轮次,默认值为0。

  • parser.add_argument('--eval', action='store_true'):定义了一个名为eval的命令行参数,它是一个标志参数(flag)。如果用户提供了这个标志参数,即输入命令行中包含了--eval,则表示进行模型的评估,而不是训练

  • parser.add_argument('--num_workers', default=2, type=int):定义了一个名为num_workers的命令行参数,用于设置用于数据加载的工作进程数量,默认值为2。这个参数可以加速数据加载过程。

这些参数允许用户在运行程序时配置数据集、保存路径、计算设备、随机种子等训练和评估相关的设置。

8、分布式训练
    # distributed training parameters
    parser.add_argument('--world_size', default=1, type=int,
                        help='number of distributed processes')
    parser.add_argument('--dist_url', default='env://', help='url used to set up distributed training')
    return parser

这段代码定义了一些与分布式训练相关的命令行参数。以下是这些参数的解释:

  • parser.add_argument('--world_size', default=1, type=int, help='number of distributed processes'):定义了一个名为world_size的命令行参数,用于设置分布式训练中的进程数量,默认值为1,表示单机训练。当设置为大于1的值时,表示要进行分布式训练,使用多个进程进行训练任务。

  • parser.add_argument('--dist_url', default='env://', help='url used to set up distributed training'):定义了一个名为dist_url的命令行参数,用于设置分布式训练的URL。默认值为'env://',通常情况下无需手动设置,而是根据环境变量自动配置分布式训练。

这些参数允许用户在需要进行分布式训练时配置分布式训练的相关设置,例如进程数量和分布式训练的URL。

三、程序主函数函数

这是程序的主函数main,它接受一个参数args,其中包含了在命令行中传递的所有配置参数。以下是函数主要步骤的解释:

def main(args):
    utils.init_distributed_mode(args) #初始化分布式训练模式
    print("git:\n  {}\n".format(utils.get_sha()))
    #判断backbone的卷积层是否冻结
    if args.frozen_weights is not None:
        assert args.masks, "Frozen training is meant for segmentation only"
    print(args)
    #部署模型到对应device上
    device = torch.device(args.device)

    # fix the seed for reproducibility固定随机种子以便结果浮现,get_rank()是分布式节点的编号
    seed = args.seed + utils.get_rank()
    torch.manual_seed(seed)
    np.random.seed(seed)
    random.seed(seed)
    #搭建网络,返回detr的网络,会有三个参数模型model,损失criterion,后处理的方式postprocessors,这里的后处理主要是为了coco的API去把最后给出的结果(左上和右下坐标)处理成(x,y,w,h)
    #本代码直接用的左上和右下坐标
    model, criterion, postprocessors = build_model(args)
    model.to(device)

    model_without_ddp = model #ddp分布式处理的缩写,默认把model赋值给没有ddp的model
    if args.distributed:
        model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
        model_without_ddp = model.module
    n_parameters = sum(p.numel() for p in model.parameters() if p.requires_grad)
    print('number of params:', n_parameters)

    param_dicts = [
        {"params": [p for n, p in model_without_ddp.named_parameters() if "backbone" not in n and p.requires_grad]},
        {
            "params": [p for n, p in model_without_ddp.named_parameters() if "backbone" in n and p.requires_grad],
            "lr": args.lr_backbone,
        },
    ] #设置学习率,backbone学习率和transformer以及ffn的均有不同,backbone直接用的是在官网上下载的一个预训练参数,已经训练好了,所以lr会设置的低一点,其他的就是常规学习率
    optimizer = torch.optim.AdamW(param_dicts, lr=args.lr,
                                  weight_decay=args.weight_decay)
    lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, args.lr_drop)#学习率调整,每隔lr_drop个epoch,lr=gamma*lr_drop,gamma默认为0.1,也就是每隔200个epoch,学习率降为原来的0.1
    #下面的就开始重要了,读取数据,看传进来的是训练数据还是验证数据,可以跳转到指定代码看一下
    dataset_train = build_dataset(image_set='train', args=args)
    dataset_val = build_dataset(image_set='val', args=args)

    if args.distributed:
        sampler_train = DistributedSampler(dataset_train)
        sampler_val = DistributedSampler(dataset_val, shuffle=False)
    else:
        sampler_train = torch.utils.data.RandomSampler(dataset_train)
        sampler_val = torch.utils.data.SequentialSampler(dataset_val)

    batch_sampler_train = torch.utils.data.BatchSampler(
        sampler_train, args.batch_size, drop_last=True)

    data_loader_train = DataLoader(dataset_train, batch_sampler=batch_sampler_train,
                                   collate_fn=utils.collate_fn, num_workers=args.num_workers)
    data_loader_val = DataLoader(dataset_val, args.batch_size, sampler=sampler_val,
                                 drop_last=False, collate_fn=utils.collate_fn, num_workers=args.num_workers)

    if args.dataset_file == "coco_panoptic":
        # We also evaluate AP during panoptic training, on original coco DS
        coco_val = datasets.coco.build("val", args)
        base_ds = get_coco_api_from_dataset(coco_val)
    else:
        base_ds = get_coco_api_from_dataset(dataset_val)

    if args.frozen_weights is not None:
        checkpoint = torch.load(args.frozen_weights, map_location='cpu')
        model_without_ddp.detr.load_state_dict(checkpoint['model'])

    output_dir = Path(args.output_dir)
    if args.resume:
        if args.resume.startswith('https'):
            checkpoint = torch.hub.load_state_dict_from_url(
                args.resume, map_location='cpu', check_hash=True)
        else:
            checkpoint = torch.load(args.resume, map_location='cpu')
        model_without_ddp.load_state_dict(checkpoint['model'])
        if not args.eval and 'optimizer' in checkpoint and 'lr_scheduler' in checkpoint and 'epoch' in checkpoint:
            optimizer.load_state_dict(checkpoint['optimizer'])
            lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])
            args.start_epoch = checkpoint['epoch'] + 1

    if args.eval:
        test_stats, coco_evaluator = evaluate(model, criterion, postprocessors,
                                              data_loader_val, base_ds, device, args.output_dir)
        if args.output_dir:
            utils.save_on_master(coco_evaluator.coco_eval["bbox"].eval, output_dir / "eval.pth")
        return

    print("Start training")
    start_time = time.time()
    for epoch in range(args.start_epoch, args.epochs):
        if args.distributed:
            sampler_train.set_epoch(epoch)
        train_stats = train_one_epoch(
            model, criterion, data_loader_train, optimizer, device, epoch,
            args.clip_max_norm)
        lr_scheduler.step()
        if args.output_dir:
            checkpoint_paths = [output_dir / 'checkpoint.pth']
            # extra checkpoint before LR drop and every 100 epochs
            if (epoch + 1) % args.lr_drop == 0 or (epoch + 1) % 100 == 0:
                checkpoint_paths.append(output_dir / f'checkpoint{epoch:04}.pth')
            for checkpoint_path in checkpoint_paths:
                utils.save_on_master({
                    'model': model_without_ddp.state_dict(),
                    'optimizer': optimizer.state_dict(),
                    'lr_scheduler': lr_scheduler.state_dict(),
                    'epoch': epoch,
                    'args': args,
                }, checkpoint_path)

        test_stats, coco_evaluator = evaluate(
            model, criterion, postprocessors, data_loader_val, base_ds, device, args.output_dir
        )

        log_stats = {**{f'train_{k}': v for k, v in train_stats.items()},
                     **{f'test_{k}': v for k, v in test_stats.items()},
                     'epoch': epoch,
                     'n_parameters': n_parameters}

        if args.output_dir and utils.is_main_process():
            with (output_dir / "log.txt").open("a") as f:
                f.write(json.dumps(log_stats) + "\n")

            # for evaluation logs
            if coco_evaluator is not None:
                (output_dir / 'eval').mkdir(exist_ok=True)
                if "bbox" in coco_evaluator.coco_eval:
                    filenames = ['latest.pth']
                    if epoch % 50 == 0:
                        filenames.append(f'{epoch:03}.pth')
                    for name in filenames:
                        torch.save(coco_evaluator.coco_eval["bbox"].eval,
                                   output_dir / "eval" / name)

    total_time = time.time() - start_time
    total_time_str = str(datetime.timedelta(seconds=int(total_time)))
    print('Training time {}'.format(total_time_str))
1、初始化分布式训练模式
def main(args):
    utils.init_distributed_mode(args) #初始化分布式训练模式
    print("git:\n  {}\n".format(utils.get_sha()))
    #判断backbone的卷积层是否冻结
    if args.frozen_weights is not None:
        assert args.masks, "Frozen training is meant for segmentation only"
    print(args)
    #部署模型到对应device上
    device = torch.device(args.device)
  1. utils.init_distributed_mode(args):这个函数调用了一个名为init_distributed_mode的工具函数,用于初始化分布式训练模式。这意味着如果用户配置了分布式训练,它将设置分布式训练的相关环境。

  2. print("git:\n {}\n".format(utils.get_sha())):这一行代码打印了关于Git仓库的信息,包括Git的SHA哈希值,以显示程序的版本信息。

  3. if args.frozen_weights is not None::这是一个条件语句,用于检查是否指定了预训练模型的路径。如果args.frozen_weights不为None,则表示指定了预训练模型的路径

  4. assert args.masks, "Frozen training is meant for segmentation only":这个断言语句用于确保如果指定了预训练模型的路径,那么也必须设置args.masksTrue,以表明预训练模型用于分割任务。如果不满足这个条件,将引发一个断言错误。

  5. print(args):打印了所有的配置参数,以便在程序运行时查看用户的设置。

  6. device = torch.device(args.device):根据args.device的设置,将模型部署到相应的计算设备上,通常是GPU('cuda')或CPU('cpu')。

2、设置随机种子
    # fix the seed for reproducibility固定随机种子以便结果浮现,get_rank()是分布式节点的编号
    seed = args.seed + utils.get_rank()
    torch.manual_seed(seed)
    np.random.seed(seed)
    random.seed(seed)
    #搭建网络,返回detr的网络,会有三个参数模型model,损失criterion,后处理的方式postprocessors,这里的后处理主要是为了coco的API去把最后给出的结果(左上和右下坐标)处理成(x,y,w,h)
    #本代码直接用的左上和右下坐标
    model, criterion, postprocessors = build_model(args)
    model.to(device)

以下是代码的解释:

  1. seed = args.seed + utils.get_rank():计算用于设置随机种子的值。args.seed是用户在命令行参数中指定的随机种子,而utils.get_rank()是用于获取分布式节点的编号。通过将它们相加,确保了每个分布式节点都有一个唯一的随机种子。

  2. torch.manual_seed(seed):使用PyTorch函数torch.manual_seed()设置随机数生成器的种子,以确保结果可重复。

  3. np.random.seed(seed):使用NumPy库函数np.random.seed()设置随机数生成器的种子,以确保NumPy的随机操作也是可重复的。

  4. random.seed(seed):使用Python标准库中的random.seed()设置Python内置的随机数生成器的种子。

  5. model, criterion, postprocessors = build_model(args):调用build_model函数构建深度学习模型,该函数会返回模型、损失函数和后处理器。模型将用于训练和推理,损失函数用于计算训练中的损失,后处理器用于将模型的输出结果进行处理,使其符合特定的格式(在这种情况下,用于COCO数据集的处理)。该函数接受args作为参数,其中包含了所有的配置参数。

  6. model.to(device):将构建的深度学习模型部署到指定的计算设备上,这里的device是根据用户配置的参数args.device来确定的,通常是GPU或CPU。模型将在所选设备上进行训练和推理。

3、分布式处理
    model_without_ddp = model #ddp分布式处理的缩写,默认把model赋值给没有ddp的model
    if args.distributed:
        model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
        model_without_ddp = model.module
    n_parameters = sum(p.numel() for p in model.parameters() if p.requires_grad)
    print('number of params:', n_parameters)

    param_dicts = [
        {"params": [p for n, p in model_without_ddp.named_parameters() if "backbone" not in n and p.requires_grad]},
        {
            "params": [p for n, p in model_without_ddp.named_parameters() if "backbone" in n and p.requires_grad],
            "lr": args.lr_backbone,
        },
    ] #设置学习率,backbone学习率和transformer以及ffn的均有不同,backbone直接用的是在官网上下载的一个预训练参数,已经训练好了,所以lr会设置的低一点,其他的就是常规学习率
    optimizer = torch.optim.AdamW(param_dicts, lr=args.lr,
                                  weight_decay=args.weight_decay)
    lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, args.lr_drop)#学习率调整,每隔lr_drop个epoch,lr=gamma*lr_drop,gamma默认为0.1,也就是每隔200个epoch,学习率降为原来的0.1
    #下面的就开始重要了,读取数据,看传进来的是训练数据还是验证数据,可以跳转到指定代码看一下
    dataset_train = build_dataset(image_set='train', args=args)
    dataset_val = build_dataset(image_set='val', args=args)

这部分代码包含以下关键步骤:

  1. model_without_ddp = model:将构建的模型赋值给model_without_ddp,这是为了后续方便使用没有经过分布式数据并行(DDP)处理的模型。

  2. 如果args.distributed为True(表示正在进行分布式训练),则将model使用DistributedDataParallel进行包装,以实现分布式数据并行。这会将模型分发到多个GPU上进行训练。同时,将model_without_ddp指定为model.module,以获得没有DDP处理的模型。

  3. 计算模型的总参数数量,并打印出来,以了解模型的规模。

  4. 创建参数字典param_dicts,用于设置不同部分的学习率。其中包括:

    • "params":用于保存不包含backbone部分的模型参数,并设置它们的学习率为args.lr(通常的学习率)。
    • "params":用于保存backbone部分的模型参数,并设置它们的学习率为args.lr_backbone(较低的学习率,因为这部分通常使用预训练的参数,不需要大幅度调整)。
  5. 创建优化器optimizer,使用AdamW算法,并传入参数字典和学习率参数。

  6. 创建学习率调度器lr_scheduler,它将按照一定的规则调整学习率,具体地,每隔args.lr_drop个epoch,将学习率降低为原来的0.1倍。

  7. 这一块就比较重要了,读取数据,看传进来的是训练数据集还是验证数据集。通过build_dataset函数根据参数image_set来构建相应的数据集,可以是训练数据集('train')或验证数据集('val'),具体的数据加载和预处理在后续步骤中完成。可以调转到指定代码看一下,点至build_dataset转至/datasets/__init__.py文件,有build_dataset(image_set, args)  ,可以看ETR纯代码分享(五)中的 build_dataset()函数实现

4、数据加载器(DataLoader)和采样器(Sampler)
    if args.distributed:
        sampler_train = DistributedSampler(dataset_train)
        sampler_val = DistributedSampler(dataset_val, shuffle=False)
    else:
        sampler_train = torch.utils.data.RandomSampler(dataset_train)
        sampler_val = torch.utils.data.SequentialSampler(dataset_val)

    batch_sampler_train = torch.utils.data.BatchSampler(
        sampler_train, args.batch_size, drop_last=True)

    data_loader_train = DataLoader(dataset_train, batch_sampler=batch_sampler_train,
                                   collate_fn=utils.collate_fn, num_workers=args.num_workers)
    data_loader_val = DataLoader(dataset_val, args.batch_size, sampler=sampler_val,
                                 drop_last=False, collate_fn=utils.collate_fn, num_workers=args.num_workers)
  1. 如果args.distributed为True(表示正在进行分布式训练),则创建分布式采样器(DistributedSamplersampler_trainsampler_val分布式采样器用于确保不同分布式节点之间的数据划分一致。如果不是分布式训练,将创建随机采样器(RandomSampler)和顺序采样器(SequentialSampler)分别用于训练和验证数据集。

  2. 使用BatchSampler创建batch_sampler_train,该采样器用于将数据按照批次加载。它接受一个采样器(这里是sampler_train)、批次大小(args.batch_size),并设置drop_last=True,表示如果最后一个批次的样本数量不足一个批次大小,就不使用该批次。

  3. 使用DataLoader创建训练数据加载器data_loader_train和验证数据加载器data_loader_val。它们分别加载训练数据集和验证数据集,并使用batch_sampler_trainsampler_val来确定样本的顺序和分布。同时,指定了collate_fn参数为utils.collate_fn,该函数用于对样本进行批次处理和填充。num_workers参数指定了数据加载过程中的并行工作进程数量。

这些数据加载器用于在训练和验证过程中从数据集中加载批次数据,并将其传递给模型进行训练和评估。采样器和加载器的配置可以根据需要进行灵活调整,以适应不同的数据集和训练需求。

5、构建用于评估的COCO数据集对象
    if args.dataset_file == "coco_panoptic":
        # We also evaluate AP during panoptic training, on original coco DS
        coco_val = datasets.coco.build("val", args)
        base_ds = get_coco_api_from_dataset(coco_val)
    else:
        base_ds = get_coco_api_from_dataset(dataset_val)

    if args.frozen_weights is not None:
        checkpoint = torch.load(args.frozen_weights, map_location='cpu')
        model_without_ddp.detr.load_state_dict(checkpoint['model'])

这部分代码用于构建用于评估的COCO数据集对象,并加载预训练模型的权重(如果指定了冻结权重的路径)。具体步骤如下:

  1. 首先,检查args.dataset_file的值是否为"coco_panoptic",如果是,则表示正在进行COCO Panoptic数据集的训练和评估。在这种情况下,会构建COCO数据集的验证集对象coco_val以便进行在原始COCO数据集上的评估。同时,base_ds设置为coco_val的API对象,用于后续的评估。如果args.dataset_file不是"coco_panoptic",则将base_ds设置为验证数据集dataset_val的API对象,以便进行一般的COCO数据集上的评估。

  2. 如果args.frozen_weights不为None(即用户指定了预训练模型的路径),则加载预训练模型的权重。首先,使用torch.load函数加载预训练模型的权重文件,指定map_location='cpu'将权重加载到CPU上。接着,使用model_without_ddp.detr.load_state_dict(checkpoint['model'])将加载的权重赋值给模型model_without_ddp的DETR部分,用于迁移学习或冻结模型的某些部分。

这些步骤准备了评估过程所需的COCO数据集对象和预训练模型的权重。评估通常在训练之后进行,用于衡量模型的性能。

6、加载模型和优化器的检查点
    output_dir = Path(args.output_dir)
    if args.resume:
        if args.resume.startswith('https'):
            checkpoint = torch.hub.load_state_dict_from_url(
                args.resume, map_location='cpu', check_hash=True)
        else:
            checkpoint = torch.load(args.resume, map_location='cpu')
        model_without_ddp.load_state_dict(checkpoint['model'])
        if not args.eval and 'optimizer' in checkpoint and 'lr_scheduler' in checkpoint and 'epoch' in checkpoint:
            optimizer.load_state_dict(checkpoint['optimizer'])
            lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])
            args.start_epoch = checkpoint['epoch'] + 1

这部分代码用于加载模型和优化器的检查点(checkpoint),以恢复模型的训练状态或进行模型评估。具体步骤如下:

  1. output_dir = Path(args.output_dir):将输出目录的路径args.output_dir转换为Path对象,以便后续的文件操作。

  2. 如果args.resume不为空(即用户提供了要恢复的检查点文件路径),则执行以下操作:

    • 如果args.resume以'https'开头,表示从远程URL加载检查点文件。使用torch.hub.load_state_dict_from_url函数加载检查点文件,指定map_location='cpu'将检查点加载到CPU上,同时设置check_hash=True以验证检查点文件的哈希值。

    • 如果args.resume不是远程URL,而是本地文件路径,则使用torch.load函数加载本地的检查点文件,同样也加载到CPU上。

  3. 使用model_without_ddp.load_state_dict(checkpoint['model'])将加载的检查点中的模型权重赋值给模型model_without_ddp,以恢复模型的状态。

  4. 如果不仅进行模型评估(args.eval为False),而且检查点文件中还包含了优化器和学习率调度器的状态信息(即'optimizer'和'lr_scheduler'在检查点中),以及当前的训练轮次('epoch'在检查点中),则执行以下操作:

    • 使用optimizer.load_state_dict(checkpoint['optimizer'])将检查点中的优化器状态恢复到当前的优化器中。

    • 使用lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])将检查点中的学习率调度器状态恢复到当前的学习率调度器中。

    • args.start_epoch设置为检查点中记录的训练轮次加1,以继续训练。

这些步骤允许用户从之前保存的检查点文件中恢复模型的状态和优化器状态,从而可以继续训练或进行模型评估。

7、模型评估
    if args.eval:
        test_stats, coco_evaluator = evaluate(model, criterion, postprocessors,
                                              data_loader_val, base_ds, device, args.output_dir)
        if args.output_dir:
            utils.save_on_master(coco_evaluator.coco_eval["bbox"].eval, output_dir / "eval.pth")
        return

如果args.eval为True,表示要进行模型的评估,代码将执行以下操作:

  1. 调用evaluate函数来进行模型的评估,评估的过程包括计算模型的损失(使用criterion)、执行后处理(postprocessors、在验证数据集上进行推理(data_loader_val、计算评估指标(例如,COCO指标),并将评估结果保存到test_statscoco_evaluator中。

  2. 如果指定了输出目录args.output_dir,则将评估结果保存到文件中,具体地,将COCO评估器(coco_evaluator)的结果保存到名为"eval.pth"的文件中,该文件位于output_dir目录下。

  3. 返回函数,结束程序。

这段代码用于在模型训练之后执行模型的评估,评估结果可以用于衡量模型的性能。如果不需要训练模型,而只是进行评估,可以将args.eval设置为True,然后运行程序即可。

8、训练循环
    print("Start training")
    start_time = time.time()
    for epoch in range(args.start_epoch, args.epochs):
        if args.distributed:
            sampler_train.set_epoch(epoch)
        train_stats = train_one_epoch(
            model, criterion, data_loader_train, optimizer, device, epoch,
            args.clip_max_norm)
        lr_scheduler.step()
        if args.output_dir:
            checkpoint_paths = [output_dir / 'checkpoint.pth']
            # extra checkpoint before LR drop and every 100 epochs
            if (epoch + 1) % args.lr_drop == 0 or (epoch + 1) % 100 == 0:
                checkpoint_paths.append(output_dir / f'checkpoint{epoch:04}.pth')
            for checkpoint_path in checkpoint_paths:
                utils.save_on_master({
                    'model': model_without_ddp.state_dict(),
                    'optimizer': optimizer.state_dict(),
                    'lr_scheduler': lr_scheduler.state_dict(),
                    'epoch': epoch,
                    'args': args,
                }, checkpoint_path)

        test_stats, coco_evaluator = evaluate(
            model, criterion, postprocessors, data_loader_val, base_ds, device, args.output_dir
        )

        log_stats = {**{f'train_{k}': v for k, v in train_stats.items()},
                     **{f'test_{k}': v for k, v in test_stats.items()},
                     'epoch': epoch,
                     'n_parameters': n_parameters}

这段代码是一个用于训练和评估深度学习模型的主要训练循环。

  1. print("Start training"):打印训练开始的消息。

  2. start_time = time.time()记录训练开始的时间,以便后续计算训练时间。

  3. for epoch in range(args.start_epoch, args.epochs)::进入一个循环,迭代训练多个轮次(epochs),从args.start_epoch开始,一直到args.epochs - 1结束。

    a. 如果使用分布式训练(args.distributedTrue),则通过sampler_train.set_epoch(epoch)来设置每个轮次的随机采样种子。

  4. train_stats = train_one_epoch(...):调用 train_one_epoch 函数来执行一个训练轮次。这个函数用于在一个轮次内训练模型,计算损失和性能指标,并返回一个字典train_stats,其中包含了训练轮次的统计信息。

  5. lr_scheduler.step()调整学习率,通常会根据训练的进展来降低学习率,以帮助模型更好地收敛。

  6. if args.output_dir::这是一个条件语句,检查是否提供了输出目录args.output_dir)。

    a. 如果提供了输出目录,执行以下操作:

  7. checkpoint_paths = [output_dir / 'checkpoint.pth']:创建一个包含一个文件路径的列表,该路径用于保存训练过程中的模型检查点。通常,'checkpoint.pth' 文件包含了模型权重、优化器状态、学习率调度器状态等信息。

  8. 在某些特定条件下,会额外保存模型检查点,包括:

    • 每当达到学习率下降的轮次(args.lr_drop的倍数)
    • 每当达到100个轮次的倍数

    这些额外的检查点文件具有特定的命名约定,如'checkpoint0000.pth''checkpoint0100.pth'等。

  9. 这段代码用于保存模型的检查点,包括模型权重、优化器状态、学习率调度器状态、当前轮次(epoch)信息以及训练时使用的参数等到指定的检查点文件路径。for checkpoint_path in checkpoint_paths::通过循环迭代 checkpoint_paths 列表中的每个检查点路径。utils.save_on_master(..., checkpoint_path):调用 utils.save_on_master() 函数,将模型和相关信息保存到指定的检查点文件中。

    • 'args': 包含了训练时使用的参数信息,即变量 args 的值。
    • 'epoch': 包含了当前轮次的信息,即变量 epoch 的值。
    • 'lr_scheduler': 包含了学习率调度器的状态信息,使用 lr_scheduler.state_dict() 来获取学习率调度器的状态字典。
    • 'optimizer': 包含了优化器的状态信息,使用 optimizer.state_dict() 来获取优化器的状态字典。
    • 'model': 包含了模型的权重信息,使用 model_without_ddp.state_dict() 来获取去除了分布式数据并行(DDP)的模型的状态字典。
    • 第二个参数 checkpoint_path 是指定的检查点文件路径,用于保存上述信息。

  10. 这段代码用于创建一个字典 log_stats,其中包含了训练和测试的各种统计信息,以及一些其他有用的信息。让我解释一下代码的每个部分:{**{f'train_{k}': v for k, v in train_stats.items()}:这是一个字典推导式,用于将 train_stats 字典中的每一项的键(key)进行处理,添加前缀"train_",然后将前缀和值(value)重新构建成一个新的字典。这样,原始训练统计信息的键会变成"train_键名"的形式,以表示这些信息与训练相关。{**{f'test_{k}': v for k, v in test_stats.items()}:类似于上面的操作,这部分代码用于将 test_stats 字典中的每一项的键添加前缀"test_",然后构建一个新的字典,表示这些信息与测试相关。最终,log_stats 是一个包含了训练和测试统计信息、当前轮次和模型参数数量等信息的字典。这些信息可以用于训练日志、性能监控、可视化或其他目的,以便跟踪和记录模型的训练和性能变化。

  11. 'epoch': epoch:将当前轮次的信息存储为 'epoch' 键对应的值,即变量 epoch 的值。

  12. 'n_parameters': n_parameters:将模型的参数数量存储为 'n_parameters' 键对应的值,即变量 n_parameters 的值。这通常用于记录模型的规模和复杂度。

  13. log_stats = { ... }:创建一个字典 log_stats,其中包含了训练和测试的各种统计信息,如训练损失、测试损失、当前轮次、模型参数数量等。

这段代码的主要作用是训练和评估深度学习模型,周期性地保存模型检查点,并记录训练和测试的性能指标。这是一个典型的训练循环,用于监控和记录模型的训练过程和性能。

9、记录日志和保存评估结果
        if args.output_dir and utils.is_main_process():
            with (output_dir / "log.txt").open("a") as f:
                f.write(json.dumps(log_stats) + "\n")

            # for evaluation logs
            if coco_evaluator is not None:
                (output_dir / 'eval').mkdir(exist_ok=True)
                if "bbox" in coco_evaluator.coco_eval:
                    filenames = ['latest.pth']
                    if epoch % 50 == 0:
                        filenames.append(f'{epoch:03}.pth')
                    for name in filenames:
                        torch.save(coco_evaluator.coco_eval["bbox"].eval,
                                   output_dir / "eval" / name)

这段代码执行了一系列操作,用于记录训练和评估的日志信息以及保存评估结果。让我解释它的主要部分:

  1. if args.output_dir and utils.is_main_process():这是一个条件语句,它检查两个条件:

    a. args.output_dir:检查是否提供了输出目录(args.output_dir)。如果提供了输出目录,则表示需要将日志信息保存到指定目录中。

    b. utils.is_main_process():这是一个函数调用,通常用于分布式训练环境中,确保只有主进程执行以下的日志记录和文件保存操作。

  2. with (output_dir / "log.txt").open("a") as f打开一个文件(log.txt)以供追加写入。这个文件通常用于保存训练和评估的日志信息

    • (output_dir / "log.txt") 构建了日志文件的完整路径,其中 output_dir 是输出目录,"log.txt" 是日志文件的名称。

    • "a" 参数表示以追加模式打开文件,这意味着如果文件已经存在,将继续在文件末尾写入内容。

  3. f.write(json.dumps(log_stats) + "\n")log_stats 字典转换为 JSON 格式的字符串,并将其写入日志文件中,然后添加一个换行符 \n

  4. if coco_evaluator is not None::这是一个条件语句,检查 coco_evaluator 是否存在。coco_evaluator 通常用于计算和保存目标检测性能指标,如 COCO 指标。

  5. (output_dir / 'eval').mkdir(exist_ok=True):创建一个名为 'eval' 的子目录,用于存储评估相关的文件。如果该目录已经存在,exist_ok=True 参数确保不会引发错误

  6. if "bbox" in coco_evaluator.coco_eval:检查是否计算了 COCO 检测性能指标('bbox' 指标)。如果计算了这个指标,执行以下操作:

    • filenames = ['latest.pth']:创建一个包含一个文件名 'latest.pth' 的列表。通常,'latest.pth' 包含了最新的评估结果。

    • if epoch % 50 == 0::这是另一个条件语句,检查当前轮次是否是 50 的倍数。如果是,将额外保存一个评估结果文件,文件名格式为 轮次号.pth,例如 001.pth050.pth 等。

    • 对于每个文件名 name,将 COCO 评估结果保存到相应的文件中,文件的路径为 output_dir / "eval" / name

总之,这段代码的主要作用是保存训练和评估的日志信息到日志文件中,以及保存评估结果(例如 COCO 指标)到特定的目录中。这些操作有助于对模型的性能进行跟踪、记录和分析。

10、训练总时长
    total_time = time.time() - start_time
    total_time_str = str(datetime.timedelta(seconds=int(total_time)))
    print('Training time {}'.format(total_time_str))

这段代码旨在提供有关训练时间的信息,以便用户了解模型训练的时长。

这段代码计算和打印出整个训练过程的总时间。让我解释代码的主要部分:

  1. total_time = time.time() - start_time:通过减去训练开始时的时间戳(start_time)和当前时间戳(使用 time.time() 获取)来计算整个训练过程的总时间(以秒为单位)

  2. total_time_str = str(datetime.timedelta(seconds=int(total_time))):将计算得到的总时间(以秒为单位)格式化为人类可读的字符串格式,使用了 Python 中的 datetime.timedelta 类。这将把总时间转换为更易于理解的格式,如 "0:02:30" 表示 2 分钟 30 秒。

  3. print('Training time {}'.format(total_time_str)):打印出训练时间,将计算得到的总时间字符串插入到格式化字符串中,以便显示在屏幕上。例如,输出可能是 "Training time 0:02:30",表示训练过程花费了2分钟30秒。

这段代码的目的是在训练结束后,向用户提供关于整个训练过程耗时的信息,以便了解训练的时间消耗。这对于评估训练的效率和规划资源非常有用。

四、脚本入口点
if __name__ == '__main__':
    parser = argparse.ArgumentParser('DETR training and evaluation script', parents=[get_args_parser()])
    args = parser.parse_args()
    if args.output_dir:
        Path(args.output_dir).mkdir(parents=True, exist_ok=True)
    main(args)

这段代码通常是一个 Python 脚本的入口点,用于执行 DETR模型的训练和评估。

  1. if __name__ == '__main__':这是一个常见的 Python 代码约定,它表示以下的代码块只有在当前脚本被直接运行时才会执行,而不是被导入为一个模块时执行。

  2. parser = argparse.ArgumentParser('DETR training and evaluation script', parents=[get_args_parser()]):创建一个参数解析器对象 parser,用于解析命令行参数。argparse.ArgumentParser 用于定义和解析命令行参数。

    • 'DETR training and evaluation script' 是一个简要的描述,用于显示在帮助信息中。
    • parents=[get_args_parser()] 是传递给参数解析器的参数,通常是包含其他参数定义的父解析器。get_args_parser() 函数返回一个包含DETR相关参数定义的父解析器。
  3. args = parser.parse_args():解析命令行参数,并将结果存储在变量 argsargs 包含了从命令行传递的参数值,以及通过参数解析器定义的默认值。

  4. if args.output_dir:这是一个条件语句,检查是否提供了输出目录(args.output_dir)。如果提供了输出目录,执行以下操作:

    • path(args.output_dir).mkdir(parents=True, exist_ok=True):创建一个路径对象 path(args.output_dir),然后使用该对象的 .mkdir() 方法创建目录。参数 parents=True 表示如果上级目录不存在,则一并创建,exist_ok=True 表示如果目录已经存在,不会引发错误。
  5. main(args)调用名为 main 的函数,并将解析后的命令行参数 args 作为参数传递给它。这是实际执行DETR训练和评估的入口函数

通常,这段代码用于设置参数解析器、解析命令行参数、创建输出目录(如果需要的话),然后调用主函数 main 来执行DETR模型的训练和评估任务。这种结构使得脚本可以根据命令行参数的不同来执行不同的任务和配置。

  • 11
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
Transformer在许多NLP(自然语言处理)任务中取得了最先进的成果。 DETR(Detection Transformer)是Facebook提出的基于Transformer的端到端目标检测方法。DETR使用CNN+Transformer进行图像目标检测,该方法没有NMS后处理步骤、没有anchor。DETR总体思路是把目标检测看成一个set prediction的问题,并且使用Transformer来预测物体边界框的集合。本课程对DETR的原理与PyTorch实现代码进行精讲,来帮助大家掌握其详细原理和具体实现。 原理精讲部分包括:Transformer的架构概述、Transformer的EncoderTransformer的DecoderDETR网络架构、DETR损失函数、DETR实验结果和分析。  代码精讲部分使用Jupyter Notebook对DETRPyTorch代码进行逐行解读,包括:安装PyTorch、 DETR官方Demo,DETR的hands-on tutorial,DETR代码精讲(数据准备、Backbone和位置编码、Transformer架构的实现)。相关课程: 《Transformer原理与代码精讲(PyTorch)》https://edu.csdn.net/course/detail/36697《Transformer原理与代码精讲(TensorFlow)》https://edu.csdn.net/course/detail/36699《ViT(Vision Transformer)原理与代码精讲》https://edu.csdn.net/course/detail/36719《DETR原理与代码精讲》https://edu.csdn.net/course/detail/36768《Swin Transformer实战目标检测:训练自己的数据集》https://edu.csdn.net/course/detail/36585《Swin Transformer实战实例分割:训练自己的数据集》https://edu.csdn.net/course/detail/36586《Swin Transformer原理与代码精讲》 https://download.csdn.net/course/detail/37045

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值