yolov8逐步分解(8)_训练过程之Epoch迭代前初始准备

yolov8逐步分解(1)--默认参数&超参配置文件加载

yolov8逐步分解(2)_DetectionTrainer类初始化过程

yolov8逐步分解(3)_trainer训练之模型加载

YOLOV8逐步分解(4)_模型的构建过程

YOLOV8逐步分解(5)_模型训练初始设置之混合精度训练AMP

YOLOV8逐步分解(6)_模型训练初始设置之image size检测batch预设及dataloder初始化

yolov8逐步分解(7)_模型训练初始设置之优化器Optimizer及学习率调度器Scheduler初始化

        本系列之前的文章1-7均为训练初始化过程,本章节将讲解训练过程的代码。

        这段代码是PyTorch训练循环实现的一部分。以下将对它进行逐步解释:

        _do_train函数负责主要的训练过程。它可以接受一个可选参数world_size,用于指定用于分布式数据并行(DDP)训练的GPU数量。

   def _do_train(self, world_size=1):
        """Train completed, evaluate and plot if specified by arguments."""
        if world_size > 1: #当前world_size = 1,不采用多卡训练
            self._setup_ddp(world_size) #多卡训练配置

        self._setup_train(world_size)
        self.epoch_time = None
        self.epoch_time_start = time.time()
        self.train_time_start = time.time()
        nb = len(self.train_loader)  # number of batches
        nw = max(round(self.args.warmup_epochs *
                       nb), 100) if self.args.warmup_epochs > 0 else -1  # number of warmup iterations # 预热迭代次数
        last_opt_step = -1
        self.run_callbacks('on_train_start')
        #输出一些训练信息,如图像大小、数据加载器worker数量、日志保存路径等
        LOGGER.info(f'Image sizes {self.args.imgsz} train, {self.args.imgsz} val\n'
                    f'Using {self.train_loader.num_workers * (world_size or 1)} dataloader workers\n'
                    f"Logging results to {colorstr('bold', self.save_dir)}\n"
                    f'Starting training for {self.epochs} epochs...')
        if self.args.close_mosaic:
            base_idx = (self.epochs - self.args.close_mosaic) * nb
            self.plot_idx.extend([base_idx, base_idx + 1, base_idx + 2])
        epoch = self.epochs  # predefine for resume fully trained model edge cases
        for epoch in range(self.start_epoch, self.epochs):
            self.epoch = epoch
            self.run_callbacks('on_train_epoch_start')
            self.model.train() #将模型设置为训练模式
            if RANK != -1:
                self.train_loader.sampler.set_epoch(epoch)
            pbar = enumerate(self.train_loader)
            # Update dataloader attributes (optional)
            if epoch == (self.epochs - self.args.close_mosaic): #最后10次训练关闭mosaic可以提升训练效果
                LOGGER.info('Closing dataloader mosaic')
                if hasattr(self.train_loader.dataset, 'mosaic'):
                    self.train_loader.dataset.mosaic = False
                if hasattr(self.train_loader.dataset, 'close_mosaic'):
                    self.train_loader.dataset.close_mosaic(hyp=self.args)
                self.train_loader.reset() #重置训练数据加载器的状态
            if RANK in (-1, 0):
                LOGGER.info(self.progress_string())
                pbar = tqdm(enumerate(self.train_loader), total=nb, bar_format=TQDM_BAR_FORMAT)
            self.tloss = None
            self.optimizer.zero_grad()#在反向传播过程中,梯度是累积的,而不是被替换掉。通过在每次迭代开始时将梯度清零,确保在当前迭代中计算梯度时是全新的,不会受到上一次迭代的梯度的影响。
            for i, batch in pbar:
                self.run_callbacks('on_train_batch_start')
                # Warmup
                ni = i + nb * epoch #计算当前迭代的总步数
                if ni <= nw: #在训练的早期阶段逐渐调整学习率和动量参数,以帮助模型更好地收敛。这通常用于处理训练初期的不稳定性。
                    xi = [0, nw]  # x interp 热身步数的起止范围
                    self.accumulate = max(1, np.interp(ni, xi, [1, self.args.nbs / self.batch_size]).round()) # 控制梯度累积的步数,根据当前迭代步数 ni 在范围 xi 内进行插值计算,并将结果四舍五入。
                    for j, x in enumerate(self.optimizer.param_groups):
                        # Bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
                        x['lr'] = np.interp(
                            ni, xi, [self.args.warmup_bias_lr if j == 0 else 0.0, x['initial_lr'] * self.lf(epoch)])
                        if 'momentum' in x:
                            x['momentum'] = np.interp(ni, xi, [self.args.warmup_momentum, self.args.momentum])
                # Forward
                with torch.cuda.amp.autocast(self.amp):
                    batch = self.preprocess_batch(batch)   #归一化 /255
                    self.loss, self.loss_items = self.model(batch) #将预处理后的批次数据输入模型进行前向传播并获取损失值和其他损失项
                    if RANK != -1:
                        self.loss *= world_size
                    self.tloss = (self.tloss * i + self.loss_items) / (i + 1) if self.tloss is not None \
                        else self.loss_items
                # Backward
                #使用self.scaler对损失值进行缩放,并调用backward()方法进行反向传播。
                #这是自动混合精度(AMP)训练中的一步,用于计算梯度并将其传播回模型的参数
                self.scaler.scale(self.loss).backward()
                # Optimize - https://pytorch.org/docs/master/notes/amp_examples.html
                if ni - last_opt_step >= self.accumulate:
                    self.optimizer_step()  #执行一步优化器的更新,根据之前计算得到的梯度来更新模型的参数。
                    last_opt_step = ni
                # Log
                mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G'  # (GB) 计算cuda内存以GB为单位
                loss_len = self.tloss.shape[0] if len(self.tloss.size()) else 1
                losses = self.tloss if loss_len > 1 else torch.unsqueeze(self.tloss, 0)
                if RANK in (-1, 0): #使用格式化字符串将训练进度、GPU内存占用和损失值等信息展示在进度条中
                    pbar.set_description(
                        ('%11s' * 2 + '%11.4g' * (2 + loss_len)) %
                        (f'{epoch + 1}/{self.epochs}', mem, *losses, batch['cls'].shape[0], batch['img'].shape[-1]))
                    self.run_callbacks('on_batch_end')
                    if self.args.plots and ni in self.plot_idx:
                        self.plot_training_samples(batch, ni)
                self.run_callbacks('on_train_batch_end')
            self.lr = {f'lr/pg{ir}': x['lr'] for ir, x in enumerate(self.optimizer.param_groups)}  # for loggers
            self.scheduler.step()
            self.run_callbacks('on_train_epoch_end')
            if RANK in (-1, 0):
                # Validation
                self.ema.update_attr(self.model, include=['yaml', 'nc', 'args', 'names', 'stride', 'class_weights'])
                final_epoch = (epoch + 1 == self.epochs) or self.stopper.possible_stop #判断是否为最后一个训练轮次或训练可能停止
                if self.args.val or final_epoch:
                    self.metrics, self.fitness = self.validate()
                self.save_metrics(metrics={**self.label_loss_items(self.tloss), **self.metrics, **self.lr})
                self.stop = self.stopper(epoch + 1, self.fitness)
                # Save model
                if self.args.save or (epoch + 1 == self.epochs): #判断是否需要保存模型
                    self.save_model()
                    self.run_callbacks('on_model_save')
            tnow = time.time()
            self.epoch_time = tnow - self.epoch_time_start
            self.epoch_time_start = tnow
            self.run_callbacks('on_fit_epoch_end')
            #清空GPU的显存。这个操作可以帮助释放GPU显存,以防止在训练过程中出现内存不足的错误。
            torch.cuda.empty_cache()  # clears GPU vRAM at end of epoch, can help with out of memory errors
            # Early Stopping
            if RANK != -1:  # if DDP training 分布训练中广播停止给其他进程
                broadcast_list = [self.stop if RANK == 0 else None]
                dist.broadcast_object_list(broadcast_list, 0)  # broadcast 'stop' to all ranks
                if RANK != 0: #如果当前进程的RANK不为0,则将广播列表中的第一个对象(即进程0广播的self.stop)赋值
             self.stop = broadcast_list[0]
            if self.stop:
                break  # must break all DDP ranks
        if RANK in (-1, 0):
            # Do final val with best.pt
            LOGGER.info(f'\n{epoch - self.start_epoch + 1} epochs completed in '
                        f'{(time.time() - self.train_time_start) / 3600:.3f} hours.')
            self.final_eval() #2.执行最终的验证(final_eval())操作。
            if self.args.plots:
                self.plot_metrics()
            self.run_callbacks('on_train_end')
        torch.cuda.empty_cache()#清空GPU的显存
        self.run_callbacks('teardown')

1. 初始准备代码讲解

        """Train completed, evaluate and plot if specified by arguments."""
        if world_size > 1: #当前world_size = 1,不采用多卡训练
            self._setup_ddp(world_size) #多卡训练配置

        self._setup_train(world_size)
        self.epoch_time = None
        self.epoch_time_start = time.time()
        self.train_time_start = time.time()
        nb = len(self.train_loader)  # number of batches
        nw = max(round(self.args.warmup_epochs *
                       nb), 100) if self.args.warmup_epochs > 0 else -1  # number of warmup iterations # 预热迭代次数
        last_opt_step = -1
        self.run_callbacks('on_train_start')

1.1 首先检查world_size是否大于1,大于1表示正在使用DDP训练。如果是,它调用_setup_ddp方法来配置DDP设置。

1.2 调用_setup_train方法,该方法实现模型的加载初始化等过程,具体内容参考本系列文章《yolov8逐步分解(3)_trainer训练之模型加载》中的具体介绍。

1.3  接下来初始化一些与时间相关的变量:

        self.epoch_time: 将保存当前训练epoch的持续时间。

        self.epoch_time_start: 记录当前训练epoch的开始时间。

        self.train_time_start: 记录整个训练过程的开始时间。

1.4 训练加载器中的批次数量存储在nb变量中。

1.5 nw变量计算预热迭代的数量,它是以下三个值中的最大值:

        round(self.args.warmup_epochs * nb): 预热epoch数乘以每个epoch的批次数。

        100: 至少100个预热迭代。

        如果self.args.warmup_epochs是0或更小,nw被设置为-1,表示没有预热。

1.6 last_opt_step变量被初始化为-1。

1.7 最后,调用run_callbacks('on_train_start')方法,可能会触发训练开始时注册的任何回调函数。

总的来说,这段代码设置了训练循环的初始状态,为实际的训练迭代做好准备。

        LOGGER.info(f'Image sizes {self.args.imgsz} train, {self.args.imgsz} val\n'
                    f'Using {self.train_loader.num_workers * (world_size or 1)} dataloader workers\n'
                    f"Logging results to {colorstr('bold', self.save_dir)}\n"
                    f'Starting training for {self.epochs} epochs...')
        if self.args.close_mosaic:
            base_idx = (self.epochs - self.args.close_mosaic) * nb
            self.plot_idx.extend([base_idx, base_idx + 1, base_idx + 2])
        epoch = self.epochs  # predefine for resume fully trained model edge cases

2.1 打印一些有关训练过程的信息:

        训练和验证时使用的图像尺寸

        用于数据加载的 worker 数量

        训练结果的保存路径

        训练将运行的 epoch 数

2.2 检查是否设置了 self.args.close_mosaic 参数。如果设置了,它会计算在最后 self.args.close_mosaic 个 epoch 中需要绘制的索引,并将它们添加到 self.plot_idx 列表中。这可能是为了在训练结束时绘制一些特定的图表或可视化效果。

2.3 将 epoch 变量设置为 self.epochs。这可能是为了处理在训练过程中恢复完全训练模型的边缘情况。

        这段代码旨在记录训练过程中的一些关键信息,以便更好地理解和监控模型的训练情况。它还可以为训练结束后的数据分析和可视化提供一些有用的信息。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值