Yolov5
网络结构
Yolov5网络包括四部分,输入端,backbone骨干网络,neck颈部网络,Prediction
Yolov5使用了CSPDarknet53作为骨干网络,用于提取图片特征,包括CBL,FOCUS,CSP,SPP模块
CBL
CBL为标准卷积模块,包括普通卷积层Conv、批量归一化层BN和LeakyReLU激活函数层
import torch
import torch.nn as nn
# CBL
def autopad(k, p=None): # kernel,padding
# pad to "same":
# H_out=H_in;W_out=W_in
# H_out=(H_in+2p-k)/s+1
# W_out=(W_in+2p-k)/s+1
if p is None:
if isinstance(k,int):
p = k//2 if k%2==1 else k//2 -1
else:
p = [x // 2 if x % 2 == 1 else x // 2 - 1 for x in k]
return p
Standard Convolution
class Conv(nn.Module):
def __init__(self,c1,c2,k=1,s=1,p=None,g=1,act=True):
super(Conv,self).__init__()
self.conv=nn.Conv2d(c1,c2,k,s,autopad(k,p),groups=g,bias=False)
self.bn=nn.BatchNorm2d(c2)
self.act=nn.LeakyReLU(0.01) if act else nn.Identity()
def forward(self, x):
return self.act(self.bn(self.conv(x)))
def fuseforward(self, x):
return self.act(self.conv(x))
Focus
Focus模块是对图片进行切片操作,具体操作是在一张图片中每隔一个像素取一个值,类似于邻近下采样,可以得到四张采样图片,四张图片互补,图片内容差不多,但是整体操作没有信息丢失。Focus将图片H*W空间维度的信息转换到了Channel通道维度,H和W均减为原来一半,Channel扩充为原来四倍,然后将新图片经过一次卷积操作,得到没有信息丢失情况下的二倍下采样特征图
class Focus(nn.Module):
# ch_in,ch_out,kernel,stride,padding,groups
def __init__(self,c1,c2,k=1,s=1,p=None,g=1,act=True):
super(Focus,self).__init__()
self.conv=Conv(c1*4,c2,k,s,p,g,act)
def forward(self,x): # (b,c,w,h)-(b,4c,w/2,h/2)
return self.conv(torch.cat([x[...,::2,::2],
x[...,1::2,::2],
x[...,::2,1::2],
x[...,1::2,1::2]],1))
CSP
ResUnit模块
class Bottleneck(nn.Module):
def __init__(self, c1, c2, shortcut=True, g=1,e=0.5):
super(Bottleneck,self).__init__()
c_ = int(c2*e)
self.cv1 = Conv(c1, c_, k=1, s=1)
self.cv2 = Conv(c_, c2, k=3, s=1, g=1)
self.add = shortcut and c1 == c2
def forward(self, x):
if self.add:
return x + self.cv2(self.cv1(x))
else:
return self.cv2(self.cv1(x))
ResUnit模块的shortcut参数设置为True,就是CSP1_X模块的组件;设置成False,就是CSP2_X模块的组件
CSP1_X模块
class BottleneckCSP(nn.Module):
def __init__(self,c1,c2,n=1,shortcut=True,g=1,e=0.5):
super(BottleneckCSP,self).__init__()
c_=int(c2*e) # hidden channels
self.cv1=Conv(c1, c_, k=1, s=1)
self.cv2=nn.Conv2d(c1, c_, k=1, s=1, bias=False)
self.cv3=nn.Conv2d(c_, c_, k=1, s=1, bias=False)
self.cv4=Conv(2*c_, c2, k=1, s=1)
self.bn=nn.BatchNorm2d(2*c_)
self.act=nn.LeakyReLU(0.1, inplace=True)
self.m=nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])
def forward(self,x):
y1=self.cv3(self.m(self.cv1(x)))
y2=self.cv2(x)
return self.cv4(self.act(self.bn(torch.cat((y1,y2),dim=1))))
CSP2_X模块
class BottleneckCSP(nn.Module):
def __init__(self,c1,c2,n=1,shortcut=False,g=1,e=0.5):
super(BottleneckCSP,self).__init__()
c_=int(c2*e) # hidden channels
self.cv1=Conv(c1,c_,k=1,s=1)
self.cv2=nn.Conv2d(c1,c_,k=1,s=1,bias=False)
self.cv3=nn.Conv2d(c_,c_,k=1,s=1,bias=False)
self.cv4=Conv(2*c_,c2,k=1,s=1)
self.bn=nn.BatchNorm2d(2*c_)
self.act=nn.LeakyReLU(0.1,inplace=True)
self.m=nn.Sequential(*[Bottleneck(c_,c_,shortcut,g,e=1.0) for _ in range(n)])
def forward(self,x):
y1=self.cv3(self.m(self.cv1(x)))
y2=self.cv2(x)
return self.cv4(self.act(self.bn(torch.cat((y1,y2),dim=1))))
SPP模块
class SPP(nn.Module):
def __init__(self,c1,c2,k=(5,9,13)):
super(SPP,self).__init__()
c_=c1//2
self.cv1=Conv(c1,c_,k=1,s=1)
self.cv2=Conv(c_*(len(k)+1),c2,k=1,s=1)
self.m=nn.ModuleList([nn.MaxPool2d(kernel_size=x,stride=1,padding=x//2) for x in k])
def forward(self,x):
x=self.cv1(x)
# [x,m1(x),m2(x),m3(x)]
return self.cv2(torch.cat([x]+[m(x) for m in self.m],1))
Yolov5的改进
1.RFP(Recursive Feature Pyramid)递归特征金字塔
∀
i
=
1
,
.
.
.
,
S
,
∀i = 1, ..., S,
∀i=1,...,S,
f
i
=
F
i
(
f
i
+
1
,
X
i
)
,
X
i
=
B
i
(
X
i
−
1
,
R
i
(
f
i
)
)
f_i=F_i(f_{i+1} ,X_i ),X_i=B_i(X_{i-1},R_i(f_i))
fi=Fi(fi+1,Xi),Xi=Bi(Xi−1,Ri(fi))
增加递归循环操作:
∀
i
=
1
,
.
.
.
,
S
,
t
=
1
,
.
.
.
T
,
∀i = 1, ..., S,t = 1, ...T,
∀i=1,...,S,t=1,...T,
f
i
t
=
F
i
t
(
f
i
+
1
t
,
X
i
t
)
,
X
i
t
=
B
i
t
(
X
i
−
1
t
,
R
i
(
f
i
t
−
1
)
)
f_i^t=F_i^t(f_{i+1}^t ,X_i^t),X_i^t=B_i^t(X_{i-1}^t,R_i(f_i^{t-1}))
fit=Fit(fi+1t,Xit),Xit=Bit(Xi−1t,Ri(fit−1))
不同于传统的FPN,PAN,RFP使用几个(T=2,即2个)递归循环,加深了特征与图像的融合,保证了细节
2.ASPP(Atrous Spatial Pyramid Pooling)膨胀空间金字塔池化
ASPP在卷积神经网络的最后一个卷积层后添加多个并行的池化分支,每个分支具有不同的采样率(或称为膨胀率)来捕获不同尺度的上下文信息。这样可以允许模型在不同层次上感知和分析图像的语义信息。
3.SAP(Switchable Atrous Convolution)
SAC通过引入不同的膨胀率的空洞卷积和权重锁定机制,使得模型能够根据不同的感受野需求自适应地调整卷积核。这样可以在减少参数和计算量的同时,提高模型的灵活性和适应性。
Yolov5损失函数
Yolov5损失函数分为三部分
1.类别损失(classes loss)
采用交叉熵损失计算类比损失
有N个类别,则预测结果是一个N维的概率分布向量
L = -sum(y_true * log(y_pred))
其中,y_true表示真实标签向量,y_pred表示模型的预测概率分布向量,log表示自然对数运算。
2.置信度损失(confidence loss)
采用二值交叉熵损失计算置信度损失
标签只有是和否两种,显然
L=-PlogP-(1-P)log(1-P)
3.定位损失
Yolov5使用CIOU loss来衡量矩形框的损失
GIoU
GIoU=IoU-(Ac-U)/Ac
# Ac是包含两张图片的最小矩形面积,U是并集,I是交集,IoU即交并比
GIoULoss=1-GIoU=2-I/U-U/Ac # 取值[0,2)
DIoU
DIoU损失函数是对GIoU的改进,引入了框的中心点距离。
定义如下:
D
I
o
U
=
I
o
U
−
d
2
C
2
DIoU = IoU - \frac{{d^2}}{{C^2}}
DIoU=IoU−C2d2 #取值范围[-1,1]
d表示两个框的中心点之间的欧氏距离。
d
=
(
x
2
−
x
1
)
2
+
(
y
2
−
y
1
)
2
d = \sqrt{{(x2 - x1)^2 + (y2 - y1)^2}}
d=(x2−x1)2+(y2−y1)2
C 表示两个框的对角线长度。
C
=
max
(
w
1
,
w
2
)
2
+
max
(
h
1
,
h
2
)
2
C = \sqrt{{\max(w1, w2)^2 + \max(h1, h2)^2}}
C=max(w1,w2)2+max(h1,h2)2
DIoU损失函数在GIoU的基础上加入了中心点距离的项,能够更好地处理框的尺度不匹配和位置偏移的问题。
DIoULoss=1-DIoU
CIoU
CIoU损失函数是对DIoU的改进,进一步考虑了框的长宽比例。
定义如下:
C
I
o
U
=
D
I
o
U
−
α
v
CIoU = DIoU - \alpha v
CIoU=DIoU−αv # CIOU取值范围[-1.5,1]
α
=
v
1
−
I
o
U
+
v
\alpha=\frac{v}{1 -IoU+v}
α=1−IoU+vv
v
=
4
π
2
(
arctan
w
1
h
1
−
arctan
w
p
h
p
)
2
v = \frac{4}{\pi^2}(\arctan\frac{w_1}{h_1}-\arctan\frac{w_p}{h_p})^2
v=π24(arctanh1w1−arctanhpwp)2
v为框A、框B的宽高比相似度,α为v的影响因子
CIoULoss=1-CIoU
其他知识
LeakyReLU激活函数
LeakyReLU激活函数是ReLu的变种,解决了Relu输入负值梯度为0造成神经元死亡
import torch
import torch.nn as nn
leaky_relu=nn.LeakyReLU(negative_slope=0.001,inplace=False)
#带泄露线性整流函数,负斜率为0.001,即输入负值时斜率固定为小值,
#inplace:是否进行原地操作(in-place operation)。如果设置为True,则会将激活函数应用于输入张量本身,而不会创建新的张量。默认值为False
input_tensor = torch.randn(1,3,50,50) # 3个通道数,图像的尺寸是50X50
output = leaky_relu(input_tensor)
卷积计算公式:
H_out=(H_in+p*2-k)/s+1
W_out=(W_in+p*2-k)/s+1
p是填充,输入特征图周围填充零值元素,常见的有valid(不填充),same(填充至相同输入输出大小)
s,步长,k,卷积核大小