解决方案

SSD网络模型详解

seo靠我 2023-09-23 14:15:41

一、主干网络为VGG16

 步骤理解

下面算一下每一层的像素值计算:

输入:300 * 300 * 3

conv3-64(卷积核的数量)-------------------------------------SEO靠我---kernel size:3 stride:1 padding:1

像素:(300 + 2 * 1 – 1 * (3 - 1)- 1 )/ 1 + 1=300 -------------------SEO靠我--输出尺寸:300 * 300 * 64

参数: (3 * 3 * 3)* 64 =1728

conv3-64----------------------------------------------SEO靠我---------------kernel size:3 stride:1 padding:1

像素: (300 + 2 * 1 – 2 - 1)/ 1 + 1=300 ----------------SEO靠我-----输出尺寸:300 * 300 * 64

参数: (3 * 3 * 64) * 64 =36864

pool2 ------------------------------------------SEO靠我----------------------kernel size:2 stride:2 padding:0

像素: (300 - 2)/ 2 = 112 -----------------------SEO靠我-----------输出尺寸:150 * 150 * 64

参数: 0

conv3-128(卷积核的数量)--------------------------------------------kernSEO靠我el size:3 stride:1 padding:1

像素: (150 + 2 * 1 - 2 - 1) / 1 + 1 = 150 -------------------输出尺寸:150 * 15SEO靠我0 * 128

参数: (3 * 3 * 64) * 128 =73728

conv3-128-------------------------------------------------------SEO靠我-----kernel size:3 stride:1 padding:1

像素: (150 + 2 * 1 -2 - 1) / 1 + 1 = 150 ---------------------输出尺SEO靠我寸:150 * 150 * 128

参数: (3 * 3 * 128) * 128 =147456

pool2-----------------------------------------------SEO靠我-------------------kernel size:2 stride:2 padding:0

像素: (150 - 2) / 2 + 1=75 ------------------------SEO靠我----------输出尺寸:75 * 75 * 128

参数:0

conv3-256(卷积核的数量)----------------------------------------------kerneSEO靠我l size:3 stride:1 padding:1

像素: (75 + 2 * 1 - 2 - 1)/ 1+1=75 -----------------------------输出尺寸:75 * 7SEO靠我5 * 256

参数:(3 * 3* 128)*256=294912

conv3-256----------------------------------------------------------SEO靠我---kernel size:3 stride:1 padding:1

像素: (75 + 2 * 1 - 2 - 1) / 1 + 1=75 --------------------------输出尺SEO靠我寸:75 * 75 * 256

参数:(3 * 3 * 256) * 256=589824

conv3-256-----------------------------------------------SEO靠我------------- kernel size:3 stride:1 padding:1

像素: (75 + 2 * 1 - 2 - 1) / 1 + 1=75 ------------------SEO靠我-----------输出尺寸:75 * 75 * 256

参数:(3 * 3 * 256)*256=589824

pool2---------------------------------------SEO靠我---------------------------kernel size:2 stride:2 padding:0

像素:(75 - 2) / 2 + 1 = 38-----------------SEO靠我--------------------输出尺寸: 38 * 38 * 256

参数:0

conv3-512(卷积核的数量)----------------------------------------SEO靠我--kernel size:3 stride:1 padding:1

像素:(38 + 2 * 1 - 2 - 1) / 1 + 1=38 ----------------------------输出尺SEO靠我寸:38 * 38 * 512

参数:(3 * 3 * 256) * 512 = 1179648

conv3-512--------------------------------------------SEO靠我-----------------kernel size:3 stride:1 padding:1

像素:(38 + 2 * 1 - 2 - 1) / 1 + 1=38 ----------------SEO靠我------------输出尺寸:38 * 38 * 512

参数:(3 * 3 * 512) * 512 = 2359296

conv3-512-----------------------------SEO靠我--------------------------------kernel size:3 stride:1 padding:1

像素:(38 + 2 * 1 - 2 - 1) / 1 + 1=38 -SEO靠我-----------输出尺寸:38 * 38 * 512第一个预测特征层

***Conv_4_3  第一个预测特征层

参数:(3 * 3 * 512) * 512 = 2359296

pool2-----SEO靠我------------------------------------------------------------- kernel size:2 stride:2 padding:0

像素:(38SEO靠我 - 2) / 2 + 1=19 -------------------------------------输出尺寸:19 * 19 * 512

参数: 0

conv3-512(卷积核的数量)------SEO靠我----------------------------------------kernel size:3 stride:1 padding:1

像素:(19 + 2 * 1 - 2 - 1) / 1 SEO靠我+ 1=19 ---------------------------输出尺寸:19 * 19 * 512

参数:(3 * 3 * 512) * 512 = 2359296

conv3-512-------SEO靠我------------------------------------------------------kernel size:3 stride:1 padding:1

像素:(19 + 2 * 1SEO靠我 - 2 - 1) / 1 + 1=19 ---------------------------输出尺寸:19 * 19 * 512

参数:(3 * 3 * 512) * 512 = 2359296

coSEO靠我nv3-512-------------------------------------------------------------kernel size:3 stride:1 padding:1SEO靠我

像素:(19 + 2 * 1 - 2 - 1) / 1 + 1=19 ---------------------------输出尺寸:19 * 19 *512   ***Conv_5_3

参数:(3 *SEO靠我 3 * 512) * 512 = 2359296

以上对应VGG16网络虚线以左的部分

pool2----------------------------------------------------SEO靠我--------------kernel size:3 stride:1 padding:1

***注意这里的池化层将原VGG16模型的kernel size:2 stride:2 padding:0变SEO靠我为了kernel size:3 stride:1 padding:1

像素:19 +2-2-1 / 1 + 1=19 ----------------------------------------输出SEO靠我尺寸:19 * 19 * 512

参数:0

conv3-1024-------------------------------------------------------------kernel siSEO靠我ze:3 stride:1 padding:1

像素:(19 + 2 * 1 - 2 - 1) / 1 + 1=19 ---------------------------输出尺寸:19 * 19 * SEO靠我1024

参数:(3 * 3 * 512) * 1024 = 4718592

得到Conv6(FC6)

conv1-1024-----------------------------------------SEO靠我--------------------kernel size:1 stride:1 padding:0

像素:(19 + 0 - 0 - 1) / 1 + 1=19 ----------输出尺寸:19SEO靠我 * 19 *1024 第二个预测特征层

参数:(1 * 1 * 1024) * 1024 = 1048576

得到Conv7(FC7) 第二个预测特征层

-------------------------SEO靠我-------------从这里往前都是VGG的结构--------------------------------------------

该代码用于获得VGG主干特征提取网络的输出。 SEO靠我 输入变量i代表的是输入图片的通道数,通常为3。300, 300, 3 -> 300, 300, 64 -> 300, 300, 64 -> 150, 150, 64 -> 150, 150, 128SEO靠我 -> 150, 150, 128 -> 75, 75, 128 -> 75, 75, 256 -> 75, 75, 256 -> 75, 75, 256 -> 38, 38, 256SEO靠我 -> 38, 38, 512 -> 38, 38, 512 -> 38, 38, 512 -> 19, 19, 512 -> 19, 19, 512 -> 19, 19, 512 -SEO靠我> 19, 19, 512 -> 19, 19, 512 -> 19, 19, 1024 -> 19, 19, 102438, 38, 512的序号是22 19, 19, 1024的序SEO靠我号是34 base = [64, 64, M, 128, 128, M, 256, 256, 256, C, 512, 512, 512, M,512, 512, 512]def vSEO靠我gg(pretrained = False):layers = []in_channels = 3for v in base:if v == M: #最大池化layers += [nn.MaxPoolSEO靠我2d(kernel_size=2, stride=2)]elif v == C: #开启ceil_mode的最大池化layers += [nn.MaxPool2d(kernel_size=2, strSEO靠我ide=2, ceil_mode=True)]else: #卷积加激活函数conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)laySEO靠我ers += [conv2d, nn.ReLU(inplace=True)]in_channels = v# 19, 19, 512 -> 19, 19, 512 pool5 = nn.MaxPoolSEO靠我2d(kernel_size=3, stride=1, padding=1) #步距为1的最大池化,不会进行高和宽的压缩# 19, 19, 512 -> 19, 19, 1024conv6 = nn.SEO靠我Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)# 19, 19, 1024 -> 19, 19, 1024conv7 = nn.ConvSEO靠我2d(1024, 1024, kernel_size=1)layers += [pool5, conv6, nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=SEO靠我True)] #conv6,conv7卷积后都跟有激活函数model = nn.ModuleList(layers)if pretrained:state_dict = load_state_dictSEO靠我_from_url("https://download.pytorch.org/models/vgg16-397923af.pth", model_dir="./model_data")state_dSEO靠我ict = {k.replace(features., ) : v for k, v in state_dict.items()}model.load_state_dict(state_dict, sSEO靠我trict = False)return modelif __name__ == "__main__":net = vgg()for i, layer in enumerate(net): # i对应SEO靠我的是层名称,layer对应的是层结构print(i, layer)

打印vgg各层,21层对应的是预测特征层1,33层对应的是预测特征层2

0 Conv2d(3, 64, kernel_size=(3, SEO靠我3), stride=(1, 1), padding=(1, 1))

1 ReLU(inplace=True)

2 Conv2d(64, 64, kernel_size=(3, 3), stride=(1SEO靠我, 1), padding=(1, 1))

3 ReLU(inplace=True)

4 MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1,SEO靠我 ceil_mode=False)

5 Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

6 ReLU(inplace=SEO靠我True)

7 Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

8 ReLU(inplace=True)

9 MaxPSEO靠我ool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)

10 Conv2d(128, 256, kernel_sizSEO靠我e=(3, 3), stride=(1, 1), padding=(1, 1))

11 ReLU(inplace=True)

12 Conv2d(256, 256, kernel_size=(3, 3),SEO靠我 stride=(1, 1), padding=(1, 1))

13 ReLU(inplace=True)

14 Conv2d(256, 256, kernel_size=(3, 3), stride=(SEO靠我1, 1), padding=(1, 1))

15 ReLU(inplace=True)

16 MaxPool2d(kernel_size=2, stride=2, padding=0, dilationSEO靠我=1, ceil_mode=True)

17 Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

18 ReLU(inpSEO靠我lace=True)

19 Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

20 ReLU(inplace=TrueSEO靠我)

21 Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

22 ReLU(inplace=True)

23 MaxPoSEO靠我ol2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)

24 Conv2d(512, 512, kernel_sizeSEO靠我=(3, 3), stride=(1, 1), padding=(1, 1))

25 ReLU(inplace=True)

26 Conv2d(512, 512, kernel_size=(3, 3), SEO靠我stride=(1, 1), padding=(1, 1))

27 ReLU(inplace=True)

28 Conv2d(512, 512, kernel_size=(3, 3), stride=(1SEO靠我, 1), padding=(1, 1))

29 ReLU(inplace=True)

30 MaxPool2d(kernel_size=3, stride=1, padding=1, dilation=SEO靠我1, ceil_mode=False)

31 Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(6, 6), dilation=SEO靠我(6, 6))

32 ReLU(inplace=True)

33 Conv2d(1024, 1024, kernel_size=(1, 1), stride=(1, 1))

34 ReLU(inplace=SEO靠我True)

conv1-256-------------------------------------------------------------kernel size:1 stride:1 paSEO靠我dding:0

像素:(19 + 0 - 0 - 1) / 1 + 1=19 ---------------------------输出尺寸:19 * 19 * 256

参数:(1 * 1 * 1024)SEO靠我 * 256 = 262144

conv3-512-------------------------------------------------------------kernel size:3 sSEO靠我tride:2 padding:1

像素:(19 + 2 * 1 - 2 - 1) / 2 + 1=10 ------输出尺寸:10 * 10 * 512 第三个预测特征层

参数:(3 * 3 * 256SEO靠我) * 512 = 1179648

得到Conv8-2 第三个预测特征层

conv1-128--------------------------------------------------------SEO靠我-----kernel size:1 stride:1 padding:0

像素:(10 + 0 - 0 - 1) / 1 + 1=10 ---------------------------输出尺寸:SEO靠我10 * 10 * 128

参数:(1 * 1 * 512) * 128 = 65536

conv3-256------------------------------------------------SEO靠我-------------kernel size:3 stride:2 padding:1

像素:(10 + 2 * 1 - 2 - 1) / 2 + 1=5 ------输出尺寸:5 * 5 *256SEO靠我 第四个预测特征层

参数:(3 * 3 * 128) * 256 = 294912

得到Conv9-2 第四个预测特征层

conv1-128---------------------------------SEO靠我----------------------------kernel size:1 stride:1 padding:0

像素:(5 + 0 - 0 - 1) / 1 + 1=5 -----------SEO靠我----------------输出尺寸:5 * 5 * 128

参数:(1 * 1 * 256) * 128 = 32768

conv3-256-----------------------------SEO靠我--------------------------------kernel size:3 stride:1 padding:0

像素:(5 + 0 - 2 - 1) / 1 + 1=3 ------输SEO靠我出尺寸:3 * 3 *256 第五个预测特征层

参数:(3 * 3 * 128) * 256 = 294912

得到Conv10-2  第五个预测特征层

conv1-128-----------------SEO靠我--------------------------------------------kernel size:1 stride:1 padding:0

像素:(3 + 0 - 0 - 1) / 1 +SEO靠我 1=3 ---------------------------输出尺寸:3 * 3 * 128

参数:(1 * 1 * 256) * 128 = 32768

conv3-256-------------SEO靠我------------------------------------------------kernel size:3 stride:1 padding:0

像素:(3 + 0 - 2 - 1) /SEO靠我 1 + 1=1 ------输出尺寸:1 * 1 *256 第六个预测特征层

参数:(3 * 3 * 128) * 256 =  294912

得到Conv11-2  第六个预测特征层

def add_eSEO靠我xtras(in_channels, backbone_name): # 构建额外卷积层(预测特征层)layers = []if backbone_name == vgg:# Block 6# 19,SEO靠我19,1024 -> 19,19,256 -> 10,10,512 预测特征层3layers += [nn.Conv2d(in_channels, 256, kernel_size=1, strideSEO靠我=1)] #通过1*1卷积降低通道数,减少运算量layers += [nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1)] #获得特征层10SEO靠我,10,512# Block 7# 10,10,512 -> 10,10,128 -> 5,5,256 预测特征层4layers += [nn.Conv2d(512, 128, kernel_sizeSEO靠我=1, stride=1)] #通过1*1卷积降低通道数,减少运算量layers += [nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1)SEO靠我] #获得特征层5,5,256# Block 8# 5,5,256 -> 5,5,128 -> 3,3,256 预测特征层5layers += [nn.Conv2d(256, 128, kernel_SEO靠我size=1, stride=1)] #通过1*1卷积降低通道数,减少运算量layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)] #获得特征SEO靠我层3,3,256# Block 9# 3,3,256 -> 3,3,128 -> 1,1,256 预测特征层6layers += [nn.Conv2d(256, 128, kernel_size=1,SEO靠我 stride=1)] #通过1*1卷积降低通道数,减少运算量layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)] #获得特征层1,1,25SEO靠我6return nn.ModuleList(layers)

 SSD整体网络结构(VGG16)

class SSD300(nn.Module):def __init__(self, num_classes,SEO靠我 backbone_name, pretrained = False):super(SSD300, self).__init__()self.num_classes = num_classesif bSEO靠我ackbone_name == "vgg":self.vgg = add_vgg(pretrained) #对应vgg主干网络self.extras = add_extras(1024, backboSEO靠我ne_name) #对应四个额外添加层self.L2Norm = L2Norm(512, 20)mbox = [4, 6, 6, 6, 4, 4]loc_layers = []conf_layers SEO靠我= []backbone_source = [21, -2]#---------------------------------------------------## 在add_vgg获得的特征层里SEO靠我# 第21层和-2层对应预测特征层38, 38, 512和19, 19, 1024# 分别是conv4-3(38,38,512)和conv7(19,19,1024)的输出# 第21层和-2层可以用来进SEO靠我行回归预测和分类预测。# k=0,v=21;k=1,v=-2; self.vgg[v].out_channels代表vgg中第v层的out_channelsfor k, v in enumerate(SEO靠我backbone_source): #可获取前两个预测特征层# 回归预测结果,输出通道数为mbox[k] * 4loc_layers += [nn.Conv2d(self.vgg[v].out_chaSEO靠我nnels, mbox[k] * 4, kernel_size = 3, padding = 1)]# 分类预测结果,输出通道数为mbox[k] * num_classesconf_layers +=SEO靠我 [nn.Conv2d(self.vgg[v].out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]#--------SEO靠我-----------------------------------------------------## 在add_extras获得的特征层里# 第1层、第3层、第5层、第7层可以用来进行回归预SEO靠我测和分类预测(注意是从零开始数的)。# shape分别为(10,10,512), (5,5,256), (3,3,256), (1,1,256)# [1::2]代表从第2个元素起,步长为2取元素;也就SEO靠我是取第1、3、5、7层# enumerate(self.extras[1::2], 2)后面这个2表示k从2开始# k=2对应extras的第一层,k=3对应extras的第三层,k=4对应extraSEO靠我s的第五层,k=5对应extras的第七层# v.out_channels分别表示extras的第1、3、5、7层的out_channelsfor k, v in enumerate(self.extSEO靠我ras[1::2], 2): #每隔两个卷积获得预测特征层,可获取后四个预测特征层# 回归预测结果,输出通道数为mbox[k] * 4loc_layers += [nn.Conv2d(v.out_chSEO靠我annels, mbox[k] * 4, kernel_size = 3, padding = 1)]# 分类预测结果,输出通道数为mbox[k] * num_classesconf_layers +SEO靠我= [nn.Conv2d(v.out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]self.loc = nn.ModuSEO靠我leList(loc_layers)self.conf = nn.ModuleList(conf_layers)self.backbone_name = backbone_namedef forwarSEO靠我d(self, x): #正向传播过程#---------------------------## x是300,300,3#---------------------------#sources = SEO靠我list()loc = list()conf = list()#---------------------------## 获得conv4_3的内容# shape为38,38,512#--------SEO靠我-------------------#if self.backbone_name == "vgg":for k in range(23): #循环vgg网络的前22层,也就是获取对应38,38,51SEO靠我2特征层x = self.vgg[k](x)#---------------------------## conv4_3的内容# 需要进行L2标准化#-------------------------SEO靠我--#s = self.L2Norm(x) #进行L2标准化sources.append(s) #加入sources#---------------------------## 获得conv7的内容#SEO靠我 shape为19,19,1024#---------------------------#if self.backbone_name == "vgg":for k in range(23, len(SEO靠我self.vgg)): #获取19,19,1024特征层x = self.vgg[k](x)sources.append(x) #加入sources#-------------------------SEO靠我------------------------------------## 在add_extras获得的特征层里# 因为是从第0层开始算的,所以我们需要获取第1,3,5,7层# 第1层、第3层、第5SEO靠我层、第7层可以用来进行回归预测和分类预测# shape分别为(10,10,512), (5,5,256), (3,3,256), (1,1,256)#-------------------------SEO靠我------------------------------------# for k, v in enumerate(self.extras): #获取四个额外预测特征层x = F.relu(v(xSEO靠我), inplace=True)if self.backbone_name == "vgg":if k % 2 == 1: #获取第1层、第3层、第5层、第7层sources.append(x) #将SEO靠我这四个特征层加入sources#-------------------------------------------------------------## 为获得的6个有效特征层添加回归预测和分类SEO靠我预测#-------------------------------------------------------------# for (x, l, c) in zip(sources, selfSEO靠我.loc, self.conf):loc.append(l(x).permute(0, 2, 3, 1).contiguous())conf.append(c(x).permute(0, 2, 3, SEO靠我1).contiguous())#-------------------------------------------------------------## 进行reshape方便堆叠#-----SEO靠我--------------------------------------------------------# loc = torch.cat([o.view(o.size(0), -1) forSEO靠我 o in loc], 1)conf = torch.cat([o.view(o.size(0), -1) for o in conf], 1)#---------------------------SEO靠我----------------------------------## loc会reshape到batch_size, num_anchors, 4# conf会reshap到batch_size,SEO靠我 num_anchors, self.num_classes#-------------------------------------------------------------# outputSEO靠我 = (loc.view(loc.size(0), -1, 4),conf.view(conf.size(0), -1, self.num_classes),)return output

在上述六个预测SEO靠我特征层上分别预测不同大小的目标,38 * 38 * 512负责预测相对较小的目标,1 * 1 *256负责预测相对较大的目标

特征层一、五、六,这三个预测特征层采用4个default box

特征层二、三SEO靠我、四,这三个预测特征层采用6个default box

二、主干网络为Resnet50 

Resnet50主干网络(只用了layer3及其之前层)

class Bottleneck(nn.Module):expSEO靠我ansion = 4def __init__(self, in_channel, out_channel, stride=1, downsample=None,):super(Bottleneck, SEO靠我self).__init__()self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,kernel_size=SEO靠我1, stride=1, bias=False) # squeeze channelsself.bn1 = nn.BatchNorm2d(out_channel)# -----------------SEO靠我------------------------self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,kerSEO靠我nel_size=3, stride=stride, bias=False, padding=1)self.bn2 = nn.BatchNorm2d(out_channel)# -----------SEO靠我------------------------------self.conv3 = nn.Conv2d(in_channels=out_channel, out_channels=out_channSEO靠我el*self.expansion,kernel_size=1, stride=1, bias=False) # unsqueeze channelsself.bn3 = nn.BatchNorm2dSEO靠我(out_channel*self.expansion)self.relu = nn.ReLU(inplace=True)self.downsample = downsampleself.strideSEO靠我 = stridedef forward(self, x):identity = xif self.downsample is not None:identity = self.downsample(SEO靠我x)out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)SEO靠我out = self.relu(out)out = self.conv3(out)out = self.bn3(out)out += identityout = self.relu(out)returSEO靠我n outclass ResNet(nn.Module):def __init__(self, block, blocks_num, num_classes=1000, include_top=TruSEO靠我e):super(ResNet, self).__init__()self.include_top = include_topself.in_channel = 64self.conv1 = nn.CSEO靠我onv2d(3, self.in_channel, kernel_size=7, stride=2,padding=3, bias=False)self.bn1 = nn.BatchNorm2d(seSEO靠我lf.in_channel)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, SEO靠我padding=1)self.layer1 = self._make_layer(block, 64, blocks_num[0])self.layer2 = self._make_layer(bloSEO靠我ck, 128, blocks_num[1], stride=2)self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)SEO靠我self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)if self.include_top:self.avgpool SEO靠我= nn.AdaptiveAvgPool2d((1, 1)) # output size = (1, 1)self.fc = nn.Linear(512 * block.expansion, num_SEO靠我classes)for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode=fSEO靠我an_out, nonlinearity=relu)def _make_layer(self, block, channel, block_num, stride=1):downsample = NoSEO靠我neif stride != 1 or self.in_channel != channel * block.expansion:downsample = nn.Sequential(nn.Conv2SEO靠我d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNormSEO靠我2d(channel * block.expansion))layers = []layers.append(block(self.in_channel, channel, downsample=doSEO靠我wnsample, stride=stride))self.in_channel = channel * block.expansionfor _ in range(1, block_num):laySEO靠我ers.append(block(self.in_channel, channel))return nn.Sequential(*layers)def forward(self, x):x = selSEO靠我f.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = SEO靠我self.layer3(x)x = self.layer4(x)if self.include_top:x = self.avgpool(x)x = torch.flatten(x, 1)x = seSEO靠我lf.fc(x)return xdef resnet50(num_classes=1000, include_top=True):return ResNet(Bottleneck, [3, 4, 6,SEO靠我 3], num_classes=num_classes, include_top=include_top) class Backbone(nn.Module):def __iniSEO靠我t__(self, pretrain_path=None):super(Backbone, self).__init__()net = resnet50() #实例化resnet50self.out_SEO靠我channels = [1024, 512, 512, 256, 256, 256] #对应每个预测特征层的channelif pretrain_path is not None: #是否传入了与训练SEO靠我模型权重net.load_state_dict(torch.load(pretrain_path))# 构建特征提取部分,提取net.children()中0到6层,分别是conv1 bn1 reluSEO靠我 maxpool layer1 layer2 layer3self.feature_extractor = nn.Sequential(*list(net.children())[:7])# 对feaSEO靠我ture_extractor中的最后一层也就是conv4_block1中的第一个残差块修改步距conv4_block1 = self.feature_extractor[-1][0]# 修改conv4SEO靠我_block1的步距,从2->1conv4_block1.conv1.stride = (1, 1) #这一行可以不要,因为本来步距就为1conv4_block1.conv2.stride = (1,SEO靠我 1)conv4_block1.downsample[0].stride = (1, 1)def forward(self, x):x = self.feature_extractor(x)returSEO靠我n xclass SSD300(nn.Module):def __init__(self, backbone=None, num_classes=21):super(SSD300, self).__iSEO靠我nit__()if backbone is None:raise Exception("backbone is None")if not hasattr(backbone, "out_channelsSEO靠我"):raise Exception("the backbone not has attribute: out_channel")self.feature_extractor = backbone #SEO靠我将backbone赋给变量feature_extractorself.num_classes = num_classes# out_channels = [1024, 512, 512, 256, 2SEO靠我56, 256] for resnet50self._build_additional_features(self.feature_extractor.out_channels)self.num_deSEO靠我faults = [4, 6, 6, 6, 4, 4] #每一个预测特征层上每个网格所生成预测框的数量location_extractors = [] #位置预测confidence_extractoSEO靠我rs = [] #置信度预测# feature_extractor.out_channels = [1024, 512, 512, 256, 256, 256] for resnet50for nd,SEO靠我 oc in zip(self.num_defaults, self.feature_extractor.out_channels):# nd is number_default_boxes, oc SEO靠我is output_channellocation_extractors.append(nn.Conv2d(oc, nd * 4, kernel_size=3, padding=1))confidenSEO靠我ce_extractors.append(nn.Conv2d(oc, nd * self.num_classes, kernel_size=3, padding=1))self.loc = nn.MoSEO靠我duleList(location_extractors)self.conf = nn.ModuleList(confidence_extractors)self._init_weights() #对SEO靠我额外的添加层结构和预测器进行权重初始化default_box = dboxes300_coco()self.compute_loss = Loss(default_box)self.encoder =SEO靠我 Encoder(default_box)self.postprocess = PostProcess(default_box)def _build_additional_features(self,SEO靠我 input_size): #input_size就是这六个预测特征层的channels"""为backbone(resnet50)添加额外的一系列卷积层,得到相应的一系列特征提取器:param inSEO靠我put_size::return:"""additional_blocks = []# input_size = [1024, 512, 512, 256, 256, 256] for resnet5SEO靠我0middle_channels = [256, 256, 128, 128, 128] #后五个额外添加层中第一个卷积层的channels# input_ch=[1024, 512, 512, 25SEO靠我6, 256]# output_ch=[512, 512, 256, 256, 256]for i, (input_ch, output_ch, middle_ch) in enumerate(zipSEO靠我(input_size[:-1], input_size[1:], middle_channels)):padding, stride = (1, 2) if i < 3 else (0, 1)laySEO靠我er = nn.Sequential(# layer1,2,3,4,5中的第一个卷积padding=0, stride=1nn.Conv2d(input_ch, middle_ch, kernel_sSEO靠我ize=1, bias=False),nn.BatchNorm2d(middle_ch),nn.ReLU(inplace=True),# layer1,2,3中的第二个卷积padding=1, strSEO靠我ide=2;layer4,5中的第二个卷积padding=0, stride=1nn.Conv2d(middle_ch, output_ch, kernel_size=3, padding=paddiSEO靠我ng, stride=stride, bias=False),nn.BatchNorm2d(output_ch),nn.ReLU(inplace=True),)additional_blocks.apSEO靠我pend(layer) #添加进列表additional_blocks = []中self.additional_blocks = nn.ModuleList(additional_blocks)deSEO靠我f _init_weights(self): #对额外的添加层结构和预测器进行权重初始化layers = [*self.additional_blocks, *self.loc, *self.confSEO靠我]for layer in layers:for param in layer.parameters():if param.dim() > 1:nn.init.xavier_uniform_(paraSEO靠我m)# Shape the classifier to the view of bboxesdef bbox_view(self, features, loc_extractor, conf_extrSEO靠我actor):locs = []confs = []# f对应每一个预测特征层,l对应每一个Feature Map的location特征预测器,c对应每一个Feature Map的confidenceSEO靠我特征预测器# 通过下面这个for循环,得到了所有预测特征层上的locs和confs回归参数for f, l, c in zip(features, loc_extractor, conf_extracSEO靠我tor):# [batch, n*4, feat_size, feat_size] -> [batch, 4, -1]locs.append(l(f).view(f.size(0), 4, -1)) SEO靠我#通过view方法调整格式,-1表示自动推理# [batch, n*classes, feat_size, feat_size] -> [batch, classes, -1]confs.appendSEO靠我(c(f).view(f.size(0), self.num_classes, -1)) #通过view方法调整格式,-1表示自动推理# 将locs, confs都在维度2上拼接locs, confsSEO靠我 = torch.cat(locs, 2).contiguous(), torch.cat(confs, 2).contiguous()return locs, confsdef forward(seSEO靠我lf, image, targets=None): #正向传播过程x = self.feature_extractor(image) #conv_4得到的预测特征层38x38x1024# FeaturSEO靠我e Map 38x38x1024, 19x19x512, 10x10x512, 5x5x256, 3x3x256, 1x1x256detection_features = torch.jit.annoSEO靠我tate(List[Tensor], []) # [x]detection_features.append(x) #将Feature Map 38x38x1024加入detection_featureSEO靠我s中for layer in self.additional_blocks:x = layer(x) #将Feature Map 38x38x1024依次通过五个额外添加层detection_featSEO靠我ures.append(x) #并把每一层输出添加进detection_features中# Feature Map 38x38x4, 19x19x6, 10x10x6, 5x5x6, 3x3x4, SEO靠我1x1x4locs, confs = self.bbox_view(detection_features, self.loc, self.conf)# For SSD 300, shall returSEO靠我n nbatch x 8732 x {nlabels, nlocs} results# 38x38x4 + 19x19x6 + 10x10x6 + 5x5x6 + 3x3x4 + 1x1x4 = 87SEO靠我32if self.training:if targets is None:raise ValueError("In training mode, targets should be passed")SEO靠我# bboxes_out (Tensor 8732 x 4), labels_out (Tensor 8732)bboxes_out = targets[boxes]bboxes_out = bboxSEO靠我es_out.transpose(1, 2).contiguous()# print(bboxes_out.is_contiguous())labels_out = targets[labels]# SEO靠我print(labels_out.is_contiguous())# ploc, plabel, gloc, glabelloss = self.compute_loss(locs, confs, bSEO靠我boxes_out, labels_out)return {"total_losses": loss}# 将预测回归参数叠加到default box上得到最终预测box,并执行非极大值抑制虑除重叠框#SEO靠我 results = self.encoder.decode_batch(locs, confs)results = self.postprocess(locs, confs)return resulSEO靠我ts

reference

2.2 SSD源码解析(Pytorch)_哔哩哔哩_bilibili

Pytorch 搭建自己的SSD目标检测平台(Bubbliiiing 深度学习 教程)_哔哩哔哩_bilibiSEO靠我li
“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2