PyTorch常見的坑匯總

  • 2019 年 10 月 7 日
  • 筆記

最近剛開始用pytorch不久,陸陸續續踩了不少坑,記錄一下,個人感覺應該都是一些很容易遇到的一些坑,也在此比較感謝幫我排坑的小夥伴,持續更新,也祝願自己遇到的坑越來越少。

首先作為tensorflow的骨灰級玩家+輕微強迫症患者,一路打怪升級,從0.6版本用到1.2,再用到1.10,經歷了tensorfow數個版本更迭,這裡不得不說一下tf.data.dataset+tfrecord使用起來效率遠比dataloader高的多。

tensorflow有一個比較好用的隊列機制,tf.inputproducer + tfrecord, 但是inputproducer有一個bug,就是無法對每個epoch單獨shuffle,它只能整體shuffle,也就意味著我們無法進行正常的訓練流程(train幾個epoch,在validation上測一個epoch,最終選一個validation上的最好的結果,進行test)。後來我當時給官方提了一個issue,官方當時的回答是,這個bug目前無法解決,但是他們在即將到來的tf1.2版本中, 推出的新型數據處理API tf.contrib.data.dataset(tf1.3版本將其合併到了tf.data.dataset)可以完美解決這個bug,並且將於tf2.0摒棄tf.input_producer。然後tf1.2版本剛出來以後,我就立馬升級並且開始tf.data.dataset踩坑,踩了大概2周多的坑,(這個新版的API其實功能並不是非常強大,有不少局限性,在此就不展開)。

好像扯遠了,回歸pytorch,首先讓我比較尷尬的是pytorch並沒有一套屬於自己的數據結構以及數據讀取演算法,dataloader個人感覺其實就是類似於tf中的feed,並沒有任何速度以及性能上的提升。

先總結一下遇到的坑:

1. 沒有比較高效的數據存儲,tensorflow有tfrecord, caffe有lmdb,cv.imread在網路訓練過程中實屬浪費時間。這裡感謝一下小智大神@智天成

解決方案:

當時看到了一個還不錯的github鏈接:

https://github.com/Lyken17/Efficient-PyTorch

主要是講如何使用lmdb,h5py,pth,lmdb,n5等數據存儲方式皆可以。

個人的感受是,h5在數據調用上比較快,但是如果要使用多執行緒讀寫,就盡量不要使用h5,因為h5的多執行緒讀寫好像比較麻煩。

http://docs.h5py.org/en/stable/mpi.html

這裡貼一下h5數據的讀寫程式碼(主要需要注意的是字元串的讀寫需要encode,decode,最好用create_dataset,直接寫的話讀的時候會報錯):

寫:      imagenametotal_.append(os.path.join('images', imagenametotal).encode())      with h5py.File(outfile) as f:          f.create_dataset('imagename', data=imagenametotal_)          f['part'] = parts_          f['S'] = Ss_          f['image'] = cvimgs  讀:  with h5py.File(outfile) as f:      imagename = [x.decode() for x in f['imagename']]      kp2ds = np.array(f['part'])      kp3ds = np.array(f['S'])      cvimgs = np.array(f['image'])

2. gpu imbalance,這裡感謝一下張航學長@張航

老生常談的問題,第一個GPU顯示記憶體佔用多一點。

張航學長提了一個開源的gpu balance的工具—PyTorch-Encoding。

https://github.com/zhanghang1989/PyTorch-Encoding

使用方法還是比較便捷的,如下所示:

from balanced_parallel import DataParallelModel, DataParallelCriterion  model = DataParallelModel(model, device_ids=gpus).cuda()  criterion = loss_fn().cuda()

這裡其實有2個注意點,第一,測試的時候需要手動將gpu合併,程式碼如下:

from torch.nn.parallel.scatter_gather import gather  preds = gather(preds, 0)

第二,當loss函數有多個組成的時候,比如 loss = loss1 + loss2 + loss3

那麼需要把這三個loss寫到一個class中,然後再forward裡面將其加起來。

其次,我們還可以用另外一個函數distributedDataParallel來解決gpu imbalance的問題。

使用方法如下:(註:此方法好像無法和h5數據同時使用)

from torch.utils.data.distributed import DistributedSampler  from torch.nn.parallel import DistributedDataParallel    torch.distributed.init_process_group(backend="nccl")  # 配置每個進程的gpu  local_rank = torch.distributed.get_rank()  torch.cuda.set_device(local_rank)  device = torch.device("cuda", local_rank)    #封裝之前要把模型移到對應的gpu  model.to(device)  model = torch.nn.parallel.DistributedDataParallel(model,device_ids=[local_rank],                                                     output_device=local_rank)    #原有的dataloader上面加一個數據sample  train_loader = torch.utils.data.DataLoader(          train_dataset,          sampler=DistributedSampler(train_dataset)      )

3. gpu利用率不高+gpu現存佔用浪費

常用配置:

(1)主函數前面加:(這個會犧牲一點點現存提高模型精度)

cudnn.benchmark = True  torch.backends.cudnn.deterministic = False  torch.backends.cudnn.enabled = True

(2)訓練時,epoch前面加:(定期清空模型,效果感覺不明顯)

torch.cuda.empty_cache()

(3)無用變數前面加:(同上,效果某些操作上還挺明顯的)

del xxx(變數名)

(4)dataloader的長度_len_設置:(dataloader會間歇式出現卡頓,設置成這樣會避免不少)

def __len__(self):      return self.images.shape[0]

(5)dataloader的預載入設置:(會在模型訓練的時候載入數據,提高一點點gpu利用率)

train_loader = torch.utils.data.DataLoader(          train_dataset,          pin_memory=True,      )

(6)網路設計很重要,外加不要初始化任何用不到的變數,因為pyroch的初始化和forward是分開的,他不會因為你不去使用,而不去初始化。

(7)最後放一張目前依舊困擾我的圖片:

可以看到,每個epoch剛開始訓練數據的時候,第一個iteration時間會佔用的非常多,pytorch這裡就做的很糟糕,並不是一個動態分配的過程,我也看到了一個看上去比較靠譜的解決方案,解決方案如下:在深度學習中餵飽 GPU

但是我看了下程式碼,可能需要重構dataloader,看了評論好像還有問題,有點懶,目前還沒有踩坑,準備後面有時間踩一下。