yolov8逐步分解(9)_训练过程之Epoch迭代过程

yolov8逐步分解(1)--默认参数&超参配置文件加载_yolov8 加载yaml配置 预测-CSDN博客

yolov8逐步分解(2)_DetectionTrainer类初始化过程_train' and 'val' are required in all data yamls.-CSDN博客

yolov8逐步分解(3)_trainer训练之模型加载_yolov8 加载模型-CSDN博客

YOLOV8逐步分解(4)_模型的构建过程_m = getattr(torch.nn, m[3:]) if "nn." in m else gl-CSDN博客

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

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

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

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

1. Epoch循环处理过程

        for epoch in range(self.start_epoch, self.epochs):self.epoch = epochself.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 = Falseif 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 = Noneself.optimizer.zero_grad()

这段代码是模型训练循环的一部分。下面详细介绍一下它的功能:

  1. 外层循环遍历训练 epoch,从 self.start_epoch 开始,一直到 self.epochs。这确保了模型会按照指定的训练轮数进行训练。
  2. 在每个 epoch 开始时,它调用 self.run_callbacks('on_train_epoch_start') 来执行注册的回调函数。这可能会触发一些自定义的回调函数,用于在训练过程中执行特定的操作,例如记录指标、调整超参数等
  3. 它使用 self.model.train() 将模型设置为训练模式。确保模型在训练阶段正确地执行前向传播和反向传播。
  4. 如果 RANK 不等于 -1,它会将训练数据加载器的 sampler 的 epoch 设置为当前 epoch。确保在分布式训练中每个进程都能访问到正确的数据。
  5. 它通过枚举训练数据加载器创建一个进度条 (pbar),以便更好地监控训练过程,关于进度条的详细介绍可以参考文章python之tqdm函数使用总结。
  6. 如果当前 epoch 是最后 self.args.close_mosaic 个 epoch,它会禁用训练数据集中的"mosaic"数据增强技术,并重置训练数据加载器的状态。这是为了在训练的最后阶段提高模型的泛化性能
  7. 如果 RANK 是 -1 或 0(即主进程),它会记录进度字符串并创建一个 tqdm 进度条。
  8. 将 self.tloss 设置为 None,并将优化器的梯度重置为零。

     这段代码负责设置训练循环,管理训练数据集,并为下一个训练步骤准备模型和优化器。

2. 每个batch循环处理过程

            for i, batch in pbar:self.run_callbacks('on_train_batch_start')# Warmupni = 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 lr0x['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])

        这段代码是训练循环中的一部分,主要用于在训练的早期阶段进行学习率和动量参数的热身(warm-up)操作。

具体步骤如下:

1. 计算当前迭代的总步数ni,通过将当前批次数i与总批次数nb乘以训练轮次数epoch相加得到。

2. 判断ni是否小于等于热身步数nw,即判断当前迭代是否处于训练的早期阶段。

3. 如果当前迭代处于训练的早期阶段,则进行学习率和动量参数的热身操作。

4. 首先定义热身步数的起止范围xi为[0, nw]。

5. 通过插值计算,根据当前迭代步数ni在范围xi内,将梯度累积的步数self.accumulate进行计算。这里使用np.interp()函数进行线性插值,将ni在xi范围内映射到[1, self.args.nbs / self.batch_size]范围,并将结果四舍五入为整数。

6.对于优化器的每个参数组,根据当前迭代步数ni在范围xi内,进行学习率和动量参数的插值计算。

        对于第一个参数组(索引为0),学习率从热身偏置学习率self.args.warmup_bias_lr逐渐下降到初始学习率x['initial_lr'] * self.lf(epoch)。

        对于其他参数组,学习率从0.0逐渐上升到初始学习率x['initial_lr'] * self.lf(epoch)。

     如果参数组中存在动量('momentum'),则动量参数从热身动量self.args.warmup_momentum逐渐增加到设定的动量参数self.args.momentum。

        这个热身操作的目的是在训练的早期阶段逐渐调整学习率和动量参数,以帮助模型更好地收敛。通过逐步增加学习率和动量参数,可以处理训练初期的不稳定性,并提升模型的训练效果。

                # Forwardwith torch.cuda.amp.autocast(self.amp):batch = self.preprocess_batch(batch)   #归一化 /255self.loss, self.loss_items = self.model(batch) #将预处理后的批次数据输入模型进行前向传播,并获取损失值和其他损失项if RANK != -1:self.loss *= world_sizeself.tloss = (self.tloss * i + self.loss_items) / (i + 1) if self.tloss is not None \else self.loss_items

这段代码是混合精度训练(mixed precision training)(参考文章深度学习之混合精度训练AMP介绍)中的一部分,用于执行前向传播操作和计算损失。

具体步骤如下:

  1. 使用torch.cuda.amp.autocast(self.amp)上下文管理器,开启混合精度训练。在该上下文中,模型的输入和输出将自动转换为半精度(float16),以减少内存使用和计算量。
  2. 对批次数据进行预处理操作,包括归一化(将像素值除以255)等。self.preprocess_batch()函数用于执行这些预处理操作。
  3. 将预处理后的批次数据输入模型进行前向传播,通过调用self.model()函数。同时,该函数返回了损失值self.loss和其他损失项self.loss_items。
  4. 如果当前进程的排名RANK不等于-1(表示当前进程处于分布式训练中),则将损失值乘以总进程数world_size。这是为了在分布式训练中,根据进程数进行损失值的归一化。
  5. 计算平均损失。首先判断是否是第一个批次(self.tloss is None)。如果是第一个批次,则将当前批次的损失项作为初始总损失项。如果不是第一个批次,则将当前批次的损失项加权平均到之前的总损失项中,以得到更新后的总损失项。这样做是为了计算平均损失,以便在训练过程中跟踪模型的整体性能。

        这段代码执行了混合精度训练的前向传播操作,包括输入数据的预处理、模型的前向计算和损失的计算。它还进行了损失的归一化和平均化,以便在训练过程中对模型的性能进行跟踪和监控。

                # Backwardself.scaler.scale(self.loss).backward()# Optimize - https://pytorch.org/docs/master/notes/amp_examples.htmlif ni - last_opt_step >= self.accumulate:self.optimizer_step()  #执行一步优化器的更新,根据之前计算得到的梯度来更新模型的参数。last_opt_step = ni

1. self.scaler.scale(self.loss).backward() 这行代码执行训练过程中的反向传播。self.loss代表计算得到的损失值,self.scaler是torch.cuda.amp.GradScaler的一个实例,用于梯度缩放。

2. 在反向传播之后,代码检查自上次优化器步骤以来的迭代次数(ni)是否大于或等于指定的累积值(self.accumulate)。如果是,则调用self.optimizer_step()函数,使用计算得到的梯度更新模型参数。这个步骤通常用于在应用优化器步骤之前累积多个迭代的梯度。

3. self.optimizer_step()代码如下:

    def optimizer_step(self):"""Perform a single step of the training optimizer with gradient clipping and EMA update."""self.scaler.unscale_(self.optimizer)  # unscale gradientstorch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=10.0)  # clip gradientsself.scaler.step(self.optimizer)self.scaler.update()self.optimizer.zero_grad()if self.ema:self.ema.update(self.model)

这段代码是一个用于训练优化器执行单个步骤的函数。它包括梯度缩放(scaling)、梯度剪裁(gradient clipping)和指数移动平均(exponential moving average)更新等步骤。

具体解释如下:

  1. self.scaler.unscale_(self.optimizer): 这行代码用于反缩放优化器的梯度。在使用混合精度训练时,梯度会进行缩放,此处将其还原为原始值。
  2. torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=10.0): 这行代码用于对梯度进行剪裁,限制其最大范数为10.0。剪裁梯度的目的是防止梯度爆炸问题。
  3. self.scaler.step(self.optimizer): 这行代码用于更新优化器的参数,执行一步优化器的更新操作。
  4. self.scaler.update(): 这行代码用于更新梯度缩放器的状态,以便在下一步训练中再次使用。
  5. self.optimizer.zero_grad(): 这行代码用于清空梯度,准备进行下一步的反向传播计算。
  6. if self.ema: self.ema.update(self.model): 这段代码用于更新指数移动平均模型(EMA)。如果指定了 EMA(exponential moving average),则将当前模型的参数更新到 EMA 模型中,用于模型参数的平滑更新。
                mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G'  # (GB) 计算cuda内存loss_len = self.tloss.shape[0] if len(self.tloss.size()) else 1losses = 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')

这段代码是在训练过程中更新进度条并显示相关信息的部分。下面是代码的解释:

  1. mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G' 这行代码用于计算当前CUDA内存占用情况,并以GB为单位进行格式化显示。如果CUDA可用,它将通过torch.cuda.memory_reserved()获取已分配的内存大小,并将其除以1E9(即10^9)转换为GB。如果CUDA不可用,则显示0GB。
  2. loss_len = self.tloss.shape[0] if len(self.tloss.size()) else 1 这行代码计算损失值的长度。self.tloss是一个张量,通过检查其维度的数量来确定长度。如果维度数量大于0,则长度为self.tloss.shape[0];否则,长度为1。
  3. losses = self.tloss if loss_len > 1 else torch.unsqueeze(self.tloss, 0) 这行代码根据损失值的长度,将self.tloss张量处理为长度为1的张量。如果损失值长度大于1,则losses保持不变;否则,使用torch.unsqueeze()在维度0上添加一个维度,将self.tloss转换为长度为1的张量。
  4. if RANK in (-1, 0): 这个条件判断语句检查当前进程的排名(RANK)是否为-1或0,用于确定只在排名为-1或0的进程上执行下面的代码块。这通常用于在多个进程进行训练时,只有主进程(排名为0)或单进程训练时才执行特定的操作。
  5. pbar.set_description(...) 这行代码用于更新进度条的描述信息。它使用格式化字符串将训练进度、GPU内存占用和损失值等信息展示在进度条中。
  6. self.run_callbacks('on_batch_end') 这行代码在每个训练批次结束时运行回调函数。
  7. if self.args.plots and ni in self.plot_idx: 这个条件语句检查是否需要绘制训练样本的图像。self.args.plots是一个标志,表示是否启用绘图功能,ni是当前迭代的索引,self.plot_idx是包含要绘制图像的迭代索引的集合。
  8. self.run_callbacks('on_train_batch_end') 这行代码在每个训练批次结束时运行回调函数。

至此每个Batch训练过程结束。

继续epoch循环剩余部分讲解。

            self.lr = {f'lr/pg{ir}': x['lr'] for ir, x in enumerate(self.optimizer.param_groups)}  # for loggersself.scheduler.step()self.run_callbacks('on_train_epoch_end')if RANK in (-1, 0):# Validationself.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 modelif self.args.save or (epoch + 1 == self.epochs): #判断是否需要保存模型self.save_model()self.run_callbacks('on_model_save')

这段代码是Epoch训练过程中batch循环结束后的一部分代码,主要包括以下内容:

1. self.lr = {f'lr/pg{ir}': x['lr'] for ir, x in enumerate(self.optimizer.param_groups)} 这行代码用于将优化器中每个参数组的学习率(lr)存储在字典self.lr中,以便后续日志记录使用。

2. self.scheduler.step() 这行代码用于更新学习率调度器(scheduler)的状态。学习率调度器根据预定义的策略在训练过程中调整学习率。

3. self.run_callbacks('on_train_epoch_end') 这行代码在每个训练轮次结束时运行回调函数。

  1. if RANK in (-1, 0): 这个条件语句检查当前进程的排名(RANK)是否为-1或0,用于确定只在排名为-1或0的进程上执行下面的代码块。
  2. self.ema.update_attr(self.model, include=['yaml', 'nc', 'args', 'names', 'stride', 'class_weights']) 这行代码使用指数移动平均(EMA)更新模型的属性。EMA可以用于在训练过程中保持模型参数的平滑版本,以提升模型的泛化能力。

6. final_epoch = (epoch + 1 == self.epochs) or self.stopper.possible_stop 这行代码判断是否为最后一个训练轮次或训练可能停止。epoch表示当前轮次,self.epochs表示总轮次,self.stopper.possible_stop表示训练是否可能停止的标志。

7. if self.args.val or final_epoch: 这个条件语句检查是否需要进行验证(validation)。self.args.val是一个标志,表示是否启用验证,final_epoch表示是否为最后一个训练轮次或训练可能停止。

8. self.metrics, self.fitness = self.validate() 这行代码用于进行验证,并将验证结果的指标(metrics)和适应度(fitness)存储在相应的变量中。

9. self.save_metrics(metrics={**self.label_loss_items(self.tloss), **self.metrics, **self.lr}) 这行代码用于保存训练过程中的指标(metrics),包括损失值(losses)、验证指标(metrics)和学习率(lr)。

10. self.stop = self.stopper(epoch + 1, self.fitness) 这行代码根据当前轮次和适应度值判断是否应该停止训练。self.stopper是一个停止条件判断器对象,根据特定的条件判断是否应该停止训练。

11. if self.args.save or (epoch + 1 == self.epochs): 这个条件语句检查是否需要保存模型。self.args.save是一个标志,表示是否启用模型保存,(epoch + 1 == self.epochs)表示是否为最后一个训练轮次。

12. self.save_model() 这行代码用于保存模型到硬盘。使用torch.save保存模型及其参数,保存内容如下:

13.self.run_callbacks('on_model_save') 这行代码在保存模型后运行回调函数。

            tnow = time.time()self.epoch_time = tnow - self.epoch_time_startself.epoch_time_start = tnowself.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 Stoppingif 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 ranksif RANK != 0: #如果当前进程的RANK不为0,则将广播列表中的第一个对象(即进程0广播self.stop)                    
self.stop = broadcast_list[0]if self.stop:break  # must break all DDP ranks

这段代码是Epoch训练过程中最后一部分代码,主要包括以下内容:

  1. tnow = time.time() 这行代码用于获取当前时间。
  2. self.epoch_time = tnow - self.epoch_time_start 这行代码计算当前轮次的训练时间,通过当前时间减去上一个轮次开始的时间。
  3. self.epoch_time_start = tnow 这行代码更新当前轮次开始的时间为当前时间,为下一轮次的计时做准备。
  4. self.run_callbacks('on_fit_epoch_end') 这行代码在每个训练轮次结束时运行回调函数。
  5. torch.cuda.empty_cache() 这行代码用于清空GPU显存,以防止在训练过程中出现内存不足的错误。这个操作可以释放GPU显存中的临时变量和不再使用的内存。
  6. if RANK != -1: 这个条件语句检查当前进程的排名(RANK)是否不为-1,用于判断是否进行分布式训练。
  7. broadcast_list = [self.stop if RANK == 0 else None] 这行代码创建了一个广播列表,其中包含了停止训练的标志(self.stop)。如果当前进程的排名为0(即主进程),则将self.stop添加到广播列表中;否则,将列表中的元素设为None。
  8. dist.broadcast_object_list(broadcast_list, 0) 这行代码将广播列表中的对象(包括停止训练的标志)广播给所有进程。0表示广播的根进程,即主进程。
  9. if RANK != 0: 这个条件语句检查当前进程的排名是否不为0,即非主进程。
  10. self.stop = broadcast_list[0] 这行代码将广播列表中的第一个对象(即进程0广播的self.stop)赋值给当前进程的self.stop变量。
  11. if self.stop: 这个条件语句检查self.stop是否为True,即是否需要停止训练。
  12. break 如果需要停止训练,则使用break语句跳出当前的训练循环,结束训练过程。

        这段代码主要涉及训练时间的计算、回调函数的执行、GPU显存的清空以及分布式训练中的停止信号广播和接收。

            if RANK in (-1, 0):# Do final val with best.ptLOGGER.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')

上述代码是train代码的最后部分代码:
1. if RANK in (-1, 0):这个代码块的目的是在变量 "RANK" 的值为 -1 或 0 时执行特定的操作。这些操作包括打印日志信息、执行最终评估(final_eval())、绘制指标图表(如果指定了相关参数)、运行回调函数、清空 GPU 内存以及运行拆卸回调函数。

2.LOGGER.info() 函数用于记录信息日志。在这段代码中,它用于记录完成的轮次数、训练所花费的时间(以小时为单位),以及其他在字符串格式中指定的相关信息。

3. final_eval() 函数的目的是执行模型的最终评估。它在完成一定数量的轮次后被调用,通常用于在单独的验证数据集上评估模型的性能。

4.条件 "if self.args.plots:" 检查变量 "plots" 是否为真或非空值。如果条件为真,表示启用了指标图表的绘制,代码将继续执行 self.plot_metrics() 函数。

5.torch.cuda.empty_cache() 函数用于清空 GPU 的显存。它释放临时变量和不再使用的内存,帮助防止训练过程中出现内存不足的错误。

6.回调函数是在训练过程中特定时间点执行的函数,用于执行额外的操作或动作。在这段代码中,回调函数在每个轮次结束时执行("on_fit_epoch_end"),以及在训练过程结束时执行("on_train_end")。回调函数的具体操作取决于其实现,根据训练过程或模型的需求而有所不同。

 至此,_do_train()循环过程逐步解析完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/22246.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP 建链(三次握手)和断链(四次握手)

TCP 建链&#xff08;三次握手&#xff09;和断链&#xff08;四次挥手&#xff09; 背景简介建链&#xff08;三次握手&#xff09;断链&#xff08;四次挥手&#xff09;序号及标志位延伸问题为什么建立连接需要握手三次&#xff0c;两次行不行&#xff1f;三次握手可以携带数…

智领未来,安全无忧:智能网联汽车监控大屏的守护之旅

在繁忙的都市中&#xff0c;驾驶者往往面临着诸多安全隐患。传统的驾驶辅助系统虽然能够提供一定的帮助&#xff0c;但在复杂多变的交通环境中&#xff0c;其局限性也逐渐显现。而智能网联汽车安全监控大屏&#xff0c;正是为了解决这一问题而诞生的。 山海鲸可视化大屏 大屏采…

基础篇04——多表查询

多表关系 一对多 多对多 多对多是通过中间表实现的 -- 创建学生表 create table student (id int auto_increment primary key comment ID,name varchar(10) comment 姓名,no varchar(3) comment 学号 ) comment 学生表;insert into student values (null, 黛绮丝, 001),(…

SLAM中四元数、流形、李群、李代数是啥?

知识点得逻辑关系如下 引言 非线性问题由于复杂的数学结构&#xff0c;多样的解空间&#xff0c;局部极值等问题求解难度大大增加。所以在求解时需要把非线性问题转化为更容易处理的形式&#xff0c;例如 数值优化方法&#xff1a;数值优化方法包括梯度下降、共轭梯度法、牛顿…

Spring boot实现基于注解的aop面向切面编程

Spring boot实现基于注解的aop面向切面编程 背景 从最开始使用Spring&#xff0c;AOP和IOC的理念就深入我心。正好&#xff0c;我需要写一个基于注解的AOP&#xff0c;被这个注解修饰的参数和属性&#xff0c;就会被拿到参数并校验参数。 一&#xff0c;引入依赖 当前sprin…

寒武纪:“国产平替”道阻且长

英伟达的一季报反映了AI装备竞赛白热化&#xff0c;科技巨头们正疯狂加码投资。 不过&#xff0c;因为众所周知的原因,英伟达最先进的产品卖不到国内&#xff0c;虽然说一定程度上限制了咱们AI的发展&#xff0c;但也给国产AI芯片公司们提供了机会&#xff0c;其中就包括我们今…

K8S==ingress简单搭建和使用

基础环境 D:\DOCKER_REPO\K8S>kubectl version Client Version: v1.29.2 Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3 Server Version: v1.29.2 D:\DOCKER_REPO\K8S>kubectl get nodes NAME STATUS ROLES AGE VERSION docker-…

01_深度学习基础知识

1. 感知机 感知机通常情况下指单层的人工神经网络,其结构与 MP 模型类似(按照生物神经元的结构和工作原理造出来的一个抽象和简化了模型,也称为神经网络的一个处理单元) 假设由一个 n 维的单层感知机,则: x 1 x_1 x1​ 至 x n x_n xn​ 为 n 维输入向量的各个分量w 1 j…

【大学物理】Interference,diffraction,polarization:光学

nature of light definition speed of light reflection dispersion huygenss principle:惠更斯原理

OBD诊断协议

上周领导需要做个OBD相关的功能&#xff0c;我对OBD没有啥概念&#xff0c;于是周末就了解下这到底是个啥东西。了解过后发现很简单&#xff0c;其实就是个UDS协议的简化版&#xff0c;OBD是英文On-Board Diagnostics的缩写&#xff0c;中文翻译为“车载自动诊断系统”&#xf…

链表反转--理解链表指针的基本操作

链表反转--理解链表指针的基本操作 链表反转的方法--主要是理解链表指针链表心得类节点是对象和指针区别&#xff1a; 链表反转的方法–主要是理解链表指针 根据值创建新列表 用一个链表指针代替整个新链表 两个链表的赋值 递归求解反向链表 用一个链表代替前后链表数…

Qt应用程序发布

一、静态编译发布 1.0:以Release模式构建工程 1.1:查看当前构建生成路径,并将所生成的.exe单独拷贝出来 1.2:将可执行文件*.exe拷贝至任一目标文件夹:D:\Temporary\QQIF 2:查看安装Qt时发布工具windeployqt.exe所在的目录 windeployqt.exe在Qt开发套件的bin目录下。Qt的每…

构建高效稳定的短视频直播系统架构

随着短视频直播的迅猛发展&#xff0c;构建一个高效稳定的短视频直播系统架构成为了互联网企业的重要挑战。本文将探讨如何构建高效稳定的短视频直播系统架构&#xff0c;以提供优质的用户体验和满足日益增长的用户需求。 ### 1. 短视频直播系统的背景 短视频直播近年来蓬勃发…

Pycharm 添加内容根

解决问题&#xff1a;包未能被正常引入时

try…except…finally语句

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 完整的异常处理语句应该包含finally代码块&#xff0c;通常情况下&#xff0c;无论程序中有无异常产生&#xff0c;finally代码块中的代码都会被执行…

2024 年该如何利用 MidJourney 创作AI艺术(详细教程)

什么是 Midjourney Midjourney 是根据文本提示创建图像的生成式人工智能的优秀范例。与 Dall-E 和 Stable Diffusion 一样&#xff0c;它已成为最受欢迎的人工智能艺术创作工具之一。与竞争对手不同的是&#xff0c;Midjourney 是自筹资金和封闭源代码的&#xff0c;因此对它的…

【WRF理论第二期】模型目录介绍

WRF理论第二期&#xff1a;模型目录介绍 1 WRF主目录2 WPS主目录3 编译后的可执行文件4 运行目录参考 了解 WRF 模型的目录结构有助于有效地管理和操作模型&#xff0c;从而确保模拟和分析工作的顺利进行。以下分解介绍WRF主目录、WPS主目录等。 Github-wrf-model/WRF 1 WRF…

leetCode-hot100-二分查找专题

二分查找 简介原理分析易错点分析例题33.搜索旋转排序数组34.在排序数组中查找元素的第一个和最后一个位置35.搜索插入位置240.搜索二维矩阵 Ⅱ 简介 二分查找&#xff0c;是指在有序&#xff08;升序/降序&#xff09;数组查找符合条件的元素&#xff0c;或者确定某个区间左右…

找不到steam_api64.dll,无法继续执行的原因及解决方法

电脑已经成为我们生活中不可或缺的一部分。然而&#xff0c;在使用电脑的过程中&#xff0c;我们经常会遇到一些常见的问题&#xff0c;其中之一就是找不到某个特定的动态链接库文件&#xff0c;比如steamapi64.dll。这个问题可能会导致某些应用程序无法正常运行&#xff0c;给…

关于博图17安装体验过程—博图17安装失败原因(STEP7 许可证找不到)

目录 一、序言 二、正片 一、序言 该失败原因是在我使用Win11专业版安装博图17时出现的问题&#xff0c;也仅代表我的体验过程&#xff01;以下我将安装过程和解决问题的过程描述一下&#xff0c;希望可以帮助和我一样自己安装博图时能够解决出现的问题。 二、正片 如果阁下…