Pytorch計算機視覺實戰(更新中)

denngamin發表於2024-04-15

第一章 人工神經網路基礎

1.1 人工智慧與傳統機器學習

學習心得:

傳統機器學習(ML):需要專業的主題專家人工提取特徵,並透過一個編寫良好的演算法來破譯給定的特徵,從而判斷這幅影像中的內容。

輸入-->人工提取特徵-->特徵-->具有淺層結構的分類器-->輸出

當存在欺騙性的圖片出現時可能會失效,我們需要建立能夠精細分類多種型別的人為規則數量可能是指數級的,,因此傳統方法在非常受限的環境中很有效。
我們可以將同樣的思路擴充套件到任何領域比如:文字或結構化資料,在過去如果想透過程式設計解決現實世界的任務,那麼必須理解關於輸入資料的所有內容,並且需要編寫儘可能多的規則來覆蓋每個場景,不僅乏味,而且不能保證所有的新場景都會遵循上述規則。

然而,透過人工神經網路,神經網路提供了特徵提取和決策分類的模組,幾乎不需要手工提取特徵,只需要標記資料(如:哪些是人,哪些不是人)和神經網路架構,這樣就消除了傳統技術(如何創造規則)帶來的負擔

輸入-->特徵學習+分類器(端到端選擇)-->輸出

但是在這裡,我們需要提供大量樣本,如:大量人和不含人的圖片輸入給模型,以便它學習特徵

1.2 人工神經網路的構建模組

可以將人工神經網路看作一個數學函式,輸入一個或者多個張量(權重),輸出一個或者多個張量(權重)連線這些輸入和輸出的運算排列稱為神經網路的架構,我們可以根據手頭任務定製。

輸入層:自變數的輸入。

隱藏(中間)層:連線輸入和輸出層,並對輸入資料進行轉換,此外隱藏層包含節點, 用於將輸入值修改為更高/更低維度的值,可以透過修改中間層節點的啟用函式實現更復雜的表示。

輸出層:輸入變數後產生期望的值,樹輸出層節點數量取決於手頭任務,及試圖預測的是連續還是分類變數,若是連續的則輸出層只有一個節點,若是n個分類則輸出層就有n個節點。

啟用函式:

深度學習指的是具有更多隱藏層

1.3 前向傳播

在開始前使用隨機權重初始化

  1. 計算隱藏層的值

  2. 進行非線性啟用

    sigmoid = 1/(1 + e^-x)
    ReLU = x if x > 0 else 0
    tanh = (e^x - e-x)/(ex + e^-x)

  3. 估算輸出層的值

  4. 計算與期望值對應的損失值
    計算連續變數的損失一般用MSE(均方誤差的平方可以保證正誤差和負誤差不相互抵消)
    np.mean(np.square(pred - y))

    計算分類變數的損失一般用二元交叉熵損失(當只有兩個類別的時候)
    -np.mean((y*np.log(p))+(1-y)*np.log(1-p))
    一般多分類用分類交叉熵
    (圖)

資料集的最終損失是所有單個損失的平均值。

補:平均絕對誤差
np.mean(np.abs(pred - y))
# 在預測輸出小於1的時候,使用均方誤差可以大大降低損失量
np.mean(np.square(pred - y))

前向傳播(Numpy)

def feed_forward(inputs, outputs, weights):       
    pre_hidden = np.dot(inputs,weights[0])+ weights[1]
    hidden = 1/(1+np.exp(-pre_hidden))
    pred_out = np.dot(hidden, weights[2]) + weights[3]
    mean_squared_error = np.mean(np.square(pred_out - outputs))
    return mean_squared_error

1.4 反向傳播

透過更新權重來減小誤差的過程稱為梯度下降

Numpy實現每次對一個引數少量更新,實現梯度下降

from copy import deepcopy
import numpy as np
import matplotlib.pyplot as plt

 # 更新權重
def update_weights(inputs, outputs, weights, lr):
    original_weights = deepcopy(weights)
    updated_weights = deepcopy(weights)
    original_loss = feed_forward(inputs, outputs, original_weights)
    # 先計算一次損失
    for i, layer in enumerate(original_weights):
        for index, weight in np.ndenumerate(layer):
            temp_weights = deepcopy(weights)
            temp_weights[i][index] += 0.0001 
            # 每次將權重和偏置的一個增加很小的量
            _loss_plus = feed_forward(inputs, outputs, temp_weights)
            # 再計算一次損失
            grad = (_loss_plus - original_loss)/(0.0001)
            # 計算梯度(引數值改變引起的損失的改變)
            updated_weights[i][index] -= grad*lr
            # 更新引數,使用學習率慢慢建立信任
    return updated_weights, original_loss

# 輸入輸出
x = np.array([[1,1]])
y = np.array([[0]])

# 引數隨機初始化
W = [
np.array([[-0.0053, 0.3793],
          [-0.5820, -0.5204],
          [-0.2723, 0.1896]], dtype=np.float32).T, 
np.array([-0.0140, 0.5607, -0.0628], dtype=np.float32), 
np.array([[ 0.1528, -0.1745, -0.1135]], dtype=np.float32).T, 
np.array([-0.5516], dtype=np.float32)
]

# 繪圖
losses = []
for epoch in range(100):
    W, loss = update_weights(x,y,W,0.01)
    losses.append(loss)
plt.plot(losses)
plt.title('Loss over increasing number of epochs')

批大小:當有成千上萬的資料的時候,如果一起輸入到網路計算損失收益不是很高,因此使用批大小進行模型訓練

1.5 鏈式法則的反向傳播

利用偏導數就可以僅僅透過利用前向傳播計算出的損失來更新引數。

1.6 學習率的影響

當學習率很小的時候,權值向最優值的移動比較慢。
當學習率稍大的時候,權值先是震盪,然後快速向最優值收斂。
當學習率很大的時候,權值會達到一個很大的值,無法達到最優值。
(權值小於最優值的時候梯度為負,反之梯度為正)
一般來說學習率越小越好(0.0001-0.01)

第二章 Pytorch基礎

2.1 Pytorch 張量

學習心得:

標量是0維張量
向量可以表示一維張量(軸0)
形狀(4,)
二維矩陣表示二維張量(上到下軸0,左到右軸1)
形狀(4,3)
三維維矩陣表示三維張量(上到下軸0,左到右軸1,外到內軸2)
形狀(4,3,2)

初始化張量

import torch
x = torch.tensor([[1,2]])
y = torch.tensor([[1],[2]])

print(x.shape)
# torch.Size([1,2]) # one entity of two items
print(y.shape)
# torch.Size([2,1]) # two entities of one item each

torch.zeros(3, 4)
# tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]])
torch.ones(3, 4)
# tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
torch.randint(low=0, high=10, size=(3,4))
# tensor([[8, 2, 5, 9],
        [6, 1, 6, 0],
        [5, 6, 9, 5]])
torch.rand(3, 4) # 0-1之間隨機
# tensor([[0.3196, 0.9387, 0.9268, 0.1246],
        [0.6700, 0.7529, 0.8687, 0.3948],
        [0.2279, 0.2309, 0.0151, 0.0339]])
torch.randn(3,4) # 服從正態分佈的隨機
# tensor([[-0.4039, -1.8015,  0.9784, -1.5263],
        [ 0.9577, -1.2826,  0.2746, -0.2621],
        [-1.4713,  0.6437,  0.3326, -1.0703]])

x = np.array([[10,20,30],[2,3,4]])
# np.ndarrary
y = torch.tensor(x)
# 將numpy轉換為張量
print(type(x), type(y))
# <class 'numpy.ndarray'> <class 'torch.Tensor'>

張量運算

x = torch.tensor([[1,2,3,4], [5,6,7,8]]) 
print(x * 10)
# tensor([[10, 20, 30, 40],
#         [50, 60, 70, 80]])

x = torch.tensor([[1,2,3,4], [5,6,7,8]]) 
y = x.add(10)
print(y)
#tensor([[11, 12, 13, 14],
#        [15, 16, 17, 18]])

重塑張量

y = torch.tensor([2, 3, 1, 0]) 
y = y.view(4,1)
y 

# tensor([[2],
    [3],
    [1],
    [0]])

#另一種重塑方法squeeze方法,只適合在某個軸上數值為1才行
x = torch.randn(10,1,10)# 三維張量軸0,軸1 軸2
z1 = torch.squeeze(x, 1) # 1表示軸為1
# z1 = x.squeeze(1)
z1.shape
# torch.Size([10, 10])

x = torch.randn(10,1,10)# 三維張量軸0,軸1 軸2
z1 = torch.unsqueeze(x, 0) # 1表示軸為1
# z1 = x.unsqueeze(0)
# torch.Size([1,10,10])

除了unsqueeze也可以用None見下

z2, z3, z4 = x[None,:,:], x[:,None,:], x[:,:,None]
# torch.Size([1, 10, 10]) 
torch.Size([10, 1, 10]) 
torch.Size([10, 10, 1])

張量的矩陣乘法

y = torch.tensor([2, 3, 1, 0])
x = torch.tensor([[1,2,3,4], [5,6,7,8]])
print(torch.matmul(x, y))
# 或者 print(x@y)

張量的連線

x = torch.randn(10,10,10)
z = torch.cat([x,x], axis=0) # np.concatenate()
print('Cat axis 0:', z.shape)
# torch.Size([20, 10, 10])

提取張量最大值

x = torch.arange(25).reshape(5,5)
print('Max:', x.shape, x.max())    
# Max: torch.Size([5, 5]) tensor(24)

x.max(dim=0)# 軸0上的最大值
# torch.return_types.max(
values=tensor([20, 21, 22, 23, 24]),
indices=tensor([4, 4, 4, 4, 4]))

x.max(dim=1)# 軸1上的最大值
# torch.return_types.max(
values=tensor([ 4,  9, 14, 19, 24]),
indices=tensor([4, 4, 4, 4, 4]))

置換張量維數(不要透過重塑張量來交換維數)

x = torch.randn(10,20,30)# (0,1,2)
z = x.permute(2,0,1) # np.permute()
print('Permute dimensions:',z.shape)
# torch.Size([30, 10, 20])

dir(torch.Tensor)

檢視張量方法

help(torch.Tensor.<method>)

對某個方法檢視如何使用

2.2 張量的自動梯度

x = torch.tensor([[2., -1.], [1., 1.]], requires_grad=True)
# requires_grad=True 引數指定為張量物件計算梯度
# tensor([[ 2., -1.],
    [ 1.,  1.]], requires_grad=True)
out = x.pow(2).sum()
out.backward()
# 計算out的梯度
x.grad
# 得到out 關於 x 的梯度
# tensor([[ 4., -2.],
          [ 2.,  2.]])

一般來說,在CPU上使用Torch張量運算仍然比Numpy要快
torch的GPU最快

2.3 Pytorch構建神經網路

import torch
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]

# 轉換為浮點物件
X = torch.tensor(x).float()
Y = torch.tensor(y).float()
print(X,Y)

# 將資料註冊到GPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = X.to(device)
Y = Y.to(device)

# 定義網路架構
import torch.nn as nn

# 對nn.Module的繼承是強制的因為是神經網路的基類
# 必須利用super().__init__()來確保繼承nn.Module就可以利用事先編寫好的功能
class MyNeuralNet(nn.Module):
def __init__(self):
    super().__init__()
    self.input_to_hidden_layer = nn.Linear(2,8)#等價於 nn.Parameter(torch.rand(2,8))
    # 具體為Linear(in_features = 2 , out_features = 8 , bias = True)
    self.hidden_layer_activation = nn.ReLU()
    self.hidden_to_output_layer = nn.Linear(8,1)# nn.Parameter(torch.rand(8,1))
# 必須使用forward作為方法名,因為Pytorch保留了,用其他的會報錯!!!
def forward(self, x):
    x = self.input_to_hidden_layer(x)
    #若 nn.Parameter(torch.rand(2,8)),則x = x @ self.input_to_hidden_layer(x)
    x = self.hidden_layer_activation(x)
    x = self.hidden_to_output_layer(x)
    #若 nn.Parameter(torch.rand(8,1)),則x = x @ self.hidden_to_output_layer(x)
    return x

# 建立例項並註冊到GPU上
mynet = MyNeuralNet().to(device)

# 獲取引數權重看一下
mynet.input_to_hidden_layer.weight

# 更詳細的看下引數
mynet.parameters()
for i in  mynet.parameters():
    print(i)

# 定義MSE損失
loss_func = nn.MSELoss()
# CrossEntropyLoss() # 多分類損失
# BCELoss # 二分類損失

# 計算損失
_Y = mynet(X)
loss_value = loss_func(_Y,Y)
# 在pytorch中約定先傳預測,再傳真實資料
print(loss_value)

# 最佳化器隨機梯度下降
from torch.optim import SGD
opt = SGD(mynet.parameters(), lr = 0.001)   

loss_history = []
for _ in range(50):
    opt.zero_grad()# 重新整理上一步計算的梯度
    loss_value = loss_func(mynet(X),Y)
    loss_value.backward()# 計算梯度
    opt.step()# 根據梯度更新權重
    loss_history.append(loss_value.item())

# 繪圖
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(loss_history)
plt.title('Loss variation over increasing epochs')
plt.xlabel('epochs')
plt.ylabel('loss value')

2.4 資料集、資料載入器和批大小

批大小:用於計算損失或更新權重的資料點的數量
在有數百萬資料點的情況下很有用,

class MyDataset(Dataset):
    def __init__(self,x,y):
        self.x = torch.tensor(x).float()
        self.y = torch.tensor(y).float()
    def __len__(self):
        return len(self.x)
    def __getitem__(self, ix):
        return self.x[ix], self.y[ix]
ds = MyDataset(X, Y)

# 從ds中獲取兩個資料點
dl = DataLoader(ds, batch_size=2, shuffle=True)

# 其餘沒變可套用上方程式碼
# 變化見下
import time
loss_history = []
start = time.time()
for _ in range(50):
    for data in dl:
        x, y = data
        opt.zero_grad()
        loss_value = loss_func(mynet(x),y)
        loss_value.backward()
        opt.step()
        loss_history.append(loss_value)
end = time.time()
print(end - start)

預測資料點

val_x = [[10,11]]
val_xf = torch.tensor(val_x).float().to('device')
mynet(val_x)

自定義損失函式

def my_mean_squared_error(_y, y):
    loss = (_y-y)**2
    loss = loss.mean()
    return loss

my_mean_squared_error(mynet(X),Y)
# 和這個效果一樣 
loss_func = nn.MSELoss()
loss_value = loss_func(mynet(X),Y)
print(loss_value)

獲取中間層的值(獲取引數上面講了)

# 種子
torch.random.manual_seed(10)

# 法1
input_to_hidden = mynet.input_to_hidden_layer(X)
hidden_activation = mynet.hidden_layer_activation(input_to_hidden)
x = mynet.hidden_to_output_layer(hidden_activation)
x

# 法2 改下類的forward函式
class MyNeuralNet(nn.Module):
def __init__(self):
    super().__init__()
    self.input_to_hidden_layer = nn.Linear(2,8)
    self.hidden_layer_activation = nn.ReLU()
    self.hidden_to_output_layer = nn.Linear(8,1)
def forward(self, x):
    hidden1 = self.input_to_hidden_layer(x)
    hidden2 = self.hidden_layer_activation(hidden1)
    x = self.hidden_to_output_layer(hidden2)
    return x, hidden1

# 呼叫
_Y, _Y_hidden = mynet(X)

2.5 使用Sequential類構建神經網路

之前是透過定義一個類來構建神經網路,

model = nn.Sequential(
nn.Linear(2, 8),
nn.ReLU(),
nn.Linear(8, 1)
).to(device)

# 這個包可以輸出一個模型的摘要(總體架構)
!pip install torch_summary
from torchsummary import summary

# 輸入模型和模型大小
summary(model, torch.zeros(2,2))

# 其餘沒變化除了模型名稱
loss_func = nn.MSELoss()
from torch.optim import SGD
opt = SGD(model.parameters(), lr = 0.001)
import time
loss_history = []
start = time.time()
for _ in range(50):
    for ix, iy in dl:
        opt.zero_grad()
        loss_value = loss_func(model(ix),iy)
        loss_value.backward()
        opt.step()
        loss_history.append(loss_value)
end = time.time()
print(end - start)

# 預測新資料
val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float().to(device)
model(val)

2.6 儲存並載入Pytorch模型

儲存模型

save_path = 'mymodel.pth'
torch.save(model.state_dict(), save_path)
# torch.save(model.state_dict(), 'mymodel.pth')
# state指的是模型的當前快照
# model.state_dict()返回一個字典

# 注:一個良好的儲存做法是在呼叫torch.save()
之前將模型註冊到CPU中,有助於模型載入到任何機器上(助人為樂)
# 所以可以torch.save(model.to('cpu').state_dict(), save_path)

載入模型

# 載入模型前需要事先對模型進行權重初始化
model = nn.Sequential(
        nn.Linear(2, 8),
        nn.ReLU(),
        nn.Linear(8, 1)
).to(device)

# 然後再載入引數
state_dict = torch.load('mymodel.pth')
model.load_state_dict(state_dict)

# 預測資料
val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float().to(device)
model(val)

相關文章