Pytorch入門第一課——基礎介紹

一、Pytorch簡介

1.1 pytorch

Pytorch是Torch在Python上的衍生。因爲Torch是一個使用Lua語言的神經網絡庫,Torch很好用,但是Lua不是特別流行,所有開發團隊將Lua的Torch移植到了更流行的語言Python上。

1.2 動態圖和靜態圖

幾乎所有的框架都是基於計算圖的,而計算圖又可以分爲靜態計算圖和動態計算圖,靜態計算圖先定義再運行,一次定義多次運行,而動態計算圖是在運行過程中被定義的,在運行的時候構建,可以多次構建多次運行。Pytorch使用的是動態圖,TensorFlow使用的是靜態圖。在Pytorch中每一次前向傳播(每一次運行代碼)都會創建一幅新的計算圖。
靜態圖一旦創建就不能修改,而且靜態圖定義的時候,使用了特殊的語法,就相當於新學一門語言,這意味着無法使用一些常用的Python語句,而是專門設計語法,同時在構建圖的時候必須把所有可能出現的情況都包含進去,這就導致了靜態圖過於龐大,可能佔用過高的顯存。
動態圖就沒有這個問題,它可以使用Python的if,while,for-loop等基礎語句,最終創建的計算圖取決你執行的條件分支。所以,使用動態圖的Pytorch的實現方式完全和Python的語法一致,簡潔直觀;而TensorFlow的實現不僅代碼冗長,而且十分不直觀。
動態圖的思想簡潔明瞭,更符合 人的思考過程。動態圖的方式使得我們可以任意修改前向傳播,還可以隨時查看變量的值。如果說靜態圖框架好比C++,每次運行都要編譯纔行,那麼動態圖框架就是Python,動態執行,可以交互式查看修改。
動態圖帶來的另外一個優勢是調試更容易,在Pytorch中,代碼報錯的地方,往往就是你代碼寫錯的地方,而靜態圖需要先根據你的代碼生成Graph對象,然後在session.run()時報錯,這種報錯幾乎很難找到對應的代碼中真正錯誤的地方。

1.3 爲什麼選擇Pytorch

Pytorch是當前難得的簡潔優雅且高效快速的框架。當前開源的框架中,沒有哪一個能夠在靈活性、易用性、速度這三個方面有兩個能同時超過Pytorch。
(1)簡潔:Pytorch的設計追求最少的封裝,儘量避免重複造輪子。Pytorch的設計遵循
tensor–>variable(autograd)–>nn.Moudle三個由低到高的抽象層次,分別代表高維數組(張量)、自動求導(變量)和神經網絡(層/模塊),而且這三個抽象之間聯繫緊密,可以同時進行修改和操作。
(2)速度:Pytorch的靈活性不以速度爲代價,運行速度比其他的框架比較快。
(3)易用:Pytorch是所有的框架中面向對象設計的最優雅的一個。接口設計來源於Torch,而Torch的接口設計以靈活性易用性而著稱。Pytorch的設計最符合人們的思維,它讓用戶儘可能地專注於實現自己的想法,即所思即所得,不需要考慮太多關於框架本身的束縛。
(4)活躍的社區:Pytorch提供了完整的文檔,循序漸進的指南。

1.4 Numpy和Torch

Torch自稱爲神經網絡界的Numpy,因爲它能將torch產生的tensor放在GPU中加速運算,就像Numpy會把array放在CPU中加速運算。Torch和Numpy有着很好的兼容性,可以自由的轉換numpy的array和torch的tensor。
(1)格式轉換

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()               #tensor轉爲numpy的array
array2tensor = torch.from_numpy(np_data)        # array轉爲pytorch的tensor
print('\nnumpy',np_data,
      '\ntorch',torch_data,
      '\ntensor2array',tensor2array,
      '\narray2tensor', array2tensor
      )

運行結果:

在這裏插入圖片描述
(2)運算
乘法運算:

import torch as t
import numpy as np

data = [[1,2],[3,4]]
tensor_data = t.FloatTensor(data)  # 將data轉爲float32類型的tensor

print('\nmatmul',
      '\nnumpy:',np.matmul(data,data),
      '\ntorch:',t.mm(tensor_data,tensor_data)
    )

運算結果:
在這裏插入圖片描述
還有求絕對值,加法,減法,sin等,詳情可見:http://pytorch.org/docs/torch.html

二、安裝Pytorch

一般是安裝兩個部分:pytorch和torchvision。前者是Pytorch的主模塊,後者是一些庫,包括一些網絡的預先訓練好的model和各種圖片等。

2.1 pip安裝

在這裏插入圖片描述

pip3 install torch===1.3.0 torchvision===0.4.1 -f https://download.pytorch.org/whl/torch_stable.html

2.2 conda安裝

在這裏插入圖片描述

conda install pytorch torchvision cudatoolkit=10.1 -c pytorch

2.3 編譯安裝

首先安裝可選依賴

conda install numpy pyyaml mkl setuptools cmake gcc cffi

然後下載Pytorch源碼

git clone https://github.com/pytorch/pytorch

最後編譯安裝

cd pytorch
python setup.py install

三、Pytorch基礎知識

3.1 Tensor

Tensor是Pytorch中重要的數據結構,可認爲是一個高維數組。他可以是一個數(標量)、一維數組(向量)、二維數組(矩陣)或更高維的數組。Tensor和numpy的array類似,但Tensor可以GPU加速。Tensor的使用和numpy的接口類似。

import numpy as np

x1 = t.Tensor(2,3)   #構建了2*3的矩陣,並未初始化
x2 = t.rand(3,4)      #使用[0,1]均勻分佈隨機初始化二維數組

print(x1,'\n',x2)
print(x1.size(),x2.size())   # 輸出矩陣的形狀

在這裏插入圖片描述
加法操作:

import torch as t
import numpy as np

x = t.rand(3,4)
y = t.rand(3,4)

# 三種加法
result1 = x + y

result2 = t.add(x,y)

result3 = t.Tensor(3,4)
t.add(x,y,out=result3) #指定輸出加法結果到result3

print(result1,'\n',result2,'\n',result3)

在這裏插入圖片描述
.cuda方法:
Tensor可以通過.cuda方法轉爲GPU的Tensor,從而加速運算。

import torch as t
import numpy as np

x = t.rand(3,4)
y = t.rand(3,4)

if t.cuda.is_available():
    x = x.cuda()
    y = y.cuda()
    result = x + y

print(result)

在這裏插入圖片描述
Tensor還支持很多操作,包括數學運算、線性代數、選擇、切片(與numpy類似)。

此外,Tensor和numpy的數組間互操作非常容易且快速。Tensor不支持的操作,可以先轉爲numpy數組處理,之後再轉回Tensor。Tensor和numpy對象共享內存,所以他們之間的轉換很快,幾乎不會消耗資源。這意味着其中一個變了,另一個也會隨之改變。

3.2 Autograd:自動微分

深度學習的算法實質上是通過反向傳播求導數,Pytorch的Autograd模塊實現了此功能。在Tensor上的所有操作,Autograd都能爲他們自動提供微分,避免手動計算導數的複雜過程。
autograd.Variable類是Autograd中的核心類,它簡單封裝了Tensor,並支持幾乎所有Tensor的操作。Tensor在被封裝爲Variable後,可以調用它的.backward實現反向傳播,自動計算所有梯度。
Variable主要包含三個屬性:
(1)data:保存Variable所包含的eTensor。
(2)grad:保存data對應的梯度,grad也是個Variable,而不是Tensor,他和data的形狀一樣。
(3)grad_fn:指向一個Function對象,這個Function用來反向傳播計算輸入的梯度。

grad在反向傳播過程中是累加的,這意味着每次運行反向傳播,梯度都會累加之前的梯度,所以反向傳播之前需要把梯度清零。

Variable和Tensor具有幾乎一致的接口,在實際使用中可以無縫切換。

3.3 神經網絡

Autograd實現了反向傳播功能,但是直接用來寫深度學習代碼在很多情況下還是有點複雜,torch.nn是專門爲神經網絡設計的模塊化接口。nn構建於Autograd之上,可以用來定義和運行神經網絡。nn.Module是nn中最重要的類,可以把它看作一個網絡的封裝,包含網絡各層定義及forward方法,調用forward(input)方法,可返回前向傳播的結果。
定義網絡時,需要繼承nn.Module,並實現它的forward方法,把網絡中具有可學習參數的層放在構造函數__init__中。如果某一層(如ReLU)不具有可學習的參數,則既可以放在構造函數中,也可以不放。
一個簡單的神經網絡:

import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        # nn.Module子類的函數必須在構造函數中執行父類的構造函數
        # 下式等價於nn.Module.__init__(self)
        super(Net,self).__init__()

        # 卷積層
        # 卷積層’1‘表示輸入圖片爲單通道,’6‘表示輸出通道數,’5‘表示卷積核爲5*5
        self.conv1 = nn.Conv2d(1,6,5)
        self.conv2 = nn.Conv2d(6,16,5)

        # 全連接層
        self.fc1 = nn.Linear(16*5*5,120)
        self.fc2 = nn.Linear(120,84)
        self.fc3 = nn.Linear(84,10)

    def forward(self, x):
        # 卷積-->激活-->池化
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)

        # reshape,’-1‘表示自適應
        x = x.view(x.size()[0],-1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()
print(net)

輸出結果:
在這裏插入圖片描述
只要在nn.Module的子類中定義了forward函數,backward函數就會被自動實現(利用Autograd)。在forward函數中可使用任何Variable支持的函數,還可以使用if,for循環,print,log等Python語法,寫法和標準的Python寫法一致。
網絡的可學習參數通過net.parameters()返回,net.named_parameters可同時返回可學習的參數及名稱。

...........
params = list(net.parameters())
print(len(params))
for name,parameters in net.named_parameters():
    print(name,':',parameters.size())

在這裏插入圖片描述
forward函數的輸入和輸出都是Variable,只有Variable才具有自動求導功能,Tensor是沒有的,所以在輸入時,需要把Tensor封裝成Variable。不過0.4版本以後,這兩個已經整合到了一起,不再需要這步了。
需要注意的是,torch.nn只支持mini-batches,不支持一次只輸入一個樣本,即一次必須是一個batch。如果只想輸入一個樣本,則用input.unsqueeze(0)將batch-size設爲1.

3.4 損失函數

nn實現了神經網絡種大多數的損失函數,例如nn.MSELoss用來計算均方誤差,nn.CrossEntropyLoss用來計算交叉熵損失。
如果對loss進行反向傳播溯源(使用grad_fn屬性),可看到它的計算圖如下:
在這裏插入圖片描述
當調用loss.backward()函數時,該圖會動態生成並自動微分,也會自動計算圖中參數的導數。

...........
input = t.randn(1,1,32,32)
output = net(input)
target = t.arange(0,10).reshape(1,10)
target = target.float()
criterion = nn.MSELoss()
loss = criterion(output,target)
net.zero_grad()
print('反向傳播之前的conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向傳播之後的conv1.bias的梯度')
print(net.conv1.bias.grad)

在這裏插入圖片描述

3.5 優化器

在反向傳播計算完所有參數的梯度後,還需要使用優化方法更新網絡的權重和參數。torch.optim中實現了深度學習中絕大多數的優化方法,例如RMSProp、Adam、SGD等,便於使用。

optimizer = optim.SGD(net.parameters(),lr=0.01)
# 在訓練過程中,先把梯度清零
optimizer.zero_grad()
# 計算損失
input = t.randn(1,1,32,32)
output = net(input)
target = t.arange(0,10).reshape(1,10)
target = target.float()
criterion = nn.MSELoss()
loss = criterion(output,target)
# 反向傳播
loss.backward()
# 更新參數
optimizer.step()

四、一個簡單神經網絡實例

主要步驟如下:
(1)使用torchvision加載並預處理CIFAR-10數據集
(2)定義網絡
(3)定義損失函數和優化器
(4)訓練網絡並更新網絡參數
(5)測試網絡

使用的數據集是CIFAR-10,這是一個常用的彩色圖片數據集,他有10個類別:airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck。每張圖片都是3×32×32,即3通道,分辨率爲32×32.

代碼如下:

import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
import torch.nn as nn
import torch as t
import torch.nn.functional as F
import torch.optim as optim

# 定義對數據的預處理
transform = transforms.Compose([
    transforms.ToTensor(),      # 轉爲Tensor
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))   # 歸一化
        ])
# 訓練集
trainset = tv.datasets.CIFAR10(
    root='I:/pycharm-project/DL_code-pytorch/data/',
    train=True,
    download=False,
    transform=transform)

trainloader = t.utils.data.DataLoader(
    trainset,
    batch_size=4,
    shuffle=True,
    num_workers=0)

# 測試集
testset = tv.datasets.CIFAR10(
    'I:/pycharm-project/DL_code-pytorch/data/',
    train=False,
    download=False,
    transform=transform)
testloader = t.utils.data.DataLoader(
    testset,
    batch_size=4,
    shuffle=False,
    num_workers=0)

classes = ('plane','car','bird','cat','deer','dog',
           'frog','horse','ship','truck')
           
# 定義網絡
class Net(nn.Module):
    def __init__(self):
        # nn.Module子類的函數必須在構造函數中執行父類的構造函數
        # 下式等價於nn.Module.__init__(self)
        super(Net,self).__init__()

        # 卷積層
        # 卷積層’1‘表示輸入圖片爲單通道,’6‘表示輸出通道數,’5‘表示卷積核爲5*5
        self.conv1 = nn.Conv2d(3,6,5)
        self.conv2 = nn.Conv2d(6,16,5)

        # 全連接層
        self.fc1 = nn.Linear(16*5*5,120)
        self.fc2 = nn.Linear(120,84)
        self.fc3 = nn.Linear(84,10)

    def forward(self, x):
        # 卷積-->激活-->池化
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)

        # reshape,’-1‘表示自適應
        x = x.view(x.size()[0],-1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

# 損失函數和優化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

# 訓練網絡
for epoch in range(2):
    running_loss = 0.0
    for i,data in enumerate(trainloader,0):
        # 輸入數據
        inputs,labels = data
        # 梯度清零
        optimizer.zero_grad()
        # 前向傳播和反向傳播
        outputs = net(inputs)
        loss = criterion(outputs,labels)
        loss.backward()
        # 更新參數
        optimizer.step()

        # 打印log信息
        running_loss += loss.item()
        if i%2000 ==1999:
            print('[%d,%5d] loss: %.3f'%(epoch+1,i+1,running_loss/2000))
            running_loss = 0.0
    print('Finished Training')

# 預測
correct = 0
total = 0
for data in testloader:
    images,labels = data
    outputs = net(images)
    _,predicted = t.max(outputs.data,1)
    total += labels.size(0)
    correct += (predicted == labels).sum()

    print('10000張測試集中的準確率爲:%d %%' % (100*correct/total))


運算結果:
在這裏插入圖片描述
在這裏插入圖片描述
最終的準確率爲55%。
程序中:Dataset對象是一個數據集,可以按下標訪問,返回形如(data,label)的數據。
Dataloader是一個可迭代的對象,它將dataset返回的每一條數據樣本拼接成一個batch,並提供多線程加速優化和數據打亂等操作。當程序對dataset的所有數據遍歷完一遍之後,對Dataloader也完成了一次迭代。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章