Pytorch 基本介紹與教學

李謦伊
謦伊的閱讀筆記
18 min readSep 19, 2021

Pytorch 是 Facebook 於 2017 年所開源的深度學習框架,因其語法簡潔、直觀的特性深受歡迎,已成為目前深度學習熱門框架之一。本文將介紹 Pytorch 的基本用法~~

Tensor 張量

Pytorch 的基本元素為 Tensor,是一個多維度的矩陣,用法與 numpy 類似,兩者能夠很方便地相互轉換,不同的是 Pytorch 可以在 GPU 上執行,而 numpy 只能在 CPU 上執行。

Tensor 定義了好幾種類型應用於 CPU 和 GPU,默認張量類型為 torch.FloatTensor。

詳細可參考官方文件 (source)

接著來試著建立張量吧!首先 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 Dataset
class 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 為執行緒數量。

詳細參數可參考官方 (source)

來試著建立 DataLoader 吧!

from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from torchvision import transforms
train_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 F
class 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)

詳細的卷積神經網路建置可參考我之前的文章:

Loss Functions、Optimizer

設定 Loss Functions 和 Optimizer 的方式很簡單,前者呼叫 torch.nn、後者呼叫 torch.optim 就可以了!

import torch.nn as nn
import torch.optim as optim
criterion = 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()

整體流程可參考官方的範例: https://colab.research.google.com/github/pytorch/tutorials/blob/gh-pages/_downloads/17a7c7cb80916fcdf921097825a0f562/cifar10_tutorial.ipynb

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"

視覺化的效果呈現如下圖:

source

--

--