机器视觉 - YoloV8 的一些理解

理解权重和偏置是什么

  • 全连接层: 输出=f(输入*权重+偏置), 权重是这个线性函数的一次性系数, 偏置是常数项.
  • 卷积层: 输出=权重卷积核和输入做卷积运算, 这里的权重是卷积核的各个元素, 卷积核也经常被叫做过滤器filter或kernel. 传统的图像处理, 图像滤波器算子需要人工指定, 比如高斯滤波器, 在深度学习中, 卷积核是通过训练学习求得的.
  • 权重和偏置的调整: 每个batch迭代完成后, 都会通过反向传播来更新权重和偏置, 权重更新由输入和梯度推导得到, 而偏置仅仅由梯度推导,不需要输入项. 更新都是以最小化损失函数为目标的.

理解batch

  • batch size: yolo 每次会从整个数据集中随机选出一个batch的数据进行前向传播训练.
  • 计算此batch上所有样本的损失函数值.
  • 通过损失对各层参数计梯度求导, 计算出权重参数新值
  • 将计算出的更新值应用到模型上, 完成参数的批量更新
  • 重复采样下一个batch, 反复完成前向和后向传播, 直至完成一个全量的epoch
  • 即yolov8采用的训练策略是batch 更新方式.

理解epoch

  • epoch 是训练一个完整数据集的过程, 训练一个epoch后, 会自动完成一次 val, 我们可以观察收敛情况.

特征图数据的变化

  • 输入图像为 640×640×3, 3为RGB通道数.
  • 接着进入卷积层+池化处理.
  • [卷积层+池化]首先通过一个 3×3x3 的卷积核进行卷积操作, 卷积核的通道数也为 3 , 生成一个 640x640x3 的特征图(feature map).
  • [卷积层+池化]然后通过非线性激活函数ReLU处理后, 是的特征图的数值取值范围为0到正无穷大.
  • [卷积层+池化]最后经过MaxPool操作, MaxPool操作相当于 2x2 滤波, 仅保留每个2x2区域中最大值, 特征图尺寸将减半到 320x320.
  • 每次卷积层+池化处理, feature map的尺寸都会减半, 经过此次卷积层+池化处理, 就可以生成 80x80 这样小尺寸的特征图, 另外, yolov8还会生成 40x40 和 20x20 特征图.
  • 最后将 80x80和 40x40 和 20x20 尺寸的特征图输入到head模块预测, 最终输出 26x26的识别矩阵.

卷积核尺寸

不同层通常使用的不同的尺寸的卷积核, 1x1 矩阵可以改变通道数.

  • 底层次卷积层通常使用较大的卷积核 3x3 或 5x5, 用于提取低级特征, 如边缘和线条等.
  • 中层次卷积层通常使用 3x3 或 1x1 小核, 进一步提取中间级别特征
  • 高层次卷积层通常使用 1x1 小核, 用来改变通道数和融合特征.
  • 最大池化层使用固定的 2x2 卷积核, 实现下采样.
  • 上采样层通常使用固定的 2x2 卷积核, 实现上采样.
  • 最后几层通常使用 1x1 小核进行预测.

识别结果矩阵的尺寸

  • 矩阵通常是26x26 或者 13x13
  • 通道数的公式为, 通道数 = cell目标个数x(类别数+1+4+2), 组成分别为:
    . cell目标个数: 是指每个cell最多能预测几个目标, 通常是一个固定值, 比如5或其他.
    . 类别数: 一般设置80个Coco数据集类别
    . 每个目标准确度概率
    . 每个目标坐标预测值, 左上和右下坐标
    . 每个目标的宽高预测值
    . 举例: 每个目标个数为5个, 通道数共计: 435=5x(80+1+4+2), 所以预测结果矩阵为 26x26x435

网络功能模块

  • backbone 网络部分: 负责对输入图像进行特征提取, 输出底层和高层特征图(feature map).
  • neck 网络部分:是将backbone输出的特征图进行融合, 输出集中的特征, 同时可以减少空间尺寸.
  • head 网络: 基于neck输出的特征, 进行最终的检测任务, 通常包括两个head:
    • 分类头 classification head, 预测每个区域中的物体类别
    • 定位头 regression head, 用来预测每个区域的物体坐标或掩码

训练过程中如何进行val和如何结束训练

  • YoloV8 在每个epoch完成后, 会使用val数据集进行验证, 来计算loss和mAP等指标
  • 如果当前epoch的loss比之前最佳的loss还大, 就会增加一个early_stops计数, 如果比最佳loss小, 则early_stops清零. 如果early_stops计数大于阈值, 则进行早停(early_stopping)操作, 即整个训练不需要一定要完整预设的epoch次数.
  • 早停机制是防止过拟合的重要手段.
  • 我们可以通过 yolov8 的 patience 设置early_stops阈值

YoloV8的Fine-tuning和预训练和from scratch训练

yolo命令行model的参数的说明既可以选择 yolov8n.pt, 也可以选择 yolov8n.yaml, 区别是:

  • model=yolov8n.pt, 即为Fine-tuning训练, yolov8n.pt 模型文件已经包含了 yolo v8网络结构、超参数、训练参数、 权重参数信息, 它是官方的pre-trained 模型文件, 官方基于大规模数据集(coco 数据集)的80个类别训练而成.
  • model=yolov8n.yaml pretrained=yolov8n.pt, 这是一个全新模型配置文件 + 预训练模型文件的组合, 训练过程将使用yaml模型配置文件, 包含模型的网络结构, 超参数和训练参数等信息, 但初始权重使用预训练的yolov8n.pt, 这样收敛速度较快, 训练过程比完整的from scratch要会短一些.
  • model=yolov8n.yaml pretrained=False ,这是from scratch 训练, yolov8n.yaml文件包含模型的网络结构, 超参数和训练参数等信息, 我们可以基于这样的模型定义+自定义数据集训练出自己的模型权重文件, 用于后续的预测. 训练耗时最长. 从一个资料中看到, 每个object需要训练2000次, yolov3 基于coco数据集共训练了5万多次, 由此可见from scratch的训练量是非常大的.

yolov8预训练模型是基于coco和imagenet大型数据集做的训练, 所以这样的预训练权重已经非常具备通用性了, 对于99%的情况都适用, 如果完成从0开始做预训练,初始权重太过随机,很难收敛,最终网路训练结果也不会太好.

如果我们修改了网络, 预训练权重基本上不能使用了, 需要从头开始训练网络, 从头训练网络需要有好的算力和大的数据集, 否则做不成.

深度学习通过冻结部分参数提升效率效率

  • 深度学习模型通常包括通用特征提取(卷积+池化)和高层分类(卷积+池化)和结果输出(全连接层)三个阶段.
  • 特征提取是通用特征, 这部分参数通常无需调整
  • 所以, 可通过冻结特征提取参数来减少计算量, 提升fine-tuning的效率, 步骤一般为:
  • (1)先冻结前面几层卷积和池化层, 仅仅更新后面负责高级特征处理的卷积和池化层(统称为later classifier)
  • (2) 最后解冻全连接层, 完成分类任务的微调
cd myEnv\Scripts

# 基于预训练 yolov8n.pt 进行自有数据的training, 换句话说, 是针对自有数据进行模型的 fine-tuning, 训练耗时较短. 
.\yolo task=detect mode=train data=coco8.yaml model=yolov8n.pt  

# 使用 yolov8n.yaml 模型定义文件重新训练, 并使用预训练的 yolov8n.pt 作为初始权重值, 训练耗时较长. 
#   pretrained参数可以设置成bool值, 也可以设置为一个pt文件
.\yolo task=detect mode=train data=coco8.yaml model=yolov8n.yaml pretrained=yolov8n.pt 

# 使用 yolov8n.yaml 模型定义文件进行完全重新训练, 训练耗时最长. 
.\yolo task=detect mode=train data=coco8.yaml model=yolov8n.yaml pretrained=False  

Yolo顶层逻辑伪代码结构

下面是伪代码, 仅用于理解yolo 训练过程的顶层逻辑

import torch
from torch.utils.tensorboard import SummaryWriter
from yolov8 import Darknet

# 初始化模型、优化器等
model = Darknet()
optimizer = SGD(model.params) 

writer = SummaryWriter('logs/yolov8')

for epoch in range(epochs):
    
    ## 每个epoch 的 train 过程
    model.train() #模型切换到训练状态
    for batch_idx, (images, targets) in enumerate(train_loader):
    
       # 前向后向传播
       pred = model(images)
       loss = calc_loss(pred, targets)
       
       optimizer.zero_grad()  
       loss.backward()
       optimizer.step()
       
       # 记录指标
       writer.add_scalar('Loss/train', loss, global_step)
       global_step += 1
    
    
    ## 每个epoch 的 validate 
    model.eval()  #模型切换到val状态
    val_loss = 0
    for images, targets in val_loader:
    
        pred = model(images)
        loss = calc_loss(pred, targets) 
        val_loss += loss
    
    val_loss /= len(val_loader)
    writer.add_scalar('Loss/val', val_loss, global_step)
    
    ## 保存最优模型
    if val_loss < best_loss:
        torch.save(model)

writer.close()
posted @ 2024-01-14 17:57  harrychinese  阅读(1009)  评论(0编辑  收藏  举报