目錄:
一、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