您的位置:首页 > 理论基础 > 计算机网络

目标检测pytorch版yolov3三——特征提取网络代码

2020-07-14 06:22 435 查看

本篇博客是我学习某位up在b站讲的pytorch版的yolov3后写的,
那位up主的b站的传送门:
https://www.bilibili.com/video/BV1A7411976Z
他的博客的传送门:
https://blog.csdn.net/weixin_44791964/article/details/105310627
他的源码的传送门:
https://github.com/bubbliiiing/yolo3-pytorch
侵删

这篇博客主要是写主干特征提取网络代码的解释,首先,我们需要了解一下什么是残差网络。
看上方的引用,下载那位大佬的代码,跟着他的代码来看更好一些。

什么是残差网络?

残差网络(Residual Block)就是将前若干层的某一层数据输出,直接跳过多层,引入到后面数据层的输入部分。
这也就意味着,后面特征层的内容,会有一部分由前面的某一层线性贡献。
结构如下:

图片来源于b站某位up主的博客:https://blog.csdn.net/weixin_44791964/article/details/105310627


残差网络主要是为了解决,由于网络深度过深而产生的学习效率变低以及准确率无法有效提升的问题。

如上图,我们将残差网络分为一个块(block)一个块(block),然后每个块可以有输入也有输出,然后残差网络就会对输入进行两部分的处理。一部分是进行正常的卷积,激活函数,标准化等等,也就是上图的垂直箭头的方向(也被称为主干边)。另一部分没有经过任何的处理,直接和尾部(也就是上图的加号部分)进行相连,也就是上图的曲线剪头的方向(也被称为残差边)。将主干边和残差边得出的结果进行一个相加。
我们的darknet53,也就是下图的虚线框内部的部分,除了第一次卷积,其他时候都是用到了残差网络。

下面就看一下这个darknet53在代码中的实现了,代码位于net–>darknet.py文件下

import torch
import torch.nn as nn
import math
from collections import OrderedDict

# 基本的darknet块,下面会用的basicblock定义
"""
需要注意的是我们首先是用了1x1的卷积来下降通道数
然后利用3x3的卷积来扩充通道数
这样做的目的是减少参数量
"""
class BasicBlock(nn.Module):
def __init__(self, inplanes, planes):
super(BasicBlock, self).__init__()

#首先定义了两组卷积加标准化和激活函数
#1x1的卷积来下降通道数
self.conv1 = nn.Conv2d(inplanes, planes[0], kernel_size=1,
stride=1, padding=0, bias=False)
self.bn1 = nn.BatchNorm2d(planes[0])
self.relu1 = nn.LeakyReLU(0.1)

#3x3的卷积来扩充通道数
self.conv2 = nn.Conv2d(planes[0], planes[1], kernel_size=3,
stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes[1])
self.relu2 = nn.LeakyReLU(0.1)

"""
下面就是forward,
在forward里面,会把我们输入进来的特征层分为两部分,
一部分是划分为残差边,
另一部分是主干边
"""
def forward(self, x):
#这里是残差边,也就是残差网络结构图中,弯曲的箭头的那条边
residual = x

#下面是主干边,也就是残差网络结构图中,垂直的箭头的那条边
#首先是进行两组卷积+激活和标准化的操作,
#然后会把输出和残差边进行一个相加,
#那么这个残差模型就构建好了
out = self.conv1(x)
out = self.bn1(out)
out = self.relu1(out)

out = self.conv2(out)
out = self.bn2(out)
out = self.relu2(out)

out += residual
return out

#这里就是定义的darknet类的地方
class DarkNet(nn.Module):
def __init__(self, layers):
super(DarkNet, self).__init__()
"""
首先进行了一系列的初始化,这里的初始化就是初始化卷积网络和残差块等等。
下面的_make_layer()就是残差块。
"""
#首先来看一下卷积
self.inplanes = 32
#这里进行的卷积是32通道的卷积,和图中虚线框内的conv2d 32x3x3的块是相对应的
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False)
#下面两行代码就是进行标准化和激活函数,得到结果之后就要传递到后面的残差块
self.bn1 = nn.BatchNorm2d(self.inplanes)
self.relu1 = nn.LeakyReLU(0.1)

#下面的部分就是残差块的部分了,我们使用_make_layer()来进行残差块的堆叠,_make_layer()的代码就是下面的_make_layer函数。
#这是第一个残差块的操作
self.layer1 = self._make_layer([32, 64], layers[0])
#这是第二个残差块的操作,所要进行的操作和第一个残差块一样,
"""
首先还是使用步长为2,卷积核大小为3来进行下采样
然后经过标准化和激活函数
然后进行两次(因为根据我们的列表,此时就是循环两次)残差块堆叠的循环,
后面的同理
"""
self.layer2 = self._make_layer([64, 128], layers[1])
self.layer3 = self._make_layer([128, 256], layers[2])
self.layer4 = self._make_layer([256, 512], layers[3])
self.layer5 = self._make_layer([512, 1024], layers[4])

self.layers_out_filters = [64, 128, 256, 512, 1024]

# 进行权值初始化
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()

#这里就是上面残差_make_layer的函数
"""
下面就是_make_layer进行的具体操作了
首先会进行卷积核为3x3,步长为2x2的卷积了,这样做的目的就是可以进行下采样
然后会进行标准化和激活函数
然后就会进行残差网络的堆叠了,主要是通过BasicBlock类来进行实现。
"""
def _make_layer(self, planes, blocks):
layers = []
# 下采样,步长为2,卷积核大小为3
layers.append(("ds_conv", nn.Conv2d(self.inplanes, planes[1], kernel_size=3,
stride=2, padding=1, bias=False)))
#标准化和激活函数
layers.append(("ds_bn", nn.BatchNorm2d(planes[1])))
layers.append(("ds_relu", nn.LeakyReLU(0.1)))

# 加入darknet模块
self.inplanes = planes[1]
"""
在这个地方进行残差块的堆叠,
block参数规定了堆叠残差块的次数
"""
for i in range(0, blocks):
layers.append(("residual_{}".format(i), BasicBlock(self.inplanes, planes)))
return nn.Sequential(OrderedDict(layers))

#在这个forward里面会把前面建立的卷积和结构块加以应用。
def forward(self, x):
#下面三行是我们最初是32通道的卷积、标准化、激活函数、
x = self.conv1(x)
x = self.bn1(x)
x = self.relu1(x)

#下面两行是对应图中的residual block 1x64以及residual block 2x128
x = self.layer1(x)
x = self.layer2(x)

#下面三行是对应虚线框内的最后三层,因为要将他们的结果输出出去,所以需要将他们的结果加以区分
out3 = self.layer3(x)
out4 = self.layer4(out3)
out5 = self.layer5(out4)

return out3, out4, out5

def darknet53(pretrained, **kwargs):
"""
在这里调用上面定义的darknet类,然后传入列表
列表的数值是1,2,8,8,4,这个数值就是对应了残差块使用的次数
"""
model = DarkNet([1, 2, 8, 8, 4])
if pretrained:
if isinstance(pretrained, str):
model.load_state_dict(torch.load(pretrained))
else:
raise Exception("darknet request a pretrained path. got [{}]".format(pretrained))
return model
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: