code: def train(hyp):
      device = torch.device(
          opt.device if torch.cuda.is_available() else "cpu"
      )
      print("Using {} device training.".format(device.type))
  
      wdir = "weights" + os.sep  # weights dir
      best = wdir + "best.pt"
      results_file = "results{}.txt".format(
          datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
      )
  
      cfg = opt.cfg
      data = opt.data
      epochs = opt.epochs
      batch_size = opt.batch_size
      accumulate = max(
          round(64 / batch_size), 1
      )  # accumulate n times before optimizer update (bs 64)
      weights = opt.weights  # initial training weights
      imgsz_train = opt.img_size
      imgsz_test = opt.img_size  # test image sizes
      multi_scale = opt.multi_scale
  
      # Image sizes
      # 图像要设置成32的倍数
      gs = 32  # (pixels) grid size
      assert (
          math.fmod(imgsz_test, gs) == 0
      ), "--img-size %g must be a %g-multiple" % (imgsz_test, gs)
      grid_min, grid_max = imgsz_test // gs, imgsz_test // gs
      if multi_scale:
          imgsz_min = opt.img_size // 1.5
          imgsz_max = opt.img_size // 0.667
  
          # 将给定的最大，最小输入尺寸向下调整到32的整数倍
          grid_min, grid_max = imgsz_min // gs, imgsz_max // gs
          imgsz_min, imgsz_max = int(grid_min * gs), int(grid_max * gs)
          imgsz_train = imgsz_max  # initialize with max size
          print(
              "Using multi_scale training, image range[{}, {}]".format(
                  imgsz_min, imgsz_max
              )
          )
  
      # configure run
      # init_seeds()  # 初始化随机种子，保证结果可复现
      data_dict = parse_data_cfg(data)
      train_path = data_dict["train"]
      test_path = data_dict["valid"]
      nc = (
          1 if opt.single_cls else int(data_dict["classes"])
      )  # number of classes
      hyp["cls"] *= (
          nc / 80
      )  # update coco-tuned hyp['cls'] to current dataset
      hyp["obj"] *= imgsz_test / 320
  
      # Remove previous results
      for f in glob.glob(results_file):
          os.remove(f)
  
      # Initialize model
      model = Darknet(cfg).to(device)
  
      # 是否冻结权重，只训练predictor的权重
      if opt.freeze_layers:
          # 索引减一对应的是predictor的索引，YOLOLayer并不是predictor
          output_layer_indices = [
              idx - 1
              for idx, module in enumerate(model.module_list)
              if isinstance(module, YOLOLayer)
          ]
          # 冻结除predictor和YOLOLayer外的所有层
          freeze_layer_indeces = [
              x
              for x in range(len(model.module_list))
              if (x not in output_layer_indices)
              and (x - 1 not in output_layer_indices)
          ]
          # Freeze non-output layers
          # 总共训练3x2=6个parameters
          for idx in freeze_layer_indeces:
              for parameter in model.module_list[idx].parameters():
                  parameter.requires_grad_(False)
      else:
          # 如果freeze_layer为False，默认仅训练除darknet53之后的部分
          # 若要训练全部权重，删除以下代码
          darknet_end_layer = 74  # only yolov3spp cfg
          # Freeze darknet53 layers
          # 总共训练21x3+3x2=69个parameters
          for idx in range(darknet_end_layer + 1):  # [0, 74]
              for parameter in model.module_list[idx].parameters():
                  parameter.requires_grad_(False)
  
      # optimizer
      pg = [p for p in model.parameters() if p.requires_grad]
      optimizer = optim.SGD(
          pg,
          lr=hyp["lr0"],
          momentum=hyp["momentum"],
          weight_decay=hyp["weight_decay"],
          nesterov=True,
      )
  
      scaler = torch.cuda.amp.GradScaler() if opt.amp else None
  
      start_epoch = 0
      best_map = 0.0
      if weights.endswith(".pt") or weights.endswith(".pth"):
          ckpt = torch.load(weights, map_location=device)
  
          # load model
          try:
              ckpt["model"] = {
                  k: v
                  for k, v in ckpt["model"].items()
                  if model.state_dict()[k].numel() == v.numel()
              }
              model.load_state_dict(ckpt["model"], strict=False)
          except KeyError as e:
              s = (
                  "%s is not compatible with %s. Specify --weights '' or specify a --cfg compatible with %s. "
                  "See https://github.com/ultralytics/yolov3/issues/657"
                  % (opt.weights, opt.cfg, opt.weights)
              )
              raise KeyError(s) from e
  
          # load optimizer
          if ckpt["optimizer"] is not None:
              optimizer.load_state_dict(ckpt["optimizer"])
              if "best_map" in ckpt.keys():
                  best_map = ckpt["best_map"]
  
          # load results
          if ckpt.get("training_results") is not None:
              with open(results_file, "w") as file:
                  file.write(
                      ckpt["training_results"]
                  )  # write results.txt
  
          # epochs
          start_epoch = ckpt["epoch"] + 1
          if epochs < start_epoch:
              print(
                  "%s has been trained for %g epochs. Fine-tuning for %g additional epochs."
                  % (opt.weights, ckpt["epoch"], epochs)
              )
              epochs += ckpt["epoch"]  # finetune additional epochs
  
          if opt.amp and "scaler" in ckpt:
              scaler.load_state_dict(ckpt["scaler"])
  
          del ckpt
  
      # Scheduler https://arxiv.org/pdf/1812.01187.pdf
      lf = (
          lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2)
          * (1 - hyp["lrf"])
          + hyp["lrf"]
      )  # cosine
      scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
      scheduler.last_epoch = start_epoch  # 指定从哪个epoch开始
  
      # Plot lr schedule
      # y = []
      # for _ in range(epochs):
      #     scheduler.step()
      #     y.append(optimizer.param_groups[0]['lr'])
      # plt.plot(y, '.-', label='LambdaLR')
      # plt.xlabel('epoch')
      # plt.ylabel('LR')
      # plt.tight_layout()
      # plt.savefig('LR.png', dpi=300)
  
      # model.yolo_layers = model.module.yolo_layers
  
      # dataset
      # 训练集的图像尺寸指定为multi_scale_range中最大的尺寸
      train_dataset = LoadImagesAndLabels(
          train_path,
          imgsz_train,
          batch_size,
          augment=True,
          hyp=hyp,  # augmentation hyperparameters
          rect=opt.rect,  # rectangular training
          cache_images=opt.cache_images,
          single_cls=opt.single_cls,
      )
  
      # 验证集的图像尺寸指定为img_size(512)
      val_dataset = LoadImagesAndLabels(
          test_path,
          imgsz_test,
          batch_size,
          hyp=hyp,
          rect=True,  # 将每个batch的图像调整到合适大小，可减少运算量(并不是512x512标准尺寸)
          cache_images=opt.cache_images,
          single_cls=opt.single_cls,
      )
  
      # dataloader
      nw = min(
          [os.cpu_count(), batch_size if batch_size > 1 else 0, 8]
      )  # number of workers
      train_dataloader = torch.utils.data.DataLoader(
          train_dataset,
          batch_size=batch_size,
          num_workers=nw,
          # Shuffle=True unless rectangular training is used
          shuffle=not opt.rect,
          pin_memory=True,
          collate_fn=train_dataset.collate_fn,
      )
  
      val_datasetloader = torch.utils.data.DataLoader(
          val_dataset,
          batch_size=batch_size,
          num_workers=nw,
          pin_memory=True,
          collate_fn=val_dataset.collate_fn,
      )
  
      # Model parameters
      model.nc = nc  # attach number of classes to model
      model.hyp = hyp  # attach hyperparameters to model
      model.gr = 1.0  # giou loss ratio (obj_loss = 1.0 or giou)
      # 计算每个类别的目标个数，并计算每个类别的比重
      # model.class_weights = labels_to_class_weights(train_dataset.labels, nc).to(device)  # attach class weights
  
      # start training
      # caching val_data when you have plenty of memory(RAM)
      # coco = None
      coco = get_coco_api_from_dataset(val_dataset)
  
      print("starting traning for %g epochs..." % epochs)
      print("Using %g dataloader workers" % nw)
      for epoch in range(start_epoch, epochs):
          mloss, lr = train_util.train_one_epoch(
              model,
              optimizer,
              train_dataloader,
              device,
              epoch,
              accumulate=accumulate,  # 迭代多少batch才训练完64张图片
              img_size=imgsz_train,  # 输入图像的大小
              multi_scale=multi_scale,
              grid_min=grid_min,  # grid的最小尺寸
              grid_max=grid_max,  # grid的最大尺寸
              gs=gs,  # grid step: 32
              print_freq=50,  # 每训练多少个step打印一次信息
              warmup=True,
              scaler=scaler,
          )
          # update scheduler
          scheduler.step()
  
          if opt.notest is False or epoch == epochs - 1:
              # evaluate on the test dataset
              result_info = train_util.evaluate(
                  model, val_datasetloader, coco=coco, device=device
              )
  
              coco_mAP = result_info[0]
              voc_mAP = result_info[1]
              coco_mAR = result_info[8]
  
              # write into tensorboard
              if tb_writer:
                  tags = [
                      "train/giou_loss",
                      "train/obj_loss",
                      "train/cls_loss",
                      "train/loss",
                      "learning_rate",
                      "mAP@[IoU=0.50:0.95]",
                      "mAP@[IoU=0.5]",
                      "mAR@[IoU=0.50:0.95]",
                  ]
  
                  for x, tag in zip(
                      mloss.tolist()
                      + [lr, coco_mAP, voc_mAP, coco_mAR],
                      tags,
                  ):
                      tb_writer.add_scalar(tag, x, epoch)
  
              # write into txt
              with open(results_file, "a") as f:
                  # 记录coco的12个指标加上训练总损失和lr
                  result_info = [
                      str(round(i, 4))
                      for i in result_info + [mloss.tolist()[-1]]
                  ] + [str(round(lr, 6))]
                  txt = "epoch:{} {}".format(
                      epoch, "  ".join(result_info)
                  )
                  f.write(txt + "\n")
  
              # update best mAP(IoU=0.50:0.95)
              if coco_mAP > best_map:
                  best_map = coco_mAP
  
              if opt.savebest is False:
                  # save weights every epoch
                  with open(results_file, "r") as f:
                      save_files = {
                          "model": model.state_dict(),
                          "optimizer": optimizer.state_dict(),
                          "training_results": f.read(),
                          "epoch": epoch,
                          "best_map": best_map,
                      }
                      if opt.amp:
                          save_files["scaler"] = scaler.state_dict()
                      torch.save(
                          save_files,
                          "./weights/yolov3spp-{}.pt".format(epoch),
                      )
              else:
                  # only save best weights
                  if best_map == coco_mAP:
                      with open(results_file, "r") as f:
                          save_files = {
                              "model": model.state_dict(),
                              "optimizer": optimizer.state_dict(),
                              "training_results": f.read(),
                              "epoch": epoch,
                              "best_map": best_map,
                          }
                          if opt.amp:
                              save_files["scaler"] = scaler.state_dict()
                          torch.save(save_files, best.format(epoch))
  
file_path: deep-learning-for-image-processing/pytorch_object_detection/yolov3_spp/train.py
imports: import datetime
  import argparse
  import yaml
  import torch.optim
  import torch.optim.lr_scheduler
  from torch.utils.tensorboard import SummaryWriter
  from models import *
  from build_utils.datasets import *
  from build_utils.utils import *
  from train_utils import train_eval_utils
  from train_utils import get_coco_api_from_dataset
label_desc: Use image node_id 1 for calling a global function with example usage: train(hyp) without return types