本文先介绍YoloV3的相关知识后,再进行代码的讲解。
YoloV3由三个网络共同实现:
主干特征提取网络->侦测网络->卷积神经网络
卷积、下采样(通过卷积利用步长的不同实现)
残差块的实现:
卷积->下采样 其中:卷积用于降通道数,下采样用于还原通道数、残差块用于减小误差,反之出现梯度消失问题
上图中,Convolutional表示卷积,黄框内表示通道数,红框内表示卷积核的大小。正如上图中所描述,进行两级卷积后,进入Residual(残差块),进行残差的操作,残差出来又是一个卷积,但是注意这里的卷积核变为3*3/2,即卷积步长改变了,这里实现的是(卷积)下采样。
用于提取特征,先用1×1的卷积核进行降通道,再用3×3的卷积核进行特征提取并恢复成原来的通道数,如此反复循环,最后通过1×1把通道降下来并输出。
因为yolov3检测时,是以三个矩形框来检测图像的,分别为大、中、小,侦测网络就是用于侦测是三个矩阵中,哪个矩阵的特征图像。 左分支(红框):用3×3的卷积提取特征后,再用1×1的卷积降通道 下分支(黄框):1×1的卷积降通道后(也可使用3×3),进行上采样,再将上采样的通道数和主干特征提取网络的26×26或13×13的通道数结合(通道数可以不同,但是形状大小必须相同)。其中,上采样采取的是插值法,本文讲解的是最邻近插值法。
最邻近插值法:
就像上图,将对应位置的特征复制,并填充,因为填充的是原有的特征,没有引入新的特征,所以不会造成误差。
当然,直接拿26×26进行侦测也是可以的,但是这就涉及到另一个网络SSD,SSD操作就是进行5次卷积,即5次特征提取,每一次特征提取都进行一次侦测,最后再输出。这种网络缺点,是网络太浅,侦测效果太差,在使用时很容易丢失一些特征,所以我们在此将上采样操作后的图像和主干特征进行拼接,弥补那些丢失的信息。
但是,为什么不用残差块进行侦测操作呢?
首先侦测是进行特征的拼接,残差是特征的融合,融合和拼接的操作是大不相同的操作,拼接不会产生新的特征,但是进行融合,就会产生融合后的新特征。融合后我们就无法看到,融合前的两个特征是什么样子的,就比如2+2 = 4,2和2融合后为4,但是只给我们4的时候,我们怎么会知道是2+2形成的4还是1+3形成的4呢,所以残差在此也不合适,相比之下拼接的话就很容易得到特征的原貌,保存了特征的完整性。
注意:网络结构根据需上述给的图来定义。
先看图再看结构:
import torch
from torch import nn
from torch.nn import functional
# 卷积块
class ConvolutionalLayer(nn.Module):
def __init__(self , in_chanel,out_chanel,kerenel_size,stride,padding,bias = False):
#参数对应 in_chanel输入通道 out_chanel输出通道 kerenel_size卷积核大小 stride步长 padding填充
super(ConvolutionalLayer,self).__init__()
self.sub_model = nn.Sequential(
nn.Conv2d(in_chanel,out_chanel,kerenel_size, stride,padding,bias = bias),
nn.BatchNorm2d(out_chanel),
#归一化
nn.LeakyReLU()
#nn.LeakyReLU()大于0部分保留原值,小于部分0取斜率
)
def forward(self,x):
return self.sub_model(x)
# 残差块
class ResidualLayer(nn.Module):
def __init__(self,in_channels,out_chanels):
super(ResidualLayer, self).__init__()
self.sub_module = nn.Sequential(
ConvolutionalLayer(in_channels,out_chanels,1,1,0),
ConvolutionalLayer(out_chanels,in_channels,3,1,1)
#如讲解中,两个卷积,一个降通道数,一个恢复通道数
)
def forward(self,x):
return self.sub_module(x)+x
# 卷积集合块 卷积神经网络
class ConvolutionalSetLayer(nn.Module):
def __init__(self,in_channels,out_channels):
super(ConvolutionalSetLayer,self).__init__()
self.sub_module = nn.Sequential(
#讲解中卷积神经网络的的5个卷积
ConvolutionalLayer(in_channels,out_channels,1,1,0),
ConvolutionalLayer(out_channels,in_channels,3,1,1),
ConvolutionalLayer(in_channels, out_channels, 1, 1, 0),
ConvolutionalLayer(out_channels, in_channels, 3, 1, 1),
ConvolutionalLayer(in_channels,out_channels,1,1,0)
)
def forward(self,x):
return self.sub_module(x)
# 降采样
class DownSamplingLayer(nn.Module):
def __init__(self,in_channels,out_channels):
super(DownSamplingLayer,self).__init__()
self.sub_module = nn.Sequential(
ConvolutionalLayer(in_channels,out_channels,3,2,1)
# 卷积修改步长实现
)
def forward(self,x):
return self.sub_module(x)
# 上采样
class UpSamplingLayer(nn.Module):
def __init__(self):
super(UpSamplingLayer,self).__init__()
def forward(self,x):
return functional.interpolate(x,scale_factor=2,mode = 'nearest')
# functional.interpolate()将图片上采样到指定的大小,采用最邻近插值法
class Yolo_v3_Net(nn.Module):
def __init__(self):
super(Yolo_v3_Net,self).__init__()
# 主干52*52
self.trunk_52 = nn.Sequential(
ConvolutionalLayer(3,32,3,1,1),
DownSamplingLayer(32,64),
ResidualLayer(64,32),
DownSamplingLayer(64,128),
ResidualLayer(128,64),
ResidualLayer(128, 64),
DownSamplingLayer(128,256),
# 残差块 数量根据说明图表
ResidualLayer(256,128 ),
ResidualLayer(256, 128),
ResidualLayer(256, 128),
ResidualLayer(256, 128),
ResidualLayer(256, 128),
ResidualLayer(256, 128),
ResidualLayer(256, 128),
ResidualLayer(256, 128)
)
# 主干26 * 26
self.trunk_26 = nn.Sequential(
DownSamplingLayer(256,512),
# 残差块
ResidualLayer(512, 256),
ResidualLayer(512, 256),
ResidualLayer(512, 256),
ResidualLayer(512, 256),
ResidualLayer(512, 256),
ResidualLayer(512, 256),
ResidualLayer(512, 256),
ResidualLayer(512, 256)
)
# 主干13*13
self.trunk_13 = nn.Sequential(
DownSamplingLayer(512,1024),
ResidualLayer(1024, 512),
ResidualLayer(1024, 512),
ResidualLayer(1024, 512),
ResidualLayer(1024, 512)
)
#以下是侦测网络实现 根据说明图表 下分支
# 进入卷积块
self.convset_13 = nn.Sequential(
ConvolutionalLayer(1024,512,3,1,1)
)
# 卷积降通道
self.detetion_13 = nn.Sequential(
ConvolutionalLayer(512,1024,3,1,1),
nn.Conv2d(1024,45,1,1,0)
)
# 上采样
self.up_13_to_26 = nn.Sequential(
ConvolutionalLayer(512,256,3,1,1),
UpSamplingLayer()
)
# 进入卷积块
self.convset_26 = nn.Sequential(
ConvolutionalLayer(768,256,3,1,1)
)
# 卷积降通道
self.detetion_26 = nn.Sequential(
ConvolutionalLayer(256, 512, 3, 1, 1),
nn.Conv2d(512, 45, 1, 1, 0)
)
# 上采样
self.up_26_to_52 = nn.Sequential(
ConvolutionalLayer(256, 128, 3, 1, 1),
UpSamplingLayer()
)
# 进入卷积块
self.convet_52 = nn.Sequential(
ConvolutionalLayer(384,128,3,1,1)
)
# 卷积降通道
self.detetion_52 = nn.Sequential(
ConvolutionalLayer(128, 256, 3, 1, 1),
nn.Conv2d(256, 45, 1, 1, 0)
)
# 定义结构连接网络图
def forward(self,x):
h_52 = self.trunk_52(x)
h_26 = self.trunk_26(h_52)
h_13 = self.trunk_13(h_26)
convset_13_out = self.convset_13(h_13)
detetion_13_out = self.detetion_13(convset_13_out)
up_13_to26_out = self.up_13_to_26(convset_13_out)
cat_13_to_26 = torch.cat((up_13_to26_out,h_26),dim = 1)
convset_26_out = self.convset_26(cat_13_to_26)
detetion_26_out = self.detetion_26(convset_26_out)
up_26_to52_out = self.up_26_to_52(convset_26_out)
cat_26_to_52 = torch.cat((up_26_to52_out,h_52),dim = 1)
convset_52_out = self.convet_52(cat_26_to_52)
detection_52_out = self.detetion_52(convset_52_out)
return detetion_13_out,detetion_26_out,detection_52_out
if __name__ == '__main__':
net = Yolo_v3_Net()
x = torch.randn(1,3,416,416)
y = net(x)
print(y[0].shape)
print(y[1].shape)
print(y[2].shape)
10.24学习笔记
下一篇Yolov3学习笔记:
(19条消息) YoloV3笔记(二):_风声向寂的博客-CSDN博客
(20条消息) YoloV3学习笔记(三):_风声向寂的博客-CSDN博客