iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 9
1
AI & Data

Knock Knock! Deep Learning系列 第 9

Day 8 / PyTorch 簡介 / PyTorch 入門(一) —— 簡介

前篇認識到 PyTorch 語法簡潔好上手、且實作上基於 dynamic computation graph 讓架構能在訓練時改變。接下來兩篇會以概略簡介和實作範例帶大家入門 PyTorch。

安裝 PyTorch

安裝 PyTorch 有很多種方式,因為會使用 Anaconda 幫我們管理環境,所以會透過他安裝。

請按照 Python 3 的版本安裝!

第一步請先到這裡選擇自己的作業系統,按照步驟安裝 Anaconda。

接著打開 terminal 跑下列指令:

# 建立一個名為 pytorch-tutorial 且使用 Python 3.8 的新環境
#(或其他 Python 3 版本)
$ conda create -n pytorch-tutorial python=3.8

# 檢查環境已被創立
$ conda env list # 會看到現在在 base,且另有剛剛建立的 pytorch-tutorial

# 進入剛剛創建的環境
$ conda activate pytorch-tutorial

# 透過 conda 安裝 PyTorch
# 請至 PyTorch 官網:https://pytorch.org/ 選好自己的環境選項,
# 複製他提供的 command 上來跑
$ conda install pytorch torchvision -c pytorch

來確認一下 Pytorch 有沒有安裝成功:

# 可以透過 Python interpreter 試著 import torch 來確認 PyTorch 安裝成功
$ python
Python 3.8.5 (default, Sep  4 2020, 02:22:02)
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>>

沒有出現 error 且出現下個 prompt 即安裝成功!Ctrl-D 可以離開 interpreter。

PyTorch install
—— 這是我在 MacOS 上的設定。

不知道 CUDA 是什麼的,他是讓 model 可以在 GPU 上訓練的接口。對新手來說還不需要,可以就選 None。

這個 cheatsheet 列了一些比較基本常用的 conda command,可以參考。

Workflow

剛剛看到的 torch 就是 PyTorch 的 package,接下來就要來看看他底下有哪些 subpackage 用來完成一個 network 的建立和訓練。

大致上從 creation 到 training 的實作流程如下:

  1. Load training data
    1. 記得整個 dataset 要先分好 training & test data
  2. Data preprocessing
    1. Image:統一 size、轉換 color、data augmentation 等等
    2. Text:word segmentation、建立 word to id mapping、添加 token 等等
    3. 打亂 data、batch data、轉乘 tensor 形式
  3. Define network structure
    1. 建立 network as Module
    2. Define structure and forward pass
  4. Define training process
    1. Loop over epochs(train 過一整組 dataset 叫 1 epoch,通常會 train 很多個 epoch)
    2. Load data in batches
    3. Log output,例如 loss、accuracy
    4. Save model
  5. Run training script

Train 完做 evaluation 則大致上是:

  1. Load test data
    1. 和前面差不多
  2. Data preprocessing
    1. 和前面差不多
  3. Load model
    1. 把 train 完存好的 model load 進來
  4. Define testing process
    1. Load data in batches
    2. Log output,例如 loss、accuracy
  5. Run test script

通常會把 training 和 testing 分開來寫比較好各自跑。兩邊 data loading 有重複可以寫在一個 file 重複利用。

下一篇會實際走過手寫辨識實作來熟悉流程。不過在這之前,先來簡單瞭解一下 PyTorch 提供的一些 package 和 API,方便之後講解。都只是簡單舉例了解概念,實際操作需要大量搜尋 documentation 理解用法。

Tensor

torch.tensor 是 PyTorch 最核心的 data type。Scalar 是 0 維、vector 是 1 維、matrix 是 2 維、tensor 則可以是任意維度,而在 neural network 裡 data 基本上都需要轉化為 tensor 處理。

他的很多 API 都跟 Numpy 類似。

建立 tensor
>>> x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=torch.float)
>>> x
tensor([[1., 2., 3.],
        [4., 5., 6.],
        [7., 8., 9.]])
>>> y = torch.eye(3)
>>> y
tensor([[ 1.,  0.,  0.],
        [ 0.,  1.,  0.],
        [ 0.,  0.,  1.]])
>>> z = torch.zeros(3, 3)
>>> z
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
基本 operation
>>> x + y
tensor([[ 2.,  2.,  3.],
        [ 4.,  6.,  6.],
        [ 7.,  8., 10.]])
>>> x.pow(2)
tensor([[ 1.,  4.,  9.],
        [16., 25., 36.],
        [49., 64., 81.]])
>>> x.matmul(x) # or, x @ x
tensor([[ 30.,  36.,  42.],
        [ 66.,  81.,  96.],
        [102., 126., 150.]])      
對不同 dimension 做 summation
>>> x.sum()
tensor(45.)
>>> x.sum(dim=0)
tensor([12., 15., 18.])
>>> x.sum(dim=1)
tensor([ 6., 15., 24.])
檢查形狀
>>> x.shape
torch.Size([3, 3])
新增新的dimension
>>> x.unsqueeze(0)
tensor([[[1., 2., 3.],
         [4., 5., 6.],
         [7., 8., 9.]]])
>>> x.unsqueeze(0).shape
torch.Size([1, 3, 3])
轉換 dimension
>>> a = torch.rand(2, 3)
>>> a
tensor([[0.4079, 0.4236, 0.2162],
        [0.8427, 0.1488, 0.8783]])
>>> a.transpose(0, 1) # 交換 dimension 0, 1
tensor([[0.4079, 0.8427],
        [0.4236, 0.1488],
        [0.2162, 0.8783]])
>>> a.permute(1, 0) # 讓 dimension 依照指定排列
tensor([[0.4079, 0.8427],
        [0.4236, 0.1488],
        [0.2162, 0.8783]])
轉換形狀
>>> b = torch.rand(2, 3, 2)
>>> b.size()
torch.Size([2, 3, 2])
>>> b.view(3, 2, 2).size() # 轉成 3 x 2 x 2
torch.Size([3, 2, 2])
>>> b.view(3, -1).size() # -1 代表根據其他 dimension 決定這個 dimension 剩下的 size
torch.Size([3, 4])

之前當助教的時候,學生特別容易搞混 viewtransposepermute 的使用,以為隨便用一個來讓 data 變成適當形狀運算即可,而忽略了數學上的含義。這還滿危險的,因為形狀對了程式就能跑下去,但最後發現訓練不好時,也很難找到哪裡出錯。

基本上 transposepermute 功能類似,都是數學上對 matrix 做 transpose 交換 dimension 的意義。但 view 沒有這層含義,比較類似單純把同一份 data 揉成不同形狀。例如 [[1, 2, 3], [4, 5, 6]] 揉成 [[1, 2], [3, 4], [5, 6]] 會用 view,但 transpose 兩個 dimension 會變成 [[1, 4], [2, 5], [3, 6]],本質上已經不同。

可以參考 [1] 更了解他們的差異,以免誤用。

Computation Graph

有了 tensor,把他們串聯成 network 就成了 computation graph。他們之間的關係會被保存,如此才能在 backward propagation 時知道如何取 gradient。

舉例來說,我們建一個小小 computation graph,為兩個 node 相加:

>>> a = torch.tensor([1., 2.], requires_grad=True)
>>> b = torch.tensor([3., 4.], requires_grad=True)
>>> c = a + b

requires_grad = True 才能計算 gradient。

我們取 loss 並呼叫 loss.backward(),即可計算 computation graph 裡 parameters 的 gradient:

>>> loss = c.sum()
>>> loss.backward()
>>> a.grad
tensor([1., 1.])
>>> b.grad
tensor([1., 1.])

怎麼知道如何計算 ab 的 gradient 的呢?因為 c 有記錄他與 ab 的關係是 sum。來看一下他的 gradient function:

>>> c.grad_fn
<AddBackward0 object at 0x7f970004f460>

很方便吧。這也是 chain rule 的力量喔。

Dataset Utilities

收納在 torch.utils.data 底下的實用工具,提供一些方便處理 data preprocessing 的功能。

DataLoader

主要集大成的 dataloader,定義 dataset 來源、batch size、要不要 shuffle 等等。

torch.utils.data.DataLoader

使用方式很簡單,定義完後在 training 或 testing 時用 for loop 簡單 iterate 過去:

loader = DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
           batch_sampler=None, num_workers=0, collate_fn=None,
           pin_memory=False, drop_last=False, timeout=0,
           worker_init_fn=None)

for batch in loader:
    # train or test with batch
Dataset

分為 map-style 和 iterable-style dataset。會需要把 data 從電腦 load 進來,preprocess 之後打包好。

Map-style: torch.utils.data.Dataset
  • Subclass 並 overwrite __getitem__()__len__()
  • Dataset 用 dataset[idx] 的方式取用
  • 可以搭配 Sampler 決定 data 取用順序
Iterable-style: torch.utils.data.IterableDataset
  • Subclass 並 overwrite __iter__()
  • Dataset 用 iter(dataset) 的方式取用
  • 取用順序定義在 __iter__()

舉例來說,可以這樣使用:

class MyIterableDataset(torch.utils.data.IterableDataset):
    def __init__(self, start, end):
        super(MyIterableDataset).__init__()
        self.n = None

    def __iter__(self):
        dataset = read_data_from_file()
        self.n = len(dataset)
        return iter(dataset)
Sampler

決定 data loading 的順序。適用於 map-style dataset。

torch.utils.data.Sampler
  • 需 overwrite __iter__()__len__()

Functions

收納於 torch.nn.functionaltorchtorch.nn,有各式常用 activation function、loss function、或常用 function 等等。

如果你點進去看的話,會發現有很多重複的 function,例如 sigmoid 有 torch.sigmoidtorch.nn.functional.sigmoidtorch.nn.Sigmoid,非常受歡迎。他們之間有什麼差別嗎?

以 sigmoid 來說,基本上除了前兩個是 Python function 而後面的是 Module class 所以使用上有些不同外,底下會呼叫同樣的 function,所以效果是一樣的。那什麼時候該用哪邊提供的 function 比較好呢?

有三種情況會比較推薦用 torch.nn

  • 考慮有沒有 state。State 是一個 object 裡保存著會變動的 data,例如 weights。當一個 function 牽涉到 state 的改變時,會推薦用 torch.nn 底下提供的 Module,因為他是一個 class 裡面會存 variables。例如 Conv2dLinear 等等。
  • 考慮 training 和 testing 行為是否不同。例如 Dropout 會在 training 時 enable,testing 時 disable,就適合用 torch.nn 底下提供的 Module,因為內部會幫你檢查是在做 training 或 testing。
  • Modules 可以用 torch.nn.Sequential 串聯在一起,建立簡單一進一出的 network。因為一定要用 Module class,像 sigmoid 這種 function 也必須用 torch.nn.Sigmoid 定義才能加進去。

如果沒有 state、training testing 行為一致、不用 Sequential,那麼可以使用 torchtorch.nn.functional 底下的 function。不過如果一時判斷不清或懶得判斷,就都用 torch.nn 吧。

至於 torchtorch.nn.functional 之間的差別,比較在於歷史發展下來的結果,基本上沒有差別。如果 torch 底下有的話就用 torch 的,因為很多在 torch.nn.functional 都被 deprecate 了。

可以參考 [2][3] 提供的解釋。

使用方法大概為:

>>> a = torch.rand(2)
>>> a
tensor([0.0530, 0.7934])
>>> torch.sigmoid(a)
tensor([0.5133, 0.6886])

那我們就來舉例一些 function。

Activation Functions
torch.sigmoid
torch.nn.functional.relu
torch.tanh
Loss Functions
torch.nn.CrossEntropyLoss
ttorch.nn.MSELoss
Dropout Functions
torch.nn.Dropout

Optimizers

收錄在 torch.optim 底下眾多的 optimizer。前面介紹 optimizer 時的數學都含在裡面了,只需要挑一個效果好的。

Optimizer 可以這樣定義:

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

而訓練時會用 optimizer.zero_grad() 把前面的 gradient 歸零(否則會累積前一輪的),等 loss 算完後 call loss.backward() 按照 computation graph 往前計算每個 parameter 的 gradient,最後用 optimizer.step() 按照各自的 optimization 方法進行 update:

for input, target in dataset:
    optimizer.zero_grad()
    output = model(input)
    loss = loss_fn(output, target)
    loss.backward()
    optimizer.step()
Optimizers
torch.optim.SGD
torch.optim.RMSprop
torch.optim.Adam

Module

最後介紹主要建立 network 的骨幹,torch.nn.Module

要建立自己的 network 很簡單的三步驟:繼承 Module class、overwrite __init__() 來定義 network 架構、 和 overwrite forward() 來定義 feed forward 時接收 input 後怎麼產生 output:

class Model(nn.Module): # 繼承 Module
    def __init__(self):
        super(Model, self).__init__()
        # 定義整個 network 的架構,這邊有兩層 fully-connected layer
        self.fc1 = nn.Linear(20, 30) # 20 x 30
        self.fc2 = nn.Linear(30, 5) # 30 x 5

    def forward(self, x):
        # 接收 input x 之後,經過兩層 fully-connected layer 
        # 和 activation function 取得 output
        x = F.relu(self.fc1(x))
        return F.relu(self.fc2(x))

Fully-connected layer 就是在兩層 node 之間,每個 node 與 node 之間都有連結。

如此一來在 training 時把 data 傳進 model 就可以取得 output:

x = torch.rand(20)
m = Model()

y = m(x)

而 network 中的很多 layer 也都是 Module,因為他們也都是有輸入輸出和 parameters 的 network,例如上面看到的 Linear layer。此外還有很多不同架構的 layer,用來應付不同需求,例如 Convolutional layer 適合在 image 提取特徵、RNN layer 適合提取時間序列的特徵。之後會再詳細介紹!

很多都有些複雜,一開始不太知道怎麼傳參數的話很正常,使用的時候就多多參考 documentation 或網路範例。

torch.nn.Linear
torch.nn.Conv2d
torch.nn.RNN

Checkpoint

下面的問題在用程式查看之前,鼓勵大家努力查 doc 想想看。

  • 定義 x = torch.tensor([[1, 2, 3], [4, 5, 6]])。那麼 x.sum(dim=0) 結果為何?x.transpose(0, 1) 結果為何?
  • Computation graph 是什麼?建立他主要是方便進行哪一步驟?
  • 把 dropout 用 Module 形式建立的好處為何?
  • 使用 optimizer 做 training 時的基本步驟為何?各自有什麼含義?
  • 建立自己的 network 需要做哪些步驟?
  • Module section 的範例中,輸出的 y 形狀為何?

參考資料

  1. ? Difference between view, reshape, transpose and permute in PyTorch
  2. ? Introduction to PyTorch
  3. PyTorch 101, Part 3: Going Deep with PyTorch
  4. PyTorch 101, Part 1: Understanding Graphs, Automatic Differentiation and Autograd

上一篇
Day 7 / PyTorch / 深度學習框架之亂
下一篇
Day 9 / PyTorch 簡介 / PyTorch 入門(二) —— MNIST 手寫數字辨識
系列文
Knock Knock! Deep Learning31

尚未有邦友留言

立即登入留言