1. 程式人生 > 實用技巧 >莫煩pytorch學習記錄

莫煩pytorch學習記錄

感謝莫煩大神Pytorch B站視訊:https://www.bilibili.com/video/av15997678?p=11

PyTorch是什麼?

它是一個基於Python的科學計算包,其主要是為了解決兩類場景:

1、一種是可以替代Numpy進行科學計算,同時還可以使用張量在GPU上進行加速運算。
2、一個深度學習的研究平臺,提供最大的靈活性和速度。

Numpy與Torch之間的轉換

import torch
import numpy as np
from torch.autograd import Variable # torch 中 Variable 模組

### Torch 自稱為神經網路界的 Numpy,
# 因為他能將 torch 產生的 tensor 放在 GPU 中加速運算 np_data = np.arange(6).reshape((2,3)) torch_data = torch.from_numpy(np_data) #torch形式 tensor2array = torch_data.numpy() # numpy形式 # torch 做的和 numpy 能很好的相容. # 比如這樣就能自由地轉換 numpy array 和 torch tensor 了 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]] )

numpy array: [[0 1 2]
              [3 4 5]] 
torch tensor: tensor([[0, 1, 2],
                      [3, 4, 5]], dtype=torch.int32) 
tensor to array: [[0 
1 2] [3 4 5]]

Torch中的數學運算與numpy的對比

API手冊

常用計算: 注意!!!!所有在pytorch裡的計算,都要先轉換為tensor的形式,不然就報錯,切記!!!

#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的封裝的函式跟pytorch的不一樣,這一點一定要區分清楚,也是很容易出問題的一個地方。

# 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 中可行
    # 關於 tensor.dot() 有了新的改變, 它只能針對於一維的陣列. 所以上面的有所改變.
    # '\ntorch: ', tensor.dot(tensor)     # torch 會轉換成 [1,2,3,4].dot([1,2,3,4) = 30.0
    # 變為
    # '\ntorch: ', torch.dot(tensor.dot(tensor))#
)

Variable

tensor = torch.FloatTensor([[1,2],[3,4]])
# 裡面的值會不停的變化. 就像一個裝雞蛋的籃子, 雞蛋數會不停變動.
# 那誰是裡面的雞蛋呢, 自然就是 Torch 的 Tensor 咯.
# 如果用一個 Variable 進行計算, 那返回的也是一個同類型的 Variable.
# 把雞蛋放到籃子裡,
variable = Variable(tensor, requires_grad=True)#requires_grad是參不參與誤差反向傳播, 要不要計算梯度

print('\n',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的計算,在正向傳播它們是看不出有什麼不同的,而且variabletensor有個很大的區別,variable是儲存變數的,是會改變的,而tensor是不會改變的,是我們輸入時就設定好的引數,variable會在反向傳播後修正自己的數值。 這是我覺得他們最大的不同。

t_out = torch.mean(tensor*tensor)       # x^2
v_out = torch.mean(variable*variable)   # x^2
print('\n',t_out)
print('\n',v_out)    # 7.5

假設mean的均值做為結果的誤差,對誤差反向傳播得到各項梯度。利用這個例子去看,在反向傳播中它們之間的不同。

v_out = torch.mean(variable*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('\n',variable.grad)    # 初始 Variable 的梯度
'''
 0.5000  1.0000
 1.5000  2.0000
'''

可以看到,在backward中已經計算好梯度了,利用*.grad將背景中計算好的variable的各項梯度print出來。

這樣如果是個網路的運算步驟也可以在backward中將各個梯度計算好。

獲取Variable裡面的資料

直接print(variable)只會輸出Variable形式的資料, 在很多時候是用不了的(比如想要用plt畫圖), 需要轉換成tensor形式.

## 獲取 Variable 裡面的資料
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.]]
"""

常用幾種激勵函式及影象

常用幾種激勵函式:relu,sigmoid,tanh,softplus

# 做一些假資料來觀看影象
x = torch.linspace(-5, 5, 200)  # x data (tensor), shape=(100, 1)
x = Variable(x)
# Torch 中的激勵函式有很多, 不過我們平時要用到的就這幾個.
# relu, sigmoid, tanh, softplus. 那我們就看看他們各自長什麼樣啦.
x_np = x.data.numpy()   # 換成 numpy array, 出圖時用

莫煩大神那時的版本用的是torch.nn.relu,但後來版本改了,直接用torch.relu就可以,其他激勵函式也一樣。

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


#用ax畫
fig, ax = plt.subplots(2,2,figsize=(8,6))
ax[0,0].plot(x_np,y_relu,c='red', label='relu')
ax[0,0].set_title('relu',fontsize=18)
ax[0,0].set_ylim(-1,5)
ax[0,0].legend()

ax[0,1].plot(x_np,y_sigmoid)
ax[1,0].plot(x_np,y_tanh)
ax[1,1].plot(x_np,y_softplus)
plt.show()

線性擬合迴歸

import torch
import matplotlib.pyplot as plt
import torch.nn.functional as F
#######捏造資料#######
x = torch.unsqueeze(torch.linspace(-1,1,500),dim=1)#x的資料,shape=(500,1)
y = x.pow(2) + 0.2*torch.rand(x.size())
# 畫圖看看捏的資料咋樣
fig,ax = plt.subplots(2,1)
ax[0].scatter(x.data.numpy(),y.data.numpy())
ax[0].set_title("Pinched data",fontsize=18)

搭建網路

# 建立一個神經網路我們可以直接運用 torch 中的體系. 先定義所有的層屬性(__init__()),
# 然後再一層層搭建(forward(x))層於層的關係連結. 建立關係的時候, 我們會用到激勵函式.
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 (
  (hidden): Linear (1 -> 10)
  (predict): Linear (10 -> 1)
)
"""

開始訓練

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

plt.ion()
for t in range(10000):
    prediction = net(x) # 餵給net訓練資料x,輸出預測值
    loss = loss_func(prediction, y) # 計算兩者的誤差,#要預測值在前,label在後

    optimizer.zero_grad() # 清空上一步的殘餘更新引數值,#net.parameters()所有引數梯度變為0
    loss.backward()       # 誤差反向傳播,計算引數更新
    optimizer.step()      # 將引數更新施加到net的parameters上,#optimizr優化parameters

視覺化訓練過程

    if t % 5 == 0:
        # plot and show learning process
        ax[1].cla()
        ax[1].scatter(x.data.numpy(), y.data.numpy())
        ax[1].plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
        ax[1].text(0.5, 0, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
        plt.pause(0.01)
#如果在指令碼中使用ion()命令開啟了互動模式,沒有使用ioff()關閉的話,
# 則影象會一閃而過,並不會常留。要想防止這種情況,需要在plt.show()之前加上ioff()命令。
plt.ioff()
plt.show()

區分型別 (分類)

捏個數據

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


# 畫圖 會出錯:會報錯,因為畫圖x和y的數量不相同,x矩陣的形狀是(200,2)的,而y矩陣的形狀是(200),
# 所以需要把x分成兩部分來畫圖才可以的。
# plt.scatter(x.data.numpy(), y.data.numpy())
# plt.show()
# 畫圖
plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=y.data.numpy(), s=100, lw=0, cmap='RdYlGn')
plt.show()

搭個網路

class Net(torch.nn.Module):
    def __init__(self,n_feature,n_hidden,n_outpot):
        super(Net,self).__init__()  #繼承__init__的功能
        self.hidden = torch.nn.Linear(n_feature,n_hidden)
        self.out = torch.nn.Linear(n_hidden,n_outpot)

    def forward(self,x):
        x = torch.relu(self.hidden(x))
        x = self.out(x)
        return x

net = Net(n_feature=2,n_hidden=10,n_outpot=2)

print(net)

訓練網路

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 上

視覺化

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

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

    optimizer.zero_grad()   # 清空上一步的殘餘更新引數值
    loss.backward()         # 誤差反向傳播, 計算引數更新值
    optimizer.step()        # 將引數更新值施加到 net 的 parameters 上
    # 接著上面來
    if t % 2 == 0:
        plt.cla()
        # 過了一道 softmax 的激勵函式後的最大概率才是預測值
        prediction = torch.max(F.softmax(out), 1)[1]
        #prediction=torch.max(F.softmax(out), 1) 中的1,表示【0,0,1】預測結果中,結果為1的結果的位置。
        pred_y = prediction.data.numpy().squeeze()#利用squeeze()函式將表示向量的陣列轉換為秩為1的陣列,這樣利用matplotlib庫函式畫圖時,就可以正常的顯示結果了
        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()

快速搭建法

  Torch 中提供了很多方便的途徑, 同樣是神經網路, 能快則快, 我們看看如何用更簡單的方式搭建同樣的迴歸神經網路.

上一節用的方法更加底層,其實有更加快速的方法,對比一下就很清楚了:

Method 1

我們用 class 繼承了一個 torch 中的神經網路結構, 然後對其進行了修改
class Net(torch.nn.Module): #我們用 class 繼承了一個 torch 中的神經網路結構, 然後對其進行了修改
    def __init__(self,n_feature,n_hidden,n_outpot):
        super(Net,self).__init__()  #繼承__init__的功能,
        self.hidden = torch.nn.Linear(n_feature,n_hidden)
        self.out = torch.nn.Linear(n_hidden,n_outpot)

    def forward(self,x):
        x = torch.relu(self.hidden(x))
        x = self.out(x)
        return x

net1 = Net(n_feature=2,n_hidden=10,n_outpot=2)

Method 2

nn庫裡一個函式就能快速搭建了,注意ReLU也算做一層加入到網路序列中

net2 = torch.nn.Sequential(
    torch.nn.Linear(2,10),
    torch.nn.ReLU(),
    torch.nn.Linear(10,2)
)
print(net1)
print(net2)

結果是類似的:就是net2中將ReLU也做為一個神經層

print(net1)
"""
Net(
  (hidden): Linear(in_features=2, out_features=10, bias=True)
  (out): Linear(in_features=10, out_features=2, bias=True)
)
"""
print(net2)
"""
Sequential(
  (0): Linear(in_features=2, out_features=10, bias=True)
  (1): ReLU()
  (2): Linear(in_features=10, out_features=2, bias=True)
)
"""

儲存與提取網路

儲存

#######捏造資料#######
torch.manual_seed(1)    # reproducible 使得每次隨機初始化的隨機數是一致的
x = torch.unsqueeze(torch.linspace(-1,1,500),dim=1)#x的資料,shape=(500,1)
y = x.pow(2) + 0.2*torch.rand(x.size())

def save():
    # 搭建網路
    net1 = torch.nn.Sequential(
        torch.nn.Linear(1, 10),
        torch.nn.ReLU(),
        torch.nn.Linear(10, 1),
    )
    optimizer = torch.optim.SGD(net1.parameters(), lr=0.5)
    loss_func = torch.nn.MSELoss()
    # 訓練
    for t in range(100):
        prediction = net1(x)
        loss = loss_func(prediction, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    torch.save(net1, 'net.pkl')  # 儲存整個網路
    torch.save(net1.state_dict(), 'net_params.pkl')  # 只儲存網路中的引數 (速度快, 佔記憶體少)

提取網路

# 這種方式將會提取整個神經網路, 網路大的時候可能會比較慢.
def restore_net():
    # restore entire net1 to net2
    net2 = torch.load('net.pkl')
    prediction = net2(x)

只提取網路引數

# 這種方式將會提取所有的引數, 然後再放到你的新建網路中.
def restore_params():
    # 新建 net3
    net3 = torch.nn.Sequential(
        torch.nn.Linear(1, 10),
        torch.nn.ReLU(),
        torch.nn.Linear(10, 1)
    )

    # 將儲存的引數複製到 net3
    net3.load_state_dict(torch.load('net_params.pkl'))
    prediction = net3(x)

完整程式碼並檢視三個網路模型的結果

import torch
import matplotlib.pyplot as plt
import torch.nn.functional as F
from matplotlib import animation
#######捏造資料#######
torch.manual_seed(1)    # reproducible 使得每次隨機初始化的隨機數是一致的
x = torch.unsqueeze(torch.linspace(-1,1,100),dim=1)#x的資料,shape=(500,1)
y = x.pow(2) + 0.2*torch.rand(x.size())



def save():
    # 搭建網路
    net1 = torch.nn.Sequential(
        torch.nn.Linear(1, 100),
        torch.nn.ReLU(),
        torch.nn.Linear(100, 1),
    )
    optimizer = torch.optim.SGD(net1.parameters(), lr=0.3)
    loss_func = torch.nn.MSELoss()
    # 訓練
    for t in range(1000):
        prediction = net1(x)
        loss = loss_func(prediction, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    torch.save(net1, 'net.pkl')  # 儲存整個網路
    torch.save(net1.state_dict(), 'net_params.pkl')  # 只儲存網路中的引數 (速度快, 佔記憶體少)

    # plot result
    plt.figure(1, figsize=(10,3))
    plt.subplot(131)
    plt.title('Net1')
    plt.scatter(x.data.numpy(), y.data.numpy())
    plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)


# 這種方式將會提取整個神經網路, 網路大的時候可能會比較慢.
def restore_net():
    # restore entire net1 to net2
    net2 = torch.load('net.pkl')
    prediction = net2(x)

    # plot result
    plt.figure(1, figsize=(10,3))
    plt.subplot(132)
    plt.title('Net2')
    plt.scatter(x.data.numpy(), y.data.numpy())
    plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)

# 這種方式將會提取所有的引數, 然後再放到你的新建網路中.
def restore_params():
    # 新建 net3
    net3 = torch.nn.Sequential(
        torch.nn.Linear(1, 100),
        torch.nn.ReLU(),
        torch.nn.Linear(100, 1)
    )

    # 將儲存的引數複製到 net3
    net3.load_state_dict(torch.load('net_params.pkl'))
    prediction = net3(x)
    # plot result
    plt.figure(1, figsize=(10,3))
    plt.subplot(133)
    plt.title('Net3')
    plt.scatter(x.data.numpy(), y.data.numpy())
    plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)



# 儲存 net1 (1. 整個網路, 2. 只有引數)
save()

# 提取整個網路
restore_net()

# 提取網路引數, 複製到新網路
restore_params()

plt.show()