一、怎么解决图片输入尺度不统一的问题
YOLOv7的矩形训练是指在训练时对输入图片进行尺寸调整,以提高模型处理长宽比差异较大的图片时的性能,同时避免过多的图像变形。具体来说,以下是矩形训练的处理过程:
1. 矩形训练的核心目标
- 目标:尽量保留图片的原始长宽比例,减少因为强制拉伸到正方形输入尺度(如640×640)而引起的失真。
- 方法:将图片按照短边进行缩放,同时保留长宽比例,然后在未填满的部分补零。
2. 小于输入尺度(如640×640)的图片
- 调整尺寸:根据图片的原始比例,将短边调整到目标尺寸(如640)。
- 比如,图片尺寸为300×500,调整后尺寸将变为384×640(保持比例,长边填满640)。
- 填充:将缩放后的图片用零(黑色像素)填充到640×640的输入尺寸。
- 填充分布在未被填充的一边,左右或上下对称填充。
3. 大于输入尺度(如640×640)的图片
- 调整尺寸:根据图片的原始比例,将图片等比例缩小到短边等于目标尺寸(如640)。
- 比如,图片尺寸为1200×800,调整后尺寸变为640×426。
- 填充:缩小后,同样对未被填满的部分(上下或左右)补零,使得最终图片尺寸达到640×640。
4. 细节说明
- 零填充的作用:
- 零填充不改变目标物体的位置分布,仅扩展背景部分。
- 确保了最终图片符合模型输入的固定尺寸需求(如640×640)。
- 大图与小图的处理一致:
- 无论原图是大于还是小于目标尺寸,都通过缩放和填充统一处理为640×640的大小。
- 模型训练效果:
- 矩形训练能够避免拉伸图片带来的形变。
- 提升模型对长宽比差异图片的鲁棒性,尤其适用于目标检测场景中目标的比例变化较大的数据集。
5. 实际应用中的注意点
- 填充的数值:通常为零,但在某些数据增强方法(如Mosaic训练)中,也可能填充随机颜色。
- 计算损失时的掩码处理:因为填充区域不包含真实信息,需要额外处理,以避免填充部分干扰模型的学习。
二、超参数解读
lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3)
lrf: 0.1 # final OneCycleLR learning rate (lr0 * lrf)
momentum: 0.937 # SGD momentum/Adam beta1
weight_decay: 0.0005 # optimizer weight decay 5e-4
warmup_epochs: 3.0 # warmup epochs (fractions ok)
warmup_momentum: 0.8 # warmup initial momentum
warmup_bias_lr: 0.1 # warmup initial bias lr
box: 0.05 # box loss gain
cls: 0.3 # cls loss gain
cls_pw: 1.0 # cls BCELoss positive_weight
obj: 0.7 # obj loss gain (scale with pixels)
obj_pw: 1.0 # obj BCELoss positive_weight
iou_t: 0.20 # IoU training threshold
anchor_t: 4.0 # anchor-multiple threshold
# anchors: 3 # anchors per output layer (0 to ignore)
fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5)
hsv_h: 0.015 # image HSV-Hue augmentation (fraction)
hsv_s: 0.7 # image HSV-Saturation augmentation (fraction)
hsv_v: 0.4 # image HSV-Value augmentation (fraction)
degrees: 0.0 # image rotation (+/- deg)
translate: 0.2 # image translation (+/- fraction)
scale: 0.9 # image scale (+/- gain)
shear: 0.0 # image shear (+/- deg)
perspective: 0.0 # image perspective (+/- fraction), range 0-0.001
flipud: 0.0 # image flip up-down (probability)
fliplr: 0.5 # image flip left-right (probability)
mosaic: 1.0 # image mosaic (probability)
mixup: 0.15 # image mixup (probability)
copy_paste: 0.0 # image copy paste (probability)
paste_in: 0.15 # image copy paste (probability), use 0 for faster training
loss_ota: 1 # use ComputeLossOTA, use 0 for faster training
这些超参数是YOLOv7的配置文件中用于训练过程的参数设置,它们分别控制优化器、损失函数、数据增强以及一些模型行为。以下是对每个超参数的详细解释:
1. 优化器相关参数
参数 | 说明 |
---|---|
lr0 | 初始学习率。默认值为SGD优化器的1E-2或Adam优化器的1E-3。学习率决定权重更新的步长。 |
lrf | 最终学习率占初始学习率的比例,用于OneCycleLR 学习率调度。例如,最终学习率 = lr0 × lrf。 |
momentum | 动量,用于SGD优化器(或者Adam优化器中的beta1)。可以加速收敛并减少震荡,典型值为0.9-0.99。 |
weight_decay | 权重衰减,用于防止过拟合,类似于L2正则化,控制模型复杂度。典型值为1e-4~5e-4。 |
warmup_epochs | 热身阶段的训练轮数,在此阶段内学习率逐渐从0增大到lr0 ,用于稳定训练初期的梯度波动。 |
warmup_momentum | 热身阶段初始动量,逐渐增加到momentum ,防止初始学习过快导致的不稳定。 |
warmup_bias_lr | 热身阶段初始bias参数的学习率。因为bias的梯度不同于权重,可能需要单独的学习率调节。 |
2. 损失函数相关参数
参数 | 说明 |
---|---|
box | 用于边界框(Box)回归损失的权重增益。控制模型对边界框预测损失的关注程度。 |
cls | 用于分类损失(BCE)权重增益。控制模型对类别预测损失的关注程度。 |
cls_pw | 分类损失中正样本的权重因子(positive_weight),用于平衡正负样本的比例。 |
obj | 用于目标置信度(Objectness)损失的权重增益。 |
obj_pw | 目标置信度损失中正样本的权重因子。 |
iou_t | IoU训练阈值,决定正负样本的划分。例如,当预测框与真实框的IoU大于该阈值时,判定为正样本。 |
anchor_t | 锚框与目标框大小比例的阈值,用于选择合适的锚框。典型值为4.0,表示目标框大小需与锚框相差不大。 |
fl_gamma | 焦点损失(Focal Loss)的gamma值,控制对难分类样本的关注程度。值越大,对难样本的关注越高。 |
3. 数据增强相关参数
参数 | 说明 |
---|---|
hsv_h | 图像HSV颜色空间的色调(Hue)变化范围(比例)。增强训练集多样性,值越大色调变化越明显。 |
hsv_s | 图像HSV颜色空间的饱和度(Saturation)变化范围(比例)。 |
hsv_v | 图像HSV颜色空间的亮度(Value)变化范围(比例)。 |
degrees | 图像旋转角度范围(±deg)。用于数据增强。 |
translate | 图像平移范围(比例)。控制目标在图像中的位置变化。 |
scale | 图像缩放比例范围(±gain)。控制目标大小变化。 |
shear | 图像剪切角度范围(±deg)。用于模拟透视变化。 |
perspective | 图像透视变换的强度范围(比例),一般为小值(如0~0.001)。 |
flipud | 上下翻转图像的概率。用于增强目标上下变化的数据集。 |
fliplr | 左右翻转图像的概率。用于增强目标左右变化的数据集。 |
mosaic | Mosaic数据增强的概率。Mosaic是一种将4张图像拼接到一起的增强方法。 |
mixup | MixUp数据增强的概率。MixUp是一种将两张图像按比例混合的增强方法。 |
copy_paste | Copy-Paste增强方法的概率。将目标从一张图片复制并粘贴到另一张图片中,增强目标多样性。 |
paste_in | Copy-Paste增强的粘贴概率,用于控制粘贴频率,为0时禁用此增强方法以加速训练。 |
4. 模型训练行为相关参数
参数 | 说明 |
---|---|
loss_ota | 是否使用OTA(Optimal Transport Assignment)作为目标匹配方法。1表示使用,0表示禁用。 |
实际应用示例
- 快速训练:
- 降低
warmup_epochs
(如设置为1.0)。 - 禁用复杂的数据增强(如
mosaic=0
,mixup=0
,copy_paste=0
)。
- 降低
- 处理高分辨率目标或小目标:
- 增大
iou_t
(如设置为0.25)以确保更多小目标参与训练。 - 调整
scale
和translate
,避免目标被削减或超出边界。
- 增大
- 复杂数据增强:
- 启用
mosaic=1.0
,mixup=0.15
,copy_paste=0.1
,以增强训练集多样性。
- 启用
三、命令行参数解读
weights: yolov7.pt
cfg: cfg/training/yolov7.yaml
data: data/neu.yaml
hyp: data/hyp.scratch.p5.yaml
epochs: 300
batch_size: 16
img_size:
- 640
- 640
rect: false
resume: false
nosave: false
notest: false
noautoanchor: false
evolve: false
bucket: ''
cache_images: false
image_weights: false
device: cpu
multi_scale: false
single_cls: false
adam: false
sync_bn: false
local_rank: -1
workers: 8
project: runs/train
entity: null
name: exp
exist_ok: false
quad: false
linear_lr: false
label_smoothing: 0.0
upload_dataset: false
bbox_interval: -1
save_period: -1
artifact_alias: latest
freeze:
- 0
world_size: 1
global_rank: -1
save_dir: runs\train\exp26
total_batch_size: 16
这些参数是YOLOv7训练配置中的关键选项,用于控制模型训练的各个方面。以下是对每个参数的详细解释:
1. 基本参数
参数 | 含义 |
---|---|
weights | 预训练权重的路径,例如yolov7.pt 表示使用YOLOv7的预训练模型权重。 |
cfg | 模型配置文件的路径,定义网络结构,例如cfg/training/yolov7.yaml 。 |
data | 数据集配置文件路径,包含训练和测试集的路径及类别信息,例如data/neu.yaml 。 |
hyp | 超参数配置文件路径,定义优化器参数、损失权重、数据增强等内容,例如data/hyp.scratch.p5.yaml 。 |
epochs | 训练的总轮数,例如300轮。 |
batch_size | 每个训练步骤处理的图片数量,较大时需要更多的显存。 |
img_size | 输入图片的大小,通常是一个列表[宽, 高] ,如[640, 640] 表示图片尺寸为640x640。 |
2. 特定训练行为
参数 | 含义 |
---|---|
rect | 是否使用矩形训练模式。True 表示根据图片的实际宽高比调整大小,False 表示所有图片缩放到同一尺寸。 |
resume | 是否从之前的检查点恢复训练。True 表示恢复,False 表示从头开始训练。 |
nosave | 是否保存模型权重。True 表示不保存,False 表示保存每个 epoch 的模型权重。 |
notest | 是否在每个 epoch 结束后测试模型性能。True 表示不测试,False 表示测试。 |
noautoanchor | 是否禁用自动调整锚框。True 表示禁用,False 表示根据数据自动调整锚框以提高检测效果。 |
evolve | 是否通过遗传算法优化超参数。True 表示启用超参数进化,False 表示使用固定的超参数。 |
3. 数据与设备设置
参数 | 含义 |
---|---|
bucket | 数据集所在云存储桶的路径,默认为空字符串(即本地数据集)。 |
cache_images | 是否将图片缓存到内存中以加快训练速度。True 表示启用,False 表示不启用。 |
image_weights | 是否根据样本权重选择图片进行训练(通常用于类别不平衡的数据集)。 |
device | 指定训练设备,例如cpu 或cuda:0 。 |
multi_scale | 是否启用多尺度训练。True 表示每个 batch 随机调整输入图片大小,以增强模型的鲁棒性。 |
single_cls | 是否将所有类别视为单一类别(用于只有一个目标类别的数据集)。 |
adam | 是否使用 Adam 优化器。True 表示使用,False 表示使用 SGD 优化器。 |
sync_bn | 是否在多卡训练时同步批归一化(Batch Normalization)。True 表示启用,仅在多 GPU 训练时有效。 |
workers | 数据加载的线程数。更多线程可以加速数据预处理,但会消耗更多的 CPU 资源。 |
4. 结果保存
参数 | 含义 |
---|---|
project | 保存训练结果的目录路径,例如runs/train 。 |
name | 本次实验的名称,例如exp 。训练结果会保存在project/name 路径下。 |
exist_ok | 是否覆盖已有实验目录。True 表示覆盖,False 表示新建目录防止覆盖已有实验结果。 |
save_dir | 实验结果保存的具体目录路径,例如runs/train/exp26 。 |
save_period | 保存模型权重的间隔 epoch 数。例如设置为 10 表示每 10 个 epoch 保存一次权重。 |
5. 高级选项
参数 | 含义 |
---|---|
quad | 是否使用四值训练(Quad Precision)。True 表示启用,仅在支持硬件上有效。 |
linear_lr | 是否使用线性学习率调度。True 表示启用,False 表示使用默认的OneCycleLR 。 |
label_smoothing | 标签平滑的系数,用于避免过拟合。例如 0.1 表示将类别标签平滑为非 0/1 的值(如 0.9 和 0.1)。 |
bbox_interval | 保存边界框可视化图像的间隔(以 epoch 为单位)。-1 表示不保存。 |
artifact_alias | 模型保存时的别名,用于标识当前实验结果,默认为latest 。 |
6. 分布式训练与冻结层
参数 | 含义 |
---|---|
freeze | 冻结的层数,用于微调模型。例如 [0] 表示冻结 backbone 的第一层。 |
local_rank | 当前进程在分布式训练中的本地 ID,用于分布式数据并行。 |
world_size | 总进程数,用于分布式训练。默认为 1,即单机训练。 |
global_rank | 当前进程的全局 ID,用于分布式训练中的进程通信。 |
7. 综合说明
这组参数的目的是为训练过程提供高度灵活的配置,既能满足简单任务的需要,又支持复杂的分布式训练和高性能优化。在实际训练中:
- 基础训练任务:仅需设置
weights
、cfg
、data
、hyp
、epochs
等核心参数。 - 高阶优化:可调整
adam
、evolve
、label_smoothing
、multi_scale
等参数。 - 分布式训练:需要配置
local_rank
、world_size
等分布式相关参数。
四、小知识点
1、rank
在分布式训练环境中,rank
通常用来标识每个进程的唯一性。具体而言:
rank = 0
:- 这个值通常表示主进程(即主节点或主 GPU),负责协调训练过程中的其他进程。
- 主进程通常会处理以下任务:
- 打印日志信息。
- 记录训练结果和模型权重。
- 进行可视化和监控(例如使用 TensorBoard 或 Weights & Biases)。
rank = -1
:- 这个值通常表示单机训练或非分布式训练。也就是说,训练没有在多个 GPU 之间分配,而是在单个设备上运行。
- 在这种情况下,代码可能会以主进程的方式运行,处理所有训练任务。
特殊性
- 这两个值的特殊性在于它们帮助程序区分哪些操作需要在主进程上执行,例如日志记录和模型保存。这样可以避免在多进程环境下重复执行相同的操作(比如每个进程都试图记录日志)。
- 通过检查
rank
的值,代码能够更加灵活地处理分布式训练和单机训练的不同场景,为不同的训练配置做出相应的处理。
总结来说,rank
的不同数值(如 -1
和 0
)用于区分训练过程中的角色和任务,从而优化训练流程,提高效率。
2、W&B 日志
如果启用了 W&B 日志,并且提供了之前训练的权重文件以及 W&B 的运行 ID,代码确实可能会使用上次训练结束时的某些参数。具体行为取决于是否是从中断的训练恢复以及训练脚本如何设计。以下是详细解释:
2.1、恢复训练的逻辑
-
权重文件的作用
- 当提供权重文件时,代码会尝试从权重文件中加载模型参数,同时可能加载其他附加信息,例如:
- 模型状态(如网络权重)。
- 优化器状态(如学习率、动量)。
- 训练状态(如当前轮次、已用的学习率调度器参数)。
- W&B 的运行 ID(如
wandb_id
)。
示例代码中:
run_id = torch.load(weights, map_location=device).get('wandb_id') if weights.endswith('.pt') and os.path.isfile(weights) else None
- 如果权重文件中包含
wandb_id
,则提取出来,用于恢复 W&B 运行。
- 当提供权重文件时,代码会尝试从权重文件中加载模型参数,同时可能加载其他附加信息,例如:
-
W&B 的作用
-
==W&B 平台不仅记录了超参数和训练指标,还可以在恢复训练时动态调整训练配置。==例如:
- 恢复上次运行的训练超参数(如学习率、批量大小)。
- 调整训练轮数,使新训练从中断的地方继续。
-
示例代码中:
weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp # WandbLogger might update weights, epochs if resuming
- 这部分逻辑允许 W&B 在恢复训练时更新权重路径、总训练轮数和超参数。
-
- 2、是否会使用上次的参数
- 是的,如果是恢复训练:
- 如果启用了 W&B 且提供了权重文件,代码可能会使用权重文件中保存的优化器状态、学习率、动量等。
- W&B 的运行 ID 还可能让训练自动恢复中断点(如继续从上次中断的轮次训练)。
- 此外,超参数
hyp
和权重weights
也可能被动态更新为上次的值。
- 否,如果是从头开始训练:
- 如果权重文件不存在,或者权重文件中没有
wandb_id
,训练不会从之前的状态恢复。 - 在这种情况下,训练将根据当前提供的超参数和配置文件从头开始。
- 如果权重文件不存在,或者权重文件中没有
- 3、关键点
- 是否使用上次训练的参数 取决于以下条件:
- 是否提供了有效的权重文件。
- 权重文件中是否包含
wandb_id
或其他恢复所需的信息。 - 是否启用了 W&B,并且允许恢复运行。
- 如果希望明确从头开始训练,可以:
- 禁用 W&B(通过设置
wandb: None
或者命令行参数关闭)。 - 不提供权重文件,或者提供的是没有附加状态的预训练权重。
- 禁用 W&B(通过设置
- 4、实际应用中的恢复训练
场景 1:从中断点恢复
- 权重文件保存了上次训练结束时的状态(模型权重、优化器状态、W&B ID 等)。
- 训练从中断的轮次继续,并沿用之前的学习率、动量等。
场景 2:从头开始训练
- 即使启用了 W&B,但不提供权重文件或运行 ID,训练会从头开始。
- 此时,W&B 仍会记录新的训练过程,但不会恢复旧的参数。
场景 3:加载预训练权重但重新训练
- 如果权重文件中没有附加状态(如优化器状态),则只加载网络权重,训练其他参数(如学习率)会根据当前配置重新初始化。
总结
启用 W&B 日志是否会使用上次训练的参数,取决于是否提供了权重文件,以及权重文件中是否包含恢复训练所需的信息。如果是明确恢复训练(提供了 wandb_id
和权重),代码会自动使用上次训练的参数,并从中断的地方继续训练;如果从头开始训练,则不会使用上次的参数。
3、优化器
优化器(Optimizer) - 知乎
优化器是深度学习中用于调整模型参数(权重和偏置)的算法。它通过最小化损失函数,帮助模型更好地拟合数据。优化器的选择会显著影响模型的收敛速度、稳定性和最终的效果。
优化器的作用
- 梯度更新: 根据损失函数对参数计算的梯度,优化器决定如何调整参数。
- 学习率控制: 控制每次参数更新的幅度。
- 动态调整: 一些优化器可以根据梯度大小、自适应调整学习率。
SGD 与 Adam 的对比
特性 | SGD(随机梯度下降) | Adam(自适应矩估量方法) |
---|---|---|
基本原理 | 使用每个参数的梯度直接更新参数。 | 同时计算梯度的均值和方差,结合使用。 |
学习率调整 | 固定学习率或手动调整。 | 自适应调整学习率(每个参数独立)。 |
收敛速度 | 通常较慢,需要仔细调整学习率。 | 收敛速度较快,对学习率不敏感。 |
全局优化能力 | 容易陷入局部最优。 | 能较好地避免局部最优。 |
对噪声的处理能力 | 对噪声敏感,更新方向可能不稳定。 | 对噪声鲁棒,更新更平滑稳定。 |
计算复杂度 | 低,简单易实现。 | 较高,需要更多计算资源。 |
适用场景 | 数据量较小,简单模型训练。 | 复杂模型、大规模数据训练。 |
其他常用优化器
优化器 | 特性与优势 | 适用场景 |
---|---|---|
Momentum | 在 SGD 基础上加入动量,使更新更平滑,能越过局部最优点。 | 需要平滑梯度的场景。 |
RMSProp | 自适应调整学习率,特别适合处理非平稳目标。 | 序列数据或 RNN 训练。 |
Adagrad | 学习率自适应调整,对稀疏特征表现优秀。 | 文本、稀疏数据任务。 |
AdaDelta | 对 Adagrad 的改进,解决其学习率下降过快的问题。 | 稳定的参数更新需求。 |
AdamW | Adam 的变体,加入了权重衰减(weight decay)处理,适合正则化要求高的任务。 | Transformer、BERT 等任务。 |
Nadam | Adam 和 Nesterov 动量的结合,提高了收敛速度。 | 需要快速优化的大模型。 |
如何选择优化器?
- 简单任务:
- 小数据集、简单模型:可以使用 SGD 或 SGD+Momentum。
- 复杂任务:
- 深度网络、大数据量:推荐 Adam 或 AdamW。
- 稀疏特征:
- 使用 Adagrad 或 RMSProp。
- 序列数据(如 RNN):
- 使用 RMSProp 或 Adam。
实际工作中的应用
- 深度网络训练: Adam 是首选,因为它收敛速度快,对超参数较为鲁棒。
- 对精度要求高的任务: SGD(+Momentum)更适合,尽管训练慢,但可能带来更高的精度。
- 大模型(如 BERT、Transformer): AdamW 表现优秀,因为它结合了权重衰减机制。
4、一阶动量与二阶动量
一阶动量和二阶动量的概念
在优化算法中,一阶动量和二阶动量是用来描述梯度变化的重要统计信息,目的是通过历史梯度信息提高模型优化的效率和稳定性。
一阶动量(First-order Moment)
定义
一阶动量是梯度值的指数加权移动平均(Exponential Moving Average, EMA),反映了梯度在优化过程中的方向和趋势。数学表达式:
m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t mt=β1mt−1+(1−β1)gt
- m t m_t mt:当前时刻的梯度一阶动量(类似于当前梯度的“平滑版本”)。
- g t g_t gt:当前时刻的梯度。
- β 1 \beta_1 β1:动量衰减系数,通常取 0.9。
直观理解
- 一阶动量可以看作是对梯度的方向进行平滑,从而避免因梯度的随机抖动而导致优化路径过于曲折。
- 在曲面(如损失函数)上,动量机制会帮助优化器沿着“平滑的路径”前进,而不是受随机梯度噪声影响而左右摇摆。
作用
- 加速梯度下降,尤其在谷底区域和高曲率路径中,避免振荡。
- 例如,在 SGD-Momentum 中,一阶动量用作更新步的加速项。
二阶动量(Second-order Moment)
定义
二阶动量是梯度的平方值的指数加权移动平均,反映了梯度的变化幅度(大小)。数学表达式:
v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 vt=β2vt−1+(1−β2)gt2
- v t v_t vt:当前时刻的梯度二阶动量(类似于梯度大小的“平滑版本”)。
- g t 2 g_t^2 gt2:当前梯度的平方。
- β 2 \beta_2 β2:动量衰减系数,通常取 0.999。
直观理解
- 二阶动量可以看作是对梯度的波动大小进行平滑,从而自适应地调整学习率。
- 当梯度变化较大时(波动大),二阶动量会提高,从而减少学习率;反之,当梯度平稳时,二阶动量会减小,从而增大学习率。
作用
- 动态调整每个参数的学习率,避免因梯度的绝对值过大或过小而导致训练不稳定。
- 例如,在 RMSProp 和 Adam 中,二阶动量被用来对学习率进行归一化。
对比:一阶动量 vs. 二阶动量
属性 一阶动量 二阶动量 定义 梯度的指数加权移动平均 梯度平方值的指数加权移动平均 反映信息 梯度的方向(趋势) 梯度的大小(波动) 公式 m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t mt=β1mt−1+(1−β1)gt v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2 vt=β2vt−1+(1−β2)gt2 作用 平滑优化路径,减少梯度抖动 调整学习率,适应梯度变化 影响 决定了优化器的“前进方向” 决定了参数更新的“步长大小” 常用优化器 Momentum、Adam RMSProp、Adam
图形解读
- 真实梯度(True Gradient):显示梯度的真实值,包含随机噪声。
- 一阶动量(蓝色曲线):平滑了真实梯度,跟随其趋势,但消除了噪声。
- 二阶动量(橙色曲线):反映了梯度波动的幅度,呈现平滑的梯度大小变化。
总结
- 一阶动量帮助优化器找到更稳定的路径,减少梯度噪声的干扰。
- 二阶动量动态调整学习率,避免参数更新过快或过慢。
- Adam 优化器将两者结合,通过动量平滑和自适应学习率的调整,使得优化更高效且稳定。
5、Adam简单流程
是的,ADAM(Adaptive Moment Estimation)的整个优化过程可以概括为如下步骤:
1. 计算损失函数的梯度
每次通过前向传播计算出损失函数 L ( θ ) L(\theta) L(θ),然后通过反向传播计算参数 θ \theta θ 的梯度:
g t = ∇ θ L ( θ t ) g_t = \nabla_\theta L(\theta_t) gt=∇θL(θt)
- g t g_t gt:表示第 t 步的梯度。
2. 计算一阶动量(梯度的指数加权平均)
一阶动量 m t m_t mt 是梯度的移动平均,用来表示梯度的历史累积方向,类似于动量优化器中的动量项:
m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t mt=β1mt−1+(1−β1)gt
- β 1 \beta_1 β1:一阶动量的衰减系数,通常取 β 1 = 0.9 \beta_1 = 0.9 β1=0.9。
- m t m_t mt:表示梯度的历史累积(方向上的趋势)。
- 如果 β 1 \beta_1 β1 较大,算法更重视历史梯度;如果 β 1 \beta_1 β1 较小,更重视当前的梯度。
偏差修正:为了解决初始时动量值偏小的问题,对 m t m_t mt 进行偏差修正:
m ^ t = m t 1 − β 1 t \hat{m}_t = \frac{m_t}{1 - \beta_1^t} m^t=1−β1tmt
3. 计算二阶动量(梯度平方的指数加权平均)
二阶动量 $v_t $用来反映梯度波动的幅度,它是梯度平方的移动平均,自适应调整学习率:
v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 vt=β2vt−1+(1−β2)gt2
- β 2 \beta_2 β2:二阶动量的衰减系数,通常取 β 2 = 0.999 \beta_2 = 0.999 β2=0.999。
- v t v_t vt:表示梯度波动的历史趋势(梯度平方的平均值)。
偏差修正:同样对 v t v_t vt 进行偏差修正:
v ^ t = v t 1 − β 2 t \hat{v}_t = \frac{v_t}{1 - \beta_2^t} v^t=1−β2tvt
4. 根据一阶和二阶动量计算更新方向
将一阶动量 m ^ t \hat{m}_t m^t 表示的方向和二阶动量 v ^ t \hat{v}_t v^t 表示的幅度结合起来,计算出更新步长:
Δ θ t = − η m ^ t v ^ t + ϵ \Delta\theta_t = - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} Δθt=−ηv^t+ϵm^t
- η \eta η:基础学习率(用户指定的全局学习率)。
- ϵ \epsilon ϵ:一个很小的常数(如 1 0 − 8 10^{-8} 10−8),防止分母为零。
- 公式中的分母会动态调整步长,梯度波动较大时步长减小,梯度波动较小时步长增大。
5. 更新参数
根据上一步计算的更新方向 Δ θ t \Delta\theta_t Δθt 对参数进行更新:
θ t = θ t − 1 + Δ θ t \theta_t = \theta_{t-1} + \Delta\theta_t θt=θt−1+Δθt
即:
θ t = θ t − 1 − η m ^ t v ^ t + ϵ \theta_t = \theta_{t-1} - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} θt=θt−1−ηv^t+ϵm^t
关键点总结
- 梯度计算: 损失函数的梯度 g t g_t gt 是优化的基础。
- 一阶动量(方向): 表示梯度的移动平均,提供“方向上的趋势”。
- 二阶动量(幅度): 表示梯度波动的平方平均,调整“步长的大小”。
- 自适应调整: 使用二阶动量动态调节每个参数的学习率。
- 参数更新: 综合一阶动量(方向)和二阶动量(步长)进行最终更新。
直观实例
假设我们优化一个简单的二次损失函数 L ( θ ) = ( θ − 3 ) 2 L(\theta) = (\theta - 3)^2 L(θ)=(θ−3)2:
初始情况:
- 初始参数 θ 0 = 10 \theta_0 = 10 θ0=10,目标是找到 θ ∗ = 3 \theta^* = 3 θ∗=3。
- 每次计算的梯度为 g t = 2 ( θ t − 3 ) g_t = 2(\theta_t - 3) gt=2(θt−3)。
每步更新(伪代码):
- 计算梯度: g t = 2 ( θ t − 3 ) g_t = 2(\theta_t - 3) gt=2(θt−3)
- 更新一阶动量: m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t mt=β1mt−1+(1−β1)gt
- 更新二阶动量: v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 vt=β2vt−1+(1−β2)gt2
- 调整更新方向: Δ θ t = − η m ^ t v ^ t + ϵ \Delta\theta_t = -\eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} Δθt=−ηv^t+ϵm^t
- 更新参数: θ t = θ t − 1 + Δ θ t \theta_t = \theta_{t-1} + \Delta\theta_t θt=θt−1+Δθt
最终效果:
- 在靠近最优解时,梯度值变小,步长也会动态缩小,优化过程更加稳定。
- 动量累积使优化路径更平滑,不会因为当前梯度波动而振荡。
直观图示
- 一阶动量: 蓝色箭头表示动量方向,会逐渐平滑并指向最优解。
- 二阶动量: 调节每次更新步长,步长与梯度波动大小呈反比。
通过一阶和二阶动量的结合,Adam 可以动态、智能地调整优化方向和步长,实现高效的参数更新。
6、反向传播
您的问题非常好,核心在于理解 梯度传播(backpropagation) 的过程。在神经网络训练过程中,损失函数不仅仅是根据最后一层的参数 w 和 b 来计算的,它实际上是通过网络的每一层(包括卷积层、全连接层等)来进行计算的。梯度的传播会通过反向传播算法(backpropagation)计算出每一层的梯度,从而更新每一层的参数。
损失函数与梯度的计算
首先,您提到的损失函数是:
L = ∑ ( w ⋅ x + b − y true ) 2 L = \sum (w \cdot x + b - y_{\text{true}})^2 L=∑(w⋅x+b−ytrue)2
这是一个典型的 均方误差(MSE) 损失函数。这个损失函数只会对最后的全连接层的权重 w 和偏置 b 产生直接的影响,确实,直接计算得到的梯度是这些参数的梯度。
但是,整个网络的训练是一个 端到端 的过程,损失函数不仅影响最后一层的参数,还会通过链式法则将影响逐层传播,最终影响到前面层(如卷积层、激活层等)的参数。
反向传播(Backpropagation)
反向传播算法的核心是通过 链式法则(Chain Rule) 来计算每个层的梯度,下面详细讲解:
- 前向传播(Forward Pass)
在神经网络训练的前向传播过程中,输入数据通过每一层,最终通过最后一层输出一个预测结果。假设是一个回归问题,最终预测为 y pred y_{\text{pred}} ypred,损失函数计算的是预测值与真实值之间的差异。
- 对于多层网络,前向传播会将输入数据通过一系列的层(包括卷积层、池化层、激活函数、全连接层等),得到输出。
- 损失函数
假设我们使用的是 均方误差(MSE)损失函数,其形式为:
L = 1 2 ∑ ( y pred − y true ) 2 L = \frac{1}{2} \sum (y_{\text{pred}} - y_{\text{true}})^2 L=21∑(ypred−ytrue)2
这里的损失函数计算的是最后输出层的预测值与真实标签之间的差异。注意,这里的 $y_{\text{pred}} $是网络最终输出的值,它依赖于整个网络的计算路径(从输入到输出层)。
- 反向传播(Backward Pass)
反向传播的过程是通过链式法则来计算每一层的梯度。链式法则的核心思想是:如果 z = f ( x ) z = f(x) z=f(x),那么:
∂ L ∂ x = ∂ L ∂ z ⋅ ∂ z ∂ x \frac{\partial L}{\partial x} = \frac{\partial L}{\partial z} \cdot \frac{\partial z}{\partial x} ∂x∂L=∂z∂L⋅∂x∂z
在神经网络中,假设最终损失函数 LL 依赖于网络的输出 y pred y_{\text{pred}} ypred,而 y pred y_{\text{pred}} ypred 又是由上一层的输出 a n − 1 a_{n-1} an−1 和权重矩阵 $W_{n-1} $等共同计算得到的,整个反向传播的过程会通过这些层依次计算梯度。
- 对于 最后一层的权重 W n W_n Wn,其梯度是直接根据损失函数计算得到的。
- 然后,损失函数对上一层的梯度会影响到前一层的梯度,依此类推。
在每一层,反向传播都会计算出该层参数(如权重和偏置)的梯度,这些梯度是损失函数对该层输出的 偏导数。
以简单的全连接层为例
假设一个简单的全连接层,输入是 x,权重是 W,偏置是 b,输出是 y pred = W x + b y_{\text{pred}} = W x + b ypred=Wx+b。
- 假设损失函数是均方误差: L = 1 2 ( y pred − y true ) 2 L = \frac{1}{2}(y_{\text{pred}} - y_{\text{true}})^2 L=21(ypred−ytrue)2。
- 对于 权重 W,损失函数对权重的梯度是: ∂ L ∂ W = ∂ L ∂ y pred ⋅ ∂ y pred ∂ W = ( y pred − y true ) ⋅ x \frac{\partial L}{\partial W} = \frac{\partial L}{\partial y_{\text{pred}}} \cdot \frac{\partial y_{\text{pred}}}{\partial W} = (y_{\text{pred}} - y_{\text{true}}) \cdot x ∂W∂L=∂ypred∂L⋅∂W∂ypred=(ypred−ytrue)⋅x
- 对于 偏置 b,损失函数对偏置的梯度是: ∂ L ∂ b = ∂ L ∂ y pred ⋅ ∂ y pred ∂ b = ( y pred − y true ) \frac{\partial L}{\partial b} = \frac{\partial L}{\partial y_{\text{pred}}} \cdot \frac{\partial y_{\text{pred}}}{\partial b} = (y_{\text{pred}} - y_{\text{true}}) ∂b∂L=∂ypred∂L⋅∂b∂ypred=(ypred−ytrue)
这两个梯度会用来更新权重和偏置。
- 前一层的梯度计算
对于前面的一层,比如是卷积层,损失函数并没有直接对这些层的参数计算梯度,但是这些层的输出会影响到后面层的输出,因此它们的梯度也是通过反向传播计算得到的。
假设某一层的输出是 a n − 1 a_{n-1} an−1,它影响了最终输出 y pred y_{\text{pred}} ypred,那么该层的梯度是:
∂ L ∂ a n − 1 = ∂ L ∂ y pred ⋅ ∂ y pred ∂ a n − 1 \frac{\partial L}{\partial a_{n-1}} = \frac{\partial L}{\partial y_{\text{pred}}} \cdot \frac{\partial y_{\text{pred}}}{\partial a_{n-1}} ∂an−1∂L=∂ypred∂L⋅∂an−1∂ypred
这个梯度会反向传播到前一层,依次计算出前面每一层的梯度。
卷积层的梯度计算
假设前面是一个卷积层,其输出是 a conv a_{\text{conv}} aconv,它影响了后面层的输出。在反向传播过程中,我们会计算卷积层的 梯度,并将其用于更新卷积核(权重)。
- 假设卷积层的输出$a_{\text{conv}} 是由前一层的输出经过卷积运算得到的: = = 是由前一层的输出经过卷积运算得到的:== 是由前一层的输出经过卷积运算得到的:==a_{\text{conv}} = W_{\text{conv}} \cdot x_{\text{input}} + b$==损失函数的梯度会通过反向传播依次计算出每个卷积核的梯度。
总结
- 损失函数(如均方误差)主要用来计算网络的输出与真实标签之间的差异,但它 不只 影响最后一层的参数。
- 反向传播通过链式法则计算每一层(包括卷积层、激活层等)的梯度。
- 通过每一层的梯度,我们可以更新网络中 所有层的参数,包括卷积层、全连接层等。
- 损失函数的梯度计算并不仅限于 w 和 b,而是逐层传播,最终更新所有层的参数。
反向传播是一个非常重要的过程,它确保了通过损失函数的反馈,每一层的参数都得到了适当的调整,从而使得整个网络能够在训练过程中逐步减小损失并优化性能。
五、优化器参数
SGD (
Parameter Group 0
dampening: 0
differentiable: False
foreach: None
fused: None
initial_lr: 0.01
lr: 0.01
maximize: False
momentum: 0.937
nesterov: True
weight_decay: 0
Parameter Group 1
dampening: 0
differentiable: False
foreach: None
fused: None
initial_lr: 0.01
lr: 0.01
maximize: False
momentum: 0.937
nesterov: True
weight_decay: 0.0005
Parameter Group 2
dampening: 0
differentiable: False
foreach: None
fused: None
initial_lr: 0.01
lr: 0.01
maximize: False
momentum: 0.937
nesterov: True
weight_decay: 0
)
这是一个使用 Stochastic Gradient Descent (SGD) 优化器的配置。该配置包含多个参数组(Parameter Group),每个组的参数可能略有不同。我们可以逐一解析各个字段的含义。
1. Parameter Group
SGD 优化器支持对模型中的不同参数进行分组,不同组可以使用不同的学习率、权重衰减(weight decay)等超参数。在这里有三个参数组(0、1、2),每个组有不同的
weight_decay
参数,这通常意味着每组参数的正则化力度不同。2. 参数解释
- dampening: 0
- 定义:动量衰减项。此项用于控制每次更新时的动量的减少。
dampening
设置为 0 表示没有动量衰减,即更新时直接考虑动量的值。- 默认值:0(没有衰减)。
- differentiable: False
- 定义:该参数表明优化器是否支持自定义求导。设置为
False
表示不支持。- 默认值:通常情况下是
False
。- foreach: None
- 定义:决定是否对每个参数组分别进行优化更新。
None
表示没有特别的并行优化方式。- 默认值:
None
。- fused: None
- 定义:指示是否使用融合的计算内核(fused kernel)。当设置为
None
时,表示未启用该特性。- 默认值:
None
。- initial_lr: 0.01
- 定义:每个参数组的初始学习率。即初始时的学习率设置为 0.01。
- 默认值:具体值由用户定义,通常是一个合理的值(如0.01)。
- lr: 0.01
- 定义:当前学习率。通常在训练过程中,学习率会有所调整(例如通过学习率衰减),此处为当前值。在这里,当前学习率仍然是 0.01。
- 默认值:等于
initial_lr
,如果没有学习率调整策略。- maximize: False
- 定义:是否最大化目标函数。
False
表示最小化目标函数,通常用于最优化问题。- 默认值:
False
。- momentum: 0.937
- 定义:动量系数。动量可以帮助加速收敛并减少震荡。在这里,动量设置为 0.937,说明使用较高的动量。动量越大,梯度更新时对过去梯度的依赖越强。
- 默认值:通常在 [0, 1) 之间设置,0.937 是一个较高的动量值。
- nesterov: True
- 定义:是否使用 Nesterov 动量。在 SGD 中,Nesterov 动量可以提供更好的收敛效果,减少震荡。当设置为
True
时,表示启用 Nesterov 动量。- 默认值:
False
(如果不设置 Nesterov)。- weight_decay: 0 / 0.0005
- 定义:权重衰减(即 L2 正则化)。在神经网络训练中,权重衰减是控制模型复杂度、避免过拟合的手段。
- 不同组的值:
- Parameter Group 0 和 2:没有权重衰减,
weight_decay = 0
。- Parameter Group 1:有权重衰减,
weight_decay = 0.0005
,说明对这一组参数应用较小的正则化。- 默认值:通常设置为
0
或一些小的值(例如0.0001
),具体取决于是否需要正则化。3. 总结
- 这个配置使用了 SGD 优化器,带有动量、Nesterov 动量和权重衰减。
- 每个参数组的学习率都为 0.01,动量为 0.937,并且都启用了 Nesterov 动量。
- 仅 Parameter Group 1 启用了权重衰减(0.0005),这意味着只有该组参数会有正则化。
这个配置通常用于具有不同正则化需求的模型,或者在多个优化器组中有不同的训练策略时,可能需要不同的权重衰减值(例如不同层的权重衰减不同)。
六、关于 p t pt pt文件读取
torch.load(weights, map_location=device)
这行代码通常用于加载一个 PyTorch 模型的 checkpoint(.pt
或 .pth
文件),并将其加载到指定的设备(如 CPU 或 GPU)。这个 .pt
文件不仅包含模型的权重(即参数),还可以包含很多其他的内容,具体取决于保存时包含了哪些信息。
1、.pt
文件中包含的内容:
- 模型权重(weights):
- 这是最基本的内容,包含模型各层的权重和偏置(
state_dict
)。 - 格式通常是一个字典(
state_dict
),字典的键是模型中各个层的名称,值是该层的权重(tensor)。
- 这是最基本的内容,包含模型各层的权重和偏置(
- 优化器状态(optimizer state):
- 如果保存了优化器的状态,那么 checkpoint 会包含优化器的状态字典。这个字典中包含了优化器的超参数、动量、学习率等信息,帮助在训练中断后恢复优化器的状态。
- 优化器状态通常是包含
state
和param_groups
的字典。
- 训练轮次(epoch):
- checkpoint 可能保存了当前训练的轮次(
epoch
),这可以帮助在训练恢复时从指定的轮次继续训练。
- checkpoint 可能保存了当前训练的轮次(
- 学习率调度器状态(learning rate scheduler state):
- 如果使用了学习率调度器(如
StepLR
,ReduceLROnPlateau
等),那么 checkpoint 还可以保存调度器的状态,这样可以继续进行学习率的调整,而不是从头开始。
- 如果使用了学习率调度器(如
- 其他训练相关信息:
- 除了上面提到的内容,checkpoint 还可以包含其他的元数据(如训练时使用的损失函数、开始时间、日志等),这通常是通过将这些信息封装到字典里一并保存的。
2、示例:保存和加载 checkpoint 的常见方式
保存时:
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'lr_scheduler_state_dict': scheduler.state_dict(),
'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pth')
加载时:
checkpoint = torch.load('checkpoint.pth', map_location=device)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
scheduler.load_state_dict(checkpoint['lr_scheduler_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
2.1具体解析:
model.state_dict()
: 保存的是模型的所有参数(如卷积核、全连接层权重等)。optimizer.state_dict()
: 保存的是优化器的状态,包括动量、参数组等。lr_scheduler.state_dict()
: 保存的是学习率调度器的状态,包含学习率的调整历史和当前状态。epoch
和loss
: 保存当前训练的轮次和最后的损失值。
2.2加载时的注意事项:
- 在加载时,需要确保模型结构与保存时一致(即
model.load_state_dict()
要加载的是匹配的权重)。 - 优化器和学习率调度器的状态可以用于恢复训练,但如果不需要恢复训练,加载时可以忽略它们。
2.3其他可能包含的信息:
- 模型的超参数(hyperparameters):如学习率、批大小、优化器类型等,通常是在训练脚本中作为配置保存。
- 模型的评估指标:例如训练集和验证集的准确率、损失值等,可以用于后续评估。
总之,除了权重信息外,checkpoint 文件还可以包含很多有用的训练状态信息,这些信息有助于在训练过程中断后恢复训练状态。
七、代码语法层面解读
1、np.concatenate(dataset.labels, 0)[:, 0].max()
mlc = np.concatenate(dataset.labels, 0)[:, 0].max() # 得到max label class
我们来详细解析这段代码:
mlc = np.concatenate(dataset.labels, 0)[:, 0].max()
。
1. 代码结构
代码可以分为以下几部分:
np.concatenate(dataset.labels, 0)
将dataset.labels
中的所有数据沿指定维度(这里是第 0 维)拼接成一个大的 NumPy 数组。[:, 0]
对拼接后的二维数组,选取其每行的第 0 列。.max()
计算选取列的最大值。最终结果
mlc
是这个选取列中的最大值。
2. 逐步解析
假设数据:
dataset.labels
dataset.labels
是一个由 NumPy 数组组成的列表(或类似结构)。
每个数组可能代表一个数据样本的标签信息。
例如:
dataset.labels = [ np.array([[0, 1], [1, 2]]), np.array([[2, 3], [3, 4]]) ]
Step 1:
np.concatenate(dataset.labels, 0)
将
dataset.labels
中的数组沿第 0 维(垂直方向)拼接:
concatenated = np.concatenate(dataset.labels, 0) print(concatenated) # 输出: # [[0 1] # [1 2] # [2 3] # [3 4]]
Step 2:
[:, 0]
从拼接后的数组中,选取每行的第 0 列:
column_0 = concatenated[:, 0] print(column_0) # 输出: # [0 1 2 3]
Step 3:
.max()
计算选取列的最大值:
max_value = column_0.max() print(max_value) # 输出: # 3
3. 代码含义
这段代码的核心目的是:
- 遍历
dataset.labels
,将其所有数据合并成一个大数组;- 从合并后的数据中,选取第 0 列;
- 计算第 0 列的最大值,赋值给变量
mlc
。
4. 实际应用场景
这段代码可能出现在深度学习或数据预处理场景中,例如:
- 目标检测任务:
dataset.labels
存储目标的标签,其中每个子数组的第 0 列可能表示目标类别 ID。mlc
表示数据集中最大的目标类别 ID,用于推断模型需要预测的类别数。- 分类任务:
dataset.labels
存储每个样本的类别标签。- 通过这段代码可以计算数据集中的最大类别编号。
2、多尺度训练
if opt.multi_scale:
# 随机生成尺寸,// gs * gs 确保生成的 sz 是 gs 的整数倍。
sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs
sf = sz / max(imgs.shape[2:]) # 计算随机尺寸(h,w)与最大尺寸的比例(缩放因子)
if sf != 1:
# 通过 math.ceil() 函数取上整,确保新尺寸是 gs 的整数倍。
ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]]
# mode='bilinear' 表示使用双线性插值算法进行缩放。双线性插值是一种平滑的插值方法,常用于图像缩放。
imgs = F.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
假设我们有以下参数:
imgsz = 640
:表示默认的图像尺寸是 640x640。imgs.shape = (8, 3, 640, 640)
:这表示输入图像的尺寸是(batch_size=8, channels=3, height=640, width=640)
,即有 8 张 RGB 图像,每张图像的尺寸是 640x640。gs = 32
:这是步长,通常是图像尺寸的一个约束条件,通常为 32 或 64。假设随机生成一个目标尺寸
sz
:sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs
假设
random.randrange(imgsz * 0.5, imgsz * 1.5 + gs)
随机生成了一个尺寸,比如 768。为了确保这个尺寸是gs
的倍数,我们进行如下处理:sz = 768 // 32 * 32 # sz = 768 // 32 * 32 = 768
所以目标尺寸
sz = 768
。计算缩放因子
sf
:接下来,我们计算缩放因子
sf
,它是目标尺寸与输入图像最大尺寸之间的比例:sf = sz / max(imgs.shape[2:]) # sz = 768, imgs.shape[2:] = [640, 640] sf = 768 / 640 # sf = 1.2
所以,缩放因子
sf = 1.2
。计算新的图像尺寸
ns
:然后我们计算新的尺寸
ns
,这里需要保证新尺寸是gs
的倍数:ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]]
对于高度和宽度
640
,我们分别计算:
- 对于高度:
math.ceil(640 * 1.2 / 32) * 32 = math.ceil(768 / 32) * 32 = math.ceil(24) * 32 = 768
- 对于宽度:
math.ceil(640 * 1.2 / 32) * 32 = math.ceil(768 / 32) * 32 = math.ceil(24) * 32 = 768
所以新的尺寸
ns = [768, 768]
。使用
F.interpolate
进行图像缩放:最后,我们使用
F.interpolate
将输入图像的尺寸从(640, 640)
缩放到(768, 768)
:imgs = F.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
size=ns
表示目标尺寸是(768, 768)
。mode='bilinear'
使用双线性插值算法进行缩放。align_corners=False
表示插值时不会对齐角点。总结:
假设输入图像的尺寸是 640x640,经过这段代码的处理后,图像会随机缩放到一个新的尺寸(例如 768x768),并且新的尺寸是
gs
(32)的倍数。这样做的目的是增强模型对不同尺寸输入的适应能力,避免在训练过程中只接触到固定尺寸的图像,从而提高模型的泛化能力。如果图像在训练过程中经过了
multi_scale
随机缩放(例如从 640x640 缩放到 768x768),那么在训练的后续过程中,通常会对图像进行统一调整回模型的默认输入尺寸(例如 640x640)。这种操作一般是在数据加载和训练时通过数据增强或者预处理流程完成的。
八、双线性插值
双线性插值(Bilinear Interpolation)是一种常用的图像插值方法,主要用于图像缩放(resize)或者旋转等操作中,尤其在处理图像大小变化时,能够生成较为平滑的图像。
1、双线性插值的概念
双线性插值是对单线性插值(线性插值)的扩展,它在两个方向上同时进行插值,即 x轴方向和y轴方向。与单线性插值仅考虑一个方向的相邻点不同,双线性插值考虑了四个邻近像素,通过对这四个像素值的加权平均来推算新像素值。
数学原理
假设我们有一个目标图像的像素位置 (x, y),而这个像素位置位于原始图像上的四个邻近像素点的中间。我们可以通过以下步骤来计算目标像素值。
设原始图像中四个邻近像素点为:
- (x1,y1)(x_1, y_1): 左上角像素
- (x2,y1)(x_2, y_1): 右上角像素
- (x1,y2)(x_1, y_2): 左下角像素
- (x2,y2)(x_2, y_2): 右下角像素
这些像素的值分别为:
- Q11Q_{11} 对应于 (x1,y1)(x_1, y_1)
- Q21Q_{21} 对应于 (x2,y1)(x_2, y_1)
- Q12Q_{12} 对应于 (x1,y2)(x_1, y_2)
- Q22Q_{22} 对应于 (x2,y2)(x_2, y_2)
目标位置 (x,y)(x, y) 位于这四个点的矩形区域内,我们可以通过以下步骤进行双线性插值:
- 在x方向上进行插值:
首先,沿着水平(x轴)方向,使用线性插值来计算两对水平像素的加权平均。
对于 y1y_1 行:
R1=(x2−x)⋅Q11+(x−x1)⋅Q21x2−x1R_1 = \frac{(x_2 - x) \cdot Q_{11} + (x - x_1) \cdot Q_{21}}{x_2 - x_1}
对于 y2y_2 行:
R2=(x2−x)⋅Q12+(x−x1)⋅Q22x2−x1R_2 = \frac{(x_2 - x) \cdot Q_{12} + (x - x_1) \cdot Q_{22}}{x_2 - x_1}
- 在y方向上进行插值:
然后,沿着垂直(y轴)方向,对上述计算得到的 R1R_1 和 R2R_2 进行线性插值。
P(x,y)=(y2−y)⋅R1+(y−y1)⋅R2y2−y1P(x, y) = \frac{(y_2 - y) \cdot R_1 + (y - y_1) \cdot R_2}{y_2 - y_1}
其中,P(x,y)P(x, y) 就是目标像素位置 (x,y)(x, y) 的插值结果。
可视化说明
假设目标像素 (x,y)(x, y) 位于原始图像四个邻近像素的矩形区域内,那么双线性插值可以看作是两个阶段的插值:
- 第一阶段(x方向插值):对于每一行 y1y_1 和 y2y_2,分别在水平方向(x轴)对两个像素进行插值,得到 R1R_1 和 R2R_2。
- 第二阶段(y方向插值):然后再沿着垂直方向(y轴)对 R1R_1 和 R2R_2 进行插值,从而得到最终的目标像素值。
举个例子
假设原始图像中四个相邻像素值如下:
- Q11=100Q_{11} = 100
- Q21=150Q_{21} = 150
- Q12=200Q_{12} = 200
- Q22=250Q_{22} = 250
我们希望计算目标像素 (x,y)(x, y) 的值,其中 xx 和 yy 分别位于两个像素之间。例如,假设 x=1.5x = 1.5, y=1.5y = 1.5。
-
首先,进行水平方向插值:
R1=(2−1.5)⋅100+(1.5−1)⋅1502−1=125R_1 = \frac{(2 - 1.5) \cdot 100 + (1.5 - 1) \cdot 150}{2 - 1} = 125
R2=(2−1.5)⋅200+(1.5−1)⋅2502−1=225R_2 = \frac{(2 - 1.5) \cdot 200 + (1.5 - 1) \cdot 250}{2 - 1} = 225
-
然后,进行垂直方向插值:
P(1.5,1.5)=(2−1.5)⋅125+(1.5−1)⋅2252−1=175P(1.5, 1.5) = \frac{(2 - 1.5) \cdot 125 + (1.5 - 1) \cdot 225}{2 - 1} = 175
因此,目标位置 (x,y)=(1.5,1.5)(x, y) = (1.5, 1.5) 对应的像素值是 175。
应用场景
- 图像缩放:双线性插值常用于图像大小调整,尤其是缩放操作,如将图像从较小尺寸缩放到较大尺寸或从大尺寸缩小到小尺寸时,能平滑过渡,避免明显的锯齿。
- 图像旋转:当图像进行旋转时,也会涉及到像素的重新分布,通常会使用双线性插值来计算旋转后的像素值。
- 视频帧插值:在视频处理中,双线性插值也常用于将图像帧的尺寸转换到统一的大小。
优缺点
- 优点:双线性插值比最近邻插值(仅取最近的一个像素值)产生的图像效果更平滑,避免了锯齿现象。
- 缺点:相比于更高阶的插值方法(如立方插值),双线性插值可能会失去一些细节,且在大幅度缩放时可能产生模糊现象。
总结来说,双线性插值是一种基于邻近四个像素值的加权平均的方法,常用于图像缩放和旋转操作,能够生成相对平滑的图像。