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