SSD与vgg目标检测网络原理

发布时间 2023-09-27 15:32:00作者: FeiYull

目录:

一、SSD

二、基于SSD的极速人脸检测

三、VGG

 

一、SSD

SSD主干网络结构(SSD是一个多级分类网络)

图1  ssd主干网络结构图

ssd中的vgg-19网络:

  SSD采用的主干网络是VGG网络,关于VGG的介绍大家可以看我的另外一篇博客https://blog.csdn.net/weixin_44791964/article/details/102779878,这里的VGG网络相比普通的VGG网络有一定的修改,主要修改的地方就是:

  • 1、将VGG16的FC6和FC7层转化为卷积层。
  • 2、去掉所有的Dropout层和FC8层;
  • 3、新增了Conv6、Conv7、Conv8、Conv9。

如图所示,输入的图片经过了改进的VGG网络(Conv1->fc7)和几个另加的卷积层(Conv6->Conv9),进行特征提取:
a、输入一张图片后,被resize到300x300的shape

b、conv1,经过两次[3,3]卷积网络,输出的特征层为64,输出为(300,300,64),再2X2最大池化,该最大池化步长为2,输出net为(150,150,64)。

c、conv2,经过两次[3,3]卷积网络,输出的特征层为128,输出net为(150,150,128),再2X2最大池化,该最大池化步长为2,输出net为(75,75,128)。

d、conv3,经过三次[3,3]卷积网络,输出的特征层为256,输出net为(75,75,256),再2X2最大池化,该最大池化步长为2,输出net为(38,38,256)。

e、conv4,经过三次[3,3]卷积网络,输出的特征层为512,输出net为(38,38,512),再2X2最大池化,该最大池化步长为2,输出net为(19,19,512)。

f、conv5,经过三次[3,3]卷积网络,输出的特征层为512,输出net为(19,19,512),再3X3最大池化,该最大池化步长为1,输出net为(19,19,512)。

g、利用卷积代替全连接层,进行了一次[3,3]卷积网络和一次[1,1]卷积网络,分别为fc6和fc7,输出的通道数为1024,因此输出的net为(19,19,1024)。

(从这里往前都是VGG的结构)

h、conv6,经过一次[1,1]卷积网络,调整通道数,一次步长为2的[3,3]卷积网络,输出的通道数为512,因此输出的net为(10,10,512)。

i、conv7,经过一次[1,1]卷积网络,调整通道数,一次步长为2的[3,3]卷积网络,输出的通道数为256,因此输出的net为(5,5,256)。

j、conv8,经过一次[1,1]卷积网络,调整通道数,一次padding为valid的[3,3]卷积网络,输出的通道数为256,因此输出的net为(3,3,256)。

k、conv9,经过一次[1,1]卷积网络,调整通道数,一次padding为valid的[3,3]卷积网络,输出的特征层为256,因此输出的net为(1,1,256)。

 

下面直接上代码(项目地址:https://github.com/bubbliiiing/ssd-pytorch):

文件vgg.py中(ssd算法对vgg-16的修改):

  代码中,如下,有个参数ceil_mode,设置为false、true的区别见下图:

layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]

 

 1 import torch.nn as nn
 2 from torchvision.models.utils import load_state_dict_from_url
 3 
 4 
 5 '''
 6 该代码用于获得VGG主干特征提取网络的输出。
 7 输入变量i代表的是输入图片的通道数,通常为3。
 8 
 9 300, 300, 3 -> 300, 300, 64 -> 300, 300, 64 -> 150, 150, 64 -> 150, 150, 128 -> 150, 150, 128 -> 75, 75, 128 ->
10 75, 75, 256 -> 75, 75, 256 -> 75, 75, 256 -> 38, 38, 256 -> 38, 38, 512 -> 38, 38, 512 -> 38, 38, 512 -> 19, 19, 512 ->
11 19, 19, 512 -> 19, 19, 512 -> 19, 19, 512 -> 19, 19, 512 -> 19, 19, 1024 -> 19, 19, 1024
12 
13 # SSD 中需要从VGG提取两个特征图(见SSD主干网络结构)
14 38, 38, 512的序号是22
15 19, 19, 1024的序号是34
16 '''
17 '''
18 卷积通道数量
19 '''
20 base = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',
21             512, 512, 512]
22 
23 def vgg(pretrained = False):
24     layers = []
25     in_channels = 3
26     for v in base:
27         if v == 'M':
28             layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
29         elif v == 'C':
30             layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
31         else:
32             conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
33             layers += [conv2d, nn.ReLU(inplace=True)]
34             in_channels = v
35     # 19, 19, 512 -> 19, 19, 512 
36     pool5 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
37     # 19, 19, 512 -> 19, 19, 1024
38     conv6 = nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)
39     # 19, 19, 1024 -> 19, 19, 1024
40     conv7 = nn.Conv2d(1024, 1024, kernel_size=1)
41     layers += [pool5, conv6,
42                nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]
43 
44     model = nn.ModuleList(layers)
45     if pretrained:
46         state_dict = load_state_dict_from_url("https://download.pytorch.org/models/vgg16-397923af.pth", model_dir="./model_data")
47         state_dict = {k.replace('features.', '') : v for k, v in state_dict.items()}
48         model.load_state_dict(state_dict, strict = False)
49     return model
50 
51 if __name__ == "__main__":
52     net = vgg()
53     for i, layer in enumerate(net):
54         print(i, layer)

文件ssd.py中:

  见图下ssd主干网络结构图,vgg中输出两个38*38*512和19*19*1024的特征图(图中的block6、7、8、9和代码中的注释的block一一对应):

 1 def add_extras(in_channels, backbone_name):
 2     layers = []
 3     if backbone_name == 'vgg':
 4         # Block 6
 5         # 19,19,1024 -> 19,19,256 -> 10,10,512
 6         layers += [nn.Conv2d(in_channels, 256, kernel_size=1, stride=1)]
 7         layers += [nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1)]
 8 
 9         # Block 7
10         # 10,10,512 -> 10,10,128 -> 5,5,256
11         layers += [nn.Conv2d(512, 128, kernel_size=1, stride=1)]
12         layers += [nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1)]
13 
14         # Block 8
15         # 5,5,256 -> 5,5,128 -> 3,3,256
16         layers += [nn.Conv2d(256, 128, kernel_size=1, stride=1)]
17         layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)]
18         
19         # Block 9
20         # 3,3,256 -> 3,3,128 -> 1,1,256
21         layers += [nn.Conv2d(256, 128, kernel_size=1, stride=1)]
22         layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)]
23     else:
24         layers += [InvertedResidual(in_channels, 512, stride=2, expand_ratio=0.2)]
25         layers += [InvertedResidual(512, 256, stride=2, expand_ratio=0.25)]
26         layers += [InvertedResidual(256, 256, stride=2, expand_ratio=0.5)]
27         layers += [InvertedResidual(256, 64, stride=2, expand_ratio=0.25)]
28         
29     return nn.ModuleList(layers)

由上图可知,我们分别取:

  • conv4_3的第三次卷积的特征;
  • fc7卷积的特征;
  • conv6的第二次卷积的特征(block6);
  • conv7的第二次卷积的特征(block7)
  • conv8的第二次卷积的特征(block8)
  • conv9的第二次卷积的特征(block9)

共六个特征层进行下一步的处理。为了和普通特征层区分,我们称之为有效特征层,来获取预测结果。

对获取到的每一个有效特征层,我们都需要对其做两个操作,分别是:

  • 一次卷积得到channel维度为:num_anchors x 4的featureMap(用于回归预测候选框的 x y w h)
  • 一次卷积得到channel维度为:num_anchors x num_classes的featureMap(用于分类预测候选框的置信度)

  而num_anchors指的是该特征层每一个特征点所拥有的先验框数量。上述提到的六个特征层,每个特征层的每个特征点对应的先验框数量分别为4、6、6、6、4、4。上述操作分别对应的对象为:

num_anchors x 4的卷积 用于预测 该特征层上 每一个网格点上 每一个先验框的变化情况。所以有:(4、6、6、6、4、4)*4 = (16、24、24、24、16、16)
num_anchors x num_classes的卷积 用于预测 该特征层上 每一个网格点上 每一个预测对应的种类。(4、6、6、6、4、4)*21 = (84、126、126、126、84、84)
所有的特征层对应的预测结果的shape如下:

  1 class SSD300(nn.Module):
  2     def __init__(self, num_classes, backbone_name, pretrained = False):
  3         super(SSD300, self).__init__()
  4         self.num_classes    = num_classes
  5         if backbone_name    == "vgg":
  6             self.vgg        = add_vgg(pretrained)
  7             self.extras     = add_extras(1024, backbone_name)
  8             self.L2Norm     = L2Norm(512, 20)
  9             mbox            = [4, 6, 6, 6, 4, 4]
 10             
 11             loc_layers      = []
 12             conf_layers     = []
 13             backbone_source = [21, -2]
 14             #---------------------------------------------------#
 15             #   在add_vgg获得的特征层里
 16             #   第21层和-2层可以用来进行回归预测和分类预测。
 17             #   分别是conv4-3(38,38,512)和conv7(19,19,1024)的输出
 18             #---------------------------------------------------#
 19             for k, v in enumerate(backbone_source):
 20                 loc_layers  += [nn.Conv2d(self.vgg[v].out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
 21                 conf_layers += [nn.Conv2d(self.vgg[v].out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
 22             #-------------------------------------------------------------#
 23             #   在add_extras获得的特征层里
 24             #   第1层、第3层、第5层、第7层可以用来进行回归预测和分类预测。
 25             #   shape分别为(10,10,512), (5,5,256), (3,3,256), (1,1,256)
 26             #-------------------------------------------------------------#  
 27             for k, v in enumerate(self.extras[1::2], 2):
 28                 loc_layers  += [nn.Conv2d(v.out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
 29                 conf_layers += [nn.Conv2d(v.out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
 30         else:
 31             self.mobilenet  = mobilenet_v2(pretrained).features
 32             self.extras     = add_extras(1280, backbone_name)
 33             self.L2Norm     = L2Norm(96, 20)
 34             mbox            = [6, 6, 6, 6, 6, 6]
 35 
 36             loc_layers      = []
 37             conf_layers     = []
 38             backbone_source = [13, -1]
 39             for k, v in enumerate(backbone_source):
 40                 loc_layers  += [nn.Conv2d(self.mobilenet[v].out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
 41                 conf_layers += [nn.Conv2d(self.mobilenet[v].out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
 42             for k, v in enumerate(self.extras, 2):
 43                 loc_layers  += [nn.Conv2d(v.out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
 44                 conf_layers += [nn.Conv2d(v.out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
 45 
 46         self.loc            = nn.ModuleList(loc_layers)
 47         self.conf           = nn.ModuleList(conf_layers)
 48         self.backbone_name  = backbone_name
 49         
 50     def forward(self, x):
 51         #---------------------------#
 52         #   x是300,300,3
 53         #---------------------------#
 54         sources = list()
 55         loc     = list()
 56         conf    = list()
 57 
 58         #---------------------------#
 59         #   获得conv4_3的内容
 60         #   shape为38,38,512
 61         #---------------------------#
 62         if self.backbone_name == "vgg":
 63             for k in range(23):
 64                 x = self.vgg[k](x)
 65         else:
 66             for k in range(14):
 67                 x = self.mobilenet[k](x)
 68         #---------------------------#
 69         #   conv4_3的内容
 70         #   需要进行L2标准化
 71         #---------------------------#
 72         s = self.L2Norm(x)
 73         sources.append(s)
 74 
 75         #---------------------------#
 76         #   获得conv7的内容
 77         #   shape为19,19,1024
 78         #---------------------------#
 79         if self.backbone_name == "vgg":
 80             for k in range(23, len(self.vgg)):
 81                 x = self.vgg[k](x)
 82         else:
 83             for k in range(14, len(self.mobilenet)):
 84                 x = self.mobilenet[k](x)
 85 
 86         sources.append(x)
 87         #-------------------------------------------------------------#
 88         #   在add_extras获得的特征层里
 89         #   第1层、第3层、第5层、第7层可以用来进行回归预测和分类预测。
 90         #   shape分别为(10,10,512), (5,5,256), (3,3,256), (1,1,256)
 91         #-------------------------------------------------------------#      
 92         for k, v in enumerate(self.extras):
 93             x = F.relu(v(x), inplace=True)
 94             if self.backbone_name == "vgg":
 95                 if k % 2 == 1:
 96                     sources.append(x)
 97             else:
 98                 sources.append(x)
 99 
100         #-------------------------------------------------------------#
101         #   为获得的6个有效特征层添加回归预测和分类预测
102         #-------------------------------------------------------------#      
103         for (x, l, c) in zip(sources, self.loc, self.conf):
104             loc.append(l(x).permute(0, 2, 3, 1).contiguous())
105             conf.append(c(x).permute(0, 2, 3, 1).contiguous())
106 
107         #-------------------------------------------------------------#
108         #   进行reshape方便堆叠
109         #-------------------------------------------------------------#  
110         loc     = torch.cat([o.view(o.size(0), -1) for o in loc], 1)
111         conf    = torch.cat([o.view(o.size(0), -1) for o in conf], 1)
112         #-------------------------------------------------------------#
113         #   loc会reshape到batch_size, num_anchors, 4
114         #   conf会reshap到batch_size, num_anchors, self.num_classes
115         #-------------------------------------------------------------#     
116         output = (
117             loc.view(loc.size(0), -1, 4),
118             conf.view(conf.size(0), -1, self.num_classes),
119         )
120         return output

  得到不同尺度每个网格中先验框的位置、置信度之后,后续训练和前向传播部署(YOLO的目标函数是将框的位置和置信度写在一个目标函数中,SSD也是),我就不多BB了,和yolo系列差不多。

   然后就是超参数有哪些????

VGG :

  如下图:以VGG-16为例子,特点是全是3*3卷积,参数相对少。然后maxpool后面必然会对featureMap的channel维度进行升维。因为maxpool会导致特征图信息丢失,所以后面接一个升维。

   如下左图,VGG-34比VGG-18训练、测试效果都差(没有给全),不是过拟合导致,而是层数加深,网络学偏了。ResNet的加入后,有明显的改观。

  • conv:特提特征,3*3提取得比5*5细粒度更细
  • relu:增加网络非线性
  • maxpool:降采样

反向传播:

      就一个链式求导+梯度下降。

参考:https://blog.csdn.net/weixin_38347387/article/details/82936585

      对于反向传播,真的不难。例如,对于某一次迭代,得到最终的loss,此时,已知中间网络任何一层输入、输出,包括每一层具体参数。对于第i层的权重参数wi,我们是可以求得loss对它的偏导f’,于是参数更新公式为:w = w – n*f’,其中n为学习率。

(思考:对于Focal loss,分类的错误的样本,loss大,而分类正确的loss值,loss小。Loss大,则一阶导数大,导致网络权重增量大,网络收敛更快。)

 

参考:https://blog.csdn.net/weixin_44791964/article/details/104981486

余思琪人脸检测:https://mp.weixin.qq.com/s?__biz=MzIyMDY2MTUyNg==&mid=2247483818&idx=1&sn=12e14dfd5154b35f14575d876785da76&chksm=97c9d3d3a0be5ac5a4e5cb05b23dd90bb73d57c2b3be6a70a139d8c28391a6185ec6297b6da0&scene=21#wechat_redirect

 

https://mp.weixin.qq.com/s?__biz=MzIzNTU5MzA1OQ==&mid=2247483937&idx=1&sn=df27185114ca8c4d5db55a65b26aed69&chksm=e8e58e2ddf92073b162e75c1525b85dbad2551893889d8a5d9dde29baf58a16e91fd5c263e64&mpshare=1&scene=21&srcid=&sharer_sharetime=1583410558996&sharer_shareid=8457d4b9d590baced734dd1ede727998&exportkey=AYPFSxpVXLxn1WLk54RH1VQ=&pass_ticket=FoKnbqhyZbDrumsm%20Z7QDd8kcPLIpaufOB8LggwGl71QJQygeG0J45UJB52tqXSg#wechat_redirect

 

https://mp.weixin.qq.com/s?__biz=MzIyMDY2MTUyNg%3D%3D&chksm=97c9d060a0be5976d73484081239c3b5acd346fdd32acdfe47ab9a5407e05470e8c050f628c8&idx=1&mid=2247483929&scene=21&sn=d453f0ebfa06289cade4d37a824519b6#wechat_redirect