一手刷卡pos機數據,我用YOLOX露了一手

 新聞資訊  |   2023-04-12 09:42  |  投稿人:pos機之家

網上有很多關于一手刷卡pos機數據,我用YOLOX露了一手的知識,也有很多人為大家解答關于一手刷卡pos機數據的問題,今天pos機之家(www.tonybus.com)為大家整理了關于這方面的知識,讓我們一起來看下吧!

本文目錄一覽:

1、一手刷卡pos機數據

一手刷卡pos機數據

作者丨葉潤源

來源丨https://www.yuque.com/yerunyuan/ar9831/tsm0id#Kfi4w

編輯丨極市平臺

YOLOX的Anchor Free(Anchor Based針對數據集聚類分析得到Anchor Box的方式,怕對泛化會有影響,尤其前期缺乏現場數據時)以及更有效的Label Assignment(SimOTA),使我下決心將目前所用的FCOS+ATSS模型換成YOLOX模型。

這次改動將YOLOX添加到了Yolov5上,在Yolov5的框架下,訓練150個epoch的yolox-s模型的mAP也達到了39.7(且未使用mixup數據增強和random resize)。

一、實驗環境:

實驗機器:

1臺PC:CPU: AMD Ryzen 7 1700X Eight-Core Processor, 內存: 32G, 顯卡: 2張GeForce GTX 1080 Ti 11G

1臺PC:CPU: AMD Ryzen 5 2600 Six-Core Processor, 內存: 32G, 顯卡: 2張GeForce GTX 1080 Ti 11G

目標部署硬件:A311D開發板(帶8位整型5TOPS算力的NPU)

軟件版本:Python版本為3.7.7,Pytorch版本為1.7.1,Cuda版本為10.1

官方YoloX版本:https://github.com/Megvii-BaseDetection/YOLOX.git

Commits: 29df1fb9bc456fcd5c35653312d7c22c9f66b9f8 (Aug 2, 2021)

官方Yolov5版本:https://github.com/ultralytics/yolov5.git

Commits: f409d8e54f9391ce21436d33334beff3a2fd4042 (Aug 4, 2021)

二、選擇適合NPU的架構試驗:1、速度實驗:

注:a、 模型在NPU上的速度實驗,并不需要把模型完整地訓練一遍,那樣太耗時,只需要將模型導出(初始化后導出或者少量圖片train一個epoch),再量化轉換為NPU的模型即可。

b、 另一方面,NPU對有些層不支持、層與層之間的搭配、或層的實現完整性差異(參見:【原創】A311D模型轉換問題 https://www.yuque.com/yerunyuan/npu/gwq7ak),會導致模型轉換成NPU模型時失敗,這樣花大力氣訓練出來模型用不上,白白浪費時間,尤其對于小公司,訓練機器資源有限,訓練一個模型一兩天時間就過去了,因為模型轉換失敗或者模型性能不達標,又要重來一遍,會推遲項目進度。

c、 對于面向產品快速部署落地而言,在開始訓練模型之前,需要先確保模型能成功轉換成目標硬件上,以及能在目標硬件上達到所需的性能要求。

1) YOLOX_S模型在NPU上640x640分辨率下純推理速度(不包括前處理和后處理)每幀需要62.3ms

2) 原來部署在NPU上的FCOS ATSS模型在同等分辨率下NPU純推理速度每幀只需要45.58ms;

我們的FCOS對解耦頭做過簡化設計,但為了融合多數據集以及自有數據集訓練(如,coco,wider face等),進行多種不同任務檢測(如,人體檢測,人臉檢測等),采用更多的解耦頭分支來規避數據集之間相互缺少的類別標簽問題。最終更換成YOLOX_S模型,也是需要實現同時檢測多個任務的功能,如果原始的YOLOX_S模型在NPU上就比FCOS在速度上差這么多,較難應用;

3) 將YOLOX_S模型的SiLU激活函數替換成ReLU激活函數在同等分辨率下NPU純推理速度每幀只需要42.61ms;

我們所用的NPU可以將Conv+ReLU融合成一個層(注意多看NPU手冊,了解哪些層的組合以及什么樣的層的參數配置對性能優化更友好),而SiLU激活函數是不會做融合的,這意味著更多的運算量以及內存訪問(在32位DDR4甚至DDR3的內存的NPU開發板上,內存訪問對性能的影響是不容忽視的),因此,只是更換了一下激活函數推理速度便提升為原來的1.46倍了;

我很想知道SiLU比ReLU到底能提升多少的AP值(但沒找到,唯一能找到的是對ImageNet數據集的分類模型來說的),如果AP提升不多,1.46倍的性能差別,覺得不值,從其他地方補回來可能更劃算;

4) 將YOLOX_S模型的SiLU激活函數替換成LeakyReLU激活函數在同等分辨率下NPU純推理速度每幀需要54.36ms

Conv+LeakyReLU不會融合成一個層,LeakyReLU也多一點運算,性能相比ReLU也慢不少;

5) YOLOX_S模型使用ReLU+SiLU激活函數,即大部分使用ReLU激活函數小部分使用SiLU激活函數(所有stride為2的Conv、SPP、所有C3中的最后一個Conv都使用SiLU)在同等分辨率下NPU純推理速度每幀需要44.22ms;

SiLU激活函數可以增加非線性, ReLU激活函數的非線性感覺還是比較有限:

而ReLU激活函數大于0部分是一條直線,只靠小于0時截斷為0實現非線性,其對非線性的表達能力相對于SiLU激活函數感覺是不如的。非線性表達能力的欠缺,感覺會讓模型收斂更慢,更難以訓練。

全部使用SiLU激活函數推理速度較低,因此,打算使用ReLU激活函數+SiLU激活函數的方式。

注:由于YOLOX代碼的模型訓練速度太慢(補充:Aug 19, 2021后的版本對訓練速度做了優化),下面使用YOLOv5的YOLOv5-s模型做實驗。

下面是不同激活函數的YOLOX_S模型的前5個epoch的mAP值,其中ReLU+Kaiming初始化,表示卷積層的初始化使用的是針對ReLU的Kaiming初始化;

表格中的每個單元第一個數值為mAP@.5:.95,第二個的數值為mAP@.5

選擇ReLU與SiLU的組合時,只使用了第一個epoch的mAP作為參考,一般第一個epoch的mAP越高,后面的收斂速度也越快,最終的mAP一般也會更高一些。像ReLU+Kaiming初始化與ReLU兩個網絡一樣,只是Conv參數的初始化方法不同,ReLU+Kaiming初始化開始的收斂要慢些(從mAP值的角度來看),但最終訓練完150個epoch其mAP@.5:.95相差不大(一個為0.339,一個為0.338),訓練迭代次數多了之后參數初始化的影響變得很小。

訓練完150epoch,各模型的mAP對比(移植了YOLOX的評估方法):

可以看到,ReLU+SiLU相比SiLU低1.1個點左右,比ReLU高1個點,ReLU+SiLU是推理速度與mAP值較好的折中方案。注:雖然Yolov5中沒有使用解耦頭和SimOTA,但測試模型速度時是帶了解耦頭的,這里也大概反映出了SiLU和ReLU對mAP值的影響。

發現kaiming的Relu卷積初始化的兩種標準差生成的參數的絕對值之和的均值,都是比默認的卷積初始化要大,分別是默認初始化的2.247倍和1.587倍(統計隨機生成的100000個參數,受隨機數影響,每次運行這個倍率會有少量變化,差別不大);

總得來說,kaiming的Relu卷積初始化比默認的卷積初始化的參數要大,如果最優化參數偏小,初始化為較大參數,在模型訓練前期收斂確實也會慢些。

2、一個想法:

統計訓練好的模型的參數,對均勻分布或正態分布做參數估計,看模型以什么方式初始化參數更合適?能否從統計角度得到更好的參數初始化方法?這對于從0開始訓練模型也許會有幫助,也許對提升AP影響不大,不過可能可以減少收斂所用的epoch數,相當于提升訓練速度。

三、合并YoloX到Yolov5中:

由于YoloX訓練速度相比Yolov5慢很多 ,而我手上只有2張1080ti顯卡的機器,YoloX訓練yolox-s配置且只訓練150epoch也要5天17小時(54.98分鐘/epoch),而Yolov5訓練yolov5-s配置且只訓練150epoch只要1天19小時(17.48分鐘/epoch),雖然yolox-s加了解耦頭和SimOTA,也不至于差那么多。因此,打算將YoloX合并到Yolov5中。:YoloX后來Aug 19, 2021的版本對訓練速度做了優化,據說速度提升2倍。

另外,將YoloX合并到Yolov5后,也好對比YoloX相對于Yolov5哪些改進比較有效。解耦頭可能有效,但也增加了不少計算量,Yolov5總結的計算量來看,Yolov5-s是17.1 GFLOPs,而YoloX-s是26.8 GFLOPs。實際在NPU上測試的推理速度,在640x640分辨率下,YoloX-s需要62.3ms,而Yolov5-s只需要52.00ms,慢了1.198倍,10.3ms差別還是不小的。如果像YoloX論文所說(下表)只相差1.1個AP也許并不值得,或者將解耦頭從2層減少到1層。

1、問題解決:

Yolov5的混合精度訓練使用的是torch自帶的torch.cuda.amp,而YoloX使用的是apex的apex.amp,使用binary_cross_entropy會引起報錯:

pair_wise_cls_loss = F.binary_cross_entropy(cls_preds_, gt_cls_per_image, reduction="none").sum(-1) # [num_gt, fg_count]

報錯如下:

Traceback (most recent call last): File "/rootfs/media/kasim/DataSet/git/yolov5/models/yolo.py", line 425, in get_losses obj_preds, File "/media/kasim/Data1/pytorch1.7_python3.7_venv/lib/python3.7/site-packages/torch/autograd/grad_mode.py", line 26, in decorate_context return func(*args, **kwargs) File "/rootfs/media/kasim/DataSet/git/yolov5/models/yolo.py", line 624, in get_assignments pair_wise_cls_loss = F.binary_cross_entropy(cls_preds_, gt_cls_per_image, reduction="none").sum(-1) # [num_gt, fg_count] File "/media/kasim/Data1/pytorch1.7_python3.7_venv/lib/python3.7/site-packages/torch/nn/functional.py", line 2526, in binary_cross_entropy input, target, weight, reduction_enum)RuntimeError: torch.nn.functional.binary_cross_entropy and torch.nn.BCELoss are unsafe to autocast.Many models use a sigmoid layer right before the binary cross entropy layer.In this case, combine the two layers using torch.nn.functional.binary_cross_entropy_with_logitsor torch.nn.BCEWithLogitsLoss. binary_cross_entropy_with_logits and BCEWithLogits aresafe to autocast.During handling of the above exception, another exception occurred:Traceback (most recent call last): File "/media/kasim/DataSet/git/yolov5/train.py", line 642, in <module> main(opt) File "/media/kasim/DataSet/git/yolov5/train.py", line 540, in main train(opt.hyp, opt, device) File "/media/kasim/DataSet/git/yolov5/train.py", line 341, in train loss, loss_items = compute_loss(pred, targets.to(device)) # loss scaled by batch_size File "/rootfs/media/kasim/DataSet/git/yolov5/utils/loss.py", line 322, in __call__ dtype=bbox_preds[0].dtype, File "/rootfs/media/kasim/DataSet/git/yolov5/models/yolo.py", line 465, in get_losses "cpu", File "/media/kasim/Data1/pytorch1.7_python3.7_venv/lib/python3.7/site-packages/torch/autograd/grad_mode.py", line 26, in decorate_context return func(*args, **kwargs) File "/rootfs/media/kasim/DataSet/git/yolov5/models/yolo.py", line 624, in get_assignments pair_wise_cls_loss = F.binary_cross_entropy(cls_preds_, gt_cls_per_image, reduction="none").sum(-1) # [num_gt, fg_count] File "/media/kasim/Data1/pytorch1.7_python3.7_venv/lib/python3.7/site-packages/torch/nn/functional.py", line 2526, in binary_cross_entropy input, target, weight, reduction_enum)RuntimeError: torch.nn.functional.binary_cross_entropy and torch.nn.BCELoss are unsafe to autocast.Many models use a sigmoid layer right before the binary cross entropy layer.In this case, combine the two layers using torch.nn.functional.binary_cross_entropy_with_logitsor torch.nn.BCEWithLogitsLoss. binary_cross_entropy_with_logits and BCEWithLogits aresafe to autocast.

大意是torch.nn.functional.binary_cross_entropy和torch.nn.BCELoss不能進行安全的自動轉換(16位浮點與32浮點之間的互轉),讓你使用torch.nn.functional.binary_cross_entropy_with_logits或torch.nn.BCEWithLogitsLoss 。

不過,YoloX標簽分配(label assign)函數get_assignments,使用的分類loss所輸入的預測類別置信度(概率)是預測類別置信度(概率)乘上預測目標置信度(概率)再開根號,偽碼如下:

cls_preds_ = (cls_preds_.sigmoid_() * obj_preds_.sigmoid_()).sqrt_()

這不是標準的logits函數,無法使用binary_cross_entropy_with_logits或BCEWithLogitsLoss來代替。其實傳遞給F.binary_cross_entropy的cls_preds_和gt_cls_per_image已經是32位浮點了(從代碼易知),因此,不需要自動轉換,通過torch.cuda.amp.autocast關閉F.binary_cross_entropy的自動轉換即可,偽碼如下:

from torch.cuda.amp import autocastwith autocast(enabled=False): pair_wise_cls_loss = F.binary_cross_entropy(cls_preds_, gt_cls_per_image, reduction="none").sum(-1) # [num_gt, fg_count]

當然也可以分別計算分類loss和目標loss再相加,畢竟最終訓練的所用的loss也是這樣。而,標簽分配(label assign)所用的loss將預測類別置信度與預測目標置信度相乘后再求loss,可能考慮到最終NMS所用的置信度也是通過他們兩者相乘得到,可能更能反應給anchor所分配的標簽的可信度(loss越小越可能分配對)。開根號可能是為了統一單位?另外,兩個小于1的值相乘也會變得更小,訓練一開始置信度可能都比較低,再乘一起值可能會更小。

2、兩個想法:

A、 如果NMS也使用的置信度在預測類別置信度與預測目標置信度相乘后再開根號對AP值會有什么影響?可能也沒什么影響,畢竟NMS比的只是置信度大小,開根號后并不影響單調性(大的還是大,小的還是小),不過對置信度閾值的選擇可能會有影響,置信度閾值的選擇可能變得更為平滑?畢竟原來的置信度有點二次關系的意味;

B、 如果訓練的總loss也加上這個loss進行輔助訓練能否提升AP值?畢竟模型推理時NMS所用的置信度便是預測類別置信度與預測目標置信度乘積;

3、優化,再快一點:

將YoloX移植到Yolov5上后,訓練1個epoch的時間從54.979分鐘降低到了27.766分鐘,訓練速度提升了1.98倍,從原來訓練150個epoch要5天17小時,降低到了2天21小時。不過,YoloX實現代碼還有優化空間,尤其是標簽分配(label assign)函數get_assignments,除了解耦頭,YoloX與Yolov5的訓練速度差距主要就在于SimOTA標簽分配函數get_assignments,對其進行優化后(在將YoloX移植到Yolov5后,在Yolov5框架上測試),get_assignments的速度從原來的4852524ns(納秒一幀,單gpu上測試,4.852ms)下降到2658222ns(2.658ms),forward+get_losses的速度從11661420ns(11.661ms)下降到9198305ns(9.198ms),訓練1個epoch的時間降低到了23.533分鐘,訓練速度再提升1.1798倍,總共提升到原始YoloX的2.336倍。

:這里的測試數據只針對我使用的機器,不同機器差別也許不一樣。

SimOTA標簽分配函數get_assignments各主要優化速度對比:

1)一些小修改:

A、注意順序:

cls_preds_.float().unsqueeze(0).repeat(num_gt, 1, 1).sigmoid_()

repeat之后數量就增多了,sigmoid_放后面無疑也增加了運算量,修改如下:

cls_preds_.float().sigmoid_().unsqueeze(0).repeat(num_gt, 1, 1)

B、不要頻繁地在gpu和cpu之間切換數據:

for gt_idx in range(num_gt): _, pos_idx = torch.topk(cost[gt_idx], k=dynamic_ks[gt_idx].item(), largest=False)

item()函數會將gpu的數據轉換為python的數據,但不要每個數據都去調用一次,如果每個數據都要轉,調用tolist()函數對整個tensor做轉換即可。

ks = dynamic_ks.tolist()for gt_idx in range(num_gt): _, pos_idx = torch.topk(cost[gt_idx], k=ks[gt_idx], largest=False)

C、注意Tensor的創建:

expanded_strides.append( torch.zeros(1, grid.shape[1]) .fill_(stride_this_level) .type_as(xin[0]))

這個Tensor的創建會先對創建的Tensor清0,再填充為stride_this_level,然后做類型轉換,其實這個可以一步做完:

expanded_strides.append( torch.full((1, grid.shape[1]), stride_this_level, dtype=xin[0].dtype, device=xin[0].device))

D、等等,每一小點修改不一定能帶來多少性能提升,不過積少成多、養成良好的編碼習慣。

2)理解pytorch的幾個函數及差別:

A、expand和repeat:它們可以完成類似的功能,但repeat會分配內存和拷貝數據,而expand不會,它只是創建新視圖,因此,如果要節省內存使用量,可以使用expand;

B、view和permute:它們使用的還是原來的內存,修改這兩個操作返回的數據,也會修改到這兩個操作輸入的數據,即它們不會分配新的內存,只是改變視圖;permute會讓tensor的內存變得不連續(is_contiguous函數返回False),我的理解是,對permute轉置后的維度,按0,1,2,3順序索引來讀寫轉置后tensor,其訪問到的內存不是連續的。不過,如果轉置的兩個維度其中一個維度為1,那么轉置后還是連續的。permute轉置后接view,view也不會讓tensor變得連續,可以使用contiguous函數使得tensor內存變得連續,不過它會分配新的內存并拷貝數據(如果這個tensor的內存是不連續時)。reshape可以完成view相似的功能,區別是reshape相當于在view后再調用了contiguous,即reshape可能會重新分配內存和拷貝數據。

C、cat:會分配新的內存和拷貝數據,非必要不去cat,尤其是那種為了方便參數傳遞,cat在一起,后面又要去拆cat來處理的情況;

D、通過切片方式獲取子tensor不會分配內存,通過list或tensor作為索引獲取子tensor會分配內存,切片方式可以節省內存但獲取的子tensor的內存是不連續的,連續的內存有時可以加速運算:

cc0 = cost[:, :2, :] # 不會分配新內存cc1 = cost[:, 0::2, :] # 不會分配新內存idx = torch.tensor([0, 1], dtype=torch.long)cc2 = cost[:, idx, :] # 會分配新內存cc3 = cost[:, [0, 1], :] # 會分配新內存print(cc1.is_contiguous(), cc2.is_contiguous(), cc3.is_contiguous())3)get_in_boxes_info優化:

原代碼:

def get_in_boxes_info( self, gt_bboxes_per_image, expanded_strides, x_shifts, y_shifts, total_num_anchors, num_gt,): expanded_strides_per_image = expanded_strides[0] x_shifts_per_image = x_shifts[0] * expanded_strides_per_image y_shifts_per_image = y_shifts[0] * expanded_strides_per_image x_centers_per_image = ( (x_shifts_per_image + 0.5 * expanded_strides_per_image) .unsqueeze(0) .repeat(num_gt, 1) ) # [n_anchor] -> [n_gt, n_anchor] y_centers_per_image = ( (y_shifts_per_image + 0.5 * expanded_strides_per_image) .unsqueeze(0) .repeat(num_gt, 1) ) gt_bboxes_per_image_l = ( (gt_bboxes_per_image[:, 0] - 0.5 * gt_bboxes_per_image[:, 2]) .unsqueeze(1) .repeat(1, total_num_anchors) ) gt_bboxes_per_image_r = ( (gt_bboxes_per_image[:, 0] + 0.5 * gt_bboxes_per_image[:, 2]) .unsqueeze(1) .repeat(1, total_num_anchors) ) gt_bboxes_per_image_t = ( (gt_bboxes_per_image[:, 1] - 0.5 * gt_bboxes_per_image[:, 3]) .unsqueeze(1) .repeat(1, total_num_anchors) ) gt_bboxes_per_image_b = ( (gt_bboxes_per_image[:, 1] + 0.5 * gt_bboxes_per_image[:, 3]) .unsqueeze(1) .repeat(1, total_num_anchors) ) b_l = x_centers_per_image - gt_bboxes_per_image_l b_r = gt_bboxes_per_image_r - x_centers_per_image b_t = y_centers_per_image - gt_bboxes_per_image_t b_b = gt_bboxes_per_image_b - y_centers_per_image bbox_deltas = torch.stack([b_l, b_t, b_r, b_b], 2) is_in_boxes = bbox_deltas.min(dim=-1).values > 0.0 is_in_boxes_all = is_in_boxes.sum(dim=0) > 0 # in fixed center center_radius = 2.5 gt_bboxes_per_image_l = (gt_bboxes_per_image[:, 0]).unsqueeze(1).repeat( 1, total_num_anchors ) - center_radius * expanded_strides_per_image.unsqueeze(0) gt_bboxes_per_image_r = (gt_bboxes_per_image[:, 0]).unsqueeze(1).repeat( 1, total_num_anchors ) + center_radius * expanded_strides_per_image.unsqueeze(0) gt_bboxes_per_image_t = (gt_bboxes_per_image[:, 1]).unsqueeze(1).repeat( 1, total_num_anchors ) - center_radius * expanded_strides_per_image.unsqueeze(0) gt_bboxes_per_image_b = (gt_bboxes_per_image[:, 1]).unsqueeze(1).repeat( 1, total_num_anchors ) + center_radius * expanded_strides_per_image.unsqueeze(0) c_l = x_centers_per_image - gt_bboxes_per_image_l c_r = gt_bboxes_per_image_r - x_centers_per_image c_t = y_centers_per_image - gt_bboxes_per_image_t c_b = gt_bboxes_per_image_b - y_centers_per_image center_deltas = torch.stack([c_l, c_t, c_r, c_b], 2) is_in_centers = center_deltas.min(dim=-1).values > 0.0 is_in_centers_all = is_in_centers.sum(dim=0) > 0 # in boxes and in centers is_in_boxes_anchor = is_in_boxes_all | is_in_centers_all is_in_boxes_and_center = ( is_in_boxes[:, is_in_boxes_anchor] & is_in_centers[:, is_in_boxes_anchor] ) return is_in_boxes_anchor, is_in_boxes_and_center

其主要計算網格中心是否在gt bboxes框中,以及網格中心是否在以gt bboxes框的中心為中心,2.5為半徑(需乘上網格的stride,相當于5個網格大小的矩形框)的矩形框(中心框)中,只要滿足其中一個即為前景anchor(fg_mask)記為is_in_boxes_anchor,兩個都滿足的anchor記為is_in_boxes_and_center(既在gt bboxes框中又在中心框中,這種框的cost要比其他前景anchor的cost要低很多,其他前景anchor的cost要加上10000)。

優化做法:

A、gt bboxes框的計算要將xywh模式(中心坐標+寬高)的框轉換為xyxy的模式(左上角坐標+右下角坐標),xyxy的模式框在IOUloss和bboxes_iou里也都會再計算一遍,覺得沒有必要,因此,把它統一到get_output_and_grid函數中算一遍就好了;

B、x_centers_per_image和y_centers_per_image在輸入圖像分辨率不變的情況下,并不需要每處理一張圖片都去計算,在一個batch里輸入圖像的分辨率都一樣的,而yolov5訓練默認是沒有帶--multi-scale選項(多尺寸圖像縮放,如,對輸入的圖像尺寸進行0.5到1.5倍的隨機縮放),即輸入圖像的分辨率都是統一為640x640(默認沒帶--multi-scale選項,可能yolov5考慮到random_perspective中也有進行圖片隨機縮放?),而yolox官方代碼是每10個迭代隨機改變一次圖像的輸入尺寸(random_resize),即使64的batch size,兩個GPU,相當于1個GPU的batch size為32,10個迭代相當于每處理320張圖片才需要計算一次;

另外,x_centers_per_image和y_centers_per_image可以合并為xy_centers_per_image,b_l、b_t、b_r、b_b可以合并為b_lt、b_rb;

C、判斷是否在以gt bboxes框的中心為中心,2.5為半徑的中心框內時,計算c_l、c_t、c_r、c_b可以用下面的偽碼表示:

gt_bboxes_per_image_l = gt_bboxes_per_image_x - center_radius * expanded_strides_per_imagegt_bboxes_per_image_t = gt_bboxes_per_image_y - center_radius * expanded_strides_per_imagegt_bboxes_per_image_r = gt_bboxes_per_image_x + center_radius * expanded_strides_per_imagegt_bboxes_per_image_b = gt_bboxes_per_image_y + center_radius * expanded_strides_per_imagec_l = x_centers_per_image - gt_bboxes_per_image_lc_t = y_centers_per_image - gt_bboxes_per_image_tc_r = gt_bboxes_per_image_r - x_centers_per_imagec_b = gt_bboxes_per_image_b - y_centers_per_imagecenter_deltas = cat(c_l, c_t, c_r, c_b)

center_radius * expanded_strides_per_image的計算是固定的,可以通過將gt_bboxes_per_image_l、gt_bboxes_per_image_t、gt_bboxes_per_image_r、gt_bboxes_per_image_b代入c_l、c_t、c_r、c_b公式將center_radius * expanded_strides_per_image的計算與x_centers_per_image、y_centers_per_image合并成固定項,在分辨率不變的情況下只需計算一遍:

c_l = x_centers_per_image - (gt_bboxes_per_image_x - center_radius * expanded_strides_per_image)c_t = y_centers_per_image - (gt_bboxes_per_image_y - center_radius * expanded_strides_per_image)c_r = (gt_bboxes_per_image_x + center_radius * expanded_strides_per_image) - x_centers_per_imagec_b = (gt_bboxes_per_image_y + center_radius * expanded_strides_per_image) - y_centers_per_imagecenter_deltas = cat(c_l, c_t, c_r, c_b)

交換整理:

c_l = -gt_bboxes_per_image_x + (x_centers_per_image + center_radius * expanded_strides_per_image)c_t = -gt_bboxes_per_image_y + (y_centers_per_image + center_radius * expanded_strides_per_image)c_r = gt_bboxes_per_image_x + (center_radius * expanded_strides_per_image - x_centers_per_image)c_b = gt_bboxes_per_image_y + (center_radius * expanded_strides_per_image - y_centers_per_image)center_deltas = cat(c_l, c_t, c_r, c_b)

c_l、c_t、c_r、c_b公式的括號項為固定值(分辨率不變情況下)可提取在get_output_and_grid函數中計算好,另外將x、y合并為1項計算,即:

center_lt = xy_centers_per_image + center_radius * expanded_strides_per_image # 固定項center_rb = center_radius * expanded_strides_per_image - xy_centers_per_image # 固定項center_ltrb = cat(center_lt, center_rb) # 固定項gt_xy_center = cat(-gt_bboxes_per_image_xy,gt_bboxes_per_image_xy)center_deltas = gt_xy_center + center_ltrb

D、最終get_in_boxes_info函數優化為:

def get_in_boxes_info( org_gt_bboxes_per_image, gt_bboxes_per_image, center_ltrbes, xy_shifts, total_num_anchors, num_gt,): xy_centers_per_image = xy_shifts.expand(num_gt, total_num_anchors, 2) gt_bboxes_per_image = gt_bboxes_per_image[:, None, :].expand(num_gt, total_num_anchors, 4) b_lt = xy_centers_per_image - gt_bboxes_per_image[..., :2] b_rb = gt_bboxes_per_image[..., 2:] - xy_centers_per_image bbox_deltas = torch.cat([b_lt, b_rb], 2) # [n_gt, n_anchor, 4] is_in_boxes = bbox_deltas.min(dim=-1).values > 0.0 # [_n_gt, _n_anchor] is_in_boxes_all = is_in_boxes.sum(dim=0) > 0 center_ltrbes = center_ltrbes.expand(num_gt, total_num_anchors, 4) org_gt_xy_center = org_gt_bboxes_per_image[:, 0:2] org_gt_xy_center = torch.cat([-org_gt_xy_center, org_gt_xy_center], dim=-1) org_gt_xy_center = org_gt_xy_center[:, None, :].expand(num_gt, total_num_anchors, 4) center_deltas = org_gt_xy_center + center_ltrbes is_in_centers = center_deltas.min(dim=-1).values > 0.0 # [_n_gt, _n_anchor] is_in_centers_all = is_in_centers.sum(dim=0) > 0 # in boxes and in centers is_in_boxes_anchor = is_in_boxes_all | is_in_centers_all # fg_mask is_in_boxes_and_center = ( is_in_boxes[:, is_in_boxes_anchor] & is_in_centers[:, is_in_boxes_anchor] ) return is_in_boxes_anchor, is_in_boxes_and_center4)bboxes_iou優化:

如上所說,已經在get_output_and_grid函數中將xywh模式(中心坐標+寬高)的框轉換為xyxy的模式(左上角坐標+右下角坐標)的框,原來代碼如下,可以看到xyxy為True時要比xyxy為False時計算量要少,因此,使用時可將xyxy設為True:

def bboxes_iou(bboxes_a, bboxes_b, xyxy=True): if bboxes_a.shape[1] != 4 or bboxes_b.shape[1] != 4: raise IndexError if xyxy: tl = torch.max(bboxes_a[:, None, :2], bboxes_b[:, :2]) br = torch.min(bboxes_a[:, None, 2:], bboxes_b[:, 2:]) area_a = torch.prod(bboxes_a[:, 2:] - bboxes_a[:, :2], 1) area_b = torch.prod(bboxes_b[:, 2:] - bboxes_b[:, :2], 1) else: tl = torch.max( (bboxes_a[:, None, :2] - bboxes_a[:, None, 2:] / 2), (bboxes_b[:, :2] - bboxes_b[:, 2:] / 2), ) br = torch.min( (bboxes_a[:, None, :2] + bboxes_a[:, None, 2:] / 2), (bboxes_b[:, :2] + bboxes_b[:, 2:] / 2), ) area_a = torch.prod(bboxes_a[:, 2:], 1) area_b = torch.prod(bboxes_b[:, 2:], 1) en = (tl < br).type(tl.type()).prod(dim=2) area_i = torch.prod(br - tl, 2) * en # * ((tl < br).all()) return area_i / (area_a[:, None] + area_b - area_i)

另外,再化簡一下計算,并增加inplace模式減少內存使用,修改如下:

def bboxes_iou(bboxes_a, bboxes_b, xyxy=True, inplace=False): if bboxes_a.shape[1] != 4 or bboxes_b.shape[1] != 4: raise IndexError if inplace: if xyxy: tl = torch.max(bboxes_a[:, None, :2], bboxes_b[:, :2]) br_hw = torch.min(bboxes_a[:, None, 2:], bboxes_b[:, 2:]) br_hw.sub_(tl) # hw br_hw.clamp_min_(0) # [rows, 2] del tl area_ious = torch.prod(br_hw, 2) # area del br_hw area_a = torch.prod(bboxes_a[:, 2:] - bboxes_a[:, :2], 1) area_b = torch.prod(bboxes_b[:, 2:] - bboxes_b[:, :2], 1) else: tl = torch.max( (bboxes_a[:, None, :2] - bboxes_a[:, None, 2:] / 2), (bboxes_b[:, :2] - bboxes_b[:, 2:] / 2), ) br_hw = torch.min( (bboxes_a[:, None, :2] + bboxes_a[:, None, 2:] / 2), (bboxes_b[:, :2] + bboxes_b[:, 2:] / 2), ) br_hw.sub_(tl) # hw br_hw.clamp_min_(0) # [rows, 2] del tl area_ious = torch.prod(br_hw, 2) # area del br_hw area_a = torch.prod(bboxes_a[:, 2:], 1) area_b = torch.prod(bboxes_b[:, 2:], 1) union = (area_a[:, None] + area_b - area_ious) area_ious.div_(union) # ious return area_ious else: if xyxy: tl = torch.max(bboxes_a[:, None, :2], bboxes_b[:, :2]) br = torch.min(bboxes_a[:, None, 2:], bboxes_b[:, 2:]) area_a = torch.prod(bboxes_a[:, 2:] - bboxes_a[:, :2], 1) area_b = torch.prod(bboxes_b[:, 2:] - bboxes_b[:, :2], 1) else: tl = torch.max( (bboxes_a[:, None, :2] - bboxes_a[:, None, 2:] / 2), (bboxes_b[:, :2] - bboxes_b[:, 2:] / 2), ) br = torch.min( (bboxes_a[:, None, :2] + bboxes_a[:, None, 2:] / 2), (bboxes_b[:, :2] + bboxes_b[:, 2:] / 2), ) area_a = torch.prod(bboxes_a[:, 2:], 1) area_b = torch.prod(bboxes_b[:, 2:], 1) hw = (br - tl).clamp(min=0) # [rows, 2] area_i = torch.prod(hw, 2) ious = area_i / (area_a[:, None] + area_b - area_i) return ious5)dynamic_k_matching優化:

原來代碼:

def dynamic_k_matching(self, cost, pair_wise_ious, gt_classes, num_gt, fg_mask): # Dynamic K # --------------------------------------------------------------- # gt box與前景anchor的匹配矩陣,維度為[num_gt, fg_count] matching_matrix = torch.zeros_like(cost) # gt_bboxes與前景anchor(fg_mask)的預測框通過bboxes_iou函數計算的兩兩之間的IOU,維度為[num_gt, fg_count] ious_in_boxes_matrix = pair_wise_ious # 每個gt bbox的候選前景anchor預測框數量,ious_in_boxes_matrix.size(1)為fg_count所有前景anchor預測框數量 n_candidate_k = min(10, ious_in_boxes_matrix.size(1)) # 計算每個gt bbox前k個最大的前景anchor預測框的IOU(gt bbox與預測框的IOU) topk_ious, _ = torch.topk(ious_in_boxes_matrix, n_candidate_k, dim=1) # 每個gt bbox以它的前k個最大前景anchor預測框的IOU之和作為動態候選數dynamic_k(至少為1) dynamic_ks = torch.clamp(topk_ious.sum(1).int(), min=1) # 為每個gt bbox選出dynamic_k個cost最小的前景anchor預測(正例索引pos_idx), # 并將匹配矩陣matching_matrix對應位置設為1,表示為此gt bbox匹配到的候選前景anchor, # 也可知道前景anchor匹配給了哪些gt bbox for gt_idx in range(num_gt): _, pos_idx = torch.topk( cost[gt_idx], k=dynamic_ks[gt_idx].item(), largest=False ) # dynamic_ks[gt_idx].item()多次在gpu和cpu中轉化數值 matching_matrix[gt_idx][pos_idx] = 1.0 del topk_ious, dynamic_ks, pos_idx # 計算每個前景anchor匹配到的gt bbox的數量 anchor_matching_gt = matching_matrix.sum(0) # 每個前景anchor只能匹配一個gt bbox,如果前景anchor匹配到的gt bbox的數量多于1個, # 只保留cost最小的那個gt bbox作為此前景anchor匹配的gt if (anchor_matching_gt > 1).sum() > 0: # anchor_matching_gt > 1算了4次,可以給它定義一個變量 _, cost_argmin = torch.min(cost[:, anchor_matching_gt > 1], dim=0) matching_matrix[:, anchor_matching_gt > 1] *= 0.0 # 乘以0,可以直接賦值為0 matching_matrix[cost_argmin, anchor_matching_gt > 1] = 1.0 # 計算前景anchor匹配到的gt bbox的數量,若此數量大于0,則表示此前景anchor有匹配到gt bbox, # 從而得到匹配到gt box的前景anchor的mask(fg_mask_inboxes) fg_mask_inboxes = matching_matrix.sum(0) > 0.0 # 計算有匹配到gt box的前景anchor的數量 num_fg = fg_mask_inboxes.sum().item() # 前景anchor(fg_mask)更新為有匹配到gt box的哪些前景anchor,沒匹配到gt box的哪些前景anchor不再作為前景anchor fg_mask[fg_mask.clone()] = fg_mask_inboxes # 求出有配對到gt box的前景anchor所匹配到的gt box的索引(前景anchor所匹配到的gt box索引) # 由于每個前景anchor只能匹配一個gt,因此只有此gt位置為1(最大值),其他位置0,因此可以使用argmax得到此最大值的位置 matched_gt_inds = matching_matrix[:, fg_mask_inboxes].argmax(0) # 獲取每個前景anchor所匹配到的gt的類別 gt_matched_classes = gt_classes[matched_gt_inds] # 求出每個前景Anchor的預測框與所匹配到的gt box的IOU pred_ious_this_matching = (matching_matrix * pair_wise_ious).sum(0)[ fg_mask_inboxes ] return num_fg, gt_matched_classes, pred_ious_this_matching, matched_gt_inds

函數的功能從上面的注釋應該也清楚了,為每個gt選擇Dynamic k個前景anchor(正樣本),k的估計為prediction aware ,先計算與每個gt最接近的10個預測(不大于前景anchor數,可能實際小于10個),再將這10個預測與gt的IOU之和作為這個gt最終的k(小于1時設為1),然后求出每個gt前k個最小cost的前景anchor預測,可以認為得到了每個gt的k個候選的前景anchor(正樣本)。

這樣一個前景anchor(正樣本)可能會被分配給了多個gt做候選,但實際上一個前景anchor(正樣本)只能匹配一個gt(如果一個預測框要預測兩個gt框,到底要預測成哪個?因此,只能預測1個),因此,需要選出與此前景anchor(正樣本)cost最小的gt,作為它最終匹配(分配)到的gt。

優化方法:A、matching_matrix在代碼中只有0和1兩種值,其實并不需要分配成cost的類型(32位浮點),定義為torch.bool或torch.uint8。cost的維度[num_gt, fg_count],num_gt為一張圖片的gt bboxes的數量,fg_count為前景anchor的數量(fg_mask為True時的總項數);

B、fg_mask在代碼的很多地方都使用到,其維度為[n_anchors_all],n_anchors_all表示所有的anchor數量,對于640x640分辨率為8400,通過它來取值,顯然要對8400個fg_mask值都要做判斷。可通過torch.nonzero函數將mask轉換為索引(fg_mask_inds = torch.nonzero(fg_mask)[..., 0]),那么就可以通過索引直接訪存,且只需訪問作為前景的anchor;

C、對求每個gt最小cost的前k個前景anchor(正樣本)的優化,原始代碼:

for gt_idx in range(num_gt): _, pos_idx = torch.topk(cost[gt_idx], k=dynamic_ks[gt_idx].item(), largest=False) matching_matrix[gt_idx][pos_idx] = 1.0

dynamic_ks[gt_idx].item()多次在gpu和cpu中轉化數值會降低速度,可以在循環外統一通過tolist()轉換,如:

ks = dynamic_ks.tolist()for gt_idx in range(num_gt): _, pos_idx = torch.topk(cost[gt_idx], k=ks[gt_idx], largest=False) matching_matrix[gt_idx][pos_idx] = 1

GPU有眾多的cuda核,每次循環執行一次torch.topk,可能很多cuda核都處于空閑狀態,沒有利用起來。這里不能并行使用torch.topk的原因是每個gt的k值可能不一樣,為此,可以用它們中最大的k值作為k。這樣,有些gt的topk會多計算,但也沒關系,由于cuda核很多反而更快:

max_k = dynamic_ks.max().item()_, pos_idxes = torch.topk(cost, k=max_k, dim=1, largest=False)

注:當前k個最小的cost中有兩個及以上相同的cost時,torch.topk返回的索引,大的索引會排在前面,小的索引會排在后面。不過,如果第k個只能包含到多個相同cost中的一個時,torch.topk返回的索引卻又是最小的那個,如,k=1,索引100和索引200處的cost同時為最小,那么此時torch.topk返回的是100,而k=2時,torch.topk返回的是200,100的順序。不知pytorch為何如此實現,沒有去深究。所以,在前max_k有相同的cost時,原來循環方式的代碼與修改后的并行代碼產生的topk結果可能會有一點差別。不過都是相同cost,選哪個索引可能影響也沒那么大。

現在問題是怎么給matching_matrix賦值。pos_idxes得到的是前max_k個索引,而不是每個gt各自的k個索引,因此,需要通過下面第3、4行代碼,選出每個gt各自的k個索引組成的pos_idxes,不過torch.masked_select會將其轉換為1維的tensor(因為k值不一樣也無法作為通常的2維tensor,每行的長度不一)。

但原來的pos_idxes的每一行索引都是從0開始計數的,因此,需要在之前加上每一行的偏移offsets,最后將matching_matrix轉為1維視圖,直接通過index_fill_函數將索引為pos_idxes的matching_matrix賦值為1:

offsets = torch.arange(0, matching_matrix.shape[0]*matching_matrix.shape[1], step=matching_matrix.shape[1])[:, None]pos_idxes.add_(offsets)masks = (torch.arange(0, max_k)[None, :].expand(num_gt, max_k) < dynamic_ks[:, None])pos_idxes = torch.masked_select(pos_idxes, masks)matching_matrix.view(-1).index_fill_(0, pos_idxes, 1)

由于給賦值多個幾行代碼,當num_gt不多于3個時,速度是沒有循環的方法快,當num_gt多于3個時,會更快,且執行速度和num_gt沒有太大關系(只測到num_gt=13)。另外,如果每個gt的k值都一樣(min_k==max_k時,有不少這種情況),可以更優化,最終代碼改為:

if num_gt > 3: min_k, max_k = torch._aminmax(dynamic_ks) min_k, max_k = min_k.item(), max_k.item() if min_k != max_k: offsets = torch.arange(0, matching_matrix.shape[0] * matching_matrix.shape[1], step=matching_matrix.shape[1], dtype=torch.int, device=device)[:, None] masks = (torch.arange(0, max_k, dtype=dynamic_ks.dtype, device=device)[None, :].expand(num_gt, max_k) < dynamic_ks[:, None]) _, pos_idxes = torch.topk(cost, k=max_k, dim=1, largest=False) pos_idxes.add_(offsets) pos_idxes = torch.masked_select(pos_idxes, masks) matching_matrix.view(-1).index_fill_(0, pos_idxes, 1) else: _, pos_idxes = torch.topk(cost, k=max_k, dim=1, largest=False) matching_matrix.scatter_(1, pos_idxes, 1)else: ks = dynamic_ks.tolist() for gt_idx in range(num_gt): _, pos_idx = torch.topk(cost[gt_idx], k=ks[gt_idx], largest=False) matching_matrix[gt_idx][pos_idx] = 1

D、求出每個前景Anchor的預測框與所匹配到的gt box的IOU,原代碼:

pred_ious_this_matching = (matching_matrix * pair_wise_ious).sum(0)[ fg_mask_inboxes]

matching_matrix由于只有0和1兩個值,且每個前景Anchor只匹配一個gt,因此,無需相乘求和的計算,可以直接索引,如下:

# pred_ious_this_matching = pair_wise_ious[:, fg_mask_inboxes_inds][matched_gt_inds, torch.arange(0, matched_gt_inds.shape[0])] # [matched_gt_inds_count]pred_ious_this_matching = pair_wise_ious.index_select(1, fg_mask_inboxes_inds).gather(dim=0, index=matched_gt_inds[None, :]) # [1, matched_gt_inds_count]

E、由于每個前景anchor只對應一個gt,不需要求和來判斷這個前景anchor有沒有匹配gt(matching_matrix.sum(0) > 0),只需要判斷其中是否有任何一項為1(matching_matrix.any(dim=0))。

另外,index_select、index_fill_等函數調用會比直接使用中括號帶索引的速度快1.x~2倍,不過中括號帶索引的執行速度也都很快,為了代碼可讀性也可以保持使用中括號帶索引的方式。

下面也不再一一說了,最終修改代碼:

def dynamic_k_matching(cost, pair_wise_ious, gt_classes, num_gt, fg_mask_inds): # Dynamic K # --------------------------------------------------------------- device = cost.device matching_matrix = torch.zeros(cost.shape, dtype=torch.uint8, device=device) # [num_gt, fg_count] ious_in_boxes_matrix = pair_wise_ious # [num_gt, fg_count] n_candidate_k = min(10, ious_in_boxes_matrix.size(1)) topk_ious, _ = torch.topk(ious_in_boxes_matrix, n_candidate_k, dim=1) dynamic_ks = topk_ious.sum(1).int().clamp_min_(1) if num_gt > 3: min_k, max_k = torch._aminmax(dynamic_ks) min_k, max_k = min_k.item(), max_k.item() if min_k != max_k: offsets = torch.arange(0, matching_matrix.shape[0] * matching_matrix.shape[1], step=matching_matrix.shape[1], dtype=torch.int, device=device)[:, None] masks = (torch.arange(0, max_k, dtype=dynamic_ks.dtype, device=device)[None, :].expand(num_gt, max_k) < dynamic_ks[:, None]) _, pos_idxes = torch.topk(cost, k=max_k, dim=1, largest=False) pos_idxes.add_(offsets) pos_idxes = torch.masked_select(pos_idxes, masks) matching_matrix.view(-1).index_fill_(0, pos_idxes, 1) del topk_ious, dynamic_ks, pos_idxes, offsets, masks else: _, pos_idxes = torch.topk(cost, k=max_k, dim=1, largest=False) matching_matrix.scatter_(1, pos_idxes, 1) del topk_ious, dynamic_ks else: ks = dynamic_ks.tolist() for gt_idx in range(num_gt): _, pos_idx = torch.topk(cost[gt_idx], k=ks[gt_idx], largest=False) matching_matrix[gt_idx][pos_idx] = 1 del topk_ious, dynamic_ks, pos_idx anchor_matching_gt = matching_matrix.sum(0) anchor_matching_one_more_gt_mask = anchor_matching_gt > 1 anchor_matching_one_more_gt_inds = torch.nonzero(anchor_matching_one_more_gt_mask) if anchor_matching_one_more_gt_inds.shape[0] > 0: anchor_matching_one_more_gt_inds = anchor_matching_one_more_gt_inds[..., 0] # _, cost_argmin = torch.min(cost[:, anchor_matching_one_more_gt_inds], dim=0) _, cost_argmin = torch.min(cost.index_select(1, anchor_matching_one_more_gt_inds), dim=0) # matching_matrix[:, anchor_matching_one_more_gt_inds] = 0 matching_matrix.index_fill_(1, anchor_matching_one_more_gt_inds, 0) matching_matrix[cost_argmin, anchor_matching_one_more_gt_inds] = 1 # fg_mask_inboxes = matching_matrix.sum(0) > 0 fg_mask_inboxes = matching_matrix.any(dim=0) fg_mask_inboxes_inds = torch.nonzero(fg_mask_inboxes)[..., 0] else: fg_mask_inboxes_inds = torch.nonzero(anchor_matching_gt)[..., 0] num_fg = fg_mask_inboxes_inds.shape[0] matched_gt_inds = matching_matrix.index_select(1, fg_mask_inboxes_inds).argmax(0) fg_mask_inds = fg_mask_inds[fg_mask_inboxes_inds] gt_matched_classes = gt_classes[matched_gt_inds] # pred_ious_this_matching = pair_wise_ious[:, fg_mask_inboxes_inds][matched_gt_inds, torch.arange(0, matched_gt_inds.shape[0])] # [matched_gt_inds_count] pred_ious_this_matching = pair_wise_ious.index_select(1, fg_mask_inboxes_inds).gather(dim=0, index=matched_gt_inds[None, :]) # [1, matched_gt_inds_count] return num_fg, gt_matched_classes, pred_ious_this_matching, matched_gt_inds, fg_mask_inds四、Yolov5下的YoloX訓練結果:

這里為兩個配置的訓練結果,每種配置都只訓練150個epoch,在最后15個epoch停止數據增強。

1、配置一:

YoloX模型,但使用Yolov5的訓練超參配置(數據增強,學習率控制等)

1)訓練命令:

python -m torch.distributed.launch --nproc_per_node 2 train.py --noautoanchor --img-size 640 --data coco.yaml --cfg models/yoloxs.yaml --hyp data/hyps/hyp.scratch.yolox.yaml --weights '' --batch-size 64 --epochs 150 --device 0,12)超參配置:

data/hyps/hyp.scratch.yolox.yaml,可以看到在數據增強方面相對于官方的yolox沒有使用mixup,也沒有使用旋轉和shear,另外,也沒有使用random resize:

lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3)lrf: 0.2 # final OneCycleLR learning rate (lr0 * lrf)momentum: 0.937 # SGD momentum/Adam beta1weight_decay: 0.0005 # optimizer weight decay 5e-4warmup_epochs: 3.0 # warmup epochs (fractions ok)warmup_momentum: 0.8 # warmup initial momentumwarmup_bias_lr: 0.1 # warmup initial bias lrbox: 0.05 # box loss gain, not usedcls: 0.5 # cls loss gain, not usedcls_pw: 1.0 # cls BCELoss positive_weight, not usedobj: 1.0 # obj loss gain (scale with pixels), not usedobj_pw: 1.0 # obj BCELoss positive_weight, not usedhsv_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.001flipud: 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)mixup_mode: "yolov5" # image mixup mode: "yolox" is yolox mixup, else yolov5 mixupmixup_scale: [0.5, 1.5] # image mixup scale, used by yolox mixup modemixup_ratio: 0.5 # image mixup ratiocopy_paste: 0.0 # segment copy-paste (probability)no_aug_epochs: 153)模型配置:

models/yoloxs.yaml,使用yolox-s配置:

# Parametersnc: 80 # number of classesdepth_multiple: 0.33 # model depth multiplewidth="360px",height="auto" />

4)COCO驗證集的結果:

對last.pt的驗證結果,150個epoch達到了39.7,比官方yolox的300個epoch的yolox-s的39.6差不多(注:官方yolox目前最新版本yolox-s的提升到了40.5,有機器資源的同學也可以train夠300個epoch看與最新官方yolox-s的mAP值差多少,后面會放增加yolox的yolov5代碼的git)。

驗證命令:

python val.py --data data/coco.yaml --weights runs/train/exp/weights/last.pt --batch-size 64 --device 0,1 --save-json --classwiseAverage Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.397 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.589 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.427 Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.223 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.443 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.516 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.324 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.541 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.585 Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.409 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.642 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.728+---------------+-------+--------------+-------+----------------+-------+| category | AP | category | AP | category | AP |+---------------+-------+--------------+-------+----------------+-------+| person | 0.535 | bicycle | 0.280 | car | 0.399 || motorcycle | 0.435 | airplane | 0.649 | bus | 0.633 || train | 0.637 | truck | 0.350 | boat | 0.249 || traffic light | 0.261 | fire hydrant | 0.647 | stop sign | 0.647 || parking meter | 0.468 | bench | 0.228 | bird | 0.328 || cat | 0.625 | dog | 0.583 | horse | 0.579 || sheep | 0.488 | cow | 0.532 | elephant | 0.639 || bear | 0.663 | zebra | 0.658 | giraffe | 0.668 || backpack | 0.125 | umbrella | 0.395 | handbag | 0.131 || tie | 0.292 | suitcase | 0.370 | frisbee | 0.645 || skis | 0.202 | snowboard | 0.279 | sports ball | 0.416 || kite | 0.433 | baseball bat | 0.270 | baseball glove | 0.349 || skateboard | 0.484 | surfboard | 0.359 | tennis racket | 0.441 || bottle | 0.357 | wine glass | 0.315 | cup | 0.387 || fork | 0.294 | knife | 0.160 | spoon | 0.158 || bowl | 0.422 | banana | 0.261 | apple | 0.165 || sandwich | 0.322 | orange | 0.281 | broccoli | 0.236 || carrot | 0.223 | hot dog | 0.349 | pizza | 0.508 || donut | 0.462 | cake | 0.362 | chair | 0.288 || couch | 0.436 | potted plant | 0.248 | bed | 0.423 || dining table | 0.293 | toilet | 0.622 | tv | 0.572 || laptop | 0.599 | mouse | 0.574 | remote | 0.246 || keyboard | 0.474 | cell phone | 0.326 | microwave | 0.560 || oven | 0.365 | toaster | 0.334 | sink | 0.384 || refrigerator | 0.549 | book | 0.140 | clock | 0.480 || vase | 0.350 | scissors | 0.231 | teddy bear | 0.451 || hair drier | 0.014 | toothbrush | 0.189 | None | None |+---------------+-------+--------------+-------+----------------+-------+2、配置二:

YoloX模型(激活函數使用relu+silu),但使用Yolov5的訓練超參配置(數據增強,學習率控制等)

1)訓練命令:

python -m torch.distributed.launch --nproc_per_node 2 train.py --noautoanchor --img-size 640 --data coco.yaml --cfg models/yoloxs_rslu.yaml --hyp data/hyps/hyp.scratch.yolox.yaml --weights '' --batch-size 64 --epochs 150 --device 0,12)超參配置:

data/hyps/hyp.scratch.yolox.yaml,使用“配置一”相同的超參配置;

3)模型配置:

models/yoloxs_rslu.yaml:

# Parametersnc: 80 # number of classesdepth_multiple: 0.33 # model depth multiplewidth="360px",height="auto" />

4)COCO驗證集的結果:

對last.pt的驗證結果,150個epoch達到了38.9,比"配置一"的39.7低0.8個點,不過在NPU上的推理速度可提升1.4倍。驗證命令:

python val.py --data data/coco.yaml --weights runs/train/exp/weights/last.pt --batch-size 64 --device 0,1 --save-json --classwiseAverage Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.389 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.581 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.419 Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.223 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.434 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.503 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.322 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.533 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.577 Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.391 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.633 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.715+---------------+-------+--------------+-------+----------------+-------+| category | AP | category | AP | category | AP |+---------------+-------+--------------+-------+----------------+-------+| person | 0.529 | bicycle | 0.279 | car | 0.395 || motorcycle | 0.417 | airplane | 0.629 | bus | 0.624 || train | 0.627 | truck | 0.319 | boat | 0.253 || traffic light | 0.254 | fire hydrant | 0.621 | stop sign | 0.651 || parking meter | 0.477 | bench | 0.219 | bird | 0.310 || cat | 0.612 | dog | 0.570 | horse | 0.565 || sheep | 0.465 | cow | 0.523 | elephant | 0.621 || bear | 0.643 | zebra | 0.644 | giraffe | 0.666 || backpack | 0.141 | umbrella | 0.376 | handbag | 0.118 || tie | 0.282 | suitcase | 0.383 | frisbee | 0.618 || skis | 0.212 | snowboard | 0.306 | sports ball | 0.416 || kite | 0.441 | baseball bat | 0.255 | baseball glove | 0.342 || skateboard | 0.462 | surfboard | 0.354 | tennis racket | 0.425 || bottle | 0.351 | wine glass | 0.297 | cup | 0.370 || fork | 0.277 | knife | 0.143 | spoon | 0.144 || bowl | 0.423 | banana | 0.242 | apple | 0.171 || sandwich | 0.307 | orange | 0.280 | broccoli | 0.210 || carrot | 0.208 | hot dog | 0.339 | pizza | 0.498 || donut | 0.447 | cake | 0.352 | chair | 0.283 || couch | 0.427 | potted plant | 0.243 | bed | 0.401 || dining table | 0.298 | toilet | 0.598 | tv | 0.570 || laptop | 0.581 | mouse | 0.574 | remote | 0.230 || keyboard | 0.469 | cell phone | 0.304 | microwave | 0.568 || oven | 0.344 | toaster | 0.342 | sink | 0.372 || refrigerator | 0.520 | book | 0.140 | clock | 0.474 || vase | 0.344 | scissors | 0.280 | teddy bear | 0.437 || hair drier | 0.001 | toothbrush | 0.207 | None | None |+---------------+-------+--------------+-------+----------------+-------+3、增加Yolox的Yolov5代碼與模型:1)代碼:

基于2021年8月31日的Commits:de534e922120b2da876e8214b976af1f82019e28的yolov5修改的代碼(保持寫文檔時與最新的yolov5版本同步,與實驗時所用版本有所不同)已提交,通過下面命令下載:

git clone https://gitee.com/SearchSource/yolov5_yolox.git

環境安裝:除了yolov5原來的環境安裝之外,還需安裝從yolox移植過來的評估工具:yoloxtools,進入yolov5_yolox目錄執行下面命令:

pip install -e .2)模型:

A、yolox-s:

百度網盤: https://pan.baidu.com/s/1i7Si3oCv3QMGYBngJUEkvg

提取碼: j4co

驗證命令:

python val.py --data data/coco.yaml --weights yolox-s.pt --batch-size 64 --device 0,1 --save-json --classwise

B、yolox-s(relu+silu):

百度網盤: https://pan.baidu.com/s/1oCHzeO6w4G9PXXLtKkVhbA

提取碼: spcp

驗證命令:

python val.py --data data/coco.yaml --weights yolox-s_rslu.pt --batch-size 64 --device 0,1 --save-json --classwise五、關于旋轉的數據增強:

官方的YoloX代碼使用了-10度到10度之間的隨機角度旋轉的數據增強,對于檢測模型里使用隨機旋轉的數據增強,個人是持保留意見的,因為旋轉之后的gt bbox是不準的。下面為旋轉數據增強實驗的代碼(扣取YoloX的random_perspective函數的旋轉部分的代碼):

def rotation(img, targets, degree=5): # Rotation and Scale M = np.eye(3) M[:2] = cv2.getRotationMatrix2D(angle=degree, center=(0, 0), scale=1.0) height, width="360px",height="auto" />

原圖(紅框為gt bbox):

旋轉5度(藍框為gt框旋轉后的框,紅框為手工重畫的gt框,其上的綠色數值為藍框與紅框的IOU):

旋轉10度(藍框為gt框旋轉后的框,紅框為手工重畫的gt框,其上的綠色數值為藍框與紅框的IOU):

可以看到旋轉后的物體框(藍框)與真實的物體框(紅框)差別還是很大的(這個也取決于旋轉的角度與旋轉的中心點、以及物體在圖像中的位置等)。這可能可以提高低IOU的AP值,不過可能也會降低高IOU的AP值,降低預測的檢測框框住物體的精準度。如果希望檢測框框的很準,可能不該使用旋轉的數據增強。如果對檢測框框的準度要求不高,能框出來就好的話,也許旋轉的數據增強可以使原來無法框出來的物體變得可以框出來。

yolov5默認的hyp.scratch.yaml配置沒有使用旋轉的數據增強(degrees為0),唯一使用了旋轉的數據增強的yolov5配置hyp.finetune.yaml,也只用了-0.373度到0.373度(degrees為0.373)的旋轉數據增強,相當于只有一個微小擾動。不知yolov5作者是否也出于相同的考慮?另外,也不知yolox作者實驗將degrees設為10對AP值是否有提升?

不過如果有物體的mask標注,旋轉后再計算mask標注的外接矩形作為gt bbox,覺得可以使用這種增強。

六、部署:

通過下面的命令將模型轉換為onnx格式的模型:

python export.py --weights ./yolox_rslu.pt --include onnx --opset 10 --simplify --deploy

將轉換為onnx格式的模型再使用NPU的工具鏈轉換格式量化等操作,從而得到能在NPU上跑的模型,不同的NPU及其工具鏈做法會有所不同,這里就不詳述NPU相關部分的轉換了。

以上就是關于一手刷卡pos機數據,我用YOLOX露了一手的知識,后面我們會繼續為大家整理關于一手刷卡pos機數據的知識,希望能夠幫助到大家!

轉發請帶上網址:http://www.tonybus.com/news/17353.html

你可能會喜歡:

版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 babsan@163.com 舉報,一經查實,本站將立刻刪除。