日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入理解 TORCH.NN

發布時間:2024/3/12 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解 TORCH.NN 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文地址:WHAT IS TORCH.NN REALLY?
本人英語學渣,如有錯誤請及時指出以便更正,使用的源碼可點擊原文地址進行下載。


pytorch提供了許多優雅的類和模塊幫助我們構建與訓練網絡,比如 torch.nn, torch.optim,Dataset等。為了充分利用這些模塊的功能,靈活操作它們解決各種不同的問題,我們需要更好地理解當我們調用這些模塊時它們到底干了些什么,為此,我們首先不調用這些模塊實現MNIST手寫字識別,僅使用最基本的 pytorch 張量函數。然后,我們逐漸增加 torch.nn, torch.optim, Dataset, or DataLoader,具體地展示每個模塊具體干了些什么,展示這些模塊是怎樣使代碼變得更加優雅靈活。
此教程適用范圍:熟悉pytorch的張量操作

加載 MNIST 數據集

我們使用經典的 MNIST 數據集,一個包含了0-9數字的二值圖像庫。

還會用到 pathlib 庫用于目錄操作,一個python3自帶的標準庫。使用 requests 下載數據集。當用到一個模塊時才會進行導入,而不會一開始全部導入,以便更好地理解每個步驟。

from pathlib import Path import requestsDATA_PATH = Path('data') PATH = DATA_PATH / "mnist"PATH.mkdir(parents=True,exit_ok=True)URL = "http://deeplearning.net/data/mnist/" FILENAME = "mnist.pkl.gz"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)

該數據集采用numpy數組格式,并使用pickle存儲,pickle是一種特定于python的格式,用于序列化數據。

import pickle import gzipwith gzip.open((PATH / FILENAME).as_posix(),"rb") as f:((x_train,y_train),(x_valid,y_valid),_) = pickle.load(f,encoding="latin-1")

每張訓練圖片分辨率為 28x28, 被存儲為 784(=28x28) 的一行。我們輸出看一下數據,首先需要轉換回 28x28的圖像。

form matplotlib import pyplot import numpy as nppyplot.imshow(x_train[0].reshape((28,28)),cmap="gray") print(x_train.shape) out: (50000,784)

PyTorch使用 torch.tensor ,所以我們需要對numpy類型數據進行轉換

import torch x_train,y_train,x_valid,y_valid = map(torch.tensor,(x_train,y_train,x_valid,y_valid)) n,c = x_train.shape x_train,x_train.shape,y_train.min(),y_train.max() print(x_train,y_train) print(x_train.shape) print(y_train.min(),y_train.max())

從頭創建神經網絡(不使用torch.nn)

讓我們僅僅使用 pytorch 中的張量操作來創建模型,假設你已經熟悉神經網絡的基礎知識(不熟悉請參考corse.fast.ai )

pytorch提供了很多創建張量的操作,我們將用這些方法來初始化權值weights和偏置 bais來創建一個線性模型。這些只是常規張量,有一個非常特別的補充:我們告訴PyTorch這些張量需要支持求導(requires_grad=True)。這樣PyTorch將記錄在張量上完成的所有操作,以便它可以在反向傳播過程中自動計算梯度!

對于權值weights,我們再初始化之后再設置 requires_grad,因為我們不想這一步包含在梯度的計算中(注:pytorch中以 _ 結尾的操作都是在原變量中(in-place)執行的)

import mathweights = torch.randn(780,10) / math.sqrt(784) weights.requires_grad_() bias = torch.zeros(10, requires_grad=True)

多虧了pytorch的自動求導功能,我們可以使用python的所有標準函數來構建模型。 我們這兒利用矩陣乘法,加法來構建線性模型。我們編寫 log_softmax函數作為激活函數。 雖然pytorch提供了大量寫好的損失函數,激活函數,你依然可以自由地編寫自己的函數替代它們。 pytorch 甚至支持創建自己的 GPU函數或者CPU矢量函數。

def log_softmax(x):return x - x.exp().sum(-1).log().unsqueeze(-1)def model(xb):return log_softmax(xb @ weights + bias) # python的廣播機制

上面的 @ 符號表示向量的點乘,接下來我們會調用一批數據(batch,64張圖片)輸入此模型。

bs = 64 # batch size xb = x_train[0:bs] # a mini-batch from x preds = model(xb) # predictions print(preds[0],preds.shape)

out:

tensor([-2.4513, -2.5024, -2.0599, -3.1052, -3.2918, -2.2665, -1.9007, -2.2588,-2.0149, -2.0287], grad_fn=<SelectBackward>) torch.Size([64, 10])

正如我們看到的,preds 張量不僅包含了一組張量,還包含了求導函數。反向傳播的時候會用到此函數。讓我們使用標準的python語句接著來實現 negative log likelihood loss 損失函數(譯者加:也被稱為交叉熵損失函數):

def nll(input,target):return -input[range(target.shape[0]),target].mean()loss_func = nll

現在用我們的損失函數來檢查我們隨機初始化的模型,待會就能看到再反向傳播之后是否會改善模型性能。

yb = y_train[0:bs] print(loss_func(preds,yb))

out:

tensor(2.3620, grad_fn=<NegBackward>)

接下來定義一個計算準確度的函數

def accuracy(out,yb):preds = torch.argmax(out,dim=1) # 得到最大值的索引return (preds == yb).float().mean()

檢查模型的準確度:

print(accuracy(preds, yb))

out:

tensor(0.0938)

現在我們開始循環訓練模型,每一步我們執行以下操作:

  • 選擇一批數據(a batch)
  • 使用模型進行預測
  • 計算損失函數
  • 反向傳播更新參數 weights 和 bias

我們現在使用 torch.no_grad() 更新參數,以避免參數更新過程被記錄入求導函數中。

然后我們清零導數,以便開始下一輪循環,否則導數會在原來的基礎上累加,而非替代原來的數

from IPython.core.debugger import set_tracelr = 0.5 # learning rate epochs = 2 # how many epochs to train forfor epoch in range(epochs):for i in range((n - 1) // bs + 1):# set_trace()start_i = i * bsend_i = start_i + bsxb = x_train[start_i:end_i]yb = y_train[start_i:end_i]pred = model(xb)loss = loss_func(pred, yb)loss.backward()with torch.no_grad():weights -= weights.grad * lrbias -= bias.grad * lrweights.grad.zero_()bias.grad.zero_()

目前為止,我們從頭創建一個迷你版的神經網絡

讓我們來檢查一下損失和準確率,并于迭代更新參數之前進行比較,我們期望得到更小的損失于更高的準確率。

print(loss_func(model(xb), yb), accuracy(model(xb), yb))

out:

tensor(0.0822, grad_fn=<NegBackward>) tensor(1.)

使用 torch.nn.functional 簡化代碼

現在我們使用torch.nn.functional重構之前的代碼,這樣會使代碼變得更加簡潔與靈活,更易理解。

首先最簡單的一步是,用 torch.nn.functional( 為了方便后面統一稱作F) 中帶有的損失函數來代替我們自己編寫的函數,使得代碼變得更簡短。這些函數都包包含于模塊 torch.nn里面,除了大量的損失函數與激活函數,里面還包含了大量用于構建網絡的函數。

如果我們的網絡中使用 negative log likelihood loss 作為損失函數, log softmax activation 作為激活函數 (即我們上面實現的損失函數與激活函數)。在pytorch中我們直接使用函數 F.cross_entropy 便可實現上面兩個函數的功能。所以我們可以用此函數代替上面實現的激活函數與損失函數。

import torch.nn.functional as Floss_func = F.cross_entropydef model(xb):return xb @ weights + bias

讓我測試一下是否和上面自己實現的函數效果一致:

print(loss_func(model))

out:

tensor(0.0822, grad_fn=<NllLossBackward>) tensor(1.)

引入 nn.Module 重構代碼

接下來我們引入 nn.Module和nn.Parameter 改進代碼。我們創建 nn.Module的子類。這個例子中我們創建一個包含權重,偏置,以及包含前向傳播的類。nn.Module含有許多的屬性與方法可供調用 (比如: .parameters .zero_grad())

from torch import nnclass Mnist_Logistic(nn.Module):def __init__(self):super().__init__()sefl.weights = nn.Parameter(torch.randn(784,10)/math.sqrt(784))self.bias = nn.Parameter(torch.zeros(10))def forward(self,xb):return xb @ self.weights + self.bias

接下來實例化我們的模型:

model = Mnist_Logistic()

現在我們可以和之前一樣使用損失函數了。注意:nn.Module 對象可以像函數一樣調用,但實際上是自動調用了對象內部的函數 forward

print(loss_func(model(xb),yb))

out:

tensor(2.2082, grad_fn=<NllLossBackward>)

在之前,我們必須進行如下得操作對權重,偏置進行更新,梯度清零:

with torch.no_grad():weights -= weights.grad * lrbias -= bias.grad * lrweights.grad.zero_()bias.grad.zero_()

現在我們可以充分利用 nn.Module 的方法屬性更簡單地完成這些操作,如下所示:

with torch.no_grad():for p in model.parameters(): p -= p.grad * lrmodel.zero_grad()

現在我們將整個訓練過程寫進函數 fit中。

def fit():for epoch in range(epoches):for i in range((n - 1) // bs + 1):start_i = i * bsend_i = start_i + bsxb = x_train[start_i:end_i]yb = y_train[start_i:end_i]pred = model(xb)loss = loss_func(pred,yb)loss.backward()with torch.no_grad():for p in model.parameters(): p -= p.grad * lrmodel.zero_grad() fit()

讓我們再一次確認損失情況:

print(loss_func(model(xb),yb))

out:

tensor(0.0812, grad_fn=<NllLossBackward>)

引入 nn.Linear 重構代碼

比起手動定義 權重 與 偏置,并且使用 self.weights和 self.bias 來計算 xb @ self.weights + self.bias的方式,我們可以使用pytorch中的 nn.Linear來定義線性層,他自動為我們實現以上權重參數的定義以及計算的過程。除了線性模型之外,pytorch還有一系列的其它網絡層供我們使用,大大簡化了我們的編程過程。

class Mnist_Logistic(nn.Module):def __init__(self):super().__init__()self.lin = nn.Linear(784,10)def forward(self,xb):return self.lin(xb)

同上面一樣實例化模型,計算損失

model = Mnist_Logistic() print(loss_func(model(xb),yb))

out:

tensor(2.2731, grad_fn=<NllLossBackward>)

訓練,并查看訓練之后的損失

fit()print(loss_func(model(xb), yb))

out:

tensor(0.0820, grad_fn=<NllLossBackward>)

引入 optim 重構代碼

接下來使用torch.optim改進訓練過程,而不用手動更新參數

之前的手動優化過程如下:

with torch.no_grad():for p in model.parameters(): p -= p.grad * lrmodel.zero_grad()

使用如下代碼替代手動的參數更新:

opt.step() # optim.zero_grad() resets the gradient to 0 and we need to call it # before computing the gradient for the next minibatch. opt.zero_grad()

結合之前的完整跟新代碼如下:

from torch import optimdef get_model():model = Mnist_Logistic()return model, optim.SGD(model.parameters(),lr=lr)model, opt = get_model() print(loss_func(model(xb),yb))for epoch in range(epoches):for i in range((n-1)//bs + 1):start_i = i *bsend_i = start_i + bsxb = x_train[start_i:end_i]yb = y_train[start_i:end_i]pred = model(xb)loss = loss_func(pred,yb)loss.backward()opt.step()opt.zero_grad()print(loss_func(model(xb),yb))

out:

tensor(2.3785, grad_fn=<NllLossBackward>) tensor(0.0802, grad_fn=<NllLossBackward>)

引入 Dataset 處理數據

pytorch定義了 Dataset 類,其中主要包含了 __len__ 函數與 __getitem__函數。此教程以創建 FacialLandmarkDataset 為例詳細地介紹了Dataset類的使用。

pytorch的 TensorDataset 是一個包含張量的數據集。通過定義長度索引等方式,使我們更好地利用索引,切片等方法迭代數據。這會讓我們很容易地在一行代碼中獲取我們地數據。

form torch.utils.data import TensorDataset

x_train y_train可以被組合進一個TensorDataset中,這會使得迭代切片更加簡單。

train_ds = TensorDataset(x_train,y_train)

之前我們獲取數據的方法如下:

xb = x_train[start_i:end_i] yb = y_train[start_i:end_i]

現在我們可以使用更簡單的方法:

xb,yb = train_ds[i*bs : i*bs +bs] model, opt = get_model()for epoch in range(epochs):for i in range((n - 1) // bs + 1):xb, yb = train_ds[i * bs: i * bs + bs]pred = model(xb)loss = loss_func(pred, yb)loss.backward()opt.step()opt.zero_grad()print(loss_func(model(xb), yb))

out:

tensor(0.0817, grad_fn=<NllLossBackward>)

引入DataLoader加載數據

DataLoader 用于批量加載數據,你可以用他來加載任何來自 Dataset的數據,它使得數據的批量加載十分容易。

from torch.utils.data import DataLoadertrain_ds = TensorDataset(x_train,y_train) train_dl = DataLoader(train_ds, batch_size=bs)

之前我們讀取數據的方式:

for i in range((n-1)//bs + 1):xb,yb = train_ds[i*bs : i*bs+bs]pred = model(xb)

現在使用dataloader加載數據:

for xb,yb in train_dl:pred = model(xb) model, opt = get_model()for epoch in range(epochs):for xb, yb in train_dl:pred = model(xb)loss = loss_func(pred, yb)loss.backward()opt.step()opt.zero_grad()print(loss_func(model(xb), yb))

out:

tensor(0.0817, grad_fn=<NllLossBackward>)

目前為止訓練模型部分我們就已經完成了,通過使用nn.Module, nn.Parameter, DataLoader, 我們的訓練模型以及得到了很大的改進。接下來讓我們開始模型的測試部分。

添加測試集

在前一部分,我們嘗試了使用訓練集訓練網絡。實際工作中,我們還會使用測試集來觀察訓練的模型是否過擬合。

打亂數據的分布有助于減小每一批(batch)數據間的關聯,有利于模型的泛化。但對于測試集來說,是否打亂數據對結果并沒有影響,反而會花費多余的時間,所以我們沒有必要打亂測試集的數據。

train_ds = TensorDataset(x_train, y_train) train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)valid_ds = TensorDataset(x_valid, y_valid) valid_dl = DataLoader(valid_ds, batch_size = bs*2)

在每訓練完一輪數據(epoch)后我們輸出測試得到的損失值。
(注:如下代碼中,我們調用model.train()和model.eval表示進入訓練模式與測試模式,以保證模型運行的準確性)

model,opt = get_model()for epoch in range(epoches):model.train()for xb, yb in train_dl:pred = model(xb)loss = loss_func(pred, yb)loss.backward()opt.step()opt.zero_grad()model.eval()with torch.no_grad():valid_loss = sum(loss_func(model(xb), yb) for xb, yb in valid_dl)print(epoch, valid_loss / len(valid_dl))

out:

0 tensor(0.3456) 1 tensor(0.2988)

創建 fit() 和 get_data() 優化代碼

我們再繼續做一點改進。因為我們再計算訓練損失和驗證損失時執行了兩次相同的操作,所以我們用一個計算每一個batch損失的函數封裝這部分代碼。

我們為訓練集添加優化器,并執行反向傳播。對于訓練集我們不添加優化器,當然也不會執行反向傳播。

def loss_batch(model, loss_func, xb , yb, opt=None):loss = loss_func(model(xb),yb)if opt is not None:loss.backward()opt.step()opt.zero_grad()return loss.item(), len(xb)

fit執行每一個epoch過程中訓練和驗證的必要操作

import numpy as np def fit(epochs, model, loss_func, opt, train_dl, valid_dl):for epoch in range(epochs):model.train()for xb, yb in train_dl:loss_batch(model, loss_func, xb, yb, opt)model.eval()with torch.no_grad():losses, nums = zip(*[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl])val_loss = np.sum(np.sum(np.multiply(losses, nums)). np.sum(nums))print(epoch, val_loss)

現在,獲取數據加載模型進行訓練的整個過程只需要三行代碼便能實現了

train_dl, valid_dl = get_data(train_ds, valid_ds, bs) model, opt = get_model() fit(epoches, model, loss_func, opt, train_dl, valid_dl)

out:

0 0.2961075816631317 1 0.28558296990394594

我們可以用這簡單的三行代碼訓練各種模型。下面讓我們看看怎么用它訓練一個卷積神經網絡。

使用卷積神經網絡

現在我們用三個卷積層來構造我們的卷積網絡。因為之前的實現的函數都沒有假定模型形式,這兒我們依然可以使用它們而不需要任何修改。

我們pytorch預定義的Conv2d類來構建我們的卷積層。我們模型有三層,每一層卷積之后都跟一個 ReLU,然后跟一個平均池化層。

class Mnist_CNN(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(1,16,kernel_size=3,stride=2,padding=1)self.conv2 = nn.Conv2d(16,16,kernel_size=3,stride=2,padding=1)self.conv3 = nn.Conv2d(16,10,kernel_size=3,stride=2,padding=1)def forward(self, xb):xb = xb.view(-1,1,28,28)xb = F.relu(self.conv1(xb))xb = F.relu(self.conv2(xb))xb = F.relu(self.conv3(xb))xb = F.avg_pool2d(xb,4)return xb.view(-1, xb.size(1))lr = 0.1

動量momentum是隨機梯度下降的一個參數,它考慮到了之前的梯度值使得訓練更快。

model = Mnist_CNN() opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)fit(epochs, model, loss_func, opt, train_dl, valid_dl)

out:

0 0.3829730714321136 1 0.2258522843360901

使用 nn.Sequential 搭建網絡

torch.nn還有另外一個方便的類可以簡化我們的代碼:Sequential, 一個Sequential對象

class Lambda(nn.Module):def __init__(self, func):super().__init__()self.func = funcdef forward(self, x):return self.func(x)def preprocess(x):return x.view(-1, 1, 28, 28)

Sequential是一種簡化代碼的好方法。 一個Sequential對象按順序執行包含在內的每一個module,使用它可以很方便地建立一個網絡。

為了更好地使用Sequential模塊,我們需要自定義 pytorch中沒實現地module。例如pytorch中沒有自帶 改變張量形狀地層,我們創建 Lambda層,以便在Sequential中調用。

model = nn.Sequential(Lambda(preprocess),nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.AvgPool2d(4),Lambda(lambda x: x.view(x.size(0), -1)), )opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)fit(epochs, model, loss_func, opt, train_dl, valid_dl)

out:

0 0.32739396529197695 1 0.25574398956298827

簡易的DataLoader

我們的網絡以及足夠精簡了,但是只能適用于MNIST數據集,因為

  • 網絡默認輸入為 28x28 的張量
  • 網絡默認最后一個卷積層大小為 4x4 (因為我們的池化層大小為4x4)

現在我們去除這兩個假設,使得網絡可以適用于所有的二維圖像。首先我們移除最初的 Lambda層,用數據預處理層替代。

def preprocess(x, y):return x.view(-1, 1, 28, 28), yclass WrappedDataLoader:def __init__(self, dl, func):self.dl = dlself.func = funcdef __len__(self):return len(self.dl)def __iter__(self):batches = iter(self.dl)for b in batches:yield (self.func(*b))train_dl, valid_dl = get_data(train_ds, valid_ds, bs) train_dl = WrappedDataLoader(train_dl, preprocess) valid_dl = WrappedDataLoader(valid_dl, preprocess)

然后,我們使用nn.AdaptiveAvgPool2d代替nn.AvgPool2d。它允許我們自定義輸出張量的維度,而于輸入的張量無關。這樣我們的網絡便可以適用于各種size的網絡。

model = nn.Sequential(nn.Conv2d(1, 16, kernal_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.AdaptiveAvgPool2d(1),Lambda(lambda x: x.view(x.size(0), -1)),)opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

out:

0 0.32888883714675904 1 0.31000419993400574

使用GPU

如果你的電腦有支持CUDA的GPU(你可以很方便地以 0.5美元/小時 的價格租到支持的云服務器),便可以使用GPU加速訓練過程。首先檢測設備是否正常支持GPU:

print(torch.cuda.is_available())

out:

Ture

接著創建一個設備對象:

dev = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

更新 preprocess(x,y)把數據移到GPU:

def preprocess(x, y):return x.view(-1, 1, 28, 28).to(dev), y.to(device)train_dl, valid_dl = get_data(train_ds, valid_ds, bs) train_dl = WrappedDataLoader(train_dl, preprocess) valid_dl = WrappedDataLoader(valid_dl, preprocess)

最后移動網絡模型到GPU:

model.to(dev) opt = optim.SGD(model.parameters(),lr=lr, momentum=0.9)

進行訓練,能發現速度快了很多:

fit(epochs, model, loss_func, opt, train_dl, valid_dl)

out:

0 0.21190375366210937 1 0.18018000435829162

總結

我們現在得到了一個通用的數據加載和模型訓練方法,我們可以在pytorch種用這種方法訓練大多的模型。想知道訓練一個模型有多簡單,回顧一下本次的代碼便可以了。

當然,除此之外本篇內容還有很多需求沒有講到,比如數據增強,超參調試,數據監控(monitoring training),遷移學習等。這些特點都以與本篇教程相似的設計方法包含于 fastai庫中。

本篇教程開頭我們承諾將會通過例程解釋 torch.nn torch.optim Dataset DataLoader等模塊,下面我們就這些模型進行總結。

  • torch.nn
    • Module: 創建一個可以像函數一樣調用地對象,包含了網絡的各種狀態,可以使用parameter方便地獲取模型地參數,并有清零梯度,循環更新參數等功能。
    • Parameter: 將模型中需要更新的參數全部打包,方便反向傳播過程中進行更新。有 requires_grad屬性的參數才會被更新。
    • functional:通常導入為F,包含了許多激活函數,損失函數等。
  • torch.optim: 包含了很多諸如SGD一樣的優化器,用來在反向傳播中跟新參數
  • Dataset: 一個帶有 __len__ __getitem__等函數的抽象接口。里面包含了 TensorDataset等類。
  • DataLoader: 輸入任意的 Dataset 并按批(batch)迭代輸出數據。

附錄

完整代碼下載地址

  • Download Python source code: nn_tutorial.py
  • Download Jupyter notebook: nn_tutorial.ipynb

總結

以上是生活随笔為你收集整理的深入理解 TORCH.NN的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。