自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

PyTorch經(jīng)驗(yàn)指南:技巧與陷阱

開發(fā) 開發(fā)工具 深度學(xué)習(xí)
PyTorch 的構(gòu)建者表明,PyTorch 的哲學(xué)是解決當(dāng)務(wù)之急,也就是說即時(shí)構(gòu)建和運(yùn)行計(jì)算圖。本文從基本概念開始介紹了 PyTorch 的使用方法、訓(xùn)練經(jīng)驗(yàn)與技巧,并展示了可能出現(xiàn)的問題與解決方案。

PyTorch 的構(gòu)建者表明,PyTorch 的哲學(xué)是解決當(dāng)務(wù)之急,也就是說即時(shí)構(gòu)建和運(yùn)行計(jì)算圖。目前,PyTorch 也已經(jīng)借助這種即時(shí)運(yùn)行的概念成為最受歡迎的框架之一,開發(fā)者能快速構(gòu)建模型與驗(yàn)證想法,并通過神經(jīng)網(wǎng)絡(luò)交換格式 ONNX 在多個(gè)框架之間快速遷移。本文從基本概念開始介紹了 PyTorch 的使用方法、訓(xùn)練經(jīng)驗(yàn)與技巧,并展示了可能出現(xiàn)的問題與解決方案。

[[238704]]

項(xiàng)目地址:https://github.com/Kaixhin/grokking-pytorch

PyTorch 是一種靈活的深度學(xué)習(xí)框架,它允許通過動(dòng)態(tài)神經(jīng)網(wǎng)絡(luò)(例如利用動(dòng)態(tài)控流——如 if 語句或 while 循環(huán)的網(wǎng)絡(luò))進(jìn)行自動(dòng)微分。它還支持 GPU 加速、分布式訓(xùn)練以及各類優(yōu)化任務(wù),同時(shí)還擁有許多更簡潔的特性。以下是作者關(guān)于如何利用 PyTorch 的一些說明,里面雖然沒有包含該庫的所有細(xì)節(jié)或最優(yōu)方法,但可能會(huì)對(duì)大家有所幫助。

神經(jīng)網(wǎng)絡(luò)是計(jì)算圖的一個(gè)子類。計(jì)算圖接收輸入數(shù)據(jù),數(shù)據(jù)被路由到對(duì)數(shù)據(jù)執(zhí)行處理的節(jié)點(diǎn),并可能被這些節(jié)點(diǎn)轉(zhuǎn)換。在深度學(xué)習(xí)中,神經(jīng)網(wǎng)絡(luò)中的神經(jīng)元(節(jié)點(diǎn))通常利用參數(shù)或可微函數(shù)轉(zhuǎn)換數(shù)據(jù),這樣可以優(yōu)化參數(shù)以通過梯度下降將損失最小化。更廣泛地說,函數(shù)是隨機(jī)的,圖結(jié)構(gòu)可以是動(dòng)態(tài)的。所以說,雖然神經(jīng)網(wǎng)絡(luò)可能非常適合數(shù)據(jù)流式編程,但 PyTorch 的 API 卻更關(guān)注命令式編程——一種編程更??紤]的形式。這令讀取代碼和推斷復(fù)雜程序變得簡單,而無需損耗不必要的性能;PyTorch 速度很快,且擁有大量優(yōu)化,作為終端用戶你毫無后顧之憂。

本文其余部分寫的是關(guān)于 grokking PyTorch 的內(nèi)容,都是基于 MINIST 官網(wǎng)實(shí)例,應(yīng)該要在學(xué)習(xí)完官網(wǎng)初學(xué)者教程后再查看。為便于閱讀,代碼以塊狀形式呈現(xiàn),并帶有注釋,因此不會(huì)像純模塊化代碼一樣被分割成不同的函數(shù)或文件。

Pytorch 基礎(chǔ)

PyTorch 使用一種稱之為 imperative / eager 的范式,即每一行代碼都要求構(gòu)建一個(gè)圖,以定義完整計(jì)算圖的一個(gè)部分。即使完整的計(jì)算圖還沒有構(gòu)建好,我們也可以獨(dú)立地執(zhí)行這些作為組件的小計(jì)算圖,這種動(dòng)態(tài)計(jì)算圖被稱為「define-by-run」方法。

PyTorch 張量

正如 PyTorch 文檔所說,如果我們熟悉 NumPy 的多維數(shù)組,那么 Torch 張量的很多操作我們能輕易地掌握。PyTorch 提供了 CPU 張量和 GPU 張量,并且極大地加速了計(jì)算的速度。

從張量的構(gòu)建與運(yùn)行就能體會(huì),相比 TensorFLow,在 PyTorch 中聲明張量、初始化張量要簡潔地多。例如,使用 torch.Tensor(5, 3) 語句就能隨機(jī)初始化一個(gè) 5×3 的二維張量,因?yàn)?PyTorch 是一種動(dòng)態(tài)圖,所以它聲明和真實(shí)賦值是同時(shí)進(jìn)行的。

在 PyTorch 中,torch.Tensor 是一種多維矩陣,其中每個(gè)元素都是單一的數(shù)據(jù)類型,且該構(gòu)造函數(shù)默認(rèn)為 torch.FloatTensor。以下是具體的張量類型:

PyTorch 張量

除了直接定義維度,一般我們還可以從 Python 列表或 NumPy 數(shù)組中創(chuàng)建張量。而且根據(jù)使用 Python 列表和元組等數(shù)據(jù)結(jié)構(gòu)的習(xí)慣,我們可以使用相似的索引方式進(jìn)行取值或賦值。PyTorch 同樣支持廣播(Broadcasting)操作,一般它會(huì)隱式地把一個(gè)數(shù)組的異常維度調(diào)整到與另一個(gè)算子相匹配的維度,以實(shí)現(xiàn)維度兼容。

自動(dòng)微分模塊

TensorFlow、Caffe 和 CNTK 等大多數(shù)框架都使用靜態(tài)計(jì)算圖,開發(fā)者必須建立或定義一個(gè)神經(jīng)網(wǎng)絡(luò),并重復(fù)使用相同的結(jié)構(gòu)來執(zhí)行模型訓(xùn)練。改變網(wǎng)絡(luò)的模式就意味著我們必須從頭開始設(shè)計(jì)并定義相關(guān)的模塊。

但 PyTorch 使用的技術(shù)為自動(dòng)微分(automatic differentiation)。在這種機(jī)制下,系統(tǒng)會(huì)有一個(gè) Recorder 來記錄我們執(zhí)行的運(yùn)算,然后再反向計(jì)算對(duì)應(yīng)的梯度。這種技術(shù)在構(gòu)建神經(jīng)網(wǎng)絡(luò)的過程中十分強(qiáng)大,因?yàn)槲覀兛梢酝ㄟ^計(jì)算前向傳播過程中參數(shù)的微分來節(jié)省時(shí)間。

從概念上來說,Autograd 會(huì)維護(hù)一個(gè)圖并記錄對(duì)變量執(zhí)行的所有運(yùn)算。這會(huì)產(chǎn)生一個(gè)有向無環(huán)圖,其中葉結(jié)點(diǎn)為輸入向量,根結(jié)點(diǎn)為輸出向量。通過從根結(jié)點(diǎn)到葉結(jié)點(diǎn)追蹤圖的路徑,我們可以輕易地使用鏈?zhǔn)椒▌t自動(dòng)計(jì)算梯度。

在內(nèi)部,Autograd 將這個(gè)圖表征為 Function 對(duì)象的圖,并且可以應(yīng)用 apply() 計(jì)算評(píng)估圖的結(jié)果。在計(jì)算前向傳播中,當(dāng) Autograd 在執(zhí)行請(qǐng)求的計(jì)算時(shí),它還會(huì)同時(shí)構(gòu)建一個(gè)表征梯度計(jì)算的圖,且每個(gè) Variable 的 .grad_fn 屬性就是這個(gè)圖的輸入單元。在前向傳播完成后,我們可以在后向傳播中根據(jù)這個(gè)動(dòng)態(tài)圖來計(jì)算梯度。

PyTorch 還有很多基礎(chǔ)的模塊,例如控制學(xué)習(xí)過程的最優(yōu)化器、搭建深度模型的神經(jīng)網(wǎng)絡(luò)模塊和數(shù)據(jù)加載與處理等。這一節(jié)展示的張量與自動(dòng)微分模塊是 PyTorch 最為核心的概念之一,讀者可查閱 PyTorch 文檔了解更詳細(xì)的內(nèi)容。

下面作者以 MNIST 為例從數(shù)據(jù)加載到模型測試具體討論了 PyTorch 的使用、思考技巧與陷阱。

PyTorch 實(shí)用指南

1. 導(dǎo)入

  1. import argparse 
  2. import torch 
  3. from torch import nn, optim 
  4. from torch.nn import functional as F 
  5. from torch.utils.data import DataLoader 
  6. from torchvision import datasets, transforms 

除了用于計(jì)算機(jī)視覺問題的 torchvision 模塊外,這些都是標(biāo)準(zhǔn)化導(dǎo)入。

2. 設(shè)置

  1. parser = argparse.ArgumentParser(description='PyTorch MNIST Example'
  2. parser.add_argument('--batch-size', type=intdefault=64metavar='N'
  3.  help='input batch size for training (default: 64)'
  4. parser.add_argument('--epochs', type=intdefault=10metavar='N'
  5.  help='number of epochs to train (default: 10)'
  6. parser.add_argument('--lr', type=floatdefault=0.01, metavar='LR'
  7.  help='learning rate (default: 0.01)'
  8. parser.add_argument('--momentum', type=floatdefault=0.5, metavar='M'
  9.  help='SGD momentum (default: 0.5)'
  10. parser.add_argument('--no-cuda', action='store_true'default=False
  11.  help='disables CUDA training'
  12. parser.add_argument('--seed', type=intdefault=1metavar='S'
  13.  help='random seed (default: 1)'
  14. parser.add_argument('--save-interval', type=intdefault=10metavar='N'
  15.  help='how many batches to wait before checkpointing'
  16. parser.add_argument('--resume', action='store_true'default=False
  17.  help='resume training from checkpoint'
  18. args = parser.parse_args() 
  19.  
  20. use_cuda = torch.cuda.is_available() and not args.no_cuda 
  21. device = torch.device('cuda' if use_cuda else 'cpu') 
  22. torch.manual_seed(args.seed) 
  23. if use_cuda: 
  24.  torch.cuda.manual_seed(args.seed) 

argparse 是在 Python 中處理命令行參數(shù)的一種標(biāo)準(zhǔn)方式。

編寫與設(shè)備無關(guān)的代碼(可用時(shí)受益于 GPU 加速,不可用時(shí)會(huì)倒退回 CPU)時(shí),選擇并保存適當(dāng)?shù)?torch.device, 不失為一種好方法,它可用于確定存儲(chǔ)張量的位置。關(guān)于與設(shè)備無關(guān)代碼的更多內(nèi)容請(qǐng)參閱官網(wǎng)文件。PyTorch 的方法是使用戶能控制設(shè)備,這對(duì)簡單示例來說有些麻煩,但是可以更容易地找出張量所在的位置——這對(duì)于 a)調(diào)試很有用,并且 b)可有效地使用手動(dòng)化設(shè)備。

對(duì)于可重復(fù)實(shí)驗(yàn),有必要為使用隨機(jī)數(shù)生成的任何數(shù)據(jù)設(shè)置隨機(jī)種子(如果也使用隨機(jī)數(shù),則包括隨機(jī)或 numpy)。要注意,cuDNN 用的是非確定算法,可以通過語句 torch.backends.cudnn.enabled = False 將其禁用。

3. 數(shù)據(jù)

  1. train_data = datasets.MNIST('data', train=Truedownload=True
  2.  transform=transforms.Compose([ 
  3.  transforms.ToTensor(), 
  4.  transforms.Normalize((0.1307,), (0.3081,))])) 
  5. test_data = datasets.MNIST('data', train=Falsetransform=transforms.Compose([ 
  6.  transforms.ToTensor(), 
  7.  transforms.Normalize((0.1307,), (0.3081,))])) 
  8.  
  9. train_loader = DataLoader(train_data, batch_size=args.batch_size, 
  10.  shuffle=Truenum_workers=4pin_memory=True
  11. test_loader = DataLoader(test_data, batch_size=args.batch_size, 
  12.  num_workers=4pin_memory=True

torchvision.transforms 對(duì)于單張圖像有非常多便利的轉(zhuǎn)換工具,例如裁剪和歸一化等。

DataLoader 包含非常多的參數(shù),除了 batch_size 和 shuffle,num_workers 和 pin_memory 對(duì)于高效加載數(shù)據(jù)同樣非常重要。例如配置 num_workers > 0 將使用子進(jìn)程異步加載數(shù)據(jù),而不是使用一個(gè)主進(jìn)程塊加載數(shù)據(jù)。參數(shù) pin_memory 使用固定 RAM 以加速 RAM 到 GPU 的轉(zhuǎn)換,且在僅使用 CPU 時(shí)不會(huì)做任何運(yùn)算。

4. 模型

  1. class Net(nn.Module): 
  2.  def __init__(self): 
  3.  super(Net, self).__init__() 
  4.  self.conv1 = nn.Conv2d(1, 10, kernel_size=5
  5.  self.conv2 = nn.Conv2d(10, 20, kernel_size=5
  6.  self.conv2_drop = nn.Dropout2d() 
  7.  self.fc1 = nn.Linear(320, 50) 
  8.  self.fc2 = nn.Linear(50, 10) 
  9.  
  10.  def forward(self, x): 
  11.  x = F.relu(F.max_pool2d(self.conv1(x), 2)) 
  12.  x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) 
  13.  xx = x.view(-1, 320) 
  14.  x = F.relu(self.fc1(x)) 
  15.  x = self.fc2(x) 
  16.  return F.log_softmax(x, dim=1
  17.  
  18. model = Net().to(device) 
  19. optimoptimiser = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) 
  20.  
  21. if args.resume: 
  22.  model.load_state_dict(torch.load('model.pth')) 
  23.  optimiser.load_state_dict(torch.load('optimiser.pth')) 

神經(jīng)網(wǎng)絡(luò)初始化一般包括變量、包含可訓(xùn)練參數(shù)的層級(jí)、可能獨(dú)立的可訓(xùn)練參數(shù)和不可訓(xùn)練的緩存器。隨后前向傳播將這些初始化參數(shù)與 F 中的函數(shù)結(jié)合,其中該函數(shù)為不包含參數(shù)的純函數(shù)。有些開發(fā)者喜歡使用完全函數(shù)化的網(wǎng)絡(luò)(如保持所有參數(shù)獨(dú)立,使用 F.conv2d 而不是 nn.Conv2d),或者完全由 layers 函數(shù)構(gòu)成的網(wǎng)絡(luò)(如使用 nn.ReLU 而不是 F.relu)。

在將 device 設(shè)置為 GPU 時(shí),.to(device) 是一種將設(shè)備參數(shù)(和緩存器)發(fā)送到 GPU 的便捷方式,且在將 device 設(shè)置為 CPU 時(shí)不會(huì)做任何處理。在將網(wǎng)絡(luò)參數(shù)傳遞給優(yōu)化器之前,把它們傳遞給適當(dāng)?shù)脑O(shè)備非常重要,不然的話優(yōu)化器不能正確地追蹤參數(shù)。

神經(jīng)網(wǎng)絡(luò)(nn.Module)和優(yōu)化器(optim.Optimizer)都能保存和加載它們的內(nèi)部狀態(tài),而.load_state_dict(state_dict) 是完成這一操作的推薦方法,我們可以從以前保存的狀態(tài)字典中加載兩者的狀態(tài)并恢復(fù)訓(xùn)練。此外,保存整個(gè)對(duì)象可能會(huì)出錯(cuò)。

這里沒討論的一些注意事項(xiàng)即前向傳播可以使用控制流,例如一個(gè)成員變量或數(shù)據(jù)本身能決定 if 語句的執(zhí)行。此外,在前向傳播的過程中打印張量也是可行的,這令 debug 更加簡單。最后,前向傳播可以使用多個(gè)參數(shù)。以下使用間斷的代碼塊展示這一點(diǎn):

  1. def forward(self, x, hx, drop=False): 
  2.  hx2 = self.rnn(x, hx) 
  3.  print(hx.mean().item(), hx.var().item()) 
  4.  if hx.max.item() > 10 or self.can_drop and drop: 
  5.  return hx 
  6.  else: 
  7.  return hx2 

5. 訓(xùn)練

  1. model.train() 
  2. train_losses = [] 
  3.  
  4. for i, (data, target) in enumerate(train_loader): 
  5.  data, target = data.to(device), target.to(device) 
  6.  optimiser.zero_grad() 
  7.  output = model(data) 
  8.  loss = F.nll_loss(output, target) 
  9.  loss.backward() 
  10.  train_losses.append(loss.item()) 
  11.  optimiser.step() 
  12.  
  13.  if i % 10 == 0: 
  14.  print(i, loss.item()) 
  15.  torch.save(model.state_dict(), 'model.pth') 
  16.  torch.save(optimiser.state_dict(), 'optimiser.pth') 
  17.  torch.save(train_losses, 'train_losses.pth') 

網(wǎng)絡(luò)模塊默認(rèn)設(shè)置為訓(xùn)練模式,這影響了某些模塊的工作方式,最明顯的是 dropout 和批歸一化。最好用.train() 對(duì)其進(jìn)行手動(dòng)設(shè)置,這樣可以把訓(xùn)練標(biāo)記向下傳播到所有子模塊。

在使用 loss.backward() 收集一系列新的梯度以及用 optimiser.step() 做反向傳播之前,有必要手動(dòng)地將由 optimiser.zero_grad() 優(yōu)化的參數(shù)梯度歸零。默認(rèn)情況下,PyTorch 會(huì)累加梯度,在單次迭代中沒有足夠資源來計(jì)算所有需要的梯度時(shí),這種做法非常便利。

PyTorch 使用一種基于 tape 的自動(dòng)化梯度(autograd)系統(tǒng),它收集按順序在張量上執(zhí)行的運(yùn)算,然后反向重放它們來執(zhí)行反向模式微分。這正是為什么 PyTorch 如此靈活并允許執(zhí)行任意計(jì)算圖的原因。如果沒有張量需要做梯度更新(當(dāng)你需要為該過程構(gòu)建一個(gè)張量時(shí),你必須設(shè)置 requires_grad=True),則不需要保存任何圖。然而,網(wǎng)絡(luò)傾向于包含需要梯度更新的參數(shù),因此任何網(wǎng)絡(luò)輸出過程中執(zhí)行的計(jì)算都將保存在圖中。因此如果想保存在該過程中得到的數(shù)據(jù),你將需要手動(dòng)禁止梯度更新,或者,更常見的做法是將其保存為一個(gè) Python 數(shù)(通過一個(gè) Python 標(biāo)量上的.item())或者 NumPy 數(shù)組。更多關(guān)于 autograd 的細(xì)節(jié)詳見官網(wǎng)文件。

截取計(jì)算圖的一種方式是使用.detach(),當(dāng)通過沿時(shí)間的截?cái)喾聪騻鞑ビ?xùn)練 RNN 時(shí),數(shù)據(jù)流傳遞到一個(gè)隱藏狀態(tài)可能會(huì)應(yīng)用這個(gè)函數(shù)。當(dāng)對(duì)損失函數(shù)求微分(其中一個(gè)成分是另一個(gè)網(wǎng)絡(luò)的輸出)時(shí),也會(huì)很方便。但另一個(gè)網(wǎng)絡(luò)不應(yīng)該用「loss - examples」的模式進(jìn)行優(yōu)化,包括在 GAN 訓(xùn)練中從生成器的輸出訓(xùn)練判別器,或使用價(jià)值函數(shù)作為基線(例如 A2C)訓(xùn)練 actor-critic 算法的策略。另一種在 GAN 訓(xùn)練(從判別器訓(xùn)練生成器)中能高效阻止梯度計(jì)算的方法是在整個(gè)網(wǎng)絡(luò)參數(shù)上建立循環(huán),并設(shè)置 param.requires_grad=False,這在微調(diào)中也很常用。

除了在控制臺(tái)/日志文件里記錄結(jié)果以外,檢查模型參數(shù)(以及優(yōu)化器狀態(tài))也是很重要的。你還可以使用 torch.save() 來保存一般的 Python 對(duì)象,但其它標(biāo)準(zhǔn)選擇還包括內(nèi)建的 pickle。

6. 測試

  1. model.eval() 
  2. test_loss, correct = 0, 0 
  3.  
  4. with torch.no_grad(): 
  5.  for data, target in test_loader: 
  6.  data, target = data.to(device), target.to(device) 
  7.  output = model(data) 
  8.  test_loss += F.nll_loss(output, target, size_average=False).item() 
  9.  pred = output.argmax(1, keepdim=True
  10.  correct += pred.eq(target.view_as(pred)).sum().item() 
  11.  
  12. test_loss /= len(test_data) 
  13. acc = correct / len(test_data) 
  14. print(acc, test_loss) 

為了早點(diǎn)響應(yīng).train(),應(yīng)利用.eval() 將網(wǎng)絡(luò)明確地設(shè)置為評(píng)估模式。

正如前文所述,計(jì)算圖通常會(huì)在使用網(wǎng)絡(luò)時(shí)生成。通過 with torch.no_grad() 使用 no_grad 上下文管理器,可以防止這種情況發(fā)生。

7. 其它

內(nèi)存有問題?可以查看官網(wǎng)文件獲取幫助。

CUDA 出錯(cuò)?它們很難調(diào)試,而且通常是一個(gè)邏輯問題,會(huì)在 CPU 上產(chǎn)生更易理解的錯(cuò)誤信息。如果你計(jì)劃使用 GPU,那最好能夠在 CPU 和 GPU 之間輕松切換。更普遍的開發(fā)技巧是設(shè)置代碼,以便在啟動(dòng)合適的項(xiàng)目(例如準(zhǔn)備一個(gè)較小/合成的數(shù)據(jù)集、運(yùn)行一個(gè) train + test epoch 等)之前快速運(yùn)行所有邏輯來檢查它。如果這是一個(gè) CUDA 錯(cuò)誤,或者你沒法切換到 CPU,設(shè)置 CUDA_LAUNCH_BLOCKING=1 將使 CUDA 內(nèi)核同步啟動(dòng),從而提供更詳細(xì)的錯(cuò)誤信息。

torch.multiprocessing,甚至只是一次運(yùn)行多個(gè) PyTorch 腳本的注意事項(xiàng)。因?yàn)?PyTorch 使用多線程 BLAS 庫來加速 CPU 上的線性代數(shù)計(jì)算,所以它通常需要使用多個(gè)內(nèi)核。如果你想一次運(yùn)行多個(gè)任務(wù),在具有多進(jìn)程或多個(gè)腳本的情況下,通過將環(huán)境變量 OMP_NUM_THREADS 設(shè)置為 1 或另一個(gè)較小的數(shù)字來手動(dòng)減少線程,這樣做減少了 CPU thrashing 的可能性。官網(wǎng)文件還有一些其它注意事項(xiàng),尤其是關(guān)于多進(jìn)程。

【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號(hào)“機(jī)器之心( id: almosthuman2014)”】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2017-03-22 09:44:04

DevOps轉(zhuǎn)型陷阱實(shí)踐

2024-09-24 13:11:18

2021-08-01 00:08:06

JsonGo標(biāo)準(zhǔn)庫

2022-12-09 11:21:07

2024-04-10 08:24:29

2025-04-27 09:45:58

JavaScript深拷貝淺拷貝

2011-07-15 17:35:19

JavaScript

2021-01-27 10:46:07

Pytorch深度學(xué)習(xí)模型訓(xùn)練

2009-11-17 11:24:00

PHP應(yīng)用技巧

2010-06-12 17:37:18

UML實(shí)踐指南

2020-07-27 08:05:56

C++語言后端

2011-07-13 16:36:11

C++

2009-12-31 10:21:53

Silverlight

2021-03-07 09:05:45

Pytorch機(jī)器學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)

2011-04-07 09:57:34

2024-05-23 10:58:49

2011-07-11 10:24:09

PHP

2011-07-07 18:39:22

SEO

2009-10-28 17:04:20

linux快速啟動(dòng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)