pytorch 搭建神經(jīng)網(wǎng)路的實(shí)現(xiàn)
1 數(shù)據(jù)
(1)導(dǎo)入數(shù)據(jù)
我們以Fashion-MNIST數(shù)據(jù)集為例,介紹一下關(guān)于pytorch的數(shù)據(jù)集導(dǎo)入。
PyTorch域庫(kù)提供許多預(yù)加載的數(shù)據(jù)集(如FashionMNIST),這些數(shù)據(jù)集是torch.utils.data.Dataset的子類(lèi),并實(shí)現(xiàn)了特定于指定數(shù)據(jù)的功能。
Fashion-MNIST是Zalando文章中的圖像數(shù)據(jù)集,包含60,000個(gè)訓(xùn)練示例和10,000個(gè)測(cè)試示例。每個(gè)示例包括28×28灰度圖像和來(lái)自10個(gè)類(lèi)中的一個(gè)的關(guān)聯(lián)標(biāo)簽。
import torch from torchvision import datasets from torchvision.transforms import ToTensor import matplotlib.pyplot as plt import os os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' # 沒(méi)有這句會(huì)報(bào)錯(cuò),具體原因我也不知道 training_data = datasets.FashionMNIST( root="../data", train=True, download=True, transform=ToTensor() ) test_data = datasets.FashionMNIST( root="../data", train=False, download=True, transform=ToTensor() )
輸出(下面的截圖不完整)
我們使用以下參數(shù)加載FashionMNIST數(shù)據(jù)集:
root是存儲(chǔ)訓(xùn)練/測(cè)試數(shù)據(jù)的路徑,
train指定訓(xùn)練或測(cè)試數(shù)據(jù)集,
download=True 如果數(shù)據(jù)集不存在于指定存儲(chǔ)路徑,那么就從網(wǎng)上下載。
transform和target_transform用于指定屬性和標(biāo)簽轉(zhuǎn)換操作,這里所說(shuō)的“轉(zhuǎn)換操作”,通常封裝在torchvision.transforms中,因此通常需要導(dǎo)入torchvision.transforms,或者導(dǎo)入這個(gè)包中的操作
(2)數(shù)據(jù)集可視化
我們可以像列表一樣手動(dòng)索引數(shù)據(jù)集:training_data[index]。我們使用matplotlib來(lái)可視化訓(xùn)練數(shù)據(jù)中的一些示例。
labels_map = { 0: "T-Shirt", 1: "Trouser", 2: "Pullover", 3: "Dress", 4: "Coat", 5: "Sandal", 6: "Shirt", 7: "Sneaker", 8: "Bag", 9: "Ankle Boot", } figure = plt.figure(figsize=(8, 8)) cols, rows = 3, 3 for i in range(1, cols * rows + 1): # 從0-len(training_data)中隨機(jī)生成一個(gè)數(shù)字(不包括右邊界) sample_idx = torch.randint(len(training_data), size=(1,)).item() img, label = training_data[sample_idx] # 獲得圖片和標(biāo)簽 figure.add_subplot(rows, cols, i) plt.title(labels_map[label]) plt.axis("off")# 坐標(biāo)軸不可見(jiàn) plt.imshow(img.squeeze(), cmap="gray") # 顯示灰度圖 plt.show()
輸出
上面的程序中,有兩個(gè)地方指的注意,一個(gè)是可以求training_data的長(zhǎng)度,另一個(gè)可以通過(guò)索引獲得單個(gè)樣本,當(dāng)然這里的樣本已經(jīng)被轉(zhuǎn)換成了張量,如下圖所示
(3)為自己制作的數(shù)據(jù)集創(chuàng)建類(lèi)
如果要導(dǎo)入自己制作的數(shù)據(jù)集,需要編寫(xiě)一個(gè)類(lèi),這個(gè)類(lèi)用于繼承torch.utils.data中的Dataset類(lèi)。自制的數(shù)據(jù)集類(lèi)必須實(shí)現(xiàn)三個(gè)函數(shù):init、len__和__getitem,分別是初始化類(lèi),求長(zhǎng)度len(obj),通過(guò)索引獲得單個(gè)樣本(像列表一樣)。
import os import pandas as pd from torch.utils.data import Dataset from torchvision.io import read_image class CustomImageDataset(Dataset): def __init__(self, annotations_file, img_dir, transform=None, target_transform=None): self.img_labels = pd.read_csv(annotations_file) self.img_dir = img_dir self.transform = transform self.target_transform = target_transform def __len__(self): return len(self.img_labels) def __getitem__(self, idx): img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0]) image = read_image(img_path) label = self.img_labels.iloc[idx, 1] if self.transform: image = self.transform(image) if self.target_transform: label = self.target_transform(label) return image, label
具體細(xì)節(jié)可以在pytorch的官網(wǎng)教程:https://pytorch.org/tutorials/beginner/basics/data_tutorial.html
Creating a Custom Dataset for your files
(4)數(shù)據(jù)集批處理
上面的程序中,雖然可以使用索引獲得樣本,但一次只能獲得單個(gè)樣本,無(wú)法像列表、張量、numpy切片一樣一次切出多個(gè)
而在訓(xùn)練模型的時(shí)候,我們希望能夠批處理,即一次處理若干個(gè)樣本,同時(shí),我們希望數(shù)據(jù)在每次遍歷完之后打亂一次,以減少過(guò)擬合,并使用Python的多處理來(lái)加快數(shù)據(jù)提取。
pytorch中,專(zhuān)門(mén)有一個(gè)類(lèi)可以實(shí)現(xiàn)上述功能,即torch.utils.data.DataLoader
下面的程序是將數(shù)據(jù)集導(dǎo)入到DataLoader中
from torch.utils.data import DataLoader train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True) # train_dataloader是一個(gè)DataLoader類(lèi)的對(duì)象 test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
training_data和test_data就是前面導(dǎo)入的數(shù)據(jù)集,由于我們指定了batch的大小是64,因?yàn)槲覀冎付藄huffle=True,所以在遍歷所有batch之后,數(shù)據(jù)將被打亂。
此時(shí)training_data和test_data仍然不是可迭代對(duì)象,還需要將其變成可迭代對(duì)象,可以使用iter函數(shù)將每一個(gè)batch轉(zhuǎn)化成可迭代對(duì)象,或者enumerate函數(shù)將其的每個(gè)batch帶上序號(hào)變成元組
用iter函數(shù)
for batch_index, (features, label) in enumerate(train_dataloader): print(batch_index) print(f"Feature batch shape: {features.size()}") print(f"Labels batch shape: {label.size()}") img = features[0].squeeze() label = label[0] plt.imshow(img, cmap="gray") plt.show() print(f"Label: {label}") break
輸出
上面的程序中,train_features, train_labels都是包含64個(gè)樣本的張量
用enumerate函數(shù)
for batch_index, (features, label) in enumerate(train_dataloader): print(batch_index) print(f"Feature batch shape: {features.size()}") print(f"Labels batch shape: {label.size()}") img = features[0].squeeze() label = label[0] plt.imshow(img, cmap="gray") plt.show() print(f"Label: {label}") break
(5)數(shù)據(jù)預(yù)處理
數(shù)據(jù)并不總是以訓(xùn)練機(jī)器學(xué)習(xí)算法所需的最終處理形式出現(xiàn),因此我們需要對(duì)數(shù)據(jù)進(jìn)行一些變換操作,使其適合于訓(xùn)練。
所有的TorchVision數(shù)據(jù)集都有兩個(gè)參數(shù),它們接受包含轉(zhuǎn)換邏輯的可調(diào)用對(duì)象:(1)transform用于修改特性,(2)target_transform用于修改標(biāo)簽
torchvision.transforms模塊提供了多種常用的轉(zhuǎn)換,這里我們介紹一下ToTensor和Lambda。
為了進(jìn)行訓(xùn)練,我們需要將FashionMNIST中的特征轉(zhuǎn)化為normalized tensors,將標(biāo)簽轉(zhuǎn)化為One-hot編碼的張量。為了完成這些變換,我們使用ToTensor和Lambda。
import torch from torchvision import datasets from torchvision.transforms import ToTensor, Lambda ds = datasets.FashionMNIST( root="data", train=True, download=True, transform=ToTensor(), target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1)) )
ToTensor將PIL圖像或NumPy ndarray轉(zhuǎn)換為FloatTensor,并將圖像的像素值(或者灰度值)縮放到[0, 1]區(qū)間
Lambda可以用于任何用戶(hù)定義的lambda函數(shù),在這里,我們定義一個(gè)函數(shù)來(lái)將整數(shù)轉(zhuǎn)換為一個(gè)one-hot編碼張量,首先建立一個(gè)長(zhǎng)度為10的0張量(之所以為10,是因?yàn)橛?0個(gè)類(lèi)別),然后調(diào)用scatter_函數(shù),把對(duì)應(yīng)的位置換成1。scatter_函數(shù)的用法如下:
更多torchvision.transforms的API詳見(jiàn):https://pytorch.org/vision/stable/transforms.html
2 神經(jīng)網(wǎng)絡(luò)
神經(jīng)網(wǎng)絡(luò)由對(duì)數(shù)據(jù)進(jìn)行操作的層/模塊組成,torch.nn提供了構(gòu)建的神經(jīng)網(wǎng)絡(luò)所需的所有構(gòu)建塊。PyTorch中的每個(gè)模塊都繼承了nn.Module,神經(jīng)網(wǎng)絡(luò)本身就是一個(gè)模塊,它由其他模塊(層)組成,這種嵌套結(jié)構(gòu)允許輕松構(gòu)建和管理復(fù)雜的體系結(jié)構(gòu)。
在下面的小節(jié)中,我們將構(gòu)建一個(gè)神經(jīng)網(wǎng)絡(luò)來(lái)對(duì)FashionMNIST數(shù)據(jù)集中的圖像進(jìn)行分類(lèi)。
(1)定義神經(jīng)網(wǎng)絡(luò)類(lèi)
我們通過(guò)繼承nn.Module來(lái)定義我們的神經(jīng)網(wǎng)絡(luò)類(lèi),并在__init__中初始化神經(jīng)網(wǎng)絡(luò)層,每一個(gè)nn.Module的子類(lèi)在forward方法中繼承了對(duì)輸入數(shù)據(jù)的操作。
在初始化方法中搭建網(wǎng)絡(luò)結(jié)構(gòu)
class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.flatten = nn.Flatten() # 打平 self.linear_relu_stack = nn.Sequential( nn.Linear(28*28, 512), # 線(xiàn)性層 nn.ReLU(), # 激活層 nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10), nn.ReLU() ) def forward(self, x): x = self.flatten(x) # 打平層 logits = self.linear_relu_stack(x) # 線(xiàn)性激活層 return logits
We create an instance of NeuralNetwork, and print its structure.
我們可以建立一個(gè)NeuralNetwork(即剛剛定義的類(lèi))的實(shí)例,并打印它的結(jié)構(gòu)
model = NeuralNetwork() print(model)
輸出
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
(5): ReLU()
)
)
使用模型時(shí),我們將輸入數(shù)據(jù)傳遞給它,這將執(zhí)行模型的forward方法,以及一些背后的操作。注意:不要直接調(diào)用model.forward() !
定義好我們自己的神經(jīng)網(wǎng)絡(luò)類(lèi)之后,我們可以隨機(jī)生成一個(gè)張量,來(lái)測(cè)試一下輸出的size是否符合要求
每個(gè)樣本傳入模型后會(huì)得到一個(gè)10維的張量,將這個(gè)張量傳入nn.Softmax的實(shí)例中,可以得到每個(gè)類(lèi)別的概率
X = torch.rand(1, 28, 28) # 生成一個(gè)樣本 logits = model(X) # 將樣本輸入到模型中,自動(dòng)調(diào)用forward方法 print(logits.size()) pred_probab = nn.Softmax(dim=1)(logits) # 實(shí)例化一個(gè)Softmax對(duì)象,并通過(guò)對(duì)象調(diào)用 y_pred = pred_probab.argmax(1) # 獲得概率最大索引 print(f"Predicted class: {y_pred}")
輸出
torch.Size([1, 10])
Predicted class: tensor([8])
(2)神經(jīng)網(wǎng)絡(luò)組件
上面搭建神經(jīng)網(wǎng)絡(luò)時(shí),我們使用了打平函數(shù)、線(xiàn)性函數(shù)、激活函數(shù),我們來(lái)看看這些函數(shù)的功能
打平層
input_image = torch.rand(3,28,28) print(input_image.size())
輸出
torch.Size([3, 28, 28])
線(xiàn)性層
layer1 = nn.Linear(in_features=28*28, out_features=20) hidden1 = layer1(flat_image) print(hidden1.size())
輸出
torch.Size([3, 20])
線(xiàn)性層其實(shí)就是實(shí)現(xiàn)了 y=w*x + b,其實(shí)是和下面的程序是等效的,但下面的程序不適合放在nn.Sequential中(但可以放在forward方法里)
w = torch.rand(784, 20) b = torch.rand((1, 20)) hidden2 = flat_image @ w + b print(hidden2.size())
輸出
torch.Size([3, 20])
nn.Sequential是一個(gè)模塊容器類(lèi),在初始化時(shí),將各個(gè)模塊按順序放入容器中,調(diào)用模型時(shí),數(shù)據(jù)按照初始化時(shí)的順序傳遞。
例如:
seq_modules = nn.Sequential( flatten, # 在nn.Sequential中可以調(diào)用其他模塊 layer1, nn.ReLU(), nn.Linear(20, 10) ) input_image = torch.rand(3,28,28) logits = seq_modules(input_image)
在nn.Sequential中可以調(diào)用其他模塊,nn.Sequential定義的模塊也可以被其他模塊調(diào)用
(3)模型參數(shù)
神經(jīng)網(wǎng)絡(luò)中的許多層都是參數(shù)化的,也就是說(shuō),在訓(xùn)練過(guò)程中會(huì)優(yōu)化相關(guān)的權(quán)值和偏差,我們可以使用模型的parameters()或named_parameters()方法訪(fǎng)問(wèn)所有參數(shù)。
model.parameters()返回的是一個(gè)參數(shù)生成器,可以用list()將其轉(zhuǎn)化為列表,例如
para_list = list(model.parameters()) # 將參數(shù)生成器轉(zhuǎn)換成列表之后,列表的第一個(gè)元素是w,第二個(gè)元素是b print(type(para_list[0])) print(f'number of linear_layers :{len(para_list)/2}') print('weights:') print(para_list[0][:2]) # 只切出兩個(gè)樣本來(lái)顯示 print('bias:') print(para_list[1][:2]) print('\nthe shape of first linear layer:', para_list[0].shape)
輸出
<class 'torch.nn.parameter.Parameter'>
number of linear_layers :3.0
weights:
tensor([[ 0.0135, 0.0206, 0.0051, ..., -0.0184, -0.0131, -0.0246],
[ 0.0127, 0.0337, 0.0177, ..., 0.0304, -0.0177, 0.0316]],
grad_fn=<SliceBackward>)
bias:
tensor([0.0333, 0.0108], grad_fn=<SliceBackward>)the shape of first linear layer: torch.Size([512, 784])
named_parameters()方法返回參數(shù)的名稱(chēng)和參數(shù)張量,例如:
print("Model structure: ", model, "\n\n") for name, param in model.named_parameters(): print(f"Layer: {name} | Size: {param.size()} | Values : \n{param[:2]} \n") # 只切出前兩行顯示
輸出
Model structure: NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
(5): ReLU()
)
)
Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values :
tensor([[ 0.0135, 0.0206, 0.0051, ..., -0.0184, -0.0131, -0.0246],
[ 0.0127, 0.0337, 0.0177, ..., 0.0304, -0.0177, 0.0316]],
grad_fn=<SliceBackward>)Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values :
tensor([0.0333, 0.0108], grad_fn=<SliceBackward>)Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values :
tensor([[ 0.0338, 0.0266, -0.0030, ..., -0.0163, -0.0096, -0.0246],
[-0.0292, 0.0302, -0.0308, ..., 0.0279, -0.0291, -0.0105]],
grad_fn=<SliceBackward>)Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values :
tensor([ 0.0137, -0.0036], grad_fn=<SliceBackward>)Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values :
tensor([[ 0.0029, -0.0025, 0.0105, ..., -0.0054, 0.0090, 0.0288],
[-0.0391, -0.0088, 0.0405, ..., 0.0376, -0.0331, -0.0342]],
grad_fn=<SliceBackward>)Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values :
tensor([0.0406, 0.0369], grad_fn=<SliceBackward>)
更多關(guān)于torch.nn的API請(qǐng)看:https://pytorch.org/docs/stable/nn.html
3 最優(yōu)化模型參數(shù)
(1)超參數(shù)
在繪制計(jì)算圖之前,需要給出超參數(shù),這里說(shuō)的超參數(shù),指的是學(xué)習(xí)率、批大小、迭代次數(shù)等
learning_rate = 1e-3 batch_size = 64 epochs = 5
(2)損失函數(shù)
回歸問(wèn)題一般用nn.MSELoss,二分類(lèi)問(wèn)題一般用nn.BCELoss,多分類(lèi)問(wèn)題一般用nn.CrossEntropyLoss,這里FashionMNIST中,標(biāo)簽有十個(gè)類(lèi)別,因此這里我們用nn.CrossEntropyLoss
# Initialize the loss function loss_fn = nn.CrossEntropyLoss()
(3)優(yōu)化方法
這里我們用隨機(jī)梯度下降,即每傳入一個(gè)樣本,更新一次參數(shù)
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
更多優(yōu)化方法的API,可以看:https://pytorch.org/docs/stable/optim.html
常用的優(yōu)化算法原理,可以看:https://zhuanlan.zhihu.com/p/78622301
4 模型的訓(xùn)練與測(cè)試
(1)訓(xùn)練循環(huán)與測(cè)試循環(huán)
每個(gè)epoch包括兩個(gè)主要部分:
訓(xùn)練循環(huán)(train_loop)——遍歷訓(xùn)練數(shù)據(jù)集并嘗試收斂到最優(yōu)參數(shù)。
驗(yàn)證/測(cè)試循環(huán)(test_loop)——遍歷測(cè)試數(shù)據(jù)集以檢查模型性能是否正在改善。
我們先把上述兩個(gè)過(guò)程封裝成函數(shù)
def train_loop(dataloader, model, loss_fn, optimizer): size = len(dataloader.dataset) for batch, (X, y) in enumerate(dataloader): # Compute prediction and loss,下面兩步相當(dāng)于繪制計(jì)算圖 pred = model(X) loss = loss_fn(pred, y) # Backpropagation optimizer.zero_grad() # 梯度信息清零 loss.backward() # 反向傳播 optimizer.step() # 一旦有了梯度,就可以更新參數(shù) if batch % 100 == 0: loss, current = loss.item(), batch * len(X) print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") def test_loop(dataloader, model, loss_fn): size = len(dataloader.dataset) num_batches = len(dataloader) test_loss, correct = 0, 0 with torch.no_grad(): # 禁用梯度跟蹤,后面會(huì)講 for X, y in dataloader: pred = model(X) test_loss += loss_fn(pred, y).item() correct += (pred.argmax(1) == y).type(torch.float).sum().item() test_loss /= num_batches correct /= size print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
現(xiàn)在可以進(jìn)行訓(xùn)練了
for t in range(epochs): print(f"Epoch {t+1}\n-------------------------------") train_loop(train_dataloader, model, loss_fn, optimizer) test_loop(test_dataloader, model, loss_fn) print("Done!")
輸出
我們可以寫(xiě)一段代碼,看看預(yù)測(cè)的圖片對(duì)不對(duì)
test_features, test_labels = next(iter(test_dataloader)) logits = model(test_features[0]) pred_probab = nn.Softmax(dim=1)(logits) pred = pred_probab.argmax(1) img = test_features[0].squeeze() # 將長(zhǎng)度為1的維度去掉 true_label = test_labels[0] plt.imshow(img, cmap="gray") plt.show() print(f"True label: {true_label}") print(f"Predict: {pred.item()}")
輸出
(2)禁用梯度跟蹤
在上面的測(cè)試循環(huán)中,使用了torch.no_grad()方法,在表示所在的with塊不對(duì)梯度進(jìn)行記錄。
默認(rèn)情況下,所有requires_grad=True的張量(在創(chuàng)建優(yōu)化器的時(shí)候,內(nèi)部就把里面的參數(shù)全部設(shè)置為了requires_grad=True)都跟蹤它們的計(jì)算歷史并支持梯度計(jì)算。但是,在某些情況下,我們并不需要這樣做,例如,測(cè)試的時(shí)候,我們只是想通過(guò)網(wǎng)絡(luò)進(jìn)行正向計(jì)算。我們可以通過(guò)使用torch.no_grad()塊包圍計(jì)算代碼來(lái)停止跟蹤計(jì)算:
z = torch.matmul(x, w)+b print(z.requires_grad) with torch.no_grad(): z = torch.matmul(x, w)+b print(z.requires_grad)
輸出
True
False
禁用梯度跟蹤主要用于下面兩種情況:
(1)將神經(jīng)網(wǎng)絡(luò)中的一些參數(shù)標(biāo)記為凍結(jié)參數(shù),這是對(duì)預(yù)先訓(xùn)練過(guò)的網(wǎng)絡(luò)進(jìn)行微調(diào)的一個(gè)常見(jiàn)的場(chǎng)景
(2)當(dāng)你只做正向傳遞時(shí),為了加快計(jì)算速度,因?yàn)椴桓櫶荻鹊膹埩康挠?jì)算會(huì)更有效率。
5 模型的保存、導(dǎo)入與GPU加速
(1)模型的保存與導(dǎo)入
PyTorch模型將學(xué)習(xí)到的參數(shù)存儲(chǔ)在一個(gè)內(nèi)部狀態(tài)字典中,稱(chēng)為state_dict,我們可以通過(guò)torch.save()將模型的參數(shù)保存到指定路徑。
保存了模型的參數(shù),還需要保存模型的形狀(即模型的結(jié)構(gòu))
# 保存模型參數(shù) torch.save(model.state_dict(), './model_weights.pth') # 保存模型結(jié)構(gòu) torch.save(model, './model.pth')
導(dǎo)入模型時(shí),需要先導(dǎo)入模型的結(jié)構(gòu),再導(dǎo)入模型的參數(shù),代碼如下:
# 導(dǎo)入模型結(jié)構(gòu) model_loaded = torch.load('./model.pth') # 如果原來(lái)的model在cuda:0上,那么導(dǎo)入之后,model_loaded也在cuda:0上 # 導(dǎo)入模型參數(shù) model_loaded.load_state_dict(torch.load('model_weights.pth'))
因?yàn)閙odel是NeuralNetwork類(lèi)的一個(gè)對(duì)象,所以在導(dǎo)入狀態(tài)前,必須先有一個(gè)NeuralNetwork對(duì)象,要么實(shí)例化一個(gè),要么通過(guò)導(dǎo)入結(jié)構(gòu)torch.load('./model.pth')導(dǎo)入一個(gè).
torch.load直接導(dǎo)入模型,不是特別推薦,原因有以下兩點(diǎn):
(1)如果A.py文件中的程序保存了model.pth,如果文件B.py想讀取這個(gè)模型,則不能直接用torch.load導(dǎo)入模型結(jié)構(gòu),必須先實(shí)例化一個(gè)NeuralNetwork對(duì)象,要么從A.py或者從其他文件中把NeuralNetwork類(lèi)給導(dǎo)進(jìn)來(lái),要么這里重寫(xiě)一個(gè)與A.py中一模一樣的NeuralNetwork類(lèi),類(lèi)名也要一樣,否則報(bào)錯(cuò)。
# 實(shí)例化一個(gè)NeuralNetwork對(duì)象 model_loaded = NeuralNetwork() # model_loaded默認(rèn)是CPU中 # 導(dǎo)入模型參數(shù) model_loaded.load_state_dict(torch.load('./gdrive/MyDrive/model_weights.pth'))
(2)如果用torch.load導(dǎo)入模型,當(dāng)我們?cè)赾uda:0上訓(xùn)練好一個(gè)模型并保存時(shí),讀取出來(lái)的模型也是默認(rèn)在cuda:0上的,如果訓(xùn)練過(guò)程的其他數(shù)據(jù)被放到了如cuda:1上,則會(huì)報(bào)錯(cuò)。而實(shí)例化創(chuàng)建模型,由于.load_state_dict可以跨設(shè)備,則無(wú)論原來(lái)的模型在什么設(shè)備上,都不妨礙把參數(shù)導(dǎo)入到新創(chuàng)建的模型對(duì)象當(dāng)中。
綜合上面兩點(diǎn),torch.load慎用,最好是先實(shí)例化后再導(dǎo)入模型狀態(tài)。
我們可以用導(dǎo)入的模型做預(yù)測(cè)
test_features, test_labels = next(iter(test_dataloader)) logits = model_loaded(test_features[0])# 使用導(dǎo)入的模型 pred_probab = nn.Softmax(dim=1)(logits) pred = pred_probab.argmax(1) img = test_features[0].squeeze() # 將長(zhǎng)度為1的維度去掉 true_label = test_labels[0] plt.imshow(img, cmap="gray") plt.show() print(f"True label: {true_label}") print(f"Predict: {pred.item()}")
輸出
(2)GPU加速
默認(rèn)情況下,張量和模型是在CPU上創(chuàng)建的。如果想讓其在GPU中操作,我們必須使用.to方法(確定GPU可用后)顯式地移動(dòng)到GPU。需要注意的是,跨設(shè)備復(fù)制大張量在時(shí)間和內(nèi)存上開(kāi)銷(xiāo)都是很大的!
# We can move our tensor to the GPU if available device = 'cuda' if torch.cuda.is_available() else 'cpu' print('Using {} device'.format(device))
輸出
Using cuda device
在初始化模型時(shí),可以將模型放入GPU中
model = NeuralNetwork().to(device)
對(duì)于張量,可以在創(chuàng)建的時(shí)候指定為在GPU上創(chuàng)建,也可以在創(chuàng)建后轉(zhuǎn)移到GPU當(dāng)中
X = torch.rand(1, 28, 28, device=device) # 創(chuàng)建時(shí)指定設(shè)備 Y = torch.rand(10).to(device) # 創(chuàng)建后轉(zhuǎn)移
當(dāng)然,張量和模型也能從GPU轉(zhuǎn)移到CPU當(dāng)中,我們可以用.device()來(lái)查看張量所在設(shè)備
另外,需要注意的是,如果需要將模型送到GPU當(dāng)中,必須在構(gòu)建優(yōu)化器之前。因?yàn)镃PU和GPU中的模型,是兩個(gè)不同的對(duì)象,構(gòu)建完優(yōu)化器再將模型放入GPU,將導(dǎo)致優(yōu)化器只優(yōu)化CPU中的模型參數(shù)。
有些電腦有多張顯卡,那么.to(‘cuda')默認(rèn)是將張量或者模型轉(zhuǎn)移到第一張顯卡(編號(hào)為0)上,如果想轉(zhuǎn)移到其他顯卡上,則用下面的程序
device = torch.device(‘cuda:2') # 2是設(shè)備號(hào),假如有八張顯卡,那么編號(hào)就是0—7
torch.save
至此,所有程序已經(jīng)完成
總結(jié)
上面的程序有點(diǎn)亂,我這里綜合一下:
# coding=utf-8 import torch import torch.nn as nn from torchvision import datasets from torch.utils.data import DataLoader from torchvision.transforms import ToTensor import matplotlib.pyplot as plt import os os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' # 沒(méi)有這句會(huì)報(bào)錯(cuò),具體原因我也不知道 # 導(dǎo)入數(shù)據(jù) training_data = datasets.FashionMNIST( root="../data", train=True, download=True, transform=ToTensor() ) test_data = datasets.FashionMNIST( root="../data", train=False, download=True, transform=ToTensor() ) # 定義超參數(shù) # 之所以在這個(gè)地方定義,是因?yàn)樵诔跏蓟疍ataLoader時(shí)需要用到batch_size learning_rate = 1e-3 batch_size = 64 epochs = 5 train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True) # train_dataloader是一個(gè)DataLoader類(lèi)的對(duì)象 test_dataloader = DataLoader(test_data, batch_size=batch_size, shuffle=True) # 搭建神經(jīng)網(wǎng)絡(luò) class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.flatten = nn.Flatten() # 打平 self.linear_relu_stack = nn.Sequential( nn.Linear(28*28, 512), # 線(xiàn)性層 nn.ReLU(), # 激活層 nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10), nn.ReLU() ) def forward(self, x): x = self.flatten(x) # 打平層 logits = self.linear_relu_stack(x) # 線(xiàn)性激活層 return logits # 確定使用設(shè)備 device = 'cuda' if torch.cuda.is_available() else 'cpu' # 實(shí)例化一個(gè)神經(jīng)網(wǎng)絡(luò)類(lèi) model = NeuralNetwork().to(device) # 確定損失函數(shù) loss_fn = nn.CrossEntropyLoss() # 確定優(yōu)化器 optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # 封裝訓(xùn)練過(guò)程 def train_loop(dataloader, model, loss_fn, optimizer): size = len(dataloader.dataset) for batch, (X, y) in enumerate(dataloader): X, y = X.to(device), y.to(device) # 將樣本和標(biāo)簽轉(zhuǎn)移到device中 # Compute prediction and loss,下面兩步相當(dāng)于繪制計(jì)算圖 pred = model(X) loss = loss_fn(pred, y) # Backpropagation optimizer.zero_grad() # 梯度信息清零 loss.backward() # 反向傳播 optimizer.step() # 一旦有了梯度,就可以更新參數(shù) if batch % 100 == 0: loss, current = loss.item(), batch * len(X) print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") # 封裝測(cè)試過(guò)程 def test_loop(dataloader, model, loss_fn): size = len(dataloader.dataset) num_batches = len(dataloader) test_loss, correct = 0, 0 with torch.no_grad(): for X, y in dataloader: X, y = X.to(device), y.to(device) # 將樣本和標(biāo)簽轉(zhuǎn)移到device中 pred = model(X) test_loss += loss_fn(pred, y).item() correct += (pred.argmax(1) == y).type(torch.float).sum().item() test_loss /= num_batches correct /= size print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n") # 訓(xùn)練 for t in range(epochs): print(f"Epoch {t+1}\n-------------------------------") train_loop(train_dataloader, model, loss_fn, optimizer) test_loop(test_dataloader, model, loss_fn) print("Done!") # 保存模型參數(shù) torch.save(model.state_dict(), './model_weights.pth') # 保存模型結(jié)構(gòu) torch.save(model, './model.pth') # 導(dǎo)入模型結(jié)構(gòu) model_loaded = torch.load('./model.pth') # 模型自動(dòng)導(dǎo)入到GPU當(dāng)中 # 導(dǎo)入模型參數(shù) model_loaded.load_state_dict(torch.load('model_weights.pth')) # 用導(dǎo)入的模型測(cè)試 test_features, test_labels = next(iter(test_dataloader)) test_features = test_features.to(device) logits = model_loaded(test_features[0])# 使用導(dǎo)入的模型 pred_probab = nn.Softmax(dim=1)(logits) pred = pred_probab.argmax(1) # 可視化 img = test_features[0].squeeze() # 將長(zhǎng)度為1的維度去掉 img = img.to('cpu') # 繪圖時(shí),需要將張量轉(zhuǎn)回到CPU當(dāng)中 true_label = test_labels[0]# 標(biāo)簽是否轉(zhuǎn)移到CPU無(wú)所謂,因?yàn)闆](méi)有用于plt的方法中 plt.imshow(img, cmap="gray") plt.show() print(f"True label: {true_label}") print(f"Predict: {pred.item()}")
到此這篇關(guān)于pytorch 搭建神經(jīng)網(wǎng)路的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)pytorch 神經(jīng)網(wǎng)路內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。