【入門】PyTorch安裝及基礎操作流程


1. 安裝

homepage: get-started

默認安裝的是 GPU-CUDA10.2 版本

pip install torch torchvision

如果是CPU:

pip install torch==1.6.0+cpu torchvision==0.7.0+cpu -f https://download.pytorch.org/whl/torch_stable.html

Windows/Mac平台或其他類型的CUDA,請參考官網。

2. 入門示例程序

homepage

csdn譯文: pytorch_with_examples

2.1. Tensors, 張量

Numpy可以處理張量,但並不支持GPU進行運算。所以 torch 模塊提供了numpy類似的張量運算接口。

Numpy is a great framework, but it cannot utilize GPUs to accelerate its numerical computations. For modern deep neural networks, GPUs often provide speedups of 50x or greater, so unfortunately numpy won’t be enough for modern deep learning.

import torch
import math


dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # Uncomment this to run on GPU

# Create random input and output data
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
y = torch.sin(x)

# Randomly initialize weights
a = torch.randn((), device=device, dtype=dtype)
b = torch.randn((), device=device, dtype=dtype)
c = torch.randn((), device=device, dtype=dtype)
d = torch.randn((), device=device, dtype=dtype)

learning_rate = 1e-6
for t in range(2000):
    # Forward pass: compute predicted y
    y_pred = a + b * x + c * x ** 2 + d * x ** 3

    # Compute and print loss
    loss = (y_pred - y).pow(2).sum().item()
    if t % 100 == 99:
        print(t, loss)

    # Backprop to compute gradients of a, b, c, d with respect to loss
    grad_y_pred = 2.0 * (y_pred - y)
    grad_a = grad_y_pred.sum()
    grad_b = (grad_y_pred * x).sum()
    grad_c = (grad_y_pred * x ** 2).sum()
    grad_d = (grad_y_pred * x ** 3).sum()

    # Update weights using gradient descent
    a -= learning_rate * grad_a
    b -= learning_rate * grad_b
    c -= learning_rate * grad_c
    d -= learning_rate * grad_d


print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')

更多:數據結構_張量,及其操作

2.2. Autograd, 自動求導

github: 譯文

在上面的例子里,需要我們手動實現神經網絡的前向和后向傳播。對於簡單的兩層網絡,手動實現前向、后向傳播不是什么難事,但是對於大型的復雜網絡就比較麻煩了。

慶幸的是,我們可以使用自動微分來自動完成神經網絡中反向傳播的計算。PyTorch中autograd包提供的正是這個功能。當使用autograd時,網絡前向傳播將定義一個計算圖;圖中的節點是tensor,邊是函數,這些函數是輸出tensor到輸入tensor的映射。這張計算圖使得在網絡中反向傳播時梯度的計算十分簡單。

這聽起來復雜,但是實際操作很簡單。如果我們想計算某些的tensor的梯度,我們只需要在建立這個tensor時加入這么一句:requires_grad=True 。這個tensor上的任何PyTorch的操作都將構造一個計算圖,從而允許我們稍后在圖中執行反向傳播。如果這個tensorx的 requires_grad=True ,那么反向傳播之后x.grad將會是另一個張量,其為x關於某個標量值的梯度。

有時可能希望防止PyTorch在 requires_grad=True 的張量執行某些操作時構建計算圖;例如,在訓練神經網絡時,我們通常不希望通過權重更新步驟進行反向傳播。在這種情況下,我們可以使用 torch.no_grad() 上下文管理器來防止構造計算圖。

# 可運行代碼見本文件夾中的 two_layer_net_autograd.py
import torch

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# N是批大小;D_in是輸入維度;
# H是隱藏層維度;D_out是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10

# 產生隨機輸入和輸出數據
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)

# 產生隨機權重tensor,將requires_grad設置為True意味着我們希望在反向傳播時候計算這些值的梯度
w1 = torch.randn(D_in, H, device=device, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, requires_grad=True)

learning_rate = 1e-6
for t in range(500):

    # 前向傳播:使用tensor的操作計算預測值y。
    # 由於w1和w2有requires_grad=True,涉及這些張量的操作將讓PyTorch構建計算圖,
    # 從而允許自動計算梯度。由於我們不再手工實現反向傳播,所以不需要保留中間值的引用。
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

    # 計算並輸出loss,loss是一個形狀為()的張量,loss.item()是這個張量對應的python數值
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())

    # 使用autograd計算反向傳播。這個調用將計算loss對所有requires_grad=True的tensor的梯度。
    # 這次調用后,w1.grad和w2.grad將分別是loss對w1和w2的梯度張量。
    loss.backward()


    # 使用梯度下降更新權重。對於這一步,我們只想對w1和w2的值進行原地改變;不想為更新階段構建計算圖,
    # 所以我們使用torch.no_grad()上下文管理器防止PyTorch為更新構建計算圖
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # 反向傳播之后手動置零梯度
        w1.grad.zero_()
        w2.grad.zero_()

2.3. 定義自己的自動求導函數

在底層,每一個原始的自動求導運算實際上是兩個在Tensor上運行的函數。其中,forward函數計算從輸入Tensors獲得的輸出Tensors。而backward函數接收輸出Tensors對於某個標量值的梯度,並且計算輸入Tensors相對於該相同標量值的梯度。

在PyTorch中,我們可以很容易地通過定義torch.autograd.Function的子類並實現forward和backward函數,來定義自己的自動求導運算。之后我們就可以使用這個新的自動梯度運算符了。然后,我們可以通過構造一個實例並像調用函數一樣,傳入包含輸入數據的tensor調用它,這樣來使用新的自動求導運算。

這個例子中,我們自定義一個自動求導函數來展示ReLU的非線性。並用它實現我們的兩層網絡:

# 可運行代碼見本文件夾中的 two_layer_net_custom_function.py
import torch

class MyReLU(torch.autograd.Function):
    """
    我們可以通過建立torch.autograd的子類來實現我們自定義的autograd函數,
    並完成張量的正向和反向傳播。
    """
    @staticmethod
    def forward(ctx, x):
        """
        在正向傳播中,我們接收到一個上下文對象和一個包含輸入的張量;
        我們必須返回一個包含輸出的張量,
        並且我們可以使用上下文對象來緩存對象,以便在反向傳播中使用。
        """
        ctx.save_for_backward(x)
        return x.clamp(min=0)

    @staticmethod
    def backward(ctx, grad_output):
        """
        在反向傳播中,我們接收到上下文對象和一個張量,
        其包含了相對於正向傳播過程中產生的輸出的損失的梯度。
        我們可以從上下文對象中檢索緩存的數據,
        並且必須計算並返回與正向傳播的輸入相關的損失的梯度。
        """
        x, = ctx.saved_tensors
        grad_x = grad_output.clone()
        grad_x[x < 0] = 0
        return grad_x


device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# N是批大小; D_in 是輸入維度;
# H 是隱藏層維度; D_out 是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10

# 產生輸入和輸出的隨機張量
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)

# 產生隨機權重的張量
w1 = torch.randn(D_in, H, device=device, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    # 正向傳播:使用張量上的操作來計算輸出值y;
    # 我們通過調用 MyReLU.apply 函數來使用自定義的ReLU
    y_pred = MyReLU.apply(x.mm(w1)).mm(w2)

    # 計算並輸出loss
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())

    # 使用autograd計算反向傳播過程。
    loss.backward()

    with torch.no_grad():
        # 用梯度下降更新權重
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # 在反向傳播之后手動清零梯度
        w1.grad.zero_()
        w2.grad.zero_()

2.4. 使用神經網絡模塊nn

計算圖和autograd是十分強大的工具,可以定義復雜的操作並自動求導;然而對於大規模的網絡,autograd太過於底層。

在構建神經網絡時,我們經常考慮將計算安排成層,其中一些具有可學習的參數,它們將在學習過程中進行優化。

TensorFlow里,有類似Keras,TensorFlow-Slim和TFLearn這種封裝了底層計算圖的高度抽象的接口,這使得構建網絡十分方便。

在PyTorch中,包nn完成了同樣的功能。nn包中定義一組大致等價於層的模塊。一個模塊接受輸入的tesnor,計算輸出的tensor,而且還保存了一些內部狀態比如需要學習的tensor的參數等。nn包中也定義了一組損失函數(loss functions),用來訓練神經網絡。

這個例子中,我們用nn包實現兩層的網絡:

# 可運行代碼見本文件夾中的 two_layer_net_nn.py
import torch

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

# N是批大小;D是輸入維度
# H是隱藏層維度;D_out是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10

# 產生輸入和輸出隨機張量
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)

# 使用nn包將我們的模型定義為一系列的層。
# nn.Sequential是包含其他模塊的模塊,並按順序應用這些模塊來產生其輸出。
# 每個線性模塊使用線性函數從輸入計算輸出,並保存其內部的權重和偏差張量。
# 在構造模型之后,我們使用.to()方法將其移動到所需的設備。
model = torch.nn.Sequential(
            torch.nn.Linear(D_in, H),
            torch.nn.ReLU(),
            torch.nn.Linear(H, D_out),
        ).to(device)

# nn包還包含常用的損失函數的定義;
# 在這種情況下,我們將使用平均平方誤差(MSE)作為我們的損失函數。
# 設置reduction='sum',表示我們計算的是平方誤差的“和”,而不是平均值;
# 這是為了與前面我們手工計算損失的例子保持一致,
# 但是在實踐中,通過設置reduction='elementwise_mean'來使用均方誤差作為損失更為常見。
loss_fn = torch.nn.MSELoss(reduction='sum')

learning_rate = 1e-4
for t in range(500):

    # 前向傳播:通過向模型傳入x計算預測的y。
    # 模塊對象重載了__call__運算符,所以可以像函數那樣調用它們。
    # 這么做相當於向模塊傳入了一個張量,然后它返回了一個輸出張量。
    y_pred = model(x)

    # 計算並打印損失。我們傳遞包含y的預測值和真實值的張量,損失函數返回包含損失的張量。
    loss = loss_fn(y_pred, y)
    print(t, loss.item())

    # 反向傳播之前清零梯度
    model.zero_grad()

    # 反向傳播:計算模型的損失對所有可學習參數的導數(梯度)。
    # 在內部,每個模塊的參數存儲在requires_grad=True的張量中,
    # 因此這個調用將計算模型中所有可學習參數的梯度。
    loss.backward()

    # 使用梯度下降更新權重。
    # 每個參數都是張量,所以我們可以像我們以前那樣可以得到它的數值和梯度
    with torch.no_grad():
        for param in model.parameters():
            param.data -= learning_rate * param.grad

2.5. 設計你自己的CNN類

通過繼承nn.Module並定義forward函數,這個forward函數可以使用其他模塊或者其他的自動求導運算來接收輸入tensor,產生輸出tensor。

# 可運行代碼見本文件夾中的 two_layer_net_module.py
import torch

class TwoLayerNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        在構造函數中,我們實例化了兩個nn.Linear模塊,並將它們作為成員變量。
        """
        super(TwoLayerNet, self).__init__()
        self.linear1 = torch.nn.Linear(D_in, H)
        self.linear2 = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        在前向傳播的函數中,我們接收一個輸入的張量,也必須返回一個輸出張量。
        我們可以使用構造函數中定義的模塊以及張量上的任意的(可微分的)操作。
        """
        h_relu = self.linear1(x).clamp(min=0)
        y_pred = self.linear2(h_relu)
        return y_pred

# N是批大小; D_in 是輸入維度;
# H 是隱藏層維度; D_out 是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10

# 產生輸入和輸出的隨機張量
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 通過實例化上面定義的類來構建我們的模型。
model = TwoLayerNet(D_in, H, D_out)

# 構造損失函數和優化器。
# SGD構造函數中對model.parameters()的調用,
# 將包含模型的一部分,即兩個nn.Linear模塊的可學習參數。
loss_fn = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
    # 前向傳播:通過向模型傳遞x計算預測值y
    y_pred = model(x)

    #計算並輸出loss
    loss = loss_fn(y_pred, y)
    print(t, loss.item())

    # 清零梯度,反向傳播,更新權重
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

2.6. 控制流和參數共享

作為動態圖和權重共享的一個例子,我們實現了一個非常奇怪的模型:一個全連接的ReLU網絡,在每一次前向傳播時,它的隱藏層的層數為隨機1到4之間的數,這樣可以多次重用相同的權重來計算。

因為這個模型可以使用普通的Python流控制來實現循環,並且我們可以通過在定義轉發時多次重用同一個模塊來實現最內層之間的權重共享。

我們利用Mudule的子類很容易實現這個模型:

# 可運行代碼見本文件夾中的 dynamic_net.py
import random
import torch

class DynamicNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        在構造函數中,我們構造了三個nn.Linear實例,它們將在前向傳播時被使用。
        """
        super(DynamicNet, self).__init__()
        self.input_linear = torch.nn.Linear(D_in, H)
        self.middle_linear = torch.nn.Linear(H, H)
        self.output_linear = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        對於模型的前向傳播,我們隨機選擇0、1、2、3,
        並重用了多次計算隱藏層的middle_linear模塊。
        由於每個前向傳播構建一個動態計算圖,
        我們可以在定義模型的前向傳播時使用常規Python控制流運算符,如循環或條件語句。
        在這里,我們還看到,在定義計算圖形時多次重用同一個模塊是完全安全的。
        這是Lua Torch的一大改進,因為Lua Torch中每個模塊只能使用一次。
        """
        h_relu = self.input_linear(x).clamp(min=0)
        for _ in range(random.randint(0, 3)):
            h_relu = self.middle_linear(h_relu).clamp(min=0)
        y_pred = self.output_linear(h_relu)
        return y_pred


# N是批大小;D是輸入維度
# H是隱藏層維度;D_out是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10

# 產生輸入和輸出隨機張量
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 實例化上面定義的類來構造我們的模型
model = DynamicNet(D_in, H, D_out)

# 構造我們的損失函數(loss function)和優化器(Optimizer)。
# 用平凡的隨機梯度下降訓練這個奇怪的模型是困難的,所以我們使用了momentum方法。
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):

    # 前向傳播:通過向模型傳入x計算預測的y。
    y_pred = model(x)

    # 計算並打印損失
    loss = criterion(y_pred, y)
    print(t, loss.item())

    # 清零梯度,反向傳播,更新權重
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

3. Pytorch編寫代碼基本步驟思想

知乎: Pytorch編寫代碼基本步驟思想

分為四大步驟:

  1. 輸入處理模塊(X 輸入數據,變成網絡能夠處理的Tensor類型)
  2. 模型構建模塊(主要負責從輸入的數據,得到預測的y^, 這就是我們經常說的前向過程)
  3. 定義代價函數和優化器模塊(注意,前向過程只會得到模型預測的結果,並不會自動求導和更新,是由這個模塊進行處理)
  4. 構建訓練過程(迭代訓練過程,就是上圖表情包的訓練迭代過程)

3.1. 數據處理

對於數據處理,最為簡單的⽅式就是將數據組織成為⼀個Tensor。但許多訓練需要⽤到mini-batch,直接組織成Tensor不便於我們操作。pytorch為我們提供了Dataset和Dataloader兩個類來方便的構建。

torch.utils.data.Dataset
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False)

3.2. 模型構建

3.3. 前向傳播與反向傳播

所有的模型都需要繼承 torch.nn.Module ,另需要定義前向傳播函數 forward(),不需要考慮反向傳播(模型自動完成)。

  • 前向傳播:即使用模型的當前參數值預測結果
  • 反向傳播:根據損失值,調整參數的過程

3.4. 激活函數

cnblog: 激活函數,損失函數,優化器

在神經網絡中,對於圖像,我們主要采用了卷積的方式來處理,也就是對每個像素點賦予一個權值,這個操作顯然就是線性的。但是對於我們的樣本來說,不一定是線性可分的,為了解決這個問題,我們可以進行線性變化,或者我們引入非線性因素,解決線性模型所不能解決的問題。

這就是為什么要有激活函數:激活函數是用來加入非線性因素的,因為線性模型的表達力不夠。

所謂激活函數,並不是去激活什么,而是指如何把“激活的神經元的特征”通過函數把特征保留並映射出來(保留特征,去除一些數據中是的冗余),這是神經網絡能解決非線性問題關鍵。

  • sigmoid
  • ReLU——“抹零”
  • softmax
  • log_softmax

3.5. 網絡骨架:nn.Moudule

3.5.1. Backbone

3.6. 定義代價函數和優化器

cnblog: 激活函數,損失函數,優化器

3.6.1. 損失函數(loss function)

  • 分類任務: 交叉熵_CrossEntropy
  • 常規回歸: MSELoss
criterion = torch.nn.MSELoss(reduction='sum')

3.6.2. 優化器(Optimizer)

梯度的方向指明了誤差擴大的方向,因此在更新權重的時候需要對其取反,從而減小權重引起的誤差。

  • SGD(梯度下降)
  • Adam
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)

3.7. 構建訓練過程

def train(epoch):  # 一個epoch的訓練
    for i, data in enumerate(dataloader, 0):
        x, y = data  # 取出minibatch數據和標簽
        y_pred = model(x)  # 前向傳播
        loss = criterion(y_pred, y)  # 計算代價函數
        optimizer.zero_grad()  # 清零梯度准備計算
        1oss.backward()  # 反向傳播
        optimizer.step()  # 更新訓練參數


免責聲明!

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



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