pytorch 基礎練習及螺旋數據分類


一、pytorch 基礎練習

主要學習了如何使用torch定義數據以及對數據的一些操作
示例代碼中進行點積運算(m @ v)要求數據類型是float,因此前面v定義的時候需要指定dtype,如下所示,

# Create tensor of numbers from 1 to 5
# 注意這里結果是1到4,沒有5,即前閉后開
v = torch.arange(1, 5,dtype=torch.float)
print(v)
# tensor([1., 2., 3., 4.])
# Scalar product 點積
# 需要float類型 
m @ v
# tensor([30., 60.])

二、螺旋數據分類(sprial classification)

配套的文字版講解理論,地址為: 中文講解 英文講解

2.1 生成用於分類的數據

1.下面代碼是下載繪圖函數到本地。(畫點的過程中要用到里面的一些函數)

!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py

2.引入基本的庫,然后初始化重要參數

import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default

# 因為colab是支持GPU的,torch 將在 GPU 上運行
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)

# 初始化隨機數種子。神經網絡的參數都是隨機初始化的,
# 不同的初始化參數往往會導致不同的結果,當得到比較好的結果時我們通常希望這個結果是可以復現的,
# 因此,在pytorch中,通過設置隨機數種子也可以達到這個目的
seed = 12345
random.seed(seed)
torch.manual_seed(seed)

N = 1000  # 每類樣本的數量
D = 2  # 每個樣本的特征維度
C = 3  # 樣本的類別
H = 100  # 神經網絡里隱層單元的數量

3.初始化 X 和 Y。 X 可以理解為特征矩陣,每一行是一個樣本,每一列代表一個特征,Y可以理解為樣本標簽。 結合代碼可以看到,X的為一個 NxC 行, D 列的矩陣。C 類樣本,每類樣本是 N個,所以是 N*C 行。每個樣本的特征維度是2,所以是2列。

在 python 中,調用 zeros 類似的函數,第一個參數是 y方向的,即矩陣的行;第二個參數是 x方向的,即矩陣的列,大家得注意下,不要搞反了。下面結合代碼看看 3000個樣本的特征是如何初始化的。

X = torch.zeros(N * C, D).to(device)
Y = torch.zeros(N * C, dtype=torch.long).to(device)
for c in range(C):
    index = 0
    t = torch.linspace(0, 1, N) # 在[0,1]間均勻的取10000個數,賦給t
    # 下面的代碼不用理解太多,總之是根據公式計算出三類樣本(可以構成螺旋形)
    # torch.randn(N) 是得到 N 個均值為0,方差為 1 的一組隨機數,注意要和 rand 區分開
    inner_var = torch.linspace( (2*math.pi/C)*c, (2*math.pi/C)*(2+c), N) + torch.randn(N) * 0.2
    
    # 每個樣本的(x,y)坐標都保存在 X 里
    # Y 里存儲的是樣本的類別,分別為 [0, 1, 2]
    for ix in range(N * c, N * (c + 1)):
        X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
        Y[ix] = c
        index += 1

print("Shapes:")
print("X:", X.size())
print("Y:", Y.size())

運行結果:

Shapes:
X: torch.Size([3000, 2])
Y: torch.Size([3000])

看一下X和Y:

# X
tensor([[-0.0000e+00,  0.0000e+00],
        [ 1.0158e-04,  9.9583e-04],
        [-1.1208e-03,  1.6589e-03],
        ...,
        [ 9.0658e-01, -4.1727e-01],
        [ 8.7314e-01, -4.8540e-01],
        [ 7.9693e-01, -6.0407e-01]], device='cuda:0')
# Y
tensor([0, 0, 0,  ..., 2, 2, 2], device='cuda:0')

4.繪圖

# visualise the data
plot_data(X, Y)

image

2.2 構建分類模型

這里記錄下用 Pytorch 訓練神經網絡,包含的5個基礎步驟:

preds = model(inputs)             ## inference
loss = criterion(preds, targets)  ## 求解loss
optimizer.zero_grad()             ## 梯度清零
loss.backward()                   ## 反向傳播求解梯度
optimizer.step()                  ## 更新權重參數

image

2.2.1 構建線性模型分類

1.定義模型並訓練

learning_rate = 1e-3
lambda_l2 = 1e-5

# nn 包用來創建線性模型
# 每一個線性模型都包含 weight 和 bias
model = nn.Sequential(
    nn.Linear(D, H),
    nn.Linear(H, C)
)
model.to(device) # 把模型放到GPU上

# nn 包含多種不同的損失函數,這里使用的是交叉熵(cross entropy loss)損失函數
criterion = torch.nn.CrossEntropyLoss()

# 這里使用 optim 包進行隨機梯度下降(stochastic gradient descent)優化
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2)

# 開始訓練
for t in range(1000):
    # 把數據輸入模型,得到預測結果
    y_pred = model(X)
    # 計算損失和准確率
    loss = criterion(y_pred, Y)
    score, predicted = torch.max(y_pred, 1)
    acc = (Y == predicted).sum().float() / len(Y)
    print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
    display.clear_output(wait=True)

    # 反向傳播前把梯度置 0 
    optimizer.zero_grad()
    # 反向傳播優化 
    loss.backward()
    # 更新全部參數
    optimizer.step()

輸出:

[EPOCH]: 999, [LOSS]: 0.861541, [ACCURACY]: 0.504

對上面的一些關鍵函數進行說明:

使用 print(y_pred.shape) 可以看到模型的預測結果,為[3000, 3]的矩陣。每個樣本的預測結果為3個,保存在 y_pred 的一行里。值最大的一個,即為預測該樣本屬於的類別。但是計算交叉熵損失時並沒有用這個預測類別predicted與真實標簽Y計算,而是將y_pred與真實標簽Y進行計算,我的理解在最后的后記中

score, predicted = torch.max(y_pred, 1) 是沿着第二個方向(即X方向)提取最大值。最大的那個值存在 score 中,所在的位置(即第幾列的最大)保存在 predicted 中。

每一次反向傳播前,都要把梯度清零,原因可以參考知乎回答
2.分類結果

# Plot trained model
print(model)
plot_model(X, Y, model)

image
上面使用 print(model) 把模型輸出,可以看到有兩層:

第一層輸入為 2(因為特征維度為主2),輸出為 100;
第二層輸入為 100 (上一層的輸出),輸出為 3(類別數)
從上面圖示可以看出,線性模型的准確率最高只能達到 50% 左右,對於這樣復雜的一個數據分布,線性模型難以實現准確分類。

2.2.2 構建兩層神經網絡分類

1.定義模型並訓練

learning_rate = 1e-3
lambda_l2 = 1e-5

# 這里可以看到,和上面模型不同的是,在兩層之間加入了一個 ReLU 激活函數
model = nn.Sequential(
    nn.Linear(D, H),
    nn.ReLU(),
    nn.Linear(H, C)
)
model.to(device)

# 下面的代碼和之前是完全一樣的,這里不過多敘述
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=lambda_l2) # built-in L2

# 訓練模型,和之前的代碼是完全一樣的
for t in range(1000):
    y_pred = model(X)
    loss = criterion(y_pred, Y)
    score, predicted = torch.max(y_pred, 1)
    acc = ((Y == predicted).sum().float() / len(Y))
    print("[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f" % (t, loss.item(), acc))
    display.clear_output(wait=True)
    
    # zero the gradients before running the backward pass.
    optimizer.zero_grad()
    # Backward pass to compute the gradient
    loss.backward()
    # Update params
    optimizer.step()

輸出:

[EPOCH]: 999, [LOSS]: 0.178408, [ACCURACY]: 0.949

2.分類結果

# Plot trained model
print(model)
plot_model(X, Y, model)

image
在兩層神經網絡里加入 ReLU 激活函數以后,分類的准確率得到了顯著提高,這是因為添加relu隱藏層后,相當於進行一次非線性變換,在新空間中線性可分了。

三、后記

1.關於nn.linear()可以查看這個博客 nn.Linear(20,30)理解是輸入有20個特征,構建30個神經元,每個神經元用20個參數與特征關聯

2.關於計算交叉熵損失時並沒有用預測類別predicted與真實標簽Y計算,而是將y_pred與真實標簽Y進行計算的理解

不能直接用預測類別predicted與真實標簽Y計算損失,因為假設樣本真實類別是3,預測類別為1和為2對於損失而言應該是一致的(都是預測錯誤),但是3-1與3-2的距離是不一樣的(這種距離是因為我們為類別編碼的順序導致的),在機器學習中需要計算兩者之間的距離(歐氏距離),這種普通的編碼方式並不能表示清楚距離,這也是為什么要采用獨熱編碼的原因,獨熱編碼可以保證任意兩種類別之間的距離是相同的。獨熱編碼

然后是為什么調用CrossEntropyLoss函數傳入y_pred與真實標簽Y就可以,CrossEntropyLoss函數內部實現時包含了LogSoftMax和 negative loglikelihood NLLLoss,傳入的參數只需要是預測后的數據及正確的標簽,該函數會對預測數據進行logSoftMax、對真實標簽進行獨熱編碼再計算NLLloss,可以參考這個博客

解讀一下計算NLLloss損失的過程:
1.生成模擬樣本及標簽,對標簽作 one_hot 編碼

import torch.nn.functional as F
import torch
data = torch.randn(5, 5)  # 隨機生成一組數據
target = torch.tensor([0, 2, 4, 3, 1])  # 標簽
one_hot = F.one_hot(target).float()  # 對標簽作 one_hot 編碼

查看data、target、one_hot的結果

#data
tensor([[ 2.4916,  1.4547,  0.5978, -0.5240,  1.3889],
        [ 0.7268, -0.0692,  0.2602, -1.3590,  0.6856],
        [ 0.4045, -0.5785,  1.2365, -0.0354, -1.5054],
        [ 0.4664,  0.2799, -0.8431,  0.2577,  0.1388],
        [-0.6344, -0.0642, -1.2059,  1.4325, -0.1954]])
#target
tensor([0, 2, 4, 3, 1])
#one_hot
tensor([[1., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0.],
        [0., 0., 0., 0., 1.],
        [0., 0., 0., 1., 0.],
        [0., 1., 0., 0., 0.]])

2.手動實現 NLLLoss() 函數功能


如果直接調用此函數不需要進行獨熱編碼,這里只是展示截圖中的公式,然后依據此公式計算該損失函數:

2.1假設上述data是已經經過模型計算得到的y_pred了,計算logsoftmax

exp = torch.exp(data)  # 以e為底作指數變換
sum = torch.sum(exp, dim=1).reshape(-1, 1)  # 按行求和
softmax = exp / sum  # 計算 softmax()
log_softmax = torch.log(softmax)  # 計算 log_softmax()

log_softmax結果:

log_softmax:
tensor([[-0.6345, -1.6714, -2.5283, -3.6500, -1.7372],
        [-1.1512, -1.9473, -1.6178, -3.2370, -1.1924],
        [-1.4961, -2.4791, -0.6641, -1.9360, -3.4060],
        [-1.2898, -1.4763, -2.5993, -1.4984, -1.6174],
        [-2.5483, -1.9781, -3.1197, -0.4813, -2.1093]])

2.2計算one_hot * log_softmax得到:

tensor([[-0.6345, -0.0000, -0.0000, -0.0000, -0.0000],
        [-0.0000, -0.0000, -1.6178, -0.0000, -0.0000],
        [-0.0000, -0.0000, -0.0000, -0.0000, -3.4060],
        [-0.0000, -0.0000, -0.0000, -1.4984, -0.0000],
        [-0.0000, -1.9781, -0.0000, -0.0000, -0.0000]])

可以看到這個結果相當於按照one_hot中真實的類別把logsoftmax的結果篩選出來,因為logsoftmax結果的每一行中的最大值是我們預測的類別,如果預測類別和真實類別是一樣的,那么篩選出來的結果就是每一行中的最大值(此絕對值是每一行最小),按照NLLloss公式對這些數據求和除以N取相反數,則這個loss將會是最小的。反之如果某一行預測類別和真實類別不一樣,那么這一行篩選出的數據不是這一行的最大值(此絕對值不是這一行的最小),那么最終的loss肯定不是最小的。
2.3計算nllloss

nllloss = -torch.sum(one_hot * log_softmax) / target.shape[0]  # 標簽乘以激活后的數據,求平均值,取反


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM