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