作者丨zyw2002@CSDN
來源丨https://blog.csdn.net/zyw2002/article/details/128732494
編輯丨極市平台
本文僅作為知識分享,如有侵權請聯系删文處理。
文章目錄
- [YOLOv8 概述]
- [模型結構]
- [Loss 計算]
- [訓練資料增強]
- [訓練政策]
- [模型推理過程]
- [網絡模型解析]
- [卷積神經單元(model.py)]
- [Yolov8實操]
- [快速入門]
- [環境配置]
- [資料集準備]
- [模型的訓練/驗證/預測/導出]
- [使用CLI]
- [使用python]
- [多任務支援]
- [檢測]
- [執行個體分割]
- [分類]
- [配置]
- [設定操作類型]
- [訓練]
- [預測]
- [驗證]
- [資料擴充]
- [日志,檢查點,繪圖和檔案管理]
- [自定義模型]
- [參考]
目标檢測——Yolo系列(YOLOv1/2/v3/4/5/x/6/7)
YOLOv8 概述
YOLOv8 算法的核心特性和改動可以歸結為如下:
- 提供了一個全新的 SOTA 模型,包括 P5 640 和 P6 1280 分辨率的目标檢測網絡和基于 YOLACT 的執行個體分割模型。和 YOLOv5 一樣,基于縮放系數也提供了 N/S/M/L/X 尺度的不同大小模型,用于滿足不同場景需求
-
Backbone:
骨幹網絡和 Neck 部分可能參考了 YOLOv7 ELAN 設計思想,将 YOLOv5 的 C3 結構換成了梯度流更豐富的 C2f 結構,并對不同尺度模型調整了不同的通道數。
屬于對模型結構精心微調,不再是無腦一套參數應用所有模型,大幅提升了模型性能。不過這個 C2f 子產品中存在 Split 等操作對特定硬體部署沒有之前那麼友好了
- Head: Head部分較yolov5而言有兩大改進:1)換成了目前主流的解耦頭結構(Decoupled-Head),将分類和檢測頭分離 2)同時也從 Anchor-Based 換成了 Anchor-Free
- Loss :1) YOLOv8抛棄了以往的IOU比對或者單邊比例的配置設定方式,而是使用了Task-Aligned Assigner正負樣本比對方式。2)并引入了 Distribution Focal Loss(DFL)
- Train:訓練的資料增強部分引入了 YOLOX 中的最後 10 epoch 關閉 Mosiac 增強的操作,可以有效地提升精度
從上面可以看出,YOLOv8 主要參考了最近提出的諸如 YOLOX、YOLOv6、YOLOv7 和 PPYOLOE 等算法的相關設計,本身的創新點不多,偏向工程實踐,主推的還是 ultralytics 這個架構本身。
下面将按照模型結構設計、Loss 計算、訓練資料增強、訓練政策和模型推理過程共 5 個部分詳細介紹 YOLOv8 目标檢測的各種改進,執行個體分割部分暫時不進行描述。
模型結構
如下圖, 左側為 YOLOv5-s,右側為 YOLOv8-s。
在暫時不考慮 Head 情況下,對比 YOLOv5 和 YOLOv8 的 yaml 配置檔案可以發現改動較小。
Backbone和Neck的具體變化
a) 第一個卷積層的 kernel 從 6x6 變成了 3x3
b) 所有的 C3 子產品換成 C2f,結構如下所示,可以發現多了更多的跳層連接配接和額外的 Split 操作
C3 | C2f |
c)去掉了 Neck 子產品中的 2 個卷積連接配接層
d) Backbone 中 C2f 的 block 數從 3-6-9-3 改成了 3-6-6-3
e) 檢視 N/S/M/L/X 等不同大小模型,可以發現 N/S 和 L/X 兩組模型隻是改了縮放系數,但是 S/M/L 等骨幹網絡的通道數設定不一樣,沒有遵循同一套縮放系數。如此設計的原因應該是同一套縮放系數下的通道設定不是最優設計,YOLOv7 網絡設計時也沒有遵循一套縮放系數作用于所有模型
Head的具體變化
從原先的耦合頭變成了解耦頭,并且從 YOLOv5 的 Anchor-Based 變成了 Anchor-Free。
C3 | C2f |
從上圖可以看出,不再有之前的 objectness 分支,隻有解耦的分類和回歸分支,并且其回歸分支使用了 Distribution Focal Loss 中提出的積分形式表示法。
Loss 計算
Loss 計算過程包括 2 個部分: 正負樣本配置設定政策和 Loss 計算。
正負樣本配置設定政策
現代目标檢測器大部分都會在正負樣本配置設定政策上面做文章,典型的如 YOLOX 的 simOTA、TOOD 的 TaskAlignedAssigner 和 RTMDet 的 DynamicSoftLabelAssigner,這類 Assigner 大都是動态配置設定政策,而 YOLOv5 采用的依然是靜态配置設定政策。考慮到動态配置設定政策的優異性,YOLOv8 算法中則直接引用了 TOOD 的 TaskAlignedAssigner。
TaskAlignedAssigner 的比對政策簡單總結為: 根據分類與回歸的分數權重的分數選擇正樣本。
Loss計算
Loss 計算包括 2 個分支: 分類和回歸分支,沒有了之前的 objectness 分支。
分類分支依然采用 BCE Loss
回歸分支需要和 Distribution Focal Loss 中提出的積分形式表示法綁定,是以使用了 Distribution Focal Loss, 同時還使用了 CIoU Loss。3 個 Loss 采用一定權重比例權重即可。
訓練資料增強
資料增強方面和 YOLOv5 差距不大,隻不過引入了 YOLOX 中提出的最後 10 個 epoch 關閉 Mosaic 的操作。假設訓練 epoch 是 500,其示意圖如下所示:
考慮到不同模型應該采用的資料增強強度不一樣,是以對于不同大小模型,有部分超參會進行修改,典型的如大模型會開啟 MixUp 和 CopyPaste。資料增強後典型效果如下所示:
訓練政策
YOLOv8 的訓練政策和 YOLOv5 沒有啥差別,最大差別就是模型的訓練總 epoch 數從 300 提升到了 500,這也導緻訓練時間急劇增加。以 YOLOv8-S 為例,其訓練政策彙總如下:
模型推理過程
YOLOv8 的推理過程和 YOLOv5 幾乎一樣,唯一差别在于前面需要對 Distribution Focal Loss 中的積分表示 bbox 形式進行解碼,變成正常的 4 次元 bbox,後續計算過程就和 YOLOv5 一樣了。
其推理和後處理過程為:
(1) bbox 積分形式轉換為 4d bbox 格式
對 Head 輸出的 bbox 分支進行轉換,利用 Softmax 和 Conv 計算将積分形式轉換為 4 維 bbox 格式
(2) 次元變換
YOLOv8 輸出特征圖尺度為 80x80、40x40 和 20x20 的三個特征圖。Head 部分輸出分類和回歸共 6 個尺度的特征圖。
将 3 個不同尺度的類别預測分支、bbox 預測分支進行拼接,并進行次元變換。為了後續友善處理,會将原先的通道次元置換到最後,類别預測分支 和 bbox 預測分支 shape 分别為 (b, 80x80+40x40+20x20, 80)=(b,8400,80),(b,8400,4)。
(3) 解碼還原到原圖尺度
分類預測分支進行 Sigmoid 計算,而 bbox 預測分支需要進行解碼,還原為真實的原圖解碼後 xyxy 格式。
(4) 門檻值過濾
周遊 batch 中的每張圖,采用 score_thr 進行門檻值過濾。在這過程中還需要考慮 multi_label 和 nms_pre,確定過濾後的檢測框數目不會多于 nms_pre。
(5) 還原到原圖尺度和 nms
基于前處理過程,将剩下的檢測框還原到網絡輸出前的原圖尺度,然後進行 nms 即可。最終輸出的檢測框不能多于 max_per_img。
有一個特别注意的點:YOLOv5 中采用的 Batch shape 推理政策,在 YOLOv8 推理中暫時沒有開啟,不清楚後面是否會開啟,在 MMYOLO 中快速測試了下,如果開啟 Batch shape 會漲大概 0.1~0.2。
網絡模型解析
卷積神經單元(model.py)
在ultralytics/nn/modules.py檔案中定義了yolov8網絡中的卷積神經單元。
autopad
- 功能: 傳回pad的大小,使得padding後輸出張量的大小不變。
- 參數:
- k: 卷積核(kernel)的大小。類型可能是一個int也可能是一個序列。
- p: 填充(padding)的大小。預設為None。
- d: 擴張率(dilation rate)的大小, 預設為1 。普通卷積的擴張率為1,空洞卷積的擴張率大于1。
假設k為原始卷積核大小,d為卷積擴張率(dilation rate),加入空洞之後的實際卷積核尺寸與原始卷積核尺寸之間的關系:k =d(k-1)+1。
則當p=k//2時,padding後輸出張量的大小不變。
def autopad(k, p=None, d=1): # kernel(卷積核), padding(填充), dilation(擴張)
# 傳回pad的大小,使得padding後輸出張量的shape不變
if d > 1: # 如果采用擴張卷積,則計算擴張後實際的kernel大小
k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] #
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # 自動pad
return p
Conv
- 功能: 标準的卷積
- 參數:輸入通道數(c1), 輸出通道數(c2), 卷積核大小(k,預設是1), 步長(s,預設是1), 填充(p,預設為None), 組(g, 預設為1), 擴張率(d,預設為1), 是否采用激活函數(act ,預設為True, 且采用SiLU為激活函數)
激活函數采用的是SiLU。
class Conv(nn.Module):
# 标準的卷積 參數(輸入通道數, 輸出通道數, 卷積核大小, 步長, 填充, 組, 擴張, 激活函數)
default_act = nn.SiLU() # 預設的激活函數
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
super().__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False) # 2維卷積,其中采用了自動填充函數。
self.bn = nn.BatchNorm2d(c2) # 使得每一個batch的特征圖均滿足均值為0,方差為1的分布規律
# 如果act=True 則采用預設的激活函數SiLU;如果act的類型是nn.Module,則采用傳入的act; 否則不采取任何動作 (nn.Identity函數相當于f(x)=x,隻用做占位,傳回原始的輸入)。
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
def forward(self, x): # 前向傳播
return self.act(self.bn(self.conv(x))) # 采用BatchNorm
def forward_fuse(self, x): # 用于Model類的fuse函數融合 Conv + BN 加速推理,一般用于測試/驗證階段
return self.act(self.conv(x)) # 不采用BatchNorm
DWConv
深度可分離卷積,繼承自Conv
g=math.gcd(c1, c2) 分組數是輸入通道(c1)和輸出通道(c2)的最大公約數。(因為分組卷積時,分組數需要能夠整除輸入通道和輸出通道)
class DWConv(Conv):
# 深度可分離卷積
def __init__(self, c1, c2, k=1, s=1, d=1, act=True): # ch_in, ch_out, kernel, stride, dilation, activation
super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act)
DWConvTranspose2d
帶有深度分離的轉置卷積,繼承自nn.ConvTranspose2d
groups=math.gcd(c1, c2) 分組數是輸入通道(c1)和輸出通道(c2)的最大公約數。(因為分組卷積時,分組數需要能夠整除輸入通道和輸出通道)
class DWConvTranspose2d(nn.ConvTranspose2d):
# Depth-wise transpose convolution
def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0): # 輸入通道, 輸出通道, 卷積核大小, 步長, padding, padding_out
super().__init__(c1, c2, k, s, p1, p2, groups=math.gcd(c1, c2))
ConvTranspose
和Conv類似,隻是把Conv2d換成了ConvTranspose2d。
class ConvTranspose(nn.Module):
# Convolution transpose 2d layer
default_act = nn.SiLU() # default activation
def __init__(self, c1, c2, k=2, s=2, p=0, bn=True, act=True):
super().__init__()
self.conv_transpose = nn.ConvTranspose2d(c1, c2, k, s, p, bias=not bn)
self.bn = nn.BatchNorm2d(c2) if bn else nn.Identity()
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
def forward(self, x):
return self.act(self.bn(self.conv_transpose(x)))
DFL(Distribution Focal Loss)
本篇文章(https://ieeexplore.ieee.org/document/9792391)提出了GFL(了Generalized Focal Loss)。GFL具體又包括Quality Focal Loss(QFL)和Distribution Focal Loss(DFL),其中QFL用于優化分類和品質估計聯合分支,DFL用于優化邊框分支。
class DFL(nn.Module):
# Integral module of Distribution Focal Loss (DFL) proposed in Generalized Focal Loss
def __init__(self, c1=16):
super().__init__()
self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)
x = torch.arange(c1, dtype=torch.float)
self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1))
self.c1 = c1
def forward(self, x):
b, c, a = x.shape # batch, channels, anchors
return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)
# return self.conv(x.view(b, self.c1, 4, a).softmax(1)).view(b, 4, a)
TransformerLayer
關于Transformer的了解和torch.nn.MultiheadAttention 的用法,請參考部落格《詳解注意力機制和Transformer》
論文《Attention is all you need》 中提出的Transformer架構。如下圖,是Transformer中的Encoder部分。
我們可以發現它和yolo中的TransformerLayer部分隻是少了層規範化(LayerNorm),以及在Feed-Forward Networks 中隻采用了兩個不帶偏置線性層,且沒有采用激活函數。
TransformerLayer代碼實作如下:
class TransformerLayer(nn.Module):
# Transformer layer (LayerNorm layers removed for better performance)
def __init__(self, c, num_heads): # c: 詞特征向量的大小 num_heads 檢測頭的個數。
super().__init__()
self.q = nn.Linear(c, c, bias=False)# 計算query, in_features=out_features=c
self.k = nn.Linear(c, c, bias=False)# 計算key
self.v = nn.Linear(c, c, bias=False)# 計算value
self.ma = nn.MultiheadAttention(embed_dim=c, num_heads=num_heads) # 多頭注意力機制
self.fc1 = nn.Linear(c, c, bias=False)
self.fc2 = nn.Linear(c, c, bias=False)
def forward(self, x):
x = self.ma(self.q(x), self.k(x), self.v(x))[0] + x # 多頭注意力機制+殘差連接配接
x = self.fc2(self.fc1(x)) + x # 兩個全連接配接層+ 殘差連接配接
return x
如果輸入是x,x的大小是(s,n,c) 。 其中n是batch size, s是源序列長度,c是詞特征向量的大小(embed_dim)。
然後x分别通過3個Linear層 (線性層的結構相同,但是可學習參數不同)計算得到鍵k、查詢q、值v。因為線性層的輸入特征數和輸出特征數均等于c, 是以k,q,v的大小也是(s,n,c)。
接着,把k、q、v作為參數輸入到多頭注意力ma中,傳回兩個結果attn_output(注意力機制的輸出)和attn_output_weights(注意力機制的權重)。在這裡,我們隻需要注意力機制的輸出就可以,是以,我們取索引0 self.ma(self.q(x), self.k(x), self.v(x))[0],它的大小是(s,n,c)。+x 表示殘差連接配接,不改變x的形狀。
self.fc2(self.fc1(x)) 表示經過兩個全連接配接層,輸出大小是(s,n,c)。+x 表示殘差連接配接,不改變x的形狀。是以最終輸出的形狀大小和輸入的形狀一樣。
Transformer Block
TransformerBlock是把若幹個TransformerLayer串聯起來。
對于圖像資料而言,輸入資料形狀是 [batch, channel, height, width],變換成 [height × width, batch, channel]。height × width把圖像中各個像素點看作一個單詞,其對應通道的資訊連在一起就是詞向量。channel就是詞向量的長度。
TransformerBlock的實作代碼如下:
class TransformerBlock(nn.Module):
def __init__(self, c1, c2, num_heads, num_layers):
super().__init__()
self.conv = None
if c1 != c2:
self.conv = Conv(c1, c2)
self.linear = nn.Linear(c2, c2) # learnable position embedding
self.tr = nn.Sequential(*(TransformerLayer(c2, num_heads) for _ in range(num_layers)))
self.c2 = c2
def forward(self, x): # x:(b,c1,w0,h0)
if self.conv is not None:
x = self.conv(x) # x:(b,c2,w,h)
b, _, w, h = x.shape
p = x.flatten(2).permute(2, 0, 1) # flatten後:(b,c2,w*h) p: (w*h,b,c2)
# linear後: (w*h,b,c2) tr後: (w*h,b,c2) permute後: (b,c2,w*h) reshape後:(b,c2,w,h)
return self.tr(p + self.linear(p)).permute(1, 2, 0).reshape(b, self.c2, w, h)
1)輸入的x大小為(b,c1,w,h)。其中b為batch size, c1 是輸入通道數大小, w 和h 分别表示圖像的寬和高。
2)經過Conv層:Conv層中的2d卷積,卷積核大小是1x1, 步長為1,無填充,擴張率為1。是以不改變w和h, 隻改變輸出通道數,形狀變為(b,c2,w,h)。Conv層中的BN和SiLU不改變形狀大小。輸出的x大小為(b,c2,w,h)
3)對x進行變換得到p: x.flatten(2)後,大小變為 (b,c2,w*h) permute(2, 0, 1)後,p的大小為(w*h,b,c2)
4) 将p輸入到線性層後,因為線性層的輸入特征數和輸出特征數相等,是以輸出的大小為(w*h,b,c2)。
+p 進行殘差連接配接後,大小不變,仍為(w*h,b,c2)
5) 然後将上一步的結果輸入到num_layers個TransformerLayer中。w*h 相當于序列長度,b是批量的大小,c2相當于詞嵌入特征長度。每個TransformerLayer的輸入和輸出的大小不變。經過若幹個TransformerLayer後,大小是(w*h,b,c2)。
6)permute(1, 2, 0)後: 形狀變為(b,c2,w*h) reshape(b, self.c2, w, h)後:(b,c2,w,h)
Bottleneck
先使用 3x3 卷積降維,剔除備援資訊;再使用 3×3 卷積升維。
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5): # ch_in, ch_out, shortcut, groups, kernels, expand
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, k[0], 1) # 輸入通道: c1, 輸出通道:c_ , 卷積核:3x3, 步長1
self.cv2 = Conv(c_, c2, k[1], 1, g=g) # 輸入通道:c_ , 輸出通道c2, 卷積核:3x3, 步長1
self.add = shortcut and c1 == c2 # 當傳入的shortcut參數為true,且c1和c2相等時,則使用殘差連接配接。
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
第一層卷積,輸入通道: c1, 輸出通道:c_ , 卷積核:3x3, 步長1
第一層卷積,輸入通道: c_, 輸出通道:c2 , 卷積核:3x3, 步長1
其中 c _ = c 2 2 c\_=\frac{c_2}{2} c_=2c2 。當c1和c2相等時,采用殘差連接配接。
BottleneckCSP
詳細請參考CSPNet的論文和源碼。
論文《CSPNet: A New Backbone that can Enhance Learning Capability of CNN》
源碼https://github.com/WongKinYiu/CrossStagePartialNetworks
class BottleneckCSP(nn.Module):
# CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
# 輸出x的大小是(b,c1,w,h)
self.cv1 = Conv(c1, c_, 1, 1) # cv1的大小為(b,c_,w,h)
self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) # cv2的大小為(b,c_,w,h)
self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) # m通過Conv2d,變成cv3,大小是(b,c_,w,h)
self.cv4 = Conv(2 * c_, c2, 1, 1)
self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3)
self.act = nn.SiLU()
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
# cv1通過n個串聯的bottleneck,變成m,大小為(b,c_,w,h)
def forward(self, x):
y1 = self.cv3(self.m(self.cv1(x))) # (b,c_,w,h)
y2 = self.cv2(x) # (b,c_,w,h)
return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))
# cat後:(b,2*c_,w,h) 傳回cv4: (b,c2,w,h)
1)輸出x的大小是(b,c1,w,h), 然後有兩條計算路徑分别計算得到y1和y2。
y1的計算路徑:先x通過cv1,大小變成(b,c_,w,h) 。cv1通過n個串聯的bottleneck,變成m,大小為(b,c_,w,h)。m通過cv3, 得到y1, 大小是(b,c_,w,h)
y2的計算路徑:x通過cv2得到y2,大小是(b,c_,w,h)
2)y1和y2在dim=1處連接配接, 大小是(b,2*c_,w,h), 然後再通過BN和SiLU,大小不變。
3)最終,通過cv4, 傳回結果的大小是(b,c2,w,h)
C3
與 BottleneckCSP 類似,但少了 1 個 Conv、1 個 BN、1 個 Act,運算量更少。總共隻有3次卷積(cv1,cv2,cv3)。
class C3(nn.Module):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2)
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
C2
C2隻有兩個卷積(cv1,cv2)的CSP Bottleneck。
class C2(nn.Module):
# CSP Bottleneck with 2 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
# 假設輸入的x大小是(b,c1,w,h)
self.c = int(c2 * e) # hidden channels e=0.5,對輸出通道進行平分。
self.cv1 = Conv(c1, 2 * self.c, 1, 1) # cv1的大小是(b,c2,w,h)
self.cv2 = Conv(2 * self.c, c2, 1) # optional act=FReLU(c2)
# self.attention = ChannelAttention(2 * self.c) # or SpatialAttention() #此處可以使用空間注意力或者跨通道的注意力機制。
self.m = nn.Sequential(*(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))) # a通過n個串聯的Bottleneck後的到m,m的大小是(b,c,w,h)
def forward(self, x):
a, b = self.cv1(x).split((self.c, self.c), 1)# 對cv進行在次元1進行平分,a和b的大小都是(b,c,w,h)
return self.cv2(torch.cat((self.m(a), b), 1)) # 把m和b在次元1進行cat後,大小是(b,c2,w,h)。最終通過cv2,大小是(b,c2,w,h)
1)輸出x的大小是(b,c1,w,h), 通過Conv層,得到cv1, cv1的大小是(b,c2,w,h)
2) 然後再dim=1的次元上對cv1進行分割,a和b的大小都是(b,c2/2,w,h)。
3) a通過n個串聯的Bottleneck後的到m,m的大小是(b,c,w,h)
4) 把m和b在次元1進行cat後,大小是(b,c2,w,h)。最終m通過cv2,輸出的大小是(b,c2,w,h)
C2f
C2f與C2相比,每個Bottleneck的輸出都會被Concat到一起。
class C2f(nn.Module):
# CSP Bottleneck with 2 convolutions
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
# 假設輸入的x大小是(b,c1,w,h)
self.c = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, 2 * self.c, 1, 1) # cv1的大小是(b,c2,w,h)
self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2)
self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)) # n個Bottleneck組成的ModuleList,可以把m看做是一個可疊代對象
def forward(self, x):
y = list(self.cv1(x).split((self.c, self.c), 1))
# cv1的大小是(b,c2,w,h),對cv1在次元1等分成兩份(假設分别是a和b),a和b的大小均是(b,c2/2,w,h)。此時y=[a,b]。
y.extend(m(y[-1]) for m in self.m)
# 然後對清單y中的最後一個張量b輸入到ModuleList中的第1個bottleneck裡,得到c,c的大小是(b,c2/2,w,h)。然後把c也加入y中。此時y=[a,b,c]
# 重複上述操作n次(因為是n個bottleneck),最終得到的y清單中一共有n+2個元素。
return self.cv2(torch.cat(y, 1))
# 對清單y中的張量在次元1進行連接配接,得到的張量大小是(b,(n+2)*c2/2,w,h)。
# 最終通過cv2,輸出張量的大小是(b,c2,w,h)
1)cv1的大小是(b,c2,w,h),對cv1在次元1等分成兩份(假設分别是a和b),a和b的大小均是(b,c2/2,w,h)。此時y=[a,b]。
2)然後對清單y中的最後一個張量b輸入到ModuleList中的第1個bottleneck裡,得到c,c的大小是(b,c2/2,w,h)。然後把c也加入y中。此時y=[a,b,c]。
3)上述步驟重複上述操作n次(因為是n個bottleneck),最終得到的y清單中一共有n+2個元素。
4)對清單y中的張量在次元1進行連接配接,得到的張量大小是(b,(n+2)*c2/2,w,h)。
5)最終通過cv2,輸出張量的大小是(b,c2,w,h)
ChannelAttention
通道注意力模型: 通道次元不變,壓縮空間次元。該子產品關注輸入圖檔中有意義的資訊。
class ChannelAttention(nn.Module):
# Channel-attention module https://github.com/open-mmlab/mmdetection/tree/v3.0.0rc1/configs/rtmdet
def __init__(self, channels: int) -> None:
super().__init__()
self.pool = nn.AdaptiveAvgPool2d(1) # 自适應平均池化後,大小為(b,c,1,1)
self.fc = nn.Conv2d(channels, channels, 1, 1, 0, bias=True)
self.act = nn.Sigmoid()
def forward(self, x: torch.Tensor) -> torch.Tensor:
return x * self.act(self.fc(self.pool(x)))
1)假設輸入的資料大小是(b,c,w,h)
2)通過自适應平均池化使得輸出的大小變為(b,c,1,1)
3)通過2d卷積和sigmod激活函數後,大小是(b,c,1,1)
4)将上一步輸出的結果和輸入的資料相乘,輸出資料大小是(b,c,w,h)。
SpatialAttention
空間注意力子產品:空間次元不變,壓縮通道次元。該子產品關注的是目标的位置資訊。
class SpatialAttention(nn.Module):
# Spatial-attention module
def __init__(self, kernel_size=7):
super().__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7' # kernel size 的大小必須是3或者7
padding = 3 if kernel_size == 7 else 1 # 當kernel_size是7時,padding=3; 當kernel_size是3時,padding=1
self.cv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.act = nn.Sigmoid()
def forward(self, x):
return x * self.act(self.cv1(torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)))
1) 假設輸入的資料x是(b,c,w,h),并進行兩路處理。
2)其中一路在通道次元上進行求平均值,得到的大小是(b,1,w,h);另外一路也在通道次元上進行求最大值,得到的大小是(b,1,w,h)。
3) 然後對上述步驟的兩路輸出進行連接配接,輸出的大小是(b,2,w,h)
4)經過一個二維卷積網絡,把輸出通道變為1,輸出大小是(b,1,w,h)
4)将上一步輸出的結果和輸入的資料x相乘,最終輸出資料大小是(b,c,w,h)。
CBAM
CBAM就是把ChannelAttention和SpatialAttention串聯在一起。
class CBAM(nn.Module):
# Convolutional Block Attention Module
def __init__(self, c1, kernel_size=7): # ch_in, kernels
super().__init__()
self.channel_attention = ChannelAttention(c1)
self.spatial_attention = SpatialAttention(kernel_size)
def forward(self, x):
return self.spatial_attention(self.channel_attention(x))
C1
總共隻有3次卷積(cv1,cv2,cv3)的Bottleneck。
class C1(nn.Module):
# CSP Bottleneck with 1 convolution
def __init__(self, c1, c2, n=1): # ch_in, ch_out, number
super().__init__()
self.cv1 = Conv(c1, c2, 1, 1)
self.m = nn.Sequential(*(Conv(c2, c2, 3) for _ in range(n)))
def forward(self, x):
y = self.cv1(x)
return self.m(y) + y
1)假設輸入的資料是(b,c1,w,h)
2) 首先通過一個Conv塊,得到y, 大小為(b,c2,w,h)
3) 然後讓y通過n個3x3的Conv塊,得到m
4) 最後讓m和y相加。
C3x
C3x 繼承自C3, 變換是Bottleneck中的卷積核大小變為(1,3)和(3,3)
class C3x(C3):
# C3 module with cross-convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
self.c_ = int(c2 * e)
self.m = nn.Sequential(*(Bottleneck(self.c_, self.c_, shortcut, g, k=((1, 3), (3, 1)), e=1) for _ in range(n)))
C3TR
C3TR繼承自C3, n 個 Bottleneck 更換為 1 個 TransformerBlock。
class C3TR(C3):
# C3 module with TransformerBlock()
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
c_ = int(c2 * e)
self.m = TransformerBlock(c_, c_, 4, n)# num_heads=4, num_layers=n
SPP
空間金字塔模型:三個MaxPool 并行連接配接,kernel size分别為5 * 5,9 * 9和13 * 13
《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》
class SPP(nn.Module):
# Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729
def __init__(self, c1, c2, k=(5, 9, 13)):
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
SPPF
這個是YOLOv5作者Glenn Jocher基于SPP提出的,速度較SPP快很多,是以叫SPP-Fast。
三個MaxPool 串行連接配接,kerner size都是5*5。效果等價于SPP,但是運算量從原來的 5 2 + 9 2 + 1 3 2 = 275 5^2+9^2+13^2=275 52+92+132=275 減少到了 3 ⋅ 5 2 = 75 3\cdot 5^2=75 3⋅52=75
池化尺寸等價于SPP中kernel size分别為5 * 5,9 * 9和13 * 13的池化層并行連接配接。
class SPPF(nn.Module):
# Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13))
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * 4, c2, 1, 1)
self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
y1 = self.m(x)
y2 = self.m(y1)
return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1))
Focus
Focus子產品在v5中是圖檔進入backbone前,對圖檔進行切片操作,具體操作是在一張圖檔中每隔一個像素拿到一個值,類似于鄰近下采樣,這樣就拿到了四張圖檔,四張圖檔互補,長的差不多,但是沒有資訊丢失,這樣一來,将W、H資訊就集中到了通道空間,輸入通道擴充了4倍,即拼接起來的圖檔相對于原先的RGB三通道模式變成了12個通道,最後将得到的新圖檔再經過卷積操作,最終得到了沒有資訊丢失情況下的二倍下采樣特征圖。
例如: 原始的640 × 640 × 3的圖像輸入Focus結構,采用切片操作,先變成320 × 320 × 12的特征圖,再經過一次卷積操作,最終變成320 × 320 × 32的特征圖。切片操作如下:
class Focus(nn.Module):
# Focus wh information into c-space
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super().__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)
# self.contract = Contract(gain=2)
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))
# return self.conv(self.contract(x))
假設x的定義如下:
第一個切片: x[..., ::2, ::2]
第二個切片: x[..., 1::2, ::2]
第三個切片: x[..., ::2, 1::2]
第四個切片: x[..., 1::2, 1::2]
把上述四個切片連接配接在一起, 可以看到w,h是原始資料的一半,通道數變為原來的四倍。
GhostConv
Ghost卷積來自華為諾亞方舟實驗室,《GhostNet: More Features from Cheap Operations》發表于2020年的CVPR上。提供了一個全新的Ghost子產品,旨在通過廉價操作生成更多的特征圖。
原理如下圖所示:
Ghost Module分為兩步操作來獲得與普通卷積一樣數量的特征圖:
Step1:少量卷積(比如正常用128個卷積核,這裡就用64個,進而減少一半的計算量);
Step2:cheap operations,用圖中的Φ表示,Φ是諸如3_3、5_5的卷積,并且是逐個特征圖的進行卷積(Depth-wise convolutional,深度卷積)。
class GhostConv(nn.Module):
# Ghost Convolution https://github.com/huawei-noah/ghostnet
def __init__(self, c1, c2, k=1, s=1, g=1, act=True): # ch_in, ch_out, kernel, stride, groups
super().__init__()
c_ = c2 // 2 # hidden channels
self.cv1 = Conv(c1, c_, k, s, None, g, act=act)
self.cv2 = Conv(c_, c_, 5, 1, None, c_, act=act) # 分組數=c_=通道數,進行point-wise的深度分離卷積
def forward(self, x):
y = self.cv1(x)
return torch.cat((y, self.cv2(y)), 1)
GhostBottleneck
class GhostBottleneck(nn.Module):
# Ghost Bottleneck https://github.com/huawei-noah/ghostnet
def __init__(self, c1, c2, k=3, s=1): # ch_in, ch_out, kernel, stride
super().__init__()
c_ = c2 // 2
self.conv = nn.Sequential(
GhostConv(c1, c_, 1, 1), # 卷積核的大小是1*1,屬于point-wise的深度可分離卷積
DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # 輸入通道數和輸出通道數相等,屬于depth-wise的深度可分離卷積
GhostConv(c_, c2, 1, 1, act=False)) #point-wise的深度可分離卷積,且不采用偏置項。
self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False), Conv(c1, c2, 1, 1,
act=False)) if s == 2 else nn.Identity()
def forward(self, x):
return self.conv(x) + self.shortcut(x)
C3Ghost
C3Ghost繼承自C3, Bottleneck更換為GhostBottleneck
class C3Ghost(C3):
# C3 module with GhostBottleneck()
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
c_ = int(c2 * e) # hidden channels
self.m = nn.Sequential(*(GhostBottleneck(c_, c_) for _ in range(n)))
Concat
當dimension=1時,将多張相同尺寸的圖像在通道次元次元上進行拼接。
class Concat(nn.Module):
# Concatenate a list of tensors along dimension
def __init__(self, dimension=1):
super().__init__()
self.d = dimension
def forward(self, x):
return torch.cat(x, self.d)
Yolov8實操
快速入門
環境配置
建立虛拟環境
# 建立名為pytorch的虛拟環境,python=3.8
conda create -n pytorch python=3.8 -y
# 檢視目前存在的虛拟環境
conda env list
# 進入pytorch
conda activate pytorch
安裝pytorch和torchvision
pip install torch-1.9.0+cu102-cp38-cp38-linux_x86_64.whl
pip install torchvision-0.10.0+cu102-cp38-cp38-linux_x86_64.whl
安裝ultralytics
git clone https://github.com/ultralytics/ultralytics
cd ultralytics
pip install -e .
資料集準備
資料集制作參考: YOLO格式資料集制作(https://blog.csdn.net/zyw2002/article/details/122995823)
# 訓練/驗證/測試 資料
train: /data/zyw/project/dataset/finalTrafficLightDataset/train/images
val: /data/zyw/project/dataset/finalTrafficLightDataset/val/images
test: /data/zyw/project/dataset/finalTrafficLightDataset/WPIDataset/images
# 類别個數
nc: 12
# 類别名稱
names: ["greenCircle", "yellowCircle", "redCircle", "greenLeft", "yellowLeft", "redLeft", "greenRight", "yellowRight", "redRight", "greenForward", "yellowForward", "redForward"]
模型的訓練/驗證/預測/導出
使用CLI
如果你想對模型進行訓練、驗證或運作推斷,并且不需要對代碼進行任何修改,那麼使用YOLO指令行接口是最簡單的入門方法。
YOLO指令行界面(command line interface, CLI) 友善在各種任務和版本上訓練、驗證或推斷模型。CLI不需要定制或代碼,可以使用yolo指令從終端運作所有任務。
-
文法
yolo task=detect mode=train model=yolov8n.yaml args...
classify predict yolov8n-cls.yaml args...
segment val yolov8n-seg.yaml args...
export yolov8n.pt format=onnx args...
注意:參數不需要’- \-'字首。這些是為後面介紹的特殊指令保留的
-
訓練示例
yolo task=detect mode=train model=yolov8n.pt data=coco128.yaml device=0
-
多GPU訓練示例
yolo task=detect mode=train model=yolov8n.pt data=coco128.yaml device=\'0,1,2,3\'
-
重寫預設的配置參數
# 文法
yolo task= ... mode= ... arg=val
# 例子: 進行10個epoch的檢測訓練,learning_rate為0.01
yolo task=detect mode=train epochs=10 lr0=0.01
-
重寫預設配置檔案
# 可以在目前工作目錄下建立一個預設配置檔案的副本
yolo task=init
# 然後可以使用cfg=name.yaml指令來傳遞新的配置檔案
yolo cfg=default.yaml
使用python
允許使用者在Python項目中輕松使用YOLOv8。它提供了加載和運作模型以及處理模型輸出的函數。該界面設計易于使用,以便使用者可以在他們的項目中快速實作目标檢測。
訓練
- 從預訓練模型開始訓練
from ultralytics import YOLO
model = YOLO("yolov8n.pt") # pass any model type
model.train(epochs=5)
- 從頭開始訓練
from ultralytics import YOLO
model = YOLO("yolov8n.yaml")
model.train(data="coco128.yaml", epochs=5)
驗證
- 訓練後驗證
from ultralytics import YOLO
model = YOLO("yolov8n.yaml")
model.train(data="coco128.yaml", epochs=5)
model.val() # It'll automatically evaluate the data you trained.
- 單獨驗證
from ultralytics import YOLO
model = YOLO("model.pt")
# 如果不設定資料的話,就使用model.pt中的data yaml檔案
model.val()
# 或者直接設定需要驗證的資料。
model.val(data="coco128.yaml")
預測
- 從源檔案中預測
from ultralytics import YOLO
model = YOLO("model.pt")
model.predict(source="0") # accepts all formats - img/folder/vid.*(mp4/format). 0 for webcam
model.predict(source="folder", show=True) # Display preds. Accepts all yolo predict arguments
- 傳回結果
from ultralytics import YOLO
model = YOLO("model.pt")
outputs = model.predict(source="0", return_outputs=True) # treat predict as a Python generator
for output in outputs:
# each output here is a dict.
# for detection
print(output["det"]) # np.ndarray, (N, 6), xyxy, score, cls
# for segmentation
print(output["det"]) # np.ndarray, (N, 6), xyxy, score, cls
print(output["segment"]) # List[np.ndarray] * N, bounding coordinates of masks
# for classify
print(output["prob"]) # np.ndarray, (num_class, ), cls prob
使用訓練器
YOLO模型類是Trainer類的進階包裝器。每個YOLO任務都有自己的從BaseTrainer繼承來的訓練器。
from ultralytics.yolo import v8 import DetectionTrainer, DetectionValidator, DetectionPredictor
# trainer
trainer = DetectionTrainer(overrides={})
trainer.train()
trained_model = trainer.best
# Validator
val = DetectionValidator(args=...)
val(model=trained_model)
# predictor
pred = DetectionPredictor(overrides={})
pred(source=SOURCE, model=trained_model)
# resume from last weight
overrides["resume"] = trainer.last
trainer = detect.DetectionTrainer(overrides=overrides)
多任務支援
yolov8支援檢測、分割、分類 三種任務。
檢測
物體檢測是一項涉及識别圖像或視訊流中物體的位置和類别的任務。
對象檢測器的輸出是一組包圍圖像中的對象的包圍框,以及每個框的類标簽和置信度分數。當你需要識别場景中感興趣的物體,但不需要知道物體的确切位置或它的确切形狀時,物體檢測是一個很好的選擇。
YOLOv8檢測模型沒有字尾,是預設的YOLOv8模型,即yolov8n.pt,并在COCO上進行預訓練。
訓練
在COCO128資料集上訓練YOLOv8n 100個epoch,圖像大小為640。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.yaml") # build a new model from scratch
model = YOLO("yolov8n.pt") # load a pretrained model (recommended for training)
# Train the model
results = model.train(data="coco128.yaml", epochs=100, imgsz=640)
- CLI
yolo task=detect mode=train data=coco128.yaml model=yolov8n.pt epochs=100 imgsz=640
驗證
驗證訓練YOLOv8n模型在COCO128資料集上的準确性。不需要傳遞參數,因為模型保留了它的訓練資料和參數作為模型屬性。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Validate the model
results = model.val() # no arguments needed, dataset and settings remembered
- CLI
yolo task=detect mode=val model=yolov8n.pt # val official model
yolo task=detect mode=val model=path/to/best.pt # val custom model
預測
使用經過訓練的YOLOv8n模型對圖像進行預測。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Predict with the model
results = model("https://ultralytics.com/images/bus.jpg") # predict on an image
- CLI
yolo task=detect mode=predict model=yolov8n.pt source="https://ultralytics.com/images/bus.jpg" # predict with official model
yolo task=detect mode=predict model=path/to/best.pt source="https://ultralytics.com/images/bus.jpg" # predict with custom model
導出
導出YOLOv8n模型到不同的格式,如ONNX, CoreML等。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom trained
# Export the model
model.export(format="onnx")
- CLI
yolo mode=export model=yolov8n.pt format=onnx # export official model
yolo mode=export model=path/to/best.pt format=onnx # export custom trained model
可用的YOLOv8導出格式包括:
Format | format= | Model |
https://pytorch.org/ | - | yolov8n.pt |
https://pytorch.org/docs/stable/jit.html | torchscript | yolov8n.torchscript |
https://onnx.ai/ | onnx | yolov8n.onnx |
https://docs.openvino.ai/latest/index.html | openvino | yolov8n_openvino_model/ |
https://developer.nvidia.com/tensorrt | engine | yolov8n.engine |
https://github.com/apple/coremltools | coreml | yolov8n.mlmodel |
https://www.tensorflow.org/guide/saved_model | saved_model | yolov8n_saved_model/ |
https://www.tensorflow.org/api_docs/python/tf/Graph | pb | yolov8n.pb |
https://www.tensorflow.org/lite | tflite | yolov8n.tflite |
https://coral.ai/docs/edgetpu/models-intro/ | edgetpu | yolov8n_edgetpu.tflite |
https://www.tensorflow.org/js | tfjs | yolov8n_web_model/ |
https://github.com/PaddlePaddle | paddle | yolov8n_paddle_model/ |
執行個體分割
執行個體分割比對象檢測更進一步,涉及識别圖像中的單個對象,并将它們從圖像的其餘部分分割出來
執行個體分割模型的輸出是一組掩碼或輪廓,它們勾勒出圖像中的每個對象,以及每個對象的類标簽和置信度分數。當你不僅需要知道物體在圖像中的位置,還需要知道它們的确切形狀時,執行個體分割非常有用。
YOLOv8分割模型使用-seg字尾,即yolov8n- seg .pt,并在COCO上進行預訓練。
訓練
在COCO128-seg資料集上訓練YOLOv8n-seg 100個epoch,圖像大小為640。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.yaml") # build a new model from scratch
model = YOLO("yolov8n-seg.pt") # load a pretrained model (recommended for training)
# Train the model
results = model.train(data="coco128-seg.yaml", epochs=100, imgsz=640)
- CLI
yolo task=segment mode=train data=coco128-seg.yaml model=yolov8n-seg.pt epochs=100 imgsz=640
驗證
在COCO128-seg資料集上驗證訓練過的YOLOv8n-seg模型的準确性。不需要傳遞參數,因為模型保留了它的訓練資料和參數作為模型屬性。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Validate the model
results = model.val() # no arguments needed, dataset and settings remembered
- CLI
yolo task=segment mode=val model=yolov8n-seg.pt # val official model
yolo task=segment mode=val model=path/to/best.pt # val custom model
預測
使用訓練過的YOLOv8n-seg模型對圖像進行預測。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Predict with the model
results = model("https://ultralytics.com/images/bus.jpg") # predict on an image
- CLI
yolo task=segment mode=predict model=yolov8n-seg.pt source="https://ultralytics.com/images/bus.jpg" # predict with official model
yolo task=segment mode=predict model=path/to/best.pt source="https://ultralytics.com/images/bus.jpg" # predict with custom model
導出
導出YOLOv8n-seg模型到不同的格式,如ONNX, CoreML等。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom trained
# Export the model
model.export(format="onnx")
- CLI
yolo mode=export model=yolov8n-seg.pt format=onnx # export official model
yolo mode=export model=path/to/best.pt format=onnx # export custom trained model
分類
圖像分類是三個任務中最簡單的,涉及到将整個圖像分類到一組預定義的類中的一個。
圖像分類器的輸出是一個單一的類标簽和一個置信度分數。當您隻需要知道圖像屬于什麼類,而不需要知道該類對象的位置或它們的确切形狀時,圖像分類是有用的。
YOLOv8分類模型使用-cls字尾,即yolov8n-cls.pt,并在ImageNet上進行預訓練。其他的使用方法和檢測與分割類似,不再贅述。
配置
YOLO設定和超參數在模型的性能、速度和準确性中起着至關重要的作用。這些設定和超參數可以在模型開發過程的各個階段影響模型的行為,包括訓練、驗證和預測。
正确地設定和調優這些參數可以對模型有效地從訓練資料中學習并推廣到新資料的能力産生重大影響。例如,選擇合适的學習率、批大小和優化算法會極大地影響模型的收斂速度和精度。同樣,設定正确的置信度門檻值和非最大抑制(NMS)門檻值也會影響模型在檢測任務上的性能。
設定操作類型
YOLO模型可用于各種任務,包括檢測、分割和分類。這些任務的不同之處在于它們産生的輸出類型和它們要解決的特定問題。
- 檢測:檢測任務涉及識别和定位圖像或視訊中感興趣的對象或區域。YOLO模型通過預測圖像中物體的邊界框和類标簽,可以用于物體檢測任務。
- 分割:分割任務包括将圖像或視訊劃分為對應于不同對象或類的區域或像素。
- 分類:分類任務包括為輸入(如圖像或文本)配置設定類标簽。YOLO模型可以通過預測輸入圖像的類别标簽來進行圖像分類任務
根據你要解決的特定問題,YOLO模型可以在不同的模式下使用。這些模式包括train、val和predict。
- 訓練(Train):訓練模式用于在資料集上訓練模型。這種模式通常在模型的開發和測試階段使用。
- Val: Val模式用于評估模型在驗證資料集上的性能。這種模式通常用于調整模型的超參數和檢測過拟合。
- Predict: Predict模式用于在新資料上使用模型進行預測。這種模式通常用于生産環境或将模型部署給使用者時。
Key | Value | Description |
task | detect | 可選擇:detect, segment, classify |
mode | train | 可選擇: train, val, predict |
resume | False | 當設定為True時,恢複上一次的任務。當設定為False時,從給定的model.pt中恢複。 |
model | null | 設定模型。格式因任務類型而異。支援model_name, model.yaml,model.pt |
data | null | 設定資料,支援多數類型 data.yaml, data_folder, dataset_name |
訓練
YOLO模型的訓練設定是指用于在資料集上訓練模型的各種超參數和配置。這些設定會影響模型的性能、速度和精度。一些常見的YOLO訓練設定包括批量大小、學習率、動量和權重衰減。其他可能影響訓練過程的因素包括優化器的選擇、損失函數的選擇、訓練資料集的大小群組成。重要的是要仔細調整和試驗這些設定,以實作給定任務的最佳性能。
Key | Value | Description |
device | ‘’ | Cuda裝置,即0或0、1、2、3或cpu。選擇可用的cuda0裝置 |
epochs | 100 | 需要訓練的epoch數 |
workers | 8 | 每個程序使用的cpu worker數。使用DDP自動伸縮 |
batch | 16 | Dataloader的batch大小 |
imgsz | 640 | Dataloader中圖像資料的大小 |
optimizer | SGD | 支援的優化器:Adam, SGD, RMSProp |
single_cls | False | 将多類資料作為單類進行訓練 |
image_weights | False | 使用權重圖像選擇進行訓練 |
rect | False | 啟用矩形訓練 |
cos_lr | False | 使用cosine LR排程器 |
lr0 | 0.01 | 初始化學習率 |
lrf | 0.01 | 最終的OneCycleLR學習率 |
momentum | 0.937 | 作為SGD的momentum和Adam的beta1 |
weight_decay | 0.0005 | 優化器權重衰減 |
warmup_epochs | 3.0 | Warmup的epoch數,支援分數 |
warmup_momentum | 0.8 | warmup的初始動量 |
warmup_bias_lr | 0.1 | Warmup的初始偏差lr |
box | 0.05 | Box loss gain |
cls | 0.5 | cls loss gain |
cls_pw | 1.0 | cls BCELoss positive_weight |
obj | 1.0 | bj loss gain (scale with pixels) |
obj_pw | 1.0 | obj BCELoss positive_weight |
iou_t | 0.20 | iou訓練時的門檻值 |
anchor_t | 4.0 | anchor-multiple門檻值 |
fl_gamma | 0.0 | focal loss gamma |
label_smoothing | 0.0 | |
nbs | 64 | nominal batch size |
overlap_mask | True | 分割:在訓練中使用掩碼重疊 |
mask_ratio | 4 | 分割:設定掩碼下采樣 |
dropout | False | 分類:訓練時使用dropout |
預測
YOLO模型的預測設定是指用于在新資料上使用模型進行預測的各種超參數和配置。這些設定會影響模型的性能、速度和精度。一些常見的YOLO預測設定包括置信度門檻值、非最大抑制(NMS)門檻值和要考慮的類别數量。其他可能影響預測過程的因素包括輸入資料的大小和格式,是否存在額外的特征(如掩碼或每個框的多個标簽),以及模型正在用于的特定任務。重要的是要仔細調整和試驗這些設定,以實作給定任務的最佳性能。
Key | Value | Description |
source | ultralytics/assets | 輸入源。支援圖檔、檔案夾、視訊、網址 |
show | False | 檢視預測圖檔 |
save_txt | False | 儲存結果到txt檔案中 |
save_conf | False | 儲存condidence scores |
save_crop | Fasle | |
hide_labels | False | 隐藏labels |
hide_conf | False | 隐藏confidence scores |
vid_stride | False | 輸入視訊幀率步長 |
line_thickness | 3 | 邊框厚度(機關:像素) |
visualize | False | 可視化模型特征 |
augment | False | 增強推理 |
agnostic_nms | False | Class-agnostic NMS |
retina_masks | False | 分割:高分辨率掩模 |
驗證
YOLO模型的驗證設定是指用于評估模型在驗證資料集上性能的各種超參數和配置。這些設定會影響模型的性能、速度和精度。一些常見的YOLO驗證設定包括批量大小、訓練期間執行驗證的頻率以及用于評估模型性能的名額。其他可能影響驗證過程的因素包括驗證資料集的大小群組成,以及模型正在用于的特定任務。重要的是要仔細調整和實驗這些設定,以確定模型在驗證資料集上表現良好,并檢測和防止過拟合。
Key | Value | Description |
noval | False | |
save_json | False | |
save_hybrid | False | |
conf | 0.001 | 置信度門檻值 |
iou | 0.6 | IoU門檻值 |
max_det | 300 | 最大檢測數 |
half | True | 使用.half()模型 |
dnn | False | 使用OpenCV DNN進行ONNX推斷 |
plots | False |
資料擴充
YOLO模型的增強設定是指應用于訓練資料的各種變換和修改,以增加資料集的多樣性和大小。這些設定會影響模型的性能、速度和精度。一些常見的YOLO增強設定包括應用的轉換類型和強度(例如随機翻轉、旋轉、裁剪、顔色變化),應用每個轉換的機率,以及是否存在其他功能,如掩碼或每個框多個标簽。其他可能影響資料擴充過程的因素包括原始資料集的大小群組成,以及模型正在用于的特定任務。重要的是要仔細調整和實驗這些設定,以確定增強後的資料集具有足夠的多樣性和代表性,以訓練高性能的模型。
Key | Value | Description |
hsv_h | 0.015 | Image HSV-Hue augmentation (fraction) |
hsv_s | 0.7 | Image HSV-Saturation augmentation (fraction) |
hsv_v | 0.4 | Image HSV-Value augmentation (fraction) |
degrees | 0.0 | Image rotation (+/- deg) |
translate | 0.1 | Image translation (+/- fraction) |
scale | 0.5 | Image scale (+/- gain) |
shear | 0.0 | Image shear (+/- deg) |
perspective | 0.0 | Image perspective (+/- fraction), range 0-0.001 |
flipud | 0.0 | Image flip up-down (probability) |
fliplr | 0.5 | Image flip left-right (probability) |
mosaic | 1.0 | Image mosaic (probability) |
mixup | 0.0 | Image mixup (probability) |
copy_paste | 0.0 | Segment copy-paste (probability) |
日志,檢查點,繪圖和檔案管理
在訓練YOLO模型時,日志記錄、檢查點、繪圖和檔案管理是重要的考慮因素。
- 日志記錄:在訓練期間記錄各種名額和統計資料通常有助于跟蹤模型的進展和診斷任何可能出現的問題。這可以通過使用日志庫(如TensorBoard)或将日志消息寫入檔案來實作。
- 檢查點:在訓練期間,定期儲存模型的檢查點是一個很好的做法。如果訓練過程被中斷,或者你想嘗試不同的訓練配置,這允許你從之前的點恢複訓練。繪圖:可視化模型的性能和訓練過程,有助于了解模型的行為方式和識别潛在問題。這可以使用matplotlib等繪圖庫完成,也可以使用TensorBoard等日志庫來繪圖。
- 檔案管理:管理訓練過程中生成的各種檔案,例如模型檢查點、日志檔案和繪圖,可能具有挑戰性。有一個清晰和有組織的檔案結構是很重要的,以便跟蹤這些檔案,并使其易于根據需要通路和分析它們。
有效的日志記錄、檢查點、繪圖和檔案管理可以幫助您跟蹤模型的進度,并使其更容易調試和優化訓練過程。
Key | Value | Description |
project: | ‘runs’ | The project name |
name: | ‘exp’ | The run name. exp gets automatically incremented if not specified, i.e, exp, exp2 … |
exist_ok: | False | |
plots | False | 在驗證時儲存圖像 |
nosave | False | 不儲存任何檔案 |
自定義模型
Ultralytics YOLO指令行和python接口都隻是基本引擎執行器的進階抽象。讓我們來看看Trainer引擎。
BaseTrainer
BaseTrainer包含通用的樣闆訓練例程。隻要遵循正确的格式,它可以針對任何基于覆寫所需功能或操作的任務進行定制。例如,你可以通過覆寫這些函數來支援你自己的自定義模型和dataloder:
- get_model(cfg, weights) - 用來建立模型的函數
- get_dataloder() - 用來建立dataloader的函數
DetectionTrainer
以下是如何使用YOLOv8 DetectionTrainer并自定義它。
from ultralytics.yolo.v8.detect import DetectionTrainer
trainer = DetectionTrainer(overrides={...})
trainer.train()
trained_model = trainer.best # get best model
自定義Detection Trainer
from ultralytics.yolo.v8.detect import DetectionTrainer
class CustomTrainer(DetectionTrainer):
def get_model(self, cfg, weights):
...
def criterion(self, preds, batch):
# get ground truth
imgs = batch["imgs"]
bboxes = batch["bboxes"]
...
return loss, loss_items # see Reference-> Trainer for details on the expected format
# callback to upload model weights
def log_model(trainer):
last_weight_path = trainer.last
...
trainer = CustomTrainer(overrides={...})
trainer.add_callback("on_train_epoch_end", log_model) # Adds to existing callback
trainer.train()
參考
詳細解讀YOLOv8的改進子產品
mmyolo:https://zhuanlan.zhihu.com/p/598566644