來自 | 知乎
地址 | https://zhuanlan.zhihu.com/p/77952356
作者 | 鬱振波
編輯 | 機器學習算法與自然語言處理公眾號
本文僅作學術分享,若侵權,請聯繫後臺刪文處理
最近剛開始用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連結,
Lyken17/Efficient-PyTorch
主要是講如何使用lmdb,h5py,pth,lmdb,n5等數據存儲方式皆可以。
個人的感受是,h5在數據調用上比較快,但是如果要使用多線程讀寫,就儘量不要使用h5,因為h5的多線程讀寫好像比較麻煩。
Parallel HDF5 - h5py 2.9.0 documentation
這裡貼一下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顯存佔用多一點。
張航學長Hang Zhang (張航)提了一個開源的gpu balance的工具--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(變量名)
4dataloader的長度_len_設置:(dataloader會間歇式出現卡頓,設置成這樣會避免不少)
def __len__(self):
return self.images.shape[0]
5dataloader的預加載設置:(會在模型訓練的時候加載數據,提高一點點gpu利用率)
train_loader = torch.utils.data.DataLoader(
train_dataset,
pin_memory=True,
)
6網絡設計很重要,外加不要初始化任何用不到的變量,因為pyroch的初始化和forward是分開的,他不會因為你不去使用,而不去初始化。
7最後放一張目前依舊困擾我的圖片:
可以看到,每個epoch剛開始訓練數據的時候,第一個iteration時間會佔用的非常多,pytorch這裡就做的很糟糕,並不是一個動態分配的過程,我也看到了一個看上去比較靠譜的解決方案,解決方案如下 @風車車:
風車車:在深度學習中餵飽gpu
但是我看了下代碼,可能需要重構dataloader,看了評論好像還有問題,有點懶,目前還沒有踩坑,準備後面有時間踩一下。
暫且更新到這裡,後續遇到什麼坑陸續補充,也歡迎大家給我補充,pytorch初學者小白一枚。
更個新;順便吐槽一下上面的dali,局限性很大,比較trick的數據預處理很難搞定。
8 apex混合單精度模型
事實證明,apex並沒有官網說的那麼玄乎,只能減低顯存,並不能提速(12G顯存大概可以降低到8G左右,效果還挺明顯的,但是,速度降低了大概1/3,好像有點得不償失)。
編譯之後提速也很有限,再此留個坑,有小夥伴能解決的可以私信我哈,如果可以解決我會仔細羅列一遍。。
最近分享了一些奇技淫巧的深度環境工具:
鬱振波:打造舒適的深度學習環境(工具篇)
重磅!憶臻自然語言處理-學術微信交流群已成立
可以掃描下方二維碼,小助手將會邀請您入群交流,
注意:請大家添加時修改備註為 [學校/公司 + 姓名 + 方向]
例如 —— 哈工大+張三+對話系統。
號主,微商請自覺繞道。謝謝!
推薦閱讀:
【長文詳解】從Transformer到BERT模型
賽爾譯文 | 從頭開始了解Transformer
百聞不如一碼!手把手教你用Python搭一個Transformer