Pytorch 是 Facebook 於 2017 年所開源的深度學習框架,因其語法簡潔、直觀的特性深受歡迎,已成為目前深度學習熱門框架之一。本文將介紹 Pytorch 的基本用法~~
- 關於 Pytorch 的環境安裝可參考官方的說明: https://pytorch.org/get-started/locally/
- 若使用 Anaconda 安裝 Pytorch,可參考我之前的文章: Anaconda 及 Deep learning framework 安裝教學
Tensor 張量
Pytorch 的基本元素為 Tensor,是一個多維度的矩陣,用法與 numpy 類似,兩者能夠很方便地相互轉換,不同的是 Pytorch 可以在 GPU 上執行,而 numpy 只能在 CPU 上執行。
Tensor 定義了好幾種類型應用於 CPU 和 GPU,默認張量類型為 torch.FloatTensor。
接著來試著建立張量吧!首先 import 需要的 library,我的 pytorch 版本為 1.9.0。
import torchimport numpy as np
可透過下列方式來建立一個指定元素的張量
torch.tensor([[1, 2], [3, 4], [5, 6]])
# === output ===
tensor([[1, 2],
[3, 4],
[5, 6]])# 可使用 dtype 來設定數據類型
torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.float64)
# === output ===
tensor([[1., 2.],
[3., 4.],
[5., 6.]], dtype=torch.float64)# 建立全為 0 的張量
torch.zeros([2, 2])
# === output ===
tensor([[0., 0.],
[0., 0.]])# 建立全為 1 的張量
torch.ones([2, 2])
# === output ===
tensor([[1., 1.],
[1., 1.]])
由於在訓練模型的時候,常常會使用 GPU 來提升訓練速度,因此可藉由 torch.device 設定分配的設備。
if torch.cuda.is_available():
cuda0 = torch.device(‘cuda:0’)
t1 = torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.float64, device=cuda0)# 也可以寫成
cuda0 = torch.device(‘cuda’, 0)# 使用第二個 GPU
cuda1 = torch.device(‘cuda’, 1)# 使用 CPU
cpu = torch.device(‘cpu’)
能夠將 Tensor 與 numpy 互相轉換,以下是 Tensor 轉換成 numpy 的用法。
numpy1 = t1.numpy()
print(“numpy1:”, numpy1)
print(“type:”, type(numpy1))
# === output ===
numpy1: [[1. 2.]
[3. 4.]
[5. 6.]]
type: <class ‘numpy.ndarray’>
接著要來介紹如何將 numpy 轉換成 Tensor。轉換方式有四種: torch.tensor、torch.Tensor、torch.as_tensor、torch.from_numpy,其中 torch.as_tensor、torch.from_numpy 是採用淺拷貝。詳細可參考: torch裡面的Tensor、as_tensor、tensor以及from_numpy究竟有何區別?
numpy2 = np.array([[1, 2, 3], [4, 5, 6]])# 第一種
tensor1 = torch.tensor(numpy2)
print(“dtype:”, tensor1.dtype)
# === output ===
dtype: torch.int64# 第二種
tensor2 = torch.Tensor(numpy2)
print(“dtype:”, tensor2.dtype)
# === output ===
dtype: torch.float32# 第三種
tensor3 = torch.as_tensor(numpy2)
print(“dtype:”, tensor3.dtype)
# === output ===
dtype: torch.int64# 第四種
tensor4 = torch.from_numpy(numpy2)
print(“dtype:”, tensor4.dtype)
# === output ===
dtype: torch.int64
Autograd
在 Pytorch 舊版本中需要先將 Tensor 轉為 Variable 後,才能進行自動求導,但在新版本中已合併了 Tensor 和 Variable,因此可以直接在 Tensor 物件中設定 requires_grad 參數來表示是否需要追蹤該 Tensor 以計算梯度,默認為 False。更詳細的部分可參考: 【深度學習理論】一文搞透pytorch中的tensor、autograd、反向傳播和計算圖
# 建立 Tensor 並設定 requires_grad 為 True
torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.float64, requires_grad=True)# 建立隨機數值的 Tensor 並設定 requires_grad 為 True
torch.randn(2, 3, requires_grad=True)
那該怎麼計算梯度呢? 首先建立計算式子,接著呼叫 backward() 方法來計算梯度,該梯度值會放置到 Tensor 物件的 grad 屬性中。當每一次呼叫 backward() 時,會採用累加的方式,將所有梯度值累積至 grad 中。
# 建立隨機數值的 Tensor 並設定 requires_grad=True
x = torch.randn(2, 3, requires_grad=True)
y = torch.randn(2, 3, requires_grad=True)
z = torch.randn(2, 3, requires_grad=True)# 計算式子
a = x * y
b = a + z
c = torch.sum(b)# 計算梯度
c.backward()# 查看 x 的梯度值
print(x.grad)
若是要停止該 Tensor 的梯度計算可使用以下五種方法: detach()、with torch.no_grad()、@torch.no_grad()、with torch.inference_mode()、@torch.inference_mode()
- detach() 會返回一個與當前計算圖分離的新 Tensor,其 requires_grad 為 False。由下列輸出可知: 當設定 detach() 後,即停止追蹤該 Tensor 的梯度計算,其梯度值為 None。
# 建立隨機數值的 Tensor 並設定 requires_grad=True
x = torch.randn(2, 3, requires_grad=True)
y = torch.randn(2, 3, requires_grad=True)
z = torch.randn(2, 3, requires_grad=True)# 計算式子
a = x * y
b = a + z
c = torch.sum(b)# 設定 detach()
d = x.detach()# 查看是否追蹤 d 的梯度計算及數值
print("d requires grad:", d. requires_grad)
print("d grad:", d)# === output ===d requires grad: Falsed grad: None
- with torch.no_grad()、with torch.inference_mode() 及裝飾器 @torch.no_grad()、@torch.inference_mode() 的用法相同,會在該作用域範圍內不構建計算圖,即不追蹤該 Tensor 的梯度計算。通常用於 inference 操作,能夠減少記憶體的使用量。
首先來看在一般操作時,計算式變量 requires_grad 為 True,表示有追蹤該 Tensor 的梯度計算。
# 建立隨機數值的 Tensor 並設定 requires_grad=True
x = torch.randn(2, 3, requires_grad=True)
print(“set x requires_grad:”, x.requires_grad)
y = torch.randn(2, 3, requires_grad=True)
z = torch.randn(2, 3, requires_grad=True)# 計算式子
a = x * y
b = a + z
c = torch.sum(b)
print(“org c requires_grad:”, c.requires_grad)# === output ===
set x requires_grad: True
org c requires_grad: True
但若使用 with torch.no_grad(),在其作用域範圍內的計算式變量 requires_grad 為 False。
# 建立隨機數值的 Tensor 並設定 requires_grad=True
x = torch.randn(2, 3, requires_grad=True)
print(“set x requires_grad:”, x.requires_grad)
y = torch.randn(2, 3, requires_grad=True)
z = torch.randn(2, 3, requires_grad=True)with torch.no_grad():
a = x * y
b = a + z
c = torch.sum(b)
print("no_grad c requires_grad:", c.requires_grad)# === output ===
set x requires_grad: True
no_grad c requires_grad: False
Datasets、DataLoader
Pytorch 提供了 torch.utils.data.Datasets、torch.utils.data.DataLoader 類別用於讀取資料和進行前處理,其中前者為資料集的抽象類別,可以採用自己定義或是官方公開數據集;後者為將定義好的資料集進行包裝並提供迭代器,藉由設定參數決定每次迭代所需的資料數量。
torch.utils.data.Datasets
- 若要定義自己的數據集,需要繼承 Datasets 抽象類別,以及重新 override __init__()、__getitem__()、__len__()。
實現方式如下,其中 torchvision.Transform 為對輸入影像進行 Data Augmentation,有很多種方法能夠使用,可參考官方 → 🔍。
另外,也可以參考: Pytorch提供之torchvision data augmentation技巧。
import torch
import torchvision
from torch.utils.data import Datasetclass Dataset(object): def __init__(self): # 定義初始化參數 # 讀取資料集路徑 def __getitem__(self, index): # 讀取每次迭代的資料集中第 idx 資料 # 進行前處理 (torchvision.Transform 等) return 資料和 label def __len__(self): # 計算資料集總共數量 return 資料集總數
- 也可以透過 torchvision.datasets.ImageFolder 來讀取資料,主要用於影像資料,默認已將資料分成不同類別,需要參數可參考官方 → 🔍。
我使用 Kaggle 的貓狗圖像 為資料集,其資料已分為貓和狗各類別放置。接著使用 torchvision.datasets.ImageFolder 來讀取資料,其中 transform 為對輸入影像進行 Data Augmentation、target_transform 為對輸入影像的 label 進行轉換。
from torchvision.datasets import ImageFolderimage_folder = ImageFolder(“./dog_cat_data”, transform=None, target_transform=None)print(image_folder.class_to_idx)# === output ==={‘cats’: 0, ‘dogs’: 1}
- 若要使用官方提供的公開數據集,可使用 TORCHVISION.DATASETS 進行下載及讀取,下載的檔案會儲存至設定的 root 路徑。
# 下載 CIFAR10
cifar_data = torchvision.datasets.CIFAR10(root=”./data”, train=True, download=True)
torch.utils.data.DataLoader
DataLoader 是一個數據加載迭代器,能夠很方便地定義每一次迭代要取樣多少資料量、是否要打亂數據 (shuffle)、使用單/多執行緒 (thread) 等等。
首先來看一下需要的參數,其中 dataset 為自定義好的 Dataset;sampler 為自己定義要從資料集選取樣本的策略 (若有指定,shuffle 必須為 False);batch_sampler 與 sampler 類似,但每次只返回一個 batch 的索引;num_workers 為執行緒數量。
來試著建立 DataLoader 吧!
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from torchvision import transformstrain_transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])# 使用 torchvision.datasets.ImageFolder 讀取貓狗資料
image_folder = ImageFolder(“./data/dog_cat_data”, transform=train_transform, target_transform=None)# 建立 DataLoader,shuffle 為 True 表示會將資料進行打亂
data_loader = DataLoader(dataset = image_folder, batch_size= 100, shuffle= True, num_workers= 2)# 列印數據
for batch_idx, (data, target) in enumerate(data_loader):
print(“data:”, data)
print(“label:”, target)
Network
神經網路的各種層結構、Loss Functions 等是由 torch.nn 來構建,而建立神經網路模型需繼承 torch.nn.Module 以及定義 __init__()、forward(),其中 __init__() 是用於建構神經網路裡的各個層、forward() 是用於 forward propagation。
import torch.nn as nn
import torch.nn.functional as Fclass Model(nn.Module):
def __init__(self):
super(network_name, self).__init__() # network layer def forward(self, x): # forward propagation return output
當創建完模型後,使用模型輸入資料進行預測時,就會直接執行 forward(),不需再另外調用。
model = Model()
output = model(data)
詳細的卷積神經網路建置可參考我之前的文章:
- 卷積神經網絡 CNN 經典模型 — LeNet、AlexNet、VGG、NiN with Pytorch code
- 卷積神經網絡 CNN 經典模型 — GoogleLeNet、ResNet、DenseNet with Pytorch code
Loss Functions、Optimizer
設定 Loss Functions 和 Optimizer 的方式很簡單,前者呼叫 torch.nn、後者呼叫 torch.optim 就可以了!
import torch.nn as nn
import torch.optim as optimcriterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
Training
接著就是來訓練模型啦!訓練的格式如下
for epoch in range(epoch_size):
train_loss = 0.0 for batch_idx, (data, target) in enumerate(train_loader):
data, target = Variable(data), Variable(target)
if CUDA:
data, target = data.cuda(), target.cuda()
# clear gradient
optimizer.zero_grad()
# Forward propagation
output = model(data)
loss = criterion(output, target)
# Calculate gradients
loss.backward()
# Update parameters
optimizer.step()
predicted = torch.max(output.data, 1)[1]
train_loss += loss.item()
Save、Load Model
訓練完模型後,可使用兩種方式來儲存模型:
只儲存模型的權重
torch.save(model.state_dict(), 'model_weights.pth')
模型所需的儲存空間較小,但如果要 load 模型時,需要先匯入模型的結構,再讀取權重
# 讀取權重
model.load_state_dict(torch.load('model_weights.pth'))
完整儲存整個模型
torch.save(model, 'model.pth')
能夠直接 load 整個模型,但載入時間較長
model = torch.load('model.pth')
TensorBoard
TensorBoard 是個藉由視覺化的方式來觀察與紀錄訓練結果的好用工具,可依照個人需求設定要記錄的項目,可參考官方 → 🔍。
from torch.utils.tensorboard import SummaryWriterlog_dir = "./log_dir/"writer = SummaryWriter(log_dir)# 寫入資料
writer.add_scalar(tag, scalar_value)writer.add_histogram(tag, values)writer.add_image(tag, img_tensor)...
使用以下指令開啟 tensorboard
tensorboard --logdir="./log_dir"
視覺化的效果呈現如下圖: