前一段時間一直在優化部署模型。這幾天終於來了需求,又要開始重操訓練一些新模型了。趁著這次機會總結了下之前的一些訓練模型的筆記,可能比較雜,拋磚引玉!當然這是不完全統計的經驗,除了訓練部分,還有很多部署的坑沒有寫。
演演算法工程師50%的時間是和資料打交道,有時候拷貝資料(分別從多個資料夾拷貝到某一資料夾);有時候篩選資料(過濾掉一些質量不好的資料);有時候把資料換個名字、加個字首(為了後續訓練的時候區分資料的特性,比如多尺度、多種影象增強策略)等等,這些工作可能一個月要重複n多次,因此最好總結起來;可以用Python或者shell指令碼來處理,或者用jupyter notebook存自己常用的檔案處理程式碼。
如果你不清楚拿到資料的來源和可靠度,可以先用 find ./ -size -1k -exec rm {} \
等命令簡單過濾一下,剛才這個命令是掃描1k(或者其他值)以下的損壞影象並刪除掉,當然也可以設定其他的引數。很多時候給你的圖不一定都是正常的圖,最好提前篩一遍,要不然後續處理很麻煩。
並不所有的資料都已經有標註資訊,如果收集了一批資料要拿去標註,正好公司也有標註人力,可以嘗試將這批資料打上預標框讓他們再去調整或者補充標框,這樣效率更高些。至於預標框怎麼打,可以先讓模型訓練一小批資料,訓練個召回率高的小模型,然後預測打框就可以,也可以用一些老模型打框;不過有一個現象比較神奇,標註人員在標註的時候,對於有預標框的資料,標註的質量反而變差了,雖然速度上來了,這是因為大部分標註人員不想調整,這時候需要你好好監督一下,要不然後續模型精度上不去大概率就是資料的問題。
有時候模型的指標不僅僅看準招,當模型給別人提供服務的時候,要看PM那邊怎麼看待這個模型輸出結果在實際場景中的使用效果;對於檢測模型最終的輸出分數,最終給到使用方的框一般是根據你取得分數閾值來設,設的低一點,那麼框就多一點(召回率高),設的高一點,那麼框就少一點(準確度高);不同方式不同場景設定不同的閾值有不同的效果,說白了模型效果好壞很大一部分依賴於場景;這個情況在實際專案中其實挺常見的,說白了loss也好, accuracy也好,都是很片面且脆弱的評估指標。與模型結構以及評測的資料分佈都有很大關係,具體如何選擇模型應該與應用場景強相關。
當模型遇到badcase的時候,簡單粗暴地增加模型的容量效果可能並不好;因為這個badcase大概率和場景強相關,這種情況下最好就是收集badcase,可能會有使用你模型的人給你提供badcase,但這種效率比較低,看提供者的心情or緊急程度;你可以直接撈一大批模型使用場景的query然後使用當前模型做檢測,收集相應類別置信度比較低的case,然後挑選出來;
測試集很重要,測試集一般不是從訓練集中切分出來的,從訓練集中切分出來的是驗證集;驗證集一般用於判斷這個模型有沒有過擬合、有沒有訓練走火入魔啦,如果想用驗證集來判斷模型好壞的話,往往並不能代表模型實際的水平;最好是有測試集,而且測試集是和模型採集批次不同訓練模型的時候比較接近實際水平的評價標準;如果沒有測試集也可以看訓練集的loss大概確定一下,一般來說只要不是demo級別的場景,模型不會輕易過擬合,我們的訓練集往往有很重的影象增強策略,每一個epoch可能影象分佈都不一樣,這時候其實也可以選取模型model_last
。
再強調下,loss和準確率不是完全正比的關係,loss波動很正常,loss低了不一定代表模型的mAP高;相反如果loss變高,模型的精度也不一定差,有可能是loss設的不夠好導致部分上升佔主導,掩蓋了另一部分正常的下降也很正常;
相關討論:https://github.com/thegregyang/LossUpAccUp 和
https://www.zhihu.com/question/318399418
計算檢測模型的mAP,實際中在計算的時候是不考慮目標框分數閾值的,也就是說我們會將所有分數大於0的檢測框送去計算mAP;但這裡要注意,計算mAP是有max_num也就是最大檢測出目標個數,根據任務需求可能是100、可能是500也可能是5000等等,當有這個限制的時候,此時框就需要根據分數來排序,取前100、前500或者前5000的框去計算;最後,如果我們需要視覺化結果在圖上畫框的話,這時候是可以卡閾值的,比如大於0.2分數閾值的要,要不然最終畫出來的圖會有很多碎框;最後的最後,別忘了NMS!
測試轉換後的模型是否正確,一定要保證輸入影象的一致;這裡的一致指的是輸入影象的數值必須一模一樣,dif為0才行;一般來說我們輸入的模型的影象範圍是0-1,通道數一般是彩色也就是RGB,不過需要注意這個彩色是否是假彩色(有時候為了傳輸節省資源會傳灰度圖再實際推理的時候變成彩色圖,對於某種場景來說,假彩色和真彩色的精度相差不大),輸入尺寸也要保持一致,是否需要padding(padding成0或者127或者255,這幾種padding方式隊對結果影響很大)、需要補成32的倍數、或者需要最大邊最小邊限制,一定要保持一致;對於類別,這樣測試模型才能夠保證準確性。
對於模型來說,如果之後考慮上線。上線的方式很多種:可以pytorch+flask直接docker上線,也可以嘗試libtorch上線,也可以TensorRT上線,當然也可以通過自研框架上線…等等等等。如果這個模型追求精度,而且是線下某一時間段跑,並不是實時,可以嘗試flask+docker的服務;如果這個模型的實時性很高,在設計模型的時候就要考慮之後的上線,那就需要考慮模型優化以及對應的伺服器推理框架了可以嘗試TensorRT+triton server;
繼續老潘的含淚經驗,緊接著AI演演算法工程師的一些含淚經驗(一),除了訓練模型階段的注意點,這次更多的是一些部署方面的經驗,希望能夠對大家有幫助。依然是拋磚引玉,持不同意見的小夥伴歡迎留言!
再次強調一下訓練集、驗證集和測試集在訓練模型中實際的角色:訓練集相當於老師佈置的作業,驗證集相當於模擬試卷,測試集相當於考試試卷,做完家庭作業直接上考卷估計大概率考不好,但是做完作業之後,再做一做模擬卷就知道大體考哪些、重點在哪裡,然後調整一下引數啥的,最後真正考試的時候就能考好;訓練集中拆分出一部分可以做驗證集、但是測試集千萬不要再取自訓練集,因為我們要保證測試集的」未知「性;驗證集雖然不會直接參與訓練,但我們依然會根據驗證集的表現情況去調整模型的一些超引數,其實這裡也算是」學習了「驗證集的知識;千萬不要把測試集搞成和驗證集一樣,」以各種形式「參與訓練,要不然就是資訊洩露。我們使用測試集作為泛化誤差的近似,所以不到最後是不能將測試集的資訊洩露出去的。
資料好壞直接影響模型好壞;在資料量初步階段的情況下,模型精度一開始可以通過改模型結構來提升,加點注意力、加點DCN、增強點backbone、或者用點其他巧妙的結構可以增加最終的精度。但是在後期想要提升模型泛化能力就需要增加訓練資料了,為什麼呢?因為此時你的badcase大部分訓練集中是沒有的,模型沒有見過badcase肯定學不會的,此時需要針對性地補充badcase;那假如badcase不好補充呢?此時影象生成就很重要了,如何生成badcase場景的訓練集圖,生成資料的質量好壞直接影響到模型的最終效果;另外影象增強也非常非常重要,我們要做的就是儘可能讓資料在影象增強後的分佈接近測試集的分佈,說白了就是通過影象生成和影象增強兩大技術模擬實際中的場景。
當有兩個資料集A和B,A有類別a和b,但只有a的GT框;B也有類別a和b,但只有b的GT框,顯然這個資料集不能直接拿來用(沒有GT框的a和b在訓練時會被當成背景),而你的模型要訓練成一個可以同時檢測a和b框,怎麼辦?四種方式:1、訓練分別檢測a和檢測b的模型,然後分別在對方資料集上進行預測幫忙打標籤,控制好分數閾值,製作好新的資料集後訓練模型;2、使用蒸餾的方式,同樣訓練分別檢測a和檢測b的模型,然後利用這兩個模型的soft-label去訓練新模型;3、修改一下loss,一般來說,我們的loss函數也會對負樣本(也就是背景)進行反向傳播,也是有損失回傳的,這裡我們修改為,如果當前圖片沒有類別a的GT框,我們關於a的損失直接置為0,讓這個類別通道不進行反向傳播,這樣就可以對沒有a框的圖片進行訓練,模型不會將a當成背景,因為模型「看都不看a一眼,也不知道a是什麼東東」,大家可以想一下最終訓練後的模型是什麼樣的呢?4、在模型的最後部分將head頭分開,一個負責檢測a一個負責檢測b,此時模型的backbone就變成了特徵提取器。
工作中,有很多場景,你需要通過舊模型去給需要訓練的新模型篩選資料,比如通過已經訓練好的檢測模型A去挑選有類別a的圖給新模型去訓練,這時就需要搭建一個小服務去實現這個過程;當然你也可以開啟你之前的舊模型python庫程式碼,然後回憶一番去找之前的demo.py和對應的一些引數;顯然這樣是比較麻煩的,最好是將之前模型總結起來隨時搭個小服務供內部使用,因為別人也可能需要使用你的模型去挑資料,小服務怎麼搭建呢?直接使用flask+Pytorch就行,不過這個qps請求大的時候會假死,不過畢竟只是篩選資料麼,可以適當降低一些qps,離線請求一晚上搞定。
目前比較好使的目標檢測框架,無非還是那些經典的、用的人多的、資源多的、部署方便的。畢竟咱們訓練模型最終的目的還是上線嘛;單階段有SSD、yolov2-v5系列、FCOS、CenterNet系列,Cornernet等等單階段系列,雙階段的faster-rcnn已經被實現了好多次了,還有mask-rcnn,也被很多人實現過了;以及最新的DETR使用transformer結構的檢測框架,上述這些都可以使用TensorRT部署;其實用什麼無非也就是看速度和精度怎麼樣,是否支援動態尺寸;不過跑分最好的不一定在你的資料上好,千萬千萬要根據資料集特點選模型,對於自己的資料集可能效果又不一樣,這個需要自己拉下來跑一下;
相關模型TensorRT部署資源:
https://github.com/grimoire/mmdetection-to-tensorrt
https://github.com/wang-xinyu/tensorrtx
再扯一句,其實很多模型最終想要部署,首要難點在於這個模型是否已經有人搞過;如果有人已經搞過並且開源,那直接複製貼上修改一下就可以,有坑別人已經幫你踩了;如果沒有開原始碼可借鑑,那麼就需要自個兒來了!首先看這個模型的backbone是否有特殊的op(比如dcn、比如senet,當然這兩個已經支援了),結構是否特殊(不僅僅是普通的折積組合,有各種reshape、roll、window-shift等特殊操作)、後處理是否複雜?我轉換過最複雜的模型,backbone有自定義op,需要自己實現、另外,這個模型有相當多的後處理,後處理還有一部分會參與訓練,也就是有學習到的引數,但是這個後處理有些操作是無法轉換為trt或者其他框架的(部分操作不支援),因此只能把這個模型拆成兩部分,一部分用TensorRT實現另一部分使用libtorc實現;其實大部分的模型都可以部署,只不過難度不一樣,只要肯多想,法子總是有的。
轉換後的模型,不論是從Pytorch->onnx還是onnx->TensorRT還是tensorflow->TFLITE,轉換前和轉換後的模型,雖然引數一樣結構一樣,但同樣的輸入,輸出不可能是完全一樣的。當然如果你輸出精度卡到小數點後4位元那應該是一樣的,但是小數點後5、6、7位那是不可能完全一模一樣的,轉換本身不可能是無失真的;舉個例子,一個檢測模型A使用Pytorch訓練,然後還有一個轉換為TensorRT的模型A`,這倆是同一個模型,而且轉換後的TensorRT也是FP32精度,你可以輸入一個亂數,發現這兩個模型的輸出對比,絕對誤差和相對誤差在1e-4的基準下為0,但是你拿這兩個模型去檢測的時候,保持所有的一致(輸入、後處理等),最終產生的檢測框,分數高的完全一致,分數低的(比如小於0.1或者0.2)會有一些不一樣的地方,而且處於邊緣的hardcase也會不一致;當然這種情況一般來說影響不大,但也需要留一個心眼。
模型的理論flops和實際模型執行的速度關係不大,要看具體執行的平臺,不要一味的以為flops低的模型速度就快。很多開源的檢測庫都是直接在Pytorch上執行進行比較,雖然都是GPU,但這個其實是沒有任何優化的,因為Pytorch是動態圖;一般的模型部署都會涉及到大大小小的優化,比如運算元融合和計算圖優化,最簡單的例子就是CONV+BN的優化,很多基於Pytorch的模型速度比較是忽略這一點的,我們比較兩個模型的速度,最好還是在實際要部署的框架和平臺去比較;不過如果這個模型引數比較多的話,那模型大概率快不了,理由很簡單,大部分的引數一般都是折積核引數、全連線引數,這些引數多了自然代表這些op操作多,自然會慢。
同一個TensorRT模型(或者Pytorch、或者其他利用GPU跑的模型)在同一個型號卡上跑,可能會因為cuda、cudnn、驅動等版本不同、或者顯示卡的硬體功耗牆設定(P0、P1、P2)不同、或者所處系統版本/核心版本不同而導致速度方面有差異,這種差異有大有小,我見過最大的,有70%的速度差異,所以不知道為什麼模型速度不一致的情況下,不妨考慮考慮這些原因。
轉換好要部署的模型後,一般需要測試這個模型的速度以及吞吐量;速度可以直接for迴圈推理跑取平均值,不過實際的吞吐量的話要模擬資料傳輸、模型執行以及排隊的時間;一般來說模型的吞吐量可以簡單地通過1000/xx計算,xx為模型執行的毫秒,不過有些任務假如輸入影象特別大,那麼這樣算是不行的,我們需要考慮實際影象傳輸的時間,是否本機、是否跨網段等等。