<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          關(guān)于煉丹,你是否知道這些細(xì)節(jié)?

          共 7027字,需瀏覽 15分鐘

           ·

          2021-12-29 17:25

          作者丨Fatescript
          來(lái)源丨h(huán)ttps://zhuanlan.zhihu.com/p/450779978
          編輯丨GiantPandaCV

          本文算是我工作一年多以來(lái)的一些想法和經(jīng)驗(yàn),最早發(fā)布在曠視研究院內(nèi)部的論壇中,本著開(kāi)放和分享的精神發(fā)布在我的知乎專欄中,如果想看干貨的話可以直接跳過(guò)動(dòng)機(jī)部分。另外,后續(xù)在這個(gè)專欄中,我會(huì)做一些關(guān)于原理和設(shè)計(jì)方面的一些分享,希望能給領(lǐng)域從業(yè)人員提供一些看待問(wèn)題的不一樣的視角。

          動(dòng)機(jī)

          前段時(shí)間走在路上,一直在思考一個(gè)問(wèn)題:我的時(shí)間開(kāi)銷很多都被拿去給別人解釋一些在我看起來(lái)顯而易見(jiàn)的問(wèn)題了,比如( https://link.zhihu.com/?target=https%3A//github.com/Megvii- BaseDetection/cvpods )里面的一些code寫(xiě)法問(wèn)題(雖然這在某些方面說(shuō)明了文檔建設(shè)的不完善),而這變相導(dǎo)致了我實(shí)際工作時(shí)間的減少,如何讓別人少問(wèn)一些我覺(jué)得答案顯而易見(jiàn)的問(wèn)題?如何讓別人提前規(guī)避一些不必要的坑?只有解決掉這樣的一些問(wèn)題,我才能從一件件繁瑣的小事中解放出來(lái),把精力放在我真正關(guān)心的事情上去。

          其實(shí)之前同事有跟我說(shuō)過(guò)類似的話,每次帶一個(gè)新人,都要告訴他:你的實(shí)現(xiàn)需要注意這里blabla,還要注意那里blabla。說(shuō)實(shí)話,我很佩服那些帶intern時(shí)候非常細(xì)致和知無(wú)不言的人,但我本性上并不喜歡每次花費(fèi)時(shí)間去解釋一些我覺(jué)得顯而易見(jiàn)的問(wèn)題,所以我寫(xiě)下了這個(gè)帖子,把我踩過(guò)的坑和留下來(lái)的經(jīng)驗(yàn)分享出去。希望能夠方便別人,同時(shí)也節(jié)約我的時(shí)間。

          加入曠視以來(lái),個(gè)人一直在做一些關(guān)于框架相關(guān)的內(nèi)容,所以內(nèi)容主要偏向于模型訓(xùn)練之類的工作。因?yàn)?一個(gè)擁有知識(shí)的人是無(wú)法想象知識(shí)在別人腦海中的樣子的(the curse of knowledge),所以我只能選取被問(wèn)的最多的,和我認(rèn)為最應(yīng)該知道的

          準(zhǔn)備好了的話,我們就啟航出發(fā)(另,這篇專欄文章會(huì)長(zhǎng)期進(jìn)行更新)。

          坑/經(jīng)驗(yàn)

          Data模塊

          1. python圖像處理用的最多的兩個(gè)庫(kù)是opencv和Pillow(PIL),但是兩者讀取出來(lái)的圖像并不一樣, opencv讀取的圖像格式的三個(gè)通道是BGR形式的,但是PIL是RGB格式的 。這個(gè)問(wèn)題看起來(lái)很小,但是衍生出來(lái)的坑可以有很多,最常見(jiàn)的場(chǎng)景就是數(shù)據(jù)增強(qiáng)和預(yù)訓(xùn)練模型中。比如有些數(shù)據(jù)增強(qiáng)的方法是基于channel維度的,比如megengine里面的HueTransform,這一行代碼 (https://github.com/MegEngine/MegEngine/blob/4d72e7071d6b8f8240edc56c6853384850b7407f/imperative/python/megengine/data/transform/vision/transform.py#L958 ) 顯然是需要確保圖像是BGR的,但是經(jīng)常會(huì)有人只看有Transform就無(wú)腦用了,從來(lái)沒(méi)有考慮過(guò)這些問(wèn)題。
          2. 接上條,RGB和BGR的另一個(gè)問(wèn)題就是導(dǎo)致預(yù)訓(xùn)練模型載入后訓(xùn)練的方式不對(duì),最常見(jiàn)的場(chǎng)景就是預(yù)訓(xùn)練模型的input channel是RGB的(例如torch官方來(lái)的預(yù)訓(xùn)練模型),然后你用cv2做數(shù)據(jù)處理,最后還忘了convert成RGB的格式,那么就是會(huì)有問(wèn)題。這個(gè)問(wèn)題應(yīng)該很多煉丹的同學(xué)沒(méi)有注意過(guò),我之前寫(xiě)CenterNet-better(https://github.com/FateScript/CenterNet-better)就發(fā)現(xiàn)CenterNet(https://github.com/xingyizhou/CenterNet)存在這么一個(gè)問(wèn)題,要知道當(dāng)時(shí)這可是一個(gè)有著3k多star的倉(cāng)庫(kù),但是從來(lái)沒(méi)有人意識(shí)到有這個(gè)問(wèn)題。當(dāng)然,依照我的經(jīng)驗(yàn),如果你訓(xùn)練的iter足夠多,即使你的channel有問(wèn)題,對(duì)于結(jié)果的影響也會(huì)非常小。不過(guò),既然能做對(duì),為啥不注意這些問(wèn)題一次性做對(duì)呢?
          3. torchvision中提供的模型,都是輸入圖像經(jīng)過(guò)了ToTensor操作train出來(lái)的。也就是說(shuō)最后在進(jìn)入網(wǎng)絡(luò)之前會(huì)統(tǒng)一除以255從而將網(wǎng)絡(luò)的輸入變到0到1之間。torchvision的文檔(https://pytorch.org/vision/stable/models.html)給出了他們使用的mean和std,也是0-1的mean和std。如果你使用torch預(yù)訓(xùn)練的模型,但是輸入還是0-255的,那么恭喜你,在載入模型上你又會(huì)踩一個(gè)大坑(要么你的圖像先除以255,要么你的code中mean和std的數(shù)值都要乘以255)。
          4. ToTensor之后接數(shù)據(jù)處理的坑。上一條說(shuō)了ToTensor之后圖像變成了0到1的,但是一些數(shù)據(jù)增強(qiáng)對(duì)數(shù)值做處理的時(shí)候,是針對(duì)標(biāo)準(zhǔn)圖像,很多人ToTensor之后接了這樣一個(gè)數(shù)據(jù)增強(qiáng),最后就是練出來(lái)的丹是廢的(心疼電費(fèi)QaQ)。
          5. 數(shù)據(jù)集里面有一個(gè)圖特別詭異,只要train到那一張圖就會(huì)炸顯存(CUDA OOM),別的圖訓(xùn)練起來(lái)都沒(méi)有問(wèn)題,應(yīng)該怎么處理?通常出現(xiàn)這個(gè)問(wèn)題,首先判斷數(shù)據(jù)本身是不是有問(wèn)題。如果數(shù)據(jù)本身有問(wèn)題,在一開(kāi)始生成Dataset對(duì)象的時(shí)候去掉就行了。如果數(shù)據(jù)本身沒(méi)有問(wèn)題,只不過(guò)因?yàn)橐恍┨厥庠驅(qū)е嘛@存炸了(比如檢測(cè)中圖像的GT boxes過(guò)多的問(wèn)題),可以catch一個(gè)CUDA OOM的error之后將一些邏輯放在CPU上,最后retry一下,這樣只是會(huì)慢一個(gè)iter,但是訓(xùn)練過(guò)程還是可以完整走完的,在我們開(kāi)源的YOLOX里有類似的參考code(https://github.com/Megvii-BaseDetection/YOLOX/blob/0.1.0/yolox/models/yolo_head.py#L330-L334)。
          6. pytorch中dataloader的坑。有時(shí)候會(huì)遇到pytorch num_workers=0(也就是單進(jìn)程)沒(méi)有問(wèn)題,但是多進(jìn)程就會(huì)報(bào)一些看不懂的錯(cuò)的現(xiàn)象,這種情況通常是因?yàn)閠orch到了ulimit的上限,更核心的原因是 torch的dataloader不會(huì)釋放文件描述符 (參考issue: https://github.com/pytorch/pytorch/issues/973)。可以u(píng)limit -n 看一下機(jī)器的設(shè)置。跑程序之前修改一下對(duì)應(yīng)的數(shù)值。
          7. opencv和dataloader的神奇聯(lián)動(dòng)。很多人經(jīng)常來(lái)問(wèn)為啥要寫(xiě)cv2.setNumThreads(0),其實(shí)是因?yàn)閏v2在做resize等op的時(shí)候會(huì)用多線程,當(dāng)torch的dataloader是多進(jìn)程的時(shí)候,多進(jìn)程套多線程,很容易就卡死了(具體哪里死鎖了我沒(méi)探究很深)。除了setNumThreads之外,通常還要加一句cv2.ocl.setUseOpenCL(False),原因是cv2使用opencl和cuda一起用的時(shí)候通常會(huì)拖慢速度,加了萬(wàn)事大吉,說(shuō)不定還能加速。感謝評(píng)論區(qū) @Yuxin Wu(https://www.zhihu.com/people/ppwwyyxx)?大大的指正
          8. dataloader會(huì)在epoch結(jié)束之后進(jìn)行類似重新加載的操作,復(fù)現(xiàn)這個(gè)問(wèn)題的code稍微有些長(zhǎng),放在后面了。這個(gè)問(wèn)題算是可以說(shuō)是一個(gè)高級(jí)bug/feature了,可能導(dǎo)致的問(wèn)題之一就是煉丹師在本地的code上進(jìn)行了一些修改,然后訓(xùn)練過(guò)程直接加載進(jìn)去了。解決方法也很簡(jiǎn)單,讓你的sampler源源不斷地產(chǎn)生數(shù)據(jù)就好,這樣即使本地code有修改也不會(huì)加載進(jìn)去。

          Module模塊

          1. BatchNorm在訓(xùn)練和推斷的時(shí)候的行為是不一致的。這也是新人最常見(jiàn)的錯(cuò)誤(類似的算子還有dropout,這里提一嘴, pytorch的dropout在eval的時(shí)候行為是Identity ,之前有遇到過(guò)實(shí)習(xí)生說(shuō)dropout加了沒(méi)效果,直到我看了他的code:x = F.dropout(x, p=0.5)
          2. BatchNorm疊加分布式訓(xùn)練的坑。在使用DDP(DistributedDataParallel)進(jìn)行訓(xùn)練的時(shí)候,每張卡上的BN統(tǒng)計(jì)量是可能不一樣的,仔細(xì)檢查broadcast_buffer這個(gè)參數(shù) 。DDP的默認(rèn)行為是在forward之前將rank0 的 buffer做一次broadcast(broadcast_buffer=True),但是一些常用的開(kāi)源檢測(cè)倉(cāng)庫(kù)是將broadcast_buffer設(shè)置成False的(參考:mmdet(https://github.com/facebookresearch/detectron2/blob/f50ec07cf220982e2c4861c5a9a17c4864ab5bfd/tools/plain_train_net.py#L206)?和 detectron2(https://github.com/facebookresearch/detectron2/blob/f50ec07cf220982e2c4861c5a9a17c4864ab5bfd/tools/plain_train_net.py#L206),我猜是在檢測(cè)任務(wù)中因?yàn)閎atchsize過(guò)小,統(tǒng)一用卡0的統(tǒng)計(jì)量會(huì)掉點(diǎn)) 這個(gè)問(wèn)題在一邊訓(xùn)練一邊測(cè)試的code中更常見(jiàn) ,比如說(shuō)你train了5個(gè)epoch,然后要分布式測(cè)試一下。一般的邏輯是將數(shù)據(jù)集分到每塊卡上,每塊卡進(jìn)行inference,最后gather到卡0上進(jìn)行測(cè)點(diǎn)。但是 因?yàn)槊繌埧ńy(tǒng)計(jì)量是不一樣的,所以和那種把卡0的模型broadcast到不同卡上測(cè)試出來(lái)的結(jié)果是不一樣的。這也是為啥通常訓(xùn)練完測(cè)的點(diǎn)和單獨(dú)起了一個(gè)測(cè)試腳本跑出來(lái)的點(diǎn)不一樣的原因 (當(dāng)然你用SyncBN就不會(huì)有這個(gè)問(wèn)題)。
          3. Pytorch的SyncBN在1.5之前一直實(shí)現(xiàn)的有bug,所以有一些老倉(cāng)庫(kù)是存在使用SyncBN之后掉點(diǎn)的問(wèn)題的。
          4. 用了多卡開(kāi)多尺度訓(xùn)練,明明尺度更小了,但是速度好像不是很理想?這個(gè)問(wèn)題涉及到多卡的原理,因?yàn)榉植际接?xùn)練的時(shí)候,在得到新的參數(shù)之后往往需要進(jìn)行一次同步。假設(shè)有兩張卡,卡0的尺度非常小,卡1的尺度非常大,那么就會(huì)出現(xiàn)卡0始終在等卡1,于是就出現(xiàn)了雖然有的尺度變小了,但是整體的訓(xùn)練速度并沒(méi)有變快的現(xiàn)象(木桶效應(yīng))。解決這個(gè)問(wèn)題的思路就是 盡量把負(fù)載拉均衡一些
          5. 多卡的小batch模擬大batch(梯度累積)的坑。假設(shè)我們?cè)趩慰ㄏ轮荒苋耣atchsize = 2,那么為了模擬一個(gè)batchsize = 8的效果,通常的做法是forward / backward 4次,不清理梯度,step一次(當(dāng)然考慮BN的統(tǒng)計(jì)量問(wèn)題這種做法和單純的batchsize=8肯定還是有一些差別的)。在多卡下,因?yàn)檎{(diào)用loss.backward的時(shí)候會(huì)做grad的同步,所以說(shuō)前三次調(diào)用backward的時(shí)候需要加ddp.no_sync(https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html?highlight=no_sync#torch.nn.parallel.DistributedDataParallel.no_sync)的context manager(不加的話,第一次bp之后,各個(gè)卡上的grad此時(shí)會(huì)進(jìn)行同步),最后一次則不需要加。當(dāng)然,我看很多倉(cāng)庫(kù)并沒(méi)有這么做,我只能理解他們就是單純想做梯度累積(BTW,加了ddp.no_sync會(huì)使得程序快一些,畢竟加了之后bp過(guò)程是無(wú)通訊的)。
          6. 浮點(diǎn)數(shù)的加法其實(shí)不遵守交換律的 ,這個(gè)通常能衍生出來(lái)GPU上的運(yùn)算結(jié)果不能嚴(yán)格復(fù)現(xiàn)的現(xiàn)象。可能一些非計(jì)算機(jī)軟件專業(yè)的同學(xué)并不理解這一件事情,直接自己開(kāi)一個(gè)python終端體驗(yàn)可能會(huì)更好:


          print(1e100?+?1e-4?+?-1e100)??#?ouptut:?0
          print(1e100?+?-1e100?+?1e-4)??#?output:?0.0001

          訓(xùn)練模塊

          1. FP16訓(xùn)練/混合精度訓(xùn)練。使用Apex訓(xùn)練混合精度模型,在保存checkpoint用于繼續(xù)訓(xùn)練的時(shí)候,除了model和optimizer本身的state_dict之外,還需要保存一下amp的state_dict,這個(gè)在amp的文檔(https://link.zhihu.com/?target=https%3A//nvidia.github.io/apex/amp.html%23checkpointing)中也有提過(guò)。(當(dāng)然,經(jīng)驗(yàn)上來(lái)說(shuō)忘了保存影響不大,會(huì)多花幾個(gè)iter search一個(gè)loss scalar出來(lái))
          2. 多機(jī)分布式訓(xùn)練卡死的問(wèn)題。好友 @NoahSYZhang(https://www.zhihu.com/people/syzhangbuaa) 遇到的一個(gè)坑。場(chǎng)景是先申請(qǐng)了兩個(gè)8卡機(jī),然后機(jī)器1和機(jī)器2用前4塊卡做通訊(local rank最大都是4,總共是兩機(jī)8卡)。可以初始化process group,但是在使用DDP的時(shí)候會(huì)卡死。原因在于pytorch在做DDP的時(shí)候會(huì)猜測(cè)一個(gè)rank,參考code(https://github.com/pytorch/pytorch/blob/0d437fe6d0ef17648072eb586484a4a5a080b094/torch/csrc/distributed/c10d/ProcessGroupNCCL.cpp#L1622-L1630)。對(duì)于上面的場(chǎng)景,第二個(gè)機(jī)器上因?yàn)榇嬖诳?到卡8,而對(duì)應(yīng)的rank也是5到8,所以DDP就會(huì)認(rèn)為自己需要同步的是卡5到卡8,于是就卡死了。

          復(fù)現(xiàn)Code

          Data部分


          from?torch.utils.data?import?DataLoader
          from?torch.utils.data?import?Dataset
          import?tqdm
          import?time


          class?SimpleDataset(Dataset):
          ????def?__init__(self,?length=400):
          ????????self.length?=?length
          ????????self.data_list?=?list(range(length))

          ????def?__getitem__(self,?index):
          ????????data?=?self.data_list[index]
          ????????time.sleep(0.1)
          ????????return?data

          ????def?__len__(self):
          ????????return?self.length


          def?train(local_rank):
          ????dataset?=?SimpleDataset()
          ????dataloader?=?DataLoader(dataset,?batch_size=1,?num_workers=2)
          ????iter_loader?=?iter(dataloader)
          ????max_iter?=?100000
          ????for?_?in?tqdm.tqdm(range(max_iter)):
          ????????try:
          ????????????_?=?next(iter_loader)
          ????????except?StopIteration:
          ????????????print("Refresh?here?!!!!!!!!")
          ????????????iter_loader?=?iter(dataloader)
          ????????????_?=?next(iter_loader)


          if?__name__?==?"__main__":
          ????import?torch.multiprocessing?as?mp
          ????mp.spawn(train,?args=(),?nprocs=2,?daemon=False)

          當(dāng)程序運(yùn)行起來(lái)的時(shí)候,可以在Dataset里面的__getitem__方法里面加一個(gè)print輸出一些內(nèi)容,在refresh之后,就會(huì)print對(duì)應(yīng)的內(nèi)容哦(看到現(xiàn)象是不是覺(jué)得自己以前煉的丹可能有問(wèn)題了呢hhh)

          一些碎碎念

          一口氣寫(xiě)了這么多條也有點(diǎn)累了,后續(xù)有踩到新坑的話我也會(huì)繼續(xù)更新這篇文章的。畢竟寫(xiě)這篇文章是希望工作中不再會(huì)有人踩類似的坑 & 煉丹的人能夠?qū)ι疃葘W(xué)習(xí)框架有意識(shí)(雖然某種程度上來(lái)講這算是個(gè)心智負(fù)擔(dān))。

          如果說(shuō)今年來(lái)什么事情是最大的收獲的話,那就是理解了一個(gè)開(kāi)放的生態(tài)是可以迸發(fā)出極強(qiáng)的活力的,也希望能看到更多的人來(lái)分享自己遇到的問(wèn)題和解決的思路。畢竟探索的答案只是一個(gè)副產(chǎn)品,過(guò)程本身才是最大的財(cái)寶。

          - The End -

          GiantPandaCV

          長(zhǎng)按二維碼關(guān)注我們

          本公眾號(hào)專注:

          1. 技術(shù)分享;

          2.?學(xué)術(shù)交流

          3.?資料共享

          歡迎關(guān)注我們,一起成長(zhǎng)!

          瀏覽 83
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人操人人摸人人操 | 影音先锋激情在线 | 亚洲AⅤ首页 | 黄色免费操逼视频 | 国产色欲一区二区精品 |