目錄
1、引言(摘要)
1.1 說明
1.2替換完成的工程請參考gitee
2、網路結構基礎
2.1YOLOv3
2.1 YOLOv4演算法
2.3 ShuffleNetv2
2.4 替換后的網路結構
3 實驗結果
3.1實驗環境配置及資料集介紹
3.1.1資料集介紹
3.1.2實驗驗環境配置
3.2實驗方案
3.3實驗結果對比
3.3.1ShuffleNetv2-YOLOv4的訓練結果
3.3.2部分訓練結果展示
3.3.3各個演算法模型對比(很多引數是錯的 看看就好 一些資料 比如map從大佬的github直接拷貝)
4、部分代碼演示
4.1 shufflenetv2(來自torchvrsion)
4.2 yolo本體
1、引言(摘要)
目標檢測是機器視覺領域內最具挑戰性的任務之一,近年來,深度學習理論及技術的快速發展,使得基于深度學習的目標檢測演算法取得了巨大進展,目標檢測實時性、準確度得到了很大的提高,但是除了準確度外,計算復雜度也是目標檢測要考慮的重要指標,過復雜的網路可能速度很慢,另外移動端設備也需要既準確又快的小模型,研究輕量化的網路模型是很有必要的,結合前人的成果、及現有的工程,我通過將輕量化網路ShuffleNetv2 代替YoloV4的CSPDarknet-53 作為主干網路,結合一定調參方法,在VOC2007與2012進行訓練,實驗證明,該輕量級YOLOv4模型效果還可以,但相比其他型別的輕量級網路仍然有差距,誠然如此,但我認為這是一次有意義的探索,
1.1 說明
該工程是借鑒了Bubbliiiing的學習小課堂_Bubbliiiing_CSDN博客-神經網路學習小記錄,睿智的目標檢測,有趣的資料結構演算法領域博主Bubbliiiing擅長神經網路學習小記錄,睿智的目標檢測,有趣的資料結構演算法,等方面的知識https://blog.csdn.net/weixin_44791964?spm=1001.2014.3001.5509
的專案
GitHub - bubbliiiing/mobilenet-yolov4-pytorch: 這是一個mobilenet-yolov4的庫,把yolov4主干網路修改成了mobilenet,修改了Panet的卷積組成,使引數量大幅度縮小,這是一個mobilenet-yolov4的庫,把yolov4主干網路修改成了mobilenet,修改了Panet的卷積組成,使引數量大幅度縮小, - GitHub - bubbliiiing/mobilenet-yolov4-pytorch: 這是一個mobilenet-yolov4的庫,把yolov4主干網路修改成了mobilenet,修改了Panet的卷積組成,使引數量大幅度縮小,
https://github.com/bubbliiiing/mobilenet-yolov4-pytorch由于我并非計算機科班出身,所以難免有錯誤,請借鑒的同學擦亮眼睛,取其精華,去其糟粕(不用私信我,因為我可能也沒時間看,看了也不一定答得上來),
1.2替換完成的工程請參考gitee
yolov4-lite-pytorch: yolov4-lite-pytorch
2、網路結構基礎
YOLOv4 演算法是在原有的YOLO 目標檢測架構的基礎上,分別在加強特征提取、增強網路模型的非線性、防止過度擬合等方面進行了優化,可謂是集CNN 百家之長,談到YOLOv4,要從YOLOv3入手,
2.1YOLOv3
YOLOv3 是 YOLO 系列演算法的第三代改進演算法,YOLOv3 采用 Darknet-53作為特征提取的主干網路(Backbone),Darknet-53 主干網路由五層殘差網路(Resnet, Res)構成,每個殘差網路由若干個殘差塊(Res Unit)、影像填充(Padding)與 CBL 組件級聯組成,CBL 殘差網路、殘差塊、CBL 組件的組成如圖 所示,每一層殘差網路中的殘差塊個數不同,隨著網路逐層加深,殘差塊數逐漸增多,特征送入 Res Unit 后,Res Unit 將輸入特征分別送入兩個通道:首先,特征經由采樣通道,分別經過卷積核大小為1 × 1的卷積操作調整維度,再經3 × 3的卷積提取高維特征;同時,輸入特征經由另一通道直接輸出,保留了輸入的原始特征;最后,將兩通道的特征相加,得到輸出特征,CBL 是 YOLO 系列演算法中提取特征的基本組件,用于代替原始的卷積程序,CBL由卷積、批正則化(Batch Normalization[47])以及 Leaky Relu激活函式構成,相較于普通的卷積操作,CBL具有更好的特征擬合能力,

詳情請看Bubbliiiing的學習小課堂_Bubbliiiing_CSDN博客-神經網路學習小記錄,睿智的目標檢測,有趣的資料結構演算法領域博主大佬文章睿智的目標檢測26——Pytorch搭建yolo3目標檢測平臺_Bubbliiiing的學習小課堂-CSDN博客_目標檢測26
2.1 YOLOv4演算法
YOLOv4 演算法的網路結構如圖1 所示,其主干特征提取網路CSPDarkNet53 結合了CSPNet和 DarkNet53 的優點,在提高網路推理速度和準確性的同時,又能降低主干網路的計算量,相較于之前的YOLOv3,YOLOv4 的頸部網路(NECK)采用SPP(空間金字塔池化)+PANET(路徑聚合網路)的網路結構,SPP(Spatial Pyramid Pooling),可以使得圖片資料矩陣以固定大小的格式輸出到YOLOv4 的預測網路模塊(Head),避免直接對圖片進行剪裁縮放造成資料資訊丟失,進而導致預測網路可信度降低的問題,同時,在提取圖片各維度特征方面,不再使用上一代演算法中的特征金字塔網路(Feature Pyramid Networks ,FPN),而是采用一種金字塔和倒金字塔并行的網路結構, 即路徑聚合網路( Path AggregationNetwork,PANET),

具體詳細分析請看Bubbliiiing的學習小課堂_Bubbliiiing_CSDN博客-神經網路學習小記錄,睿智的目標檢測,有趣的資料結構演算法領域博主大佬的文章
睿智的目標檢測30——Pytorch搭建YoloV4目標檢測平臺_Bubbliiiing的學習小課堂-CSDN博客_yolov4目標檢測
2.3 ShuffleNetv2
ShuffleNetv2 是在 ShuffleNetv1 基礎上提出的升級版本,并且在同等復雜度下比ShuffleNetv1 和 MobileNetv2 地準確度更高,該模型的研究者提出僅使用每秒計算浮點數(FLOPs)并不能準確衡量模型的復雜度和速度,根據實驗結果提出了 4 個適用原則:(1)channel 大小相同時使用最小的記憶體;(2)組卷積使用過多會增多記憶體使用量;(3)模型碎片化會降低并行度;(4)元素級的運算非常重要,ShuffleNetv2 模型借鑒了 DenseNet 結構,使用 Concat 方操作代替了 DenseNet 結構中的 Add 操作,可以實作特征的重用,與 DenseNet 的不同之處在于:ShuffleNetv2 不是密集的 Concat,并且在 Concat 操作后通過 Channel Shuffle 層來混洗特征,這也是 ShuffleNetv2的速度和精度都優于 ShuffleNetv1 的一個重要原因,
ShuffleNet V2 網路模型的基本組成單元大致可分為兩種,第一種如圖2 中a 部分所示,在特征圖輸入后有一個通道分支(channel split)操作,該操作將輸入通道數為c 的特征圖分為c?c′和c′,左邊的分支不做任何操作,右邊的分支包含了3 個卷積操作,并且兩個1*1 卷積已經由ShuffleNetv1 中的分組卷積更換為普通卷積,最后再將這兩個分支通道中的資料用Concat+Channel Shuffle操作進行合并,這樣不僅可以使得該基礎模塊的輸入輸出通道數一樣,而且避免Add操作,加快模型了的推理速度,最后進行通道重組(channelshuffle)操作,值得注意的是,b中沒有了channel split操作,因此該基本模塊的輸出通道數是輸入通道數的兩倍,其左右分支的操作程序和a 基本一致,此處不在贅述,

詳情請看Bubbliiiing的學習小課堂_Bubbliiiing_CSDN博客-神經網路學習小記錄,睿智的目標檢測,有趣的資料結構演算法領域博主大佬文章
神經網路學習小記錄47——ShuffleNetV2模型的復現詳解_Bubbliiiing的學習小課堂-CSDN博客_shufflenetv2模型
2.4 替換后的網路結構
ShufflenetV2系列網路可用于進行分類,其主干部分的作用是進行特征提取,我們可以使用ShufflenetV2系列網路代替YOLOv4當中的CSPdarknet53進行特征提取,將三個初步的有效特征層相同shape的特征層進行加強特征提取,便可以將ShufflenetV2系列替換進YOLOv4當中了,
對于YOLOv4來講,我們需要取出它的最后三個shape的有效特征層進行加強特征提取,在代碼中,我們取出了out1、out2、out3,

替換教程請看Bubbliiiing的學習小課堂_Bubbliiiing_CSDN博客-神經網路學習小記錄,睿智的目標檢測,有趣的資料結構演算法領域博主大佬的文章
睿智的目標檢測49——Pytorch 利用mobilenet系列(v1,v2,v3)搭建yolov4目標檢測平臺_Bubbliiiing的學習小課堂-CSDN博客
3 實驗結果
3.1實驗環境配置及資料集介紹
3.1.1資料集介紹
PASCAL VOC[82](The PASCAL Visual Object Classes Challenge)曾經是計算機視覺領域中一個世界級的挑戰賽,促進了計算機視覺中目標檢測和語意分割等任務的發展,催生了大量杰出的演算法模型,
PASCAL VOC資料集共有20個類,包含了生活中常見的物體,其中鳥、瓶子、盆栽植物等屬于尺寸較小的物體,目前較為常用的是 PASCAL VOC 2007 和 PASCAL VOC 2012 兩種資料集,其中 PASCAL VOC 2007 包含訓練集 5011 張,驗證集 4952張,PASCAL VOC 2012 包含訓練集 5717 張,測驗集 5823 張,
這里將VOC2007與VOC2012的資料集進行了合并訓練,所以資料集的容量為21503張圖片,在每輪訓練時都會取90%的照片用于訓練,另外10%的照片實時檢測訓練效果,
即是:訓練集:測驗集=9:1;訓練集中(訓練集:驗證集9:1),
3.1.2實驗驗環境配置
實驗平臺
由于條件受限,訓練所用的是筆記本平臺:RTX3060(6G)+16G記憶體 windows10系統,與 CPU 相比,GPU 有并行處理架構,因此可以進行更高效、速度更快的運算,適合處理影像這種引數量大,計算復雜的資料,
演算法框架
運用Anconda3(Python3.8)+Pytorch1.8+Cuda11.2軟體進行搭建,
Pytorch 是基于 Torch 框架開發,使用 Python 語言作為底層代碼,且支持動態網路調整,相比于 Tensorflow,Pytorch結構更加簡潔清晰,易開發除錯,綜合上手難度,代碼繁雜程度以及開發靈活性考慮,本文選用Pytorch 作為本文實驗環境的框架,
3.2實驗方案
本實驗共分為三部分包含1組實驗,本實驗包含五個模型:ShuffleNetv2-YOLOv4、MobileNetv1-YOLOv4、MobileNetv2-YOLOv4、MobileNetv3-YOLOv4、YOLOv4-Tiny和在資料集VOC2007+VOC2012進行訓練與驗證,接著進行橫向向對比,對比不同模型在同一資料集上的表現,
訓練程序
訓練階段的引數設定如下:
使用Pytorch的官方預訓練權重加載ShuffleNetv2,接著凍結ShuffleNetv2的主干網路,對PANET以及YOLO- Head進行訓練,練輪次為50,初始學習率為1e-3,標簽平滑值0.005,batch size 設為16,每迭代一次同時輸入16張圖片進行訓練,
解凍ShuffleNetv2的主干網路,對整個網路進行訓練,練輪次為100,初始學習率為1e-4,標簽平滑值0.005,batch size 設為 8,每迭代一次同時輸入8張圖片進行訓練,
評價指標
MAP
本文方法的目的主要是在保證模型精度和速度的同時,減少模型的訓練時間和模型記憶體占比.通過mAP(mean average precision)和FPS(frameper second)對模型的測驗性能進行評價,具體的運算式如下:

式中:P 為準確率,R 為召回率,TP 為真陽性樣本數, FP 為假陽性樣本數, FN 為假陰性數樣本.AP 表示P-R 曲線下的面積,綜合考慮精確率和召回率的影響,反映了模型對不同種類識別的好壞程度. mAP 全稱為 Mean Average Precision,指平均準確率,對多個資料集的 AP 求平均值,mAP 通常為目標檢測中度量檢測準確率的指標,
FPS
目標檢測模型中另外一個重要的指標為速度,只有保證檢測速度快,才能達到實時檢測的目的,這在自動駕駛等場景中非常重要,我們通常使用每秒幀率(Frame Per Second,FPS目標檢測模型的速度,即每秒鐘處理的影像數,我們在測驗訓練好的模型時可以計算出處理一張圖片需要的時間為 t,則一秒鐘該目標檢測模型可以處理 1/t 張圖片,
3.3實驗結果對比
3.3.1ShuffleNetv2-YOLOv4的訓練結果

圖7.ShuffleNetv2-YOLOv4的訓練結果map

圖7.ShuffleNetv2-YOLOv4的訓練結果log-avg miss rate
表1 ShuffleNetv2-YOLOv4的訓練結果
| 演算法 | MAP | 模型大小 | FPS | 模型引數量 | FLOPS |
| ShuffleNetv2-YOLOv4 | 75.04 | 44789K | 51.59 | 10.64M | 3.71GFlops |
(以上結果可能不太準確哈,主要是資料劃分跟大佬的不太一樣,還有一些引數計算有一點問題)
3.3.2部分訓練結果展示

圖8.ShuffleNetv2-YOLOv4的預測結果展示
3.3.3各個演算法模型對比(很多引數是錯的 看看就好 一些資料 比如map從大佬的github直接拷貝)
詳細見大佬工程
GitHub - bubbliiiing/mobilenet-yolov4-pytorch: 這是一個mobilenet-yolov4的庫,把yolov4主干網路修改成了mobilenet,修改了Panet的卷積組成,使引數量大幅度縮小,
表2 各個演算法模型對比
| 演算法 | MAP | 模型大小 | FPS | 模型引數量 | FLOPS |
| ShuffleNetv2-YOLOv4 | 75.04 | 44.8M | 51.59 | 10.64M | 3.71GFlops |
| Mobilenetv1-YOLOv4 | 79.72 | 53.5M | 65.72 | 12.69M | 5.3GFlops |
| Mobilenetv2-YOLOv4 | 80.12 | 47.6M | 52.83 | 10.80M | (*) |
| Mobilenetv3-YOLOv4 | 79.01 | 55.53M | 47.20 | 11.2M | 3.82GFlops |
| Gostnet- YOLOv4 | 78.69 | 43.76M | 38.92 | 11.2M | 3.82GFlops |
首先本文將ShuffleNetv2-YOLOv4、MobileNetv1-YOLOv4、MobileNetv2-YOLOv4、MobileNetv3-YOLOv4、Gostnet-YOLOv4、YOLOv4-Tiny相同軟硬體環境和資料集下進行VOC2007+2012目標檢測實驗,輸入圖片固定為416*416 像素,對比二者在正確率、檢測速度以及模型大小上的差距,其實驗結果如表所示,
4、部分代碼演示
4.1 shufflenetv2(來自torchvrsion)
import torch as t
import torch.nn as nn
import math
from collections import OrderedDict
import torch
__all__ = ['shufflenet2']
#### The model below is defined by myself
def channel_shuffle(x, groups=2):
bat_size, channels, w, h = x.shape
group_c = channels // groups
x = x.view(bat_size, groups, group_c, w, h)
x = t.transpose(x, 1, 2).contiguous()
x = x.view(bat_size, -1, w, h)
return x
# used in the block
def conv_1x1_bn(in_c, out_c, stride=1):
return nn.Sequential(
nn.Conv2d(in_c, out_c, 1, stride, 0, bias=False),
nn.BatchNorm2d(out_c),
nn.ReLU(True)
)
def conv_bn(in_c, out_c, stride=2):
return nn.Sequential(
nn.Conv2d(in_c, out_c, 3, stride, 1, bias=False),
nn.BatchNorm2d(out_c),
nn.ReLU(True)
)
class ShuffleBlock(nn.Module):
def __init__(self, in_c, out_c, downsample=False):
super(ShuffleBlock, self).__init__()
self.downsample = downsample
half_c = out_c // 2
if downsample:
self.branch1 = nn.Sequential(
# 3*3 dw conv, stride = 2
nn.Conv2d(in_c, in_c, 3, 2, 1, groups=in_c, bias=False),
nn.BatchNorm2d(in_c),
# 1*1 pw conv
nn.Conv2d(in_c, half_c, 1, 1, 0, bias=False),
nn.BatchNorm2d(half_c),
nn.ReLU(True)
)
self.branch2 = nn.Sequential(
# 1*1 pw conv
nn.Conv2d(in_c, half_c, 1, 1, 0, bias=False),
nn.BatchNorm2d(half_c),
nn.ReLU(True),
# 3*3 dw conv, stride = 2
nn.Conv2d(half_c, half_c, 3, 2, 1, groups=half_c, bias=False),
nn.BatchNorm2d(half_c),
# 1*1 pw conv
nn.Conv2d(half_c, half_c, 1, 1, 0, bias=False),
nn.BatchNorm2d(half_c),
nn.ReLU(True)
)
else:
# in_c = out_c
assert in_c == out_c
self.branch2 = nn.Sequential(
# 1*1 pw conv
nn.Conv2d(half_c, half_c, 1, 1, 0, bias=False),
nn.BatchNorm2d(half_c),
nn.ReLU(True),
# 3*3 dw conv, stride = 1
nn.Conv2d(half_c, half_c, 3, 1, 1, groups=half_c, bias=False),
nn.BatchNorm2d(half_c),
# 1*1 pw conv
nn.Conv2d(half_c, half_c, 1, 1, 0, bias=False),
nn.BatchNorm2d(half_c),
nn.ReLU(True)
)
def forward(self, x):
out = None
if self.downsample:
# if it is downsampling, we don't need to do channel split
out = t.cat((self.branch1(x), self.branch2(x)), 1)
else:
# channel split
channels = x.shape[1]
c = channels // 2
x1 = x[:, :c, :, :]
x2 = x[:, c:, :, :]
out = t.cat((x1, self.branch2(x2)), 1)
return channel_shuffle(out, 2)
class ShuffleNet2(nn.Module):
def __init__(self, input_size=416, net_type=1):
super(ShuffleNet2, self).__init__()
assert input_size % 32 == 0 # 因為一共會下采樣32倍
self.layers_out_filters = [24, 116, 232, 1024] # used for shufflenet v2
self.stage_repeat_num = [4, 8, 4]
if net_type == 0.5:
self.out_channels = [3, 24, 48, 96, 192, 1024]
elif net_type == 1:
self.out_channels = [3, 24, 116, 232, 464, 1024]
elif net_type == 1.5:
self.out_channels = [3, 24, 176, 352, 704, 1024]
elif net_type == 2:
self.out_channels = [3, 24, 244, 488, 976, 2948]
elif net_type == -1:
self.out_channels = [3, 24, 128, 256, 512, 1024]
else:
print("the type is error, you should choose 0.5, 1, 1.5 or 2")
# let's start building layers
self.conv1 = nn.Conv2d(3, self.out_channels[1], 3, 2, 1)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
in_c = self.out_channels[1]
self.stage2 = []
self.stage3 = []
self.stage4 = []
for stage_idx in range(len(self.stage_repeat_num)):
out_c = self.out_channels[2+stage_idx]
repeat_num = self.stage_repeat_num[stage_idx]
stage = []
for i in range(repeat_num):
if i == 0:
stage.append(ShuffleBlock(in_c, out_c, downsample=True))
else:
stage.append(ShuffleBlock(in_c, in_c, downsample=False))
in_c = out_c
if stage_idx == 0:
self.stage2 = stage
elif stage_idx == 1:
self.stage3 = stage
elif stage_idx == 2:
self.stage4 = stage
else:
print("error")
# self.stages = nn.Sequential(*self.stages)
self.stage2 = nn.Sequential(*self.stage2) # 58 * 58 * 116
self.stage3 = nn.Sequential(*self.stage3) # 26 * 26 * 232
self.stage4 = nn.Sequential(*self.stage4)
in_c = self.out_channels[-2]
out_c = self.out_channels[-1]
self.conv5 = conv_1x1_bn(in_c, out_c, 1) # 13 * 13 * 1024
# self.g_avg_pool = nn.AvgPool2d(kernel_size=(int)(input_size/32)) # 如果輸入的是224,則此處為7
# # fc layer
# self.fc = nn.Linear(out_c, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.maxpool(x)
out3 = self.stage2(x)
out4 = self.stage3(out3)
out5 = self.stage4(out4)
out5 = self.conv5(out5)
# x = self.g_avg_pool(x)
# x = x.view(-1, self.out_channels[-1])
# x = self.fc(x)
return out3, out4, out5
def shufflenet2(pretrained, **kwargs):
"""Constructs a darknet-53 model.
"""
model = ShuffleNet2()
if pretrained:
state_dict = torch.load('./shufflenetv2_x1-5666bf0f80.pth')
# model.load_state_dict(t.load(pretrained))
model.load_state_dict(state_dict, strict=True)
return model
if __name__ == "__main__":
from torchsummary import summary
# 需要使用device來指定網路在GPU還是CPU運行
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = shufflenet2(pretrained=False).to(device);
summary(model, input_size=(3,416,416))
4.2 yolo本體
from collections import OrderedDict
import torch
import torch.nn as nn
import torch.nn.functional as F
from nets.densenet import _Transition, densenet121, densenet169, densenet201
from nets.ghostnet import ghostnet
from nets.mobilenet_v1 import mobilenet_v1
from nets.mobilenet_v2 import mobilenet_v2
from nets.mobilenet_v3 import mobilenet_v3
from nets.shufflenet import shufflenet2
from nets.shufflenetv2 import shufflenet_v2_x1_0
class MobileNetV1(nn.Module):
def __init__(self, pretrained = False):
super(MobileNetV1, self).__init__()
self.model = mobilenet_v1(pretrained=pretrained)
def forward(self, x):
out3 = self.model.stage1(x)
out4 = self.model.stage2(out3)
out5 = self.model.stage3(out4)
return out3, out4, out5
class MobileNetV2(nn.Module):
def __init__(self, pretrained = False):
super(MobileNetV2, self).__init__()
self.model = mobilenet_v2(pretrained=pretrained)
def forward(self, x):
out3 = self.model.features[:7](x)
out4 = self.model.features[7:14](out3)
out5 = self.model.features[14:18](out4)
return out3, out4, out5
class MobileNetV3(nn.Module):
def __init__(self, pretrained = False):
super(MobileNetV3, self).__init__()
self.model = mobilenet_v3(pretrained=pretrained)
def forward(self, x):
out3 = self.model.features[:7](x)
out4 = self.model.features[7:13](out3)
out5 = self.model.features[13:16](out4)
return out3, out4, out5
class GhostNet(nn.Module):
def __init__(self, pretrained=True):
super(GhostNet, self).__init__()
model = ghostnet()
if pretrained:
state_dict = torch.load("model_data/ghostnet_weights.pth")
model.load_state_dict(state_dict)
del model.global_pool
del model.conv_head
del model.act2
del model.classifier
del model.blocks[9]
self.model = model
def forward(self, x):
x = self.model.conv_stem(x)
x = self.model.bn1(x)
x = self.model.act1(x)
feature_maps = []
for idx, block in enumerate(self.model.blocks):
x = block(x)
if idx in [2,4,6,8]:
feature_maps.append(x)
return feature_maps[1:]
class ShufflenetV2(nn.Module):
def __init__(self, pretrained = False):
super(ShufflenetV2, self).__init__()
# self.model = shufflenet2(pretrained=pretrained)
self.model = shufflenet_v2_x1_0(pretrained=pretrained)
def forward(self, x):
# out3, out4, out5 = self.model(x)
# return out3, out4, out5
x = self.model.conv1(x)
x = self.model.maxpool(x)
out3 = self.model.stage2(x)
out4 = self.model.stage3(out3)
out5 = self.model.stage4(out4)
out5 = self.model.conv5(out5)
return out3, out4, out5
class Densenet(nn.Module):
def __init__(self, backbone, pretrained=False):
super(Densenet, self).__init__()
densenet = {
"densenet121" : densenet121,
"densenet169" : densenet169,
"densenet201" : densenet201
}[backbone]
model = densenet(pretrained)
del model.classifier
self.model = model
def forward(self, x):
feature_maps = []
for block in self.model.features:
if type(block)==_Transition:
for _, subblock in enumerate(block):
x = subblock(x)
if type(subblock)==nn.Conv2d:
feature_maps.append(x)
else:
x = block(x)
x = F.relu(x, inplace=True)
feature_maps.append(x)
return feature_maps[1:]
def conv2d(filter_in, filter_out, kernel_size, groups=1, stride=1):
pad = (kernel_size - 1) // 2 if kernel_size else 0
return nn.Sequential(OrderedDict([
("conv", nn.Conv2d(filter_in, filter_out, kernel_size=kernel_size, stride=stride, padding=pad, groups=groups, bias=False)),
("bn", nn.BatchNorm2d(filter_out)),
("relu", nn.ReLU6(inplace=True)),
]))
def conv_dw(filter_in, filter_out, stride = 1):
return nn.Sequential(
nn.Conv2d(filter_in, filter_in, 3, stride, 1, groups=filter_in, bias=False),
nn.BatchNorm2d(filter_in),
nn.ReLU6(inplace=True),
nn.Conv2d(filter_in, filter_out, 1, 1, 0, bias=False),
nn.BatchNorm2d(filter_out),
nn.ReLU6(inplace=True),
)
#---------------------------------------------------#
# SPP結構,利用不同大小的池化核進行池化
# 池化后堆疊
#---------------------------------------------------#
class SpatialPyramidPooling(nn.Module):
def __init__(self, pool_sizes=[5, 9, 13]):
super(SpatialPyramidPooling, self).__init__()
self.maxpools = nn.ModuleList([nn.MaxPool2d(pool_size, 1, pool_size//2) for pool_size in pool_sizes])
def forward(self, x):
features = [maxpool(x) for maxpool in self.maxpools[::-1]]
features = torch.cat(features + [x], dim=1)
return features
#---------------------------------------------------#
# 卷積 + 上采樣
#---------------------------------------------------#
class Upsample(nn.Module):
def __init__(self, in_channels, out_channels):
super(Upsample, self).__init__()
self.upsample = nn.Sequential(
conv2d(in_channels, out_channels, 1),
nn.Upsample(scale_factor=2, mode='nearest')
)
def forward(self, x,):
x = self.upsample(x)
return x
#---------------------------------------------------#
# 三次卷積塊
#---------------------------------------------------#
def make_three_conv(filters_list, in_filters):
m = nn.Sequential(
conv2d(in_filters, filters_list[0], 1),
conv_dw(filters_list[0], filters_list[1]),
conv2d(filters_list[1], filters_list[0], 1),
)
return m
#---------------------------------------------------#
# 五次卷積塊
#---------------------------------------------------#
def make_five_conv(filters_list, in_filters):
m = nn.Sequential(
conv2d(in_filters, filters_list[0], 1),
conv_dw(filters_list[0], filters_list[1]),
conv2d(filters_list[1], filters_list[0], 1),
conv_dw(filters_list[0], filters_list[1]),
conv2d(filters_list[1], filters_list[0], 1),
)
return m
#---------------------------------------------------#
# 最后獲得yolov4的輸出
#---------------------------------------------------#
def yolo_head(filters_list, in_filters):
m = nn.Sequential(
conv_dw(in_filters, filters_list[0]),
nn.Conv2d(filters_list[0], filters_list[1], 1),
)
return m
#---------------------------------------------------#
# yolo_body
#---------------------------------------------------#
class YoloBody(nn.Module):
def __init__(self, anchors_mask, num_classes, backbone="mobilenetv2", pretrained=False):
super(YoloBody, self).__init__()
#---------------------------------------------------#
# 生成mobilnet的主干模型,獲得三個有效特征層,
#---------------------------------------------------#
if backbone == "mobilenetv1":
#---------------------------------------------------#
# 52,52,256;26,26,512;13,13,1024
#---------------------------------------------------#
self.backbone = MobileNetV1(pretrained=pretrained)
in_filters = [256, 512, 1024]
elif backbone == "mobilenetv2":
#---------------------------------------------------#
# 52,52,32;26,26,92;13,13,320
#---------------------------------------------------#
self.backbone = MobileNetV2(pretrained=pretrained)
in_filters = [32, 96, 320]
elif backbone == "mobilenetv3":
#---------------------------------------------------#
# 52,52,40;26,26,112;13,13,160
#---------------------------------------------------#
self.backbone = MobileNetV3(pretrained=pretrained)
in_filters = [40, 112, 160]
elif backbone == "ghostnet":
#---------------------------------------------------#
# 52,52,40;26,26,112;13,13,160
#---------------------------------------------------#
self.backbone = GhostNet(pretrained=pretrained)
in_filters = [40, 112, 160]
elif backbone == "shufflenet2":
#---------------------------------------------------#
# 58 * 58 * 116; 26 * 26 * 232; 13 * 13 * 1024
#---------------------------------------------------#
self.backbone = ShufflenetV2(pretrained=pretrained)
in_filters = [116, 232, 1024]
elif backbone in ["densenet121", "densenet169", "densenet201"]:
#---------------------------------------------------#
# 52,52,256;26,26,512;13,13,1024
#---------------------------------------------------#
self.backbone = Densenet(backbone, pretrained=pretrained)
in_filters = {
"densenet121" : [256, 512, 1024],
"densenet169" : [256, 640, 1664],
"densenet201" : [256, 896, 1920]
}[backbone]
else:
raise ValueError('Unsupported backbone - `{}`, Use mobilenetv1, mobilenetv2, mobilenetv3, ghostnet, densenet121, densenet169, densenet201.'.format(backbone))
self.conv1 = make_three_conv([512, 1024], in_filters[2])
self.SPP = SpatialPyramidPooling()
self.conv2 = make_three_conv([512, 1024], 2048)
self.upsample1 = Upsample(512, 256)
self.conv_for_P4 = conv2d(in_filters[1], 256,1)
self.make_five_conv1 = make_five_conv([256, 512], 512)
self.upsample2 = Upsample(256, 128)
self.conv_for_P3 = conv2d(in_filters[0], 128,1)
self.make_five_conv2 = make_five_conv([128, 256], 256)
# 3*(5+num_classes) = 3*(5+20) = 3*(4+1+20)=75
self.yolo_head3 = yolo_head([256, len(anchors_mask[0]) * (5 + num_classes)], 128)
self.down_sample1 = conv_dw(128, 256, stride = 2)
self.make_five_conv3 = make_five_conv([256, 512], 512)
# 3*(5+num_classes) = 3*(5+20) = 3*(4+1+20)=75
self.yolo_head2 = yolo_head([512, len(anchors_mask[1]) * (5 + num_classes)], 256)
self.down_sample2 = conv_dw(256, 512, stride = 2)
self.make_five_conv4 = make_five_conv([512, 1024], 1024)
# 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
self.yolo_head1 = yolo_head([1024, len(anchors_mask[2]) * (5 + num_classes)], 512)
def forward(self, x):
# backbone
x2, x1, x0 = self.backbone(x)
# 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,2048
P5 = self.conv1(x0)
P5 = self.SPP(P5)
# 13,13,2048 -> 13,13,512 -> 13,13,1024 -> 13,13,512
P5 = self.conv2(P5)
# 13,13,512 -> 13,13,256 -> 26,26,256
P5_upsample = self.upsample1(P5)
# 26,26,512 -> 26,26,256
P4 = self.conv_for_P4(x1)
# 26,26,256 + 26,26,256 -> 26,26,512
P4 = torch.cat([P4,P5_upsample],axis=1)
# 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256
P4 = self.make_five_conv1(P4)
# 26,26,256 -> 26,26,128 -> 52,52,128
P4_upsample = self.upsample2(P4)
# 52,52,256 -> 52,52,128
P3 = self.conv_for_P3(x2)
# 52,52,128 + 52,52,128 -> 52,52,256
P3 = torch.cat([P3,P4_upsample],axis=1)
# 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128
P3 = self.make_five_conv2(P3)
# 52,52,128 -> 26,26,256
P3_downsample = self.down_sample1(P3)
# 26,26,256 + 26,26,256 -> 26,26,512
P4 = torch.cat([P3_downsample,P4],axis=1)
# 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256
P4 = self.make_five_conv3(P4)
# 26,26,256 -> 13,13,512
P4_downsample = self.down_sample2(P4)
# 13,13,512 + 13,13,512 -> 13,13,1024
P5 = torch.cat([P4_downsample,P5],axis=1)
# 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512
P5 = self.make_five_conv4(P5)
#---------------------------------------------------#
# 第三個特征層
# y3=(batch_size,75,52,52)
#---------------------------------------------------#
out2 = self.yolo_head3(P3)
#---------------------------------------------------#
# 第二個特征層
# y2=(batch_size,75,26,26)
#---------------------------------------------------#
out1 = self.yolo_head2(P4)
#---------------------------------------------------#
# 第一個特征層
# y1=(batch_size,75,13,13)
#---------------------------------------------------#
out0 = self.yolo_head1(P5)
return out0, out1, out2
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/413648.html
標籤:其他
下一篇:Auto-Encoders原理
