Pytorch學習筆記|莫凡Python

元辰辰辰辰辰辰發表於2020-11-02

PyTorch 和 Tensorflow的區別

據 PyTorch 自己介紹, 他們家的最大優點就是建立的神經網路是動態的, 對比靜態的 Tensorflow, 他能更有效地處理一些問題, 比如說 RNN 變化時間長度的輸出. 而我認為, 各家有各家的優勢和劣勢, 所以我們要以中立的態度. 兩者都是大公司, Tensorflow 自己說自己在分散式訓練上下了很大的功夫, 那我就預設 Tensorflow 在這一點上要超出 PyTorch, 但是 Tensorflow 的靜態計算圖使得他在 RNN 上有一點點被動 (雖然它用其他途徑解決了), 不過用 PyTorch 的時候, 你會對這種動態的 RNN 有更好的理解.

而且 Tensorflow 的高度工業化, 它的底層程式碼… 你是看不懂的. PyTorch 好那麼一點點, 如果你深入 API, 你至少能比看 Tensorflow 多看懂一點點 PyTorch 的底層在幹嘛.

最後我的建議就是:

如果你是學生, 隨便選一個學, 或者稍稍偏向 PyTorch, 因為寫程式碼的時候應該更好理解. 懂了一個模組, 轉換 Tensorflow 或者其他的模組都好說.
如果是上班了, 跟著你公司來, 公司用什麼, 你就用什麼, 不要脫群.

用 Numpy 還是 Torch

Torch 自稱為神經網路界的 Numpy, 因為他能將 torch 產生的 tensor 放在 GPU 中加速運算 (前提是你有合適的 GPU), 就像 Numpy 會把 array 放在 CPU 中加速運算. 所以神經網路的話, 當然是用 Torch 的 tensor 形式資料最好咯. 就像 Tensorflow 當中的 tensor 一樣.

當然, 我們對 Numpy 還是愛不釋手的, 因為我們太習慣 numpy 的形式了. 不過 torch 看出來我們的喜愛, 他把 torch 做的和 numpy 能很好的相容. 比如這樣就能自由地轉換 numpy array 和 torch tensor 了:

import torch
import numpy as np

np_data = np.arange(6).reshape((2, 3))
torch_data = torch.from_numpy(np_data)
tensor2array = torch_data.numpy()
print(
    '\nnumpy array:', np_data,          # [[0 1 2], [3 4 5]]
    '\ntorch tensor:', torch_data,      #  0  1  2 \n 3  4  5    [torch.LongTensor of size 2x3]
    '\ntensor to array:', tensor2array, # [[0 1 2], [3 4 5]]
)

Torch 中的數學運算

其實 torch 中 tensor 的運算和 numpy array 的如出一轍, 我們就以對比的形式來看. 如果想了解 torch 中其它更多有用的運算子, API就是你要去的地方.

 #abs 絕對值計算
data = [-1, -2, 1, 2]
tensor = torch.FloatTensor(data)  # 轉換成32位浮點 tensor
print(
    '\nabs',
    '\nnumpy: ', np.abs(data),          # [1 2 1 2]
    '\ntorch: ', torch.abs(tensor)      # [1 2 1 2]
)

# sin   三角函式 sin
print(
    '\nsin',
    '\nnumpy: ', np.sin(data),      # [-0.84147098 -0.90929743  0.84147098  0.90929743]
    '\ntorch: ', torch.sin(tensor)  # [-0.8415 -0.9093  0.8415  0.9093]
)

# mean  均值
print(
    '\nmean',
    '\nnumpy: ', np.mean(data),         # 0.0
    '\ntorch: ', torch.mean(tensor)     # 0.0
)

除了簡單的計算, 矩陣運算才是神經網路中最重要的部分. 所以我們展示下矩陣的乘法. 注意一下包含了一個 numpy 中可行, 但是 torch 中不可行的方式.

# matrix multiplication 矩陣點乘
data = [[1,2], [3,4]]
tensor = torch.FloatTensor(data)  # 轉換成32位浮點 tensor
# correct method
print(
    '\nmatrix multiplication (matmul)',
    '\nnumpy: ', np.matmul(data, data),     # [[7, 10], [15, 22]]
    '\ntorch: ', torch.mm(tensor, tensor)   # [[7, 10], [15, 22]]
)

# !!!!  下面是錯誤的方法 !!!!
data = np.array(data)
print(
    '\nmatrix multiplication (dot)',
    '\nnumpy: ', data.dot(data),        # [[7, 10], [15, 22]] 在numpy 中可行
    '\ntorch: ', tensor.dot(tensor)     # torch 會轉換成 [1,2,3,4].dot([1,2,3,4) = 30.0
)

新版本中(>=0.3.0), 關於 tensor.dot() 有了新的改變, 它只能針對於一維的陣列. 所以上面的有所改變.

tensor.dot(tensor)     # torch 會轉換成 [1,2,3,4].dot([1,2,3,4) = 30.0

# 變為
torch.dot(tensor.dot(tensor)

變數 (Variable)

什麼是 Variable
在 Torch 中的 Variable 就是一個存放會變化的值的地理位置. 裡面的值會不停的變化. 就像一個裝雞蛋的籃子, 雞蛋數會不停變動. 那誰是裡面的雞蛋呢, 自然就是 Torch 的 Tensor 咯. 如果用一個 Variable 進行計算, 那返回的也是一個同型別的 Variable.

我們定義一個 Variable:

import torch
from torch.autograd import Variable # torch 中 Variable 模組

# 先生雞蛋
tensor = torch.FloatTensor([[1,2],[3,4]])
# 把雞蛋放到籃子裡, requires_grad是參不參與誤差反向傳播, 要不要計算梯度
variable = Variable(tensor, requires_grad=True)

print(tensor)
"""
 1  2
 3  4
[torch.FloatTensor of size 2x2]
"""

print(variable)
"""
Variable containing:
 1  2
 3  4
[torch.FloatTensor of size 2x2]

Variable 計算, 梯度
我們再對比一下 tensor 的計算和 variable 的計算.

t_out = torch.mean(tensor*tensor)       # x^2
v_out = torch.mean(variable*variable)   # x^2
print(t_out)
print(v_out)    # 7.5
到目前為止, 我們看不出什麼不同, 但是時刻記住, Variable 計算時, 它在背景幕布後面一步步默默地搭建著一個龐大的系統, 叫做計算圖, computational graph. 這個圖是用來幹嘛的? 原來是將所有的計算步驟 (節點) 都連線起來, 最後進行誤差反向傳遞的時候, 一次性將所有 variable 裡面的修改幅度 (梯度) 都計算出來, 而 tensor 就沒有這個能力啦.

v_out = torch.mean(variable*variable) 就是在計算圖中新增的一個計算步驟, 計算誤差反向傳遞的時候有他一份功勞, 我們就來舉個例子:

v_out.backward()    # 模擬 v_out 的誤差反向傳遞

# 下面兩步看不懂沒關係, 只要知道 Variable 是計算圖的一部分, 可以用來傳遞誤差就好.
# v_out = 1/4 * sum(variable*variable) 這是計算圖中的 v_out 計算步驟
# 針對於 v_out 的梯度就是, d(v_out)/d(variable) = 1/4*2*variable = variable/2

print(variable.grad)    # 初始 Variable 的梯度
'''
 0.5000  1.0000
 1.5000  2.0000
'''
獲取 Variable 裡面的資料 
直接print(variable)只會輸出 Variable 形式的資料, 在很多時候是用不了的(比如想要用 plt 畫圖), 所以我們要轉換一下, 將它變成 tensor 形式.

print(variable)     #  Variable 形式
"""
Variable containing:
 1  2
 3  4
[torch.FloatTensor of size 2x2]
"""

print(variable.data)    # tensor 形式
"""
 1  2
 3  4
[torch.FloatTensor of size 2x2]
"""

print(variable.data.numpy())    # numpy 形式
"""
[[ 1.  2.]
 [ 3.  4.]]
"""

Torch 中的激勵函式

Torch 中的激勵函式有很多, 不過我們平時要用到的就這幾個. relu, sigmoid, tanh, softplus. 那我們就看看他們各自長什麼樣啦.

import torch
import torch.nn.functional as F     # 激勵函式都在這
from torch.autograd import Variable

# 做一些假資料來觀看影像
x = torch.linspace(-5, 5, 200)  # x data (tensor), shape=(100, 1)
x = Variable(x)
接著就是做生成不同的激勵函式資料:

x_np = x.data.numpy()   # 換成 numpy array, 出圖時用

# 幾種常用的 激勵函式
y_relu = F.relu(x).data.numpy()
y_sigmoid = F.sigmoid(x).data.numpy()
y_tanh = F.tanh(x).data.numpy()
y_softplus = F.softplus(x).data.numpy()
# y_softmax = F.softmax(x)  softmax 比較特殊, 不能直接顯示, 不過他是關於概率的, 用於分類

接著我們開始畫圖, 畫圖的程式碼也在下面:

在這裡插入圖片描述

import matplotlib.pyplot as plt  # python 的視覺化模組, 我有教程 (https://mofanpy.com/tutorials/data-manipulation/plt/)

plt.figure(1, figsize=(8, 6))
plt.subplot(221)
plt.plot(x_np, y_relu, c='red', label='relu')
plt.ylim((-1, 5))
plt.legend(loc='best')

plt.subplot(222)
plt.plot(x_np, y_sigmoid, c='red', label='sigmoid')
plt.ylim((-0.2, 1.2))
plt.legend(loc='best')

plt.subplot(223)
plt.plot(x_np, y_tanh, c='red', label='tanh')
plt.ylim((-1.2, 1.2))
plt.legend(loc='best')

plt.subplot(224)
plt.plot(x_np, y_softplus, c='red', label='softplus')
plt.ylim((-0.2, 6))
plt.legend(loc='best')

plt.show()

關係擬合 (迴歸)

要點

我會這次會來見證神經網路是如何通過簡單的形式將一群資料用一條線條來表示. 或者說, 是如何在資料當中找到他們的關係, 然後用神經網路模型來建立一個可以代表他們關係的線條.

在這裡插入圖片描述

建立資料集

我們建立一些假資料來模擬真實的情況. 比如一個一元二次函式: y = a * x^2 + b, 我們給 y 資料加上一點噪聲來更加真實的展示它.

import torch
import matplotlib.pyplot as plt

x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)  # x data (tensor), shape=(100, 1)
y = x.pow(2) + 0.2*torch.rand(x.size())                 # noisy y data (tensor), shape=(100, 1)

# 畫圖
plt.scatter(x.data.numpy(), y.data.numpy())
plt.show()

建立神經網路

建立一個神經網路我們可以直接運用 torch 中的體系. 先定義所有的層屬性(init()), 然後再一層層搭建(forward(x))層於層的關係連結. 建立關係的時候, 我們會用到激勵函式, 如果還不清楚激勵函式用途的同學, 這裡有非常好的一篇動畫教程.

import torch
import torch.nn.functional as F     # 激勵函式都在這

class Net(torch.nn.Module):  # 繼承 torch 的 Module
    def __init__(self, n_feature, n_hidden, n_output):
        super(Net, self).__init__()     # 繼承 __init__ 功能
        # 定義每層用什麼樣的形式
        self.hidden = torch.nn.Linear(n_feature, n_hidden)   # 隱藏層線性輸出
        self.predict = torch.nn.Linear(n_hidden, n_output)   # 輸出層線性輸出

    def forward(self, x):   # 這同時也是 Module 中的 forward 功能
        # 正向傳播輸入值, 神經網路分析出輸出值
        x = F.relu(self.hidden(x))      # 激勵函式(隱藏層的線性值)
        x = self.predict(x)             # 輸出值
        return x

net = Net(n_feature=1, n_hidden=10, n_output=1)

print(net)  # net 的結構
"""
Net (
  (hidden): Linear (1 -> 10)
  (predict): Linear (10 -> 1)
)
"""

訓練網路

訓練的步驟很簡單, 如下:

# optimizer 是訓練的工具
optimizer = torch.optim.SGD(net.parameters(), lr=0.2)  # 傳入 net 的所有引數, 學習率
loss_func = torch.nn.MSELoss()      # 預測值和真實值的誤差計算公式 (均方差)

for t in range(100):
    prediction = net(x)     # 餵給 net 訓練資料 x, 輸出預測值

    loss = loss_func(prediction, y)     # 計算兩者的誤差

    optimizer.zero_grad()   # 清空上一步的殘餘更新引數值
    loss.backward()         # 誤差反向傳播, 計算引數更新值
    optimizer.step()        # 將引數更新值施加到 net 的 parameters 上

視覺化訓練過程

為了視覺化整個訓練的過程, 更好的理解是如何訓練, 我們如下操作:

import matplotlib.pyplot as plt

plt.ion()   # 畫圖
plt.show()

for t in range(200):

    ...
    loss.backward()
    optimizer.step()

    # 接著上面來
    if t % 5 == 0:
        # plot and show learning process
        plt.cla()
        plt.scatter(x.data.numpy(), y.data.numpy())
        plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
        plt.text(0.5, 0, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color':  'red'})
        plt.pause(0.1)

區分型別 (分類)

在這裡插入圖片描述

建立資料集

我們建立一些假資料來模擬真實的情況. 比如兩個二次分佈的資料, 不過他們的均值都不一樣.

import torch
import matplotlib.pyplot as plt

# 假資料
n_data = torch.ones(100, 2)         # 資料的基本形態
x0 = torch.normal(2*n_data, 1)      # 型別0 x data (tensor), shape=(100, 2)
y0 = torch.zeros(100)               # 型別0 y data (tensor), shape=(100, )
x1 = torch.normal(-2*n_data, 1)     # 型別1 x data (tensor), shape=(100, 1)
y1 = torch.ones(100)                # 型別1 y data (tensor), shape=(100, )

# 注意 x, y 資料的資料形式是一定要像下面一樣 (torch.cat 是在合併資料)
x = torch.cat((x0, x1), 0).type(torch.FloatTensor)  # FloatTensor = 32-bit floating
y = torch.cat((y0, y1), ).type(torch.LongTensor)    # LongTensor = 64-bit integer

# plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=y.data.numpy(), s=100, lw=0, cmap='RdYlGn')
# plt.show()

# 畫圖
plt.scatter(x.data.numpy(), y.data.numpy())
plt.show()

建立神經網路

建立一個神經網路我們可以直接運用 torch 中的體系. 先定義所有的層屬性(init()), 然後再一層層搭建(forward(x))層於層的關係連結. 這個和我們在前面 regression 的時候的神經網路基本沒差. 建立關係的時候, 我們會用到激勵函式, 如果還不清楚激勵函式用途的同學, 這裡有非常好的一篇動畫教程.

import torch
import torch.nn.functional as F     # 激勵函式都在這

class Net(torch.nn.Module):     # 繼承 torch 的 Module
    def __init__(self, n_feature, n_hidden, n_output):
        super(Net, self).__init__()     # 繼承 __init__ 功能
        self.hidden = torch.nn.Linear(n_feature, n_hidden)   # 隱藏層線性輸出
        self.out = torch.nn.Linear(n_hidden, n_output)       # 輸出層線性輸出

    def forward(self, x):
        # 正向傳播輸入值, 神經網路分析出輸出值
        x = F.relu(self.hidden(x))      # 激勵函式(隱藏層的線性值)
        x = self.out(x)                 # 輸出值, 但是這個不是預測值, 預測值還需要再另外計算
        return x

net = Net(n_feature=2, n_hidden=10, n_output=2) # 幾個類別就幾個 output

print(net)  # net 的結構
"""
Net (
  (hidden): Linear (2 -> 10)
  (out): Linear (10 -> 2)
)
"""

訓練網路

訓練的步驟很簡單, 如下:

# optimizer 是訓練的工具
optimizer = torch.optim.SGD(net.parameters(), lr=0.02)  # 傳入 net 的所有引數, 學習率
# 算誤差的時候, 注意真實值!不是! one-hot 形式的, 而是1D Tensor, (batch,)
# 但是預測值是2D tensor (batch, n_classes)
loss_func = torch.nn.CrossEntropyLoss()

for t in range(100):
    out = net(x)     # 餵給 net 訓練資料 x, 輸出分析值

    loss = loss_func(out, y)     # 計算兩者的誤差

    optimizer.zero_grad()   # 清空上一步的殘餘更新引數值
    loss.backward()         # 誤差反向傳播, 計算引數更新值
    optimizer.step()        # 將引數更新值施加到 net 的 parameters 上

視覺化訓練過程

為了視覺化整個訓練的過程, 更好的理解是如何訓練, 我們如下操作:

import matplotlib.pyplot as plt

plt.ion()   # 畫圖
plt.show()

for t in range(100):

    ...
    loss.backward()
    optimizer.step()

    # 接著上面來
    if t % 2 == 0:
        plt.cla()
        # 過了一道 softmax 的激勵函式後的最大概率才是預測值
        prediction = torch.max(F.softmax(out), 1)[1]
        pred_y = prediction.data.numpy().squeeze()
        target_y = y.data.numpy()
        plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn')
        accuracy = sum(pred_y == target_y)/200.  # 預測中有多少和真實值一樣
        plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color':  'red'})
        plt.pause(0.1)

plt.ioff()  # 停止畫圖
plt.show()

快速搭建

我們先看看之前寫神經網路時用到的步驟. 我們用 net1 代表這種方式搭建的神經網路.

class Net(torch.nn.Module):
    def __init__(self, n_feature, n_hidden, n_output):
        super(Net, self).__init__()
        self.hidden = torch.nn.Linear(n_feature, n_hidden)
        self.predict = torch.nn.Linear(n_hidden, n_output)

    def forward(self, x):
        x = F.relu(self.hidden(x))
        x = self.predict(x)
        return x

net1 = Net(1, 10, 1)   # 這是我們用這種方式搭建的 net1

我們用 class 繼承了一個 torch 中的神經網路結構, 然後對其進行了修改, 不過還有更快的一招, 用一句話就概括了上面所有的內容!

net2 = torch.nn.Sequential(
    torch.nn.Linear(1, 10),
    torch.nn.ReLU(),
    torch.nn.Linear(10, 1)
)
我們再對比一下兩者的結構:

print(net1)
"""
Net (
  (hidden): Linear (1 -> 10)
  (predict): Linear (10 -> 1)
)
"""
print(net2)
"""
Sequential (
  (0): Linear (1 -> 10)
  (1): ReLU ()
  (2): Linear (10 -> 1)
)
"""

我們會發現 net2 多顯示了一些內容, 這是為什麼呢? 原來他把激勵函式也一同納入進去了, 但是 net1 中, 激勵函式實際上是在 forward() 功能中才被呼叫的. 這也就說明了, 相比 net2, net1 的好處就是, 你可以根據你的個人需要更加個性化你自己的前向傳播過程, 比如(RNN). 不過如果你不需要七七八八的過程, 相信 net2 這種形式更適合你.

批訓練

Torch 中提供了一種幫你整理你的資料結構的好東西, 叫做 DataLoader, 我們能用它來包裝自己的資料, 進行批訓練.
DataLoader 是 torch 給你用來包裝你的資料的工具. 所以你要講自己的 (numpy array 或其他) 資料形式裝換成 Tensor, 然後再放進這個包裝器中. 使用 DataLoader 有什麼好處呢? 就是他們幫你有效地迭代資料, 舉例:

import torch
import torch.utils.data as Data
torch.manual_seed(1)    # reproducible

BATCH_SIZE = 5      # 批訓練的資料個數

x = torch.linspace(1, 10, 10)       # x data (torch tensor)
y = torch.linspace(10, 1, 10)       # y data (torch tensor)

# 先轉換成 torch 能識別的 Dataset
torch_dataset = Data.TensorDataset(data_tensor=x, target_tensor=y)

# 把 dataset 放入 DataLoader
loader = Data.DataLoader(
    dataset=torch_dataset,      # torch TensorDataset format
    batch_size=BATCH_SIZE,      # mini batch size
    shuffle=True,               # 要不要打亂資料 (打亂比較好)
    num_workers=2,              # 多執行緒來讀資料
)

for epoch in range(3):   # 訓練所有!整套!資料 3 次
    for step, (batch_x, batch_y) in enumerate(loader):  # 每一步 loader 釋放一小批資料用來學習
        # 假設這裡就是你訓練的地方...

        # 打出來一些資料
        print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
              batch_x.numpy(), '| batch y: ', batch_y.numpy())

"""
Epoch:  0 | Step:  0 | batch x:  [ 6.  7.  2.  3.  1.] | batch y:  [  5.   4.   9.   8.  10.]
Epoch:  0 | Step:  1 | batch x:  [  9.  10.   4.   8.   5.] | batch y:  [ 2.  1.  7.  3.  6.]
Epoch:  1 | Step:  0 | batch x:  [  3.   4.   2.   9.  10.] | batch y:  [ 8.  7.  9.  2.  1.]
Epoch:  1 | Step:  1 | batch x:  [ 1.  7.  8.  5.  6.] | batch y:  [ 10.   4.   3.   6.   5.]
Epoch:  2 | Step:  0 | batch x:  [ 3.  9.  2.  6.  7.] | batch y:  [ 8.  2.  9.  5.  4.]
Epoch:  2 | Step:  1 | batch x:  [ 10.   4.   8.   1.   5.] | batch y:  [  1.   7.   3.  10.   6.]
"""

可以看出, 每步都匯出了5個資料進行學習. 然後每個 epoch 的匯出資料都是先打亂了以後再匯出.

真正方便的還不是這點. 如果我們改變一下 BATCH_SIZE = 8, 這樣我們就知道, step=0 會匯出8個資料, 但是, step=1 時資料庫中的資料不夠 8個, 這時怎麼辦呢:

BATCH_SIZE = 8      # 批訓練的資料個數

...

for ...:
    for ...:
        ...
        print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
              batch_x.numpy(), '| batch y: ', batch_y.numpy())
"""
Epoch:  0 | Step:  0 | batch x:  [  6.   7.   2.   3.   1.   9.  10.   4.] | batch y:  [  5.   4.   9.   8.  10.   2.   1.   7.]
Epoch:  0 | Step:  1 | batch x:  [ 8.  5.] | batch y:  [ 3.  6.]
Epoch:  1 | Step:  0 | batch x:  [  3.   4.   2.   9.  10.   1.   7.   8.] | batch y:  [  8.   7.   9.   2.   1.  10.   4.   3.]
Epoch:  1 | Step:  1 | batch x:  [ 5.  6.] | batch y:  [ 6.  5.]
Epoch:  2 | Step:  0 | batch x:  [  3.   9.   2.   6.   7.  10.   4.   8.] | batch y:  [ 8.  2.  9.  5.  4.  1.  7.  3.]
Epoch:  2 | Step:  1 | batch x:  [ 1.  5.] | batch y:  [ 10.   6.]
"""

這時, 在 step=1 就只給你返回這個 epoch 中剩下的資料就好了.

卷積神經網路

https://mofanpy.com/tutorials/machine-learning/torch/CNN/

什麼是迴圈神經網路 RNN (Recurrent Neural Network)

相關文章