優(yōu)化Pytorch模型訓(xùn)練的小技巧
在本文中,我將描述并展示4種不同的Pytorch訓(xùn)練技巧的代碼,這些技巧是我個(gè)人發(fā)現(xiàn)的,用于改進(jìn)我的深度學(xué)習(xí)模型的訓(xùn)練。
混合精度
在一個(gè)常規(guī)的訓(xùn)練循環(huán)中,PyTorch以32位精度存儲(chǔ)所有浮點(diǎn)數(shù)變量。對(duì)于那些在嚴(yán)格的約束下訓(xùn)練模型的人來說,這有時(shí)會(huì)導(dǎo)致他們的模型占用過多的內(nèi)存,迫使他們使用更小的模型和更小的批處理大小進(jìn)行更慢的訓(xùn)練過程。所以在模型中以16位精度存儲(chǔ)所有變量/數(shù)字可以改善并修復(fù)大部分這些問題,比如顯著減少模型的內(nèi)存消耗,加速訓(xùn)練循環(huán),同時(shí)仍然保持模型的性能/精度。
在Pytorch中將所有計(jì)算轉(zhuǎn)換為16位精度非常簡(jiǎn)單,只需要幾行代碼。這里是:
- scaler = torch.cuda.amp.GradScaler()
上面的方法創(chuàng)建一個(gè)梯度縮放標(biāo)量,以最大程度避免使用fp16進(jìn)行運(yùn)算時(shí)的梯度下溢。
- optimizer.zero_grad()
- with torch.cuda.amp.autocast():
- output = model(input).to(device)
- loss = criterion(output, correct_answer).to(device)
- scaler.scale(loss).backward()
- scaler.step(optimizer)
- scaler.update()
當(dāng)使用loss和優(yōu)化器進(jìn)行反向傳播時(shí),您需要使用scale .scale(loss),而不是使用loss.backward()和optimizer.step()。使用scaler.step(optimizer)來更新優(yōu)化器。這允許你的標(biāo)量轉(zhuǎn)換所有的梯度,并在16位精度做所有的計(jì)算,最后用scaler.update()來更新縮放標(biāo)量以使其適應(yīng)訓(xùn)練的梯度。
當(dāng)以16位精度做所有事情時(shí),可能會(huì)有一些數(shù)值不穩(wěn)定,導(dǎo)致您可能使用的一些函數(shù)不能正常工作。只有某些操作在16位精度下才能正常工作。具體可參考官方的文檔。
進(jìn)度條
有一個(gè)進(jìn)度條來表示每個(gè)階段的訓(xùn)練完成的百分比是非常有用的。為了獲得進(jìn)度條,我們將使用tqdm庫(kù)。以下是如何下載并導(dǎo)入它:
- pip install tqdm
- from tqdm import tqdm
在你的訓(xùn)練和驗(yàn)證循環(huán)中,你必須這樣做:
- for index, batch in tqdm(enumerate(loader), total = len(loader), position = 0, leave = True):
訓(xùn)練和驗(yàn)證循環(huán)添加tqdm代碼后將得到一個(gè)進(jìn)度條,它表示您的模型完成的訓(xùn)練的百分比。它應(yīng)該是這樣的:

在圖中,691代表我的模型需要完成多少批,7:28代表我的模型在691批上的總時(shí)間,1.54 it/s代表我的模型在每批上花費(fèi)的平均時(shí)間。
梯度積累
如果您遇到CUDA內(nèi)存不足的錯(cuò)誤,這意味著您已經(jīng)超出了您的計(jì)算資源。為了解決這個(gè)問題,你可以做幾件事,包括把所有東西都轉(zhuǎn)換成16位精度,減少模型的批處理大小,更換更小的模型等等。
但是有時(shí)切換到16位精度并不能完全解決問題。解決這個(gè)問題最直接的方法是減少批處理大小,但是假設(shè)您不想減少批處理大小可以使用梯度累積來模擬所需的批大小。請(qǐng)注意,CUDA內(nèi)存不足問題的另一個(gè)解決方案是簡(jiǎn)單地使用多個(gè)GPU,但這是一個(gè)很多人無法使用的選項(xiàng)。
假設(shè)你的機(jī)器/模型只能支持16的批處理大小,增加它會(huì)導(dǎo)致CUDA內(nèi)存不足錯(cuò)誤,并且您希望批處理大小為32。梯度累加的工作原理是:以16個(gè)批的規(guī)模運(yùn)行模型兩次,將計(jì)算出的每個(gè)批的梯度累加起來,最后在這兩次前向傳播和梯度累加之后執(zhí)行一個(gè)優(yōu)化步驟。
要理解梯度積累,重要的是要理解在訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí)所做的具體功能。假設(shè)你有以下訓(xùn)練循環(huán):
- model = model.train()
- for index, batch in enumerate(train_loader):
- input = batch[0].to(device)
- correct_answer = batch[1].to(device)
- optimizer.zero_grad()
- output = model(input).to(device)
- loss = criterion(output, correct_answer).to(device)
- loss.backward()
- optimizer.step()
看看上面的代碼,需要記住的關(guān)鍵是loss.backward()為模型創(chuàng)建并存儲(chǔ)梯度,而optimizer.step()實(shí)際上更新權(quán)重。在如果在調(diào)用優(yōu)化器之前兩次調(diào)用loss.backward()就會(huì)對(duì)梯度進(jìn)行累加。下面是如何在PyTorch中實(shí)現(xiàn)梯度累加:
- model = model.train()
- optimizer.zero_grad()
- for index, batch in enumerate(train_loader):
- input = batch[0].to(device)
- correct_answer = batch[1].to(device)
- output = model(input).to(device)
- loss = criterion(output, correct_answer).to(device)
- loss.backward()
- if (index+1) % 2 == 0:
- optimizer.step()
- optimizer.zero_grad()
在上面的例子中,我們的機(jī)器只能支持16批大小的批量,我們想要32批大小的批量,我們本質(zhì)上計(jì)算2批的梯度,然后更新實(shí)際權(quán)重。這導(dǎo)致有效批大小為32。
譯者注:梯度累加只是一個(gè)折中方案,經(jīng)過我們的測(cè)試,如果對(duì)梯度進(jìn)行累加,那么最后一次loss.backward()的梯度會(huì)比前幾次反向傳播的權(quán)重高,具體為什么我們也不清楚,哈。雖然有這樣的問題,但是使用這種方式進(jìn)行訓(xùn)練還是有效果的。
16位精度的梯度累加非常類似。
- model = model.train()
- optimizer.zero_grad()
- for index, batch in enumerate(train_loader):
- input = batch[0].to(device)
- correct_answer = batch[1].to(device)
- with torch.cuda.amp.autocast():
- output = model(input).to(device)
- loss = criterion(output, correct_answer).to(device)
- scaler.scale(loss).backward()
- if (index+1) % 2 == 0:
- scaler.step(optimizer)
- scaler.update()
- optimizer.zero_grad()
結(jié)果評(píng)估
在大多數(shù)機(jī)器學(xué)習(xí)項(xiàng)目中,人們傾向于手動(dòng)計(jì)算用于評(píng)估的指標(biāo)。盡管計(jì)算準(zhǔn)確率、精度、召回率和F1等指標(biāo)并不困難,但在某些情況下,您可能希望擁有這些指標(biāo)的某些變體,如加權(quán)精度、召回率和F1。計(jì)算這些可能需要更多的工作,如果你的實(shí)現(xiàn)可能不正確、高效、快速且無錯(cuò)誤地計(jì)算所有這些指標(biāo),可以使用sklearns classification_report庫(kù)。這是一個(gè)專門為計(jì)算這些指標(biāo)而設(shè)計(jì)的庫(kù)。
- from sklearn.metrics import classification_report
- y_pred = [0, 1, 0, 0, 1]
- y_correct = [1, 1, 0, 1, 1]print(classification_report(y_correct, y_pred))
上面的代碼用于二進(jìn)制分類。你可以為更多的目的配置這個(gè)函數(shù)。第一個(gè)列表表示模型的預(yù)測(cè),第二個(gè)列表表示正確數(shù)值。上面的代碼將輸出:

結(jié)論
在這篇文章中,我討論了4種pytorch中優(yōu)化深度神經(jīng)網(wǎng)絡(luò)訓(xùn)練的方法。16位精度減少內(nèi)存消耗,梯度積累可以通過模擬使用更大的批大小,tqdm進(jìn)度條和sklearns的classification_report兩個(gè)方便的庫(kù),可以輕松地跟蹤模型的訓(xùn)練和評(píng)估模型的性能。就我個(gè)人而言,我總是用上面所有的訓(xùn)練技巧來訓(xùn)練我的神經(jīng)網(wǎng)絡(luò),并且在必要的時(shí)候我使用梯度積累。
最后,如果你使用的是pytorch或者是pytorch的初學(xué)者,可以使用這個(gè)庫(kù):
github/deephub-ai/torch-handle
他會(huì)對(duì)你有很大的幫助。