- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊 | 接辅导、项目定制
文件路径:yolov8\ultralytics-main\ultralytics\nn\modules\***
1.conv.py
包含卷积层相关的实现。
import math
inport numpy as np
import torch
import torch.nn as nn
__all__ = ('Conv', 'Conv2', 'LightConv', 'DWConv', 'DWConvTranspose2d', 'ConvTranspose', 'Focus', 'GhostConv',
'ChannelAttention', 'SpatialAttention', 'CBAM', 'Concat', 'RepConv')
开头列举了该文件中定义的所有模型,如果需要新加一个模块,就需要在文件开头将其名称加入。(这是YOLOv8新增的一个类似声明的内容)
1.1 autopad
def autopad(k, p=None, d=1): # kernel, padding, dilation
"""Pad to 'same' shape outputs."""
if d > 1:
k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
return p
功能: 返回pad的大小,使得padding后输出张量的大小不变。
参数:
k : 卷积核(kernel)的大小。类型可能是一个int , 也可能是一个序列 。
p : 填充(padding)的大小。默认为None 。
d : 扩张率(dilation rate)的大小,默认为1 。普通卷积的扩张率为1,空洞卷积的扩张率大于1。
通常用于自动计算卷积层所需的填充(padding),以保持特征图(feature map)的空间维度或者使其符合某种设计意图,如保留边界信息或者使得卷积操作的输出大小等同于输入大小(即所谓的 ‘same’ padding)。
这个函数的作用是,给定一个卷积核尺寸 k、填充 p 和扩张率 d,它会返回一个计算出来的填充大小 p。这样做的目的在于简化网络设计过程,并使得特征图的大小在卷积运算后保持不变,或者是依照设计者的其他特定需求。
1.2 conv
class Conv(nn.Module):
"""Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""
default_act = nn.SiLU() # default activation
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
"""Initialize Conv layer with given arguments including activation."""
super().__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
def forward(self, x):
"""Apply convolution, batch normalization and activation to input tensor."""
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
"""Perform transposed convolution of 2D data."""
return self.act(self.conv(x))
- 功能: 标准卷积模块。
- 参数:
- 输入通道数(
c1
) - 输出通道数(
c2
) - 卷积核大小(
k
,默认是1) - 步长(
s
, 默认是1) - 填充(
p
,默认为None) - 组(
g
,默认为1) - 扩张率(
d
,默认为1) - 是否采用激活函数(
act
,默认为True,且采用SiLU为激活函数)
- 输入通道数(
在前向传播方法forward中,首先对输入张量x进行卷积操作self.conv(x),然后对卷积结果进行批归一化self.bn,最后使用激活函数self.act进行激活,并返回结果。
forward_fuse方法,用于执行转置卷积操作。他对输入张量x执行卷积操作self.conv(x),然后使用激活函数self.act进行激活,并返回结果。
1.3 Focus
Focus是原作者自己设计出来,为了减少浮点数和提高速度,而不是增加feature map的,本质就是将图像进行切片,类似于下采样取值,将员图像的宽高信息切分,聚合到channel通道中。结构如下所示:
class Focus(nn.Module):
"""Focus wh information into c-space."""
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in(输入通道数),ch_out(输出通道数),卷积核大小,步长,填充,分组数
super().__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)
# self.contract = Contract(gain=2) # 降维模块(可选)
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2) (输入x的维度为:批大小,通道数,宽度,高度;输入y的维度为:批大小,4倍通道数,宽度的一半,高度的一半)
#[开始索引:结束索引:步长] x[..., ::2, ::2]最后两个维度,::2表示从0开始,隔两个取一个,即取偶数
return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))
# 通过将输入张量x沿通道维度进行拼接四次得到新的输入,然后经过卷积层self.conv处理
# 如果启用降维模块:
# return self.conv(self.contract(x))
2.block.py
包含神经网络中的基础块,例如C3,SPPF等。
2.1. C2f
C2f模块就是参考了C3模块以及ELAN的思想进行的设计,让YOLOv8可以在保证轻量化的同时获得更加丰富的梯度流信息。
C2f模块全称为Cross Stage Partial Network Fusion模块,其作用是在不同层次的特征图之间进行信息融合。具体来说,C2F模块主要包括两个部分:SPP(Spatial Pyramid Pooling)和PAN(Path Aggregation Network)。
首先是SPP,它通过构建具有不同尺度池化层的金字塔结构,实现了对不同尺寸目标的有效特征提取。这样能够使得网络具备更好的感知能力,能够识别不同尺寸的物体。
其次是PAN,它主要解决不同尺度特征图之间信息融合的问题。PAN模块采用了多个跨舞台部分网络融合(CSP)模块,将来自浅层和深层特征图的信息进行融合。这样可以提高网络的感知范围,提高目标检测的准确性。
通过使用C2F模块,YOLOv8能够在保持高检测精度的情况下,提高目标检测的速度和效率。C2F模块的引入使得网络具备更好的感知能力和更强的信息融合能力,提高了目标检测的准确性和鲁棒性。
class C2f(nn.Module):
"""C2F层,快速实现带有2个卷积的CSP Bottleneck"""
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): # ch_in为输入通道数,ch_out为输出通道数,n表示重复次数,shortcut表示是否使用快捷连接,g表示分组数,e表示扩展比例
super().__init__()
self.c = int(c2 * e) # 隐藏通道数
self.cv1 = Conv(c1, 2 * self.c, 1, 1) # 第一个卷积层,用于将输入通道转换为2倍的隐藏通道数
self.cv2 = Conv((2 + n) * self.c, c2, 1) # 第二个卷积层,用于将隐藏通道数转换为输出通道数;也可选使用激活函数FReLU(c2)
self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)) # 创建包含n个Bottleneck块的ModuleList
def forward(self, x):
"""通过C2f层进行前向传播"""
y = list(self.cv1(x).chunk(2, 1)) # 将cv1的输出通道分为两部分
y.extend(m(y[-1]) for m in self.m) # 将y的最后一个部分输入n个Bottleneck块中,得到一系列的输出
return self.cv2(torch.cat(y, 1)) # 将这些输出连接起来并输入到cv2中进行最终的输出
def forward_split(self, x):
#前向传播执行这个操作的时候,会提前声明,例如m.forward=m.forward_split,这个时候就按照这个定义的顺序执行操作。
"""使用split()而不是chunk()进行前向传播"""
y = list(self.cv1(x).split((self.c, self.c), 1)) # 使用split函数将cv1的输出按指定大小分割
y.extend(m(y[-1]) for m in self.m) # 将y的最后一个部分输入n个Bottleneck块中,得到一系列的输出
return self.cv2(torch.cat(y, 1)) # 将这些输出连接起来并输入到cv2中进行最终的输出
3.head.py
yolov8中的head.py文件主要负责检测模型的头部结构,包括处理检测结果的后处理工作。在这个文件中,首先会定义检测的头部结构,例如在yolov8中常用的YOLOv3检测头部结构。通过定义检测头部的结构,可以更好地处理检测结果,包括筛选出符合条件的目标物体以及对目标物体进行位置和类别的标记。
同时,head.py文件也包括了一些后处理的操作,比如对检测结果进行筛选,去除重复的目标物体或者对目标物体进行位置的微调。此外,head.py文件还会对检测结果进行类别的识别,将目标物体的类别信息进行标记,并将结果输出为可读的格式。
除此之外,head.py文件还会根据检测结果生成对应的边界框,并且根据边界框的位置和大小将目标物体在图像中进行标记。这些操作都是在head.py文件中完成的。
class Detect(nn.Module):
"""YOLOv8 Detect head for detection models."""
dynamic = False # force grid reconstruction
export = False # export mode
shape = None
anchors = torch.empty(0) # init
strides = torch.empty(0) # init
def __init__(self, nc=80, ch=()):
"""Initializes the YOLOv8 detection layer with specified number of classes and channels."""
super().__init__()
self.nc = nc # number of classes
self.nl = len(ch) # number of detection layers
self.reg_max = 16 # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
self.no = nc + self.reg_max * 4 # number of outputs per anchor
self.stride = torch.zeros(self.nl) # strides computed during build
c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100)) # channels
self.cv2 = nn.ModuleList(
nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()
def forward(self, x):
"""Concatenates and returns predicted bounding boxes and class probabilities."""
shape = x[0].shape # BCHW
for i in range(self.nl):
x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
if self.training:
return x
elif self.dynamic or self.shape != shape:
self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
self.shape = shape
x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
if self.export and self.format in ('saved_model', 'pb', 'tflite', 'edgetpu', 'tfjs'): # avoid TF FlexSplitV ops
box = x_cat[:, :self.reg_max * 4]
cls = x_cat[:, self.reg_max * 4:]
else:
box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)
dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
if self.export and self.format in ('tflite', 'edgetpu'):
# Normalize xywh with image size to mitigate quantization error of TFLite integer models as done in YOLOv5:
# https://github.com/ultralytics/yolov5/blob/0c8de3fca4a702f8ff5c435e67f378d1fce70243/models/tf.py#L307-L309
# See this PR for details: https://github.com/ultralytics/ultralytics/pull/1695
img_h = shape[2] * self.stride[0]
img_w = shape[3] * self.stride[0]
img_size = torch.tensor([img_w, img_h, img_w, img_h], device=dbox.device).reshape(1, 4, 1)
dbox /= img_size
y = torch.cat((dbox, cls.sigmoid()), 1)
return y if self.export else (y, x)
def bias_init(self):
"""Initialize Detect() biases, WARNING: requires stride availability."""
m = self # self.model[-1] # Detect() module
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
# ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # nominal class frequency
for a, b, s in zip(m.cv2, m.cv3, m.stride): # from
a[-1].bias.data[:] = 1.0 # box
b[-1].bias.data[:m.nc] = math.log(5 / m.nc / (640 / s) ** 2) # cls (.01 objects, 80 classes, 640 img)