1. 程式人生 > >PyTorch教程001

PyTorch教程001

第一節 基本的操作

什麼是PyTorch?

它是基於Python語言的科學計算工具包,其設計目的包括以下兩個方面:

  • 替代Numpy從而可以使用強大的GPU進行計算
  • 一個深讀學習的研究平臺,致力於提供最大的靈活性與速度

我們開始吧~

Tensors

Tensors和Numpy的ndarrays很像,與之不同之處在於Tensor可以在GPU上進行運算從而獲得更快的計算速度。

from __future__ import print_function
import torch

利用pyTorch構造一個未初始化的5×3的矩陣:

x = torch.Tensor(5
,3) print(x)
-9.3921e+17  4.5628e-41 -9.3921e+17
 4.5628e-41  0.0000e+00  0.0000e+00
 0.0000e+00  0.0000e+00  0.0000e+00
 0.0000e+00  0.0000e+00  0.0000e+00
 0.0000e+00  0.0000e+00  0.0000e+00
[torch.FloatTensor of size 5x3]

類似於上一步,構造一個隨機(值域為[0,1])初始化的矩陣:

x = torch.rand(5,3)
print(x)
 0.9144  0.5597  0.7737
 0.4661  0.0467  0.1902
 0.7286  0.9863  0.7427
 0.5697  0.6273  0.0305
 0.5571  0.3872  0.0925
[torch.FloatTensor of size 5x3]

獲得其尺寸:

print(x.size())
torch.Size([5, 3])

* Note: * Torch.Size實際上是一個tuple,因此支援相同的操作。

操作

用於資料操作的語法有很多,下面來看加法的操作:

Addition:語法一

y = torch.rand(5,3)
print(x+y)
 1.5705  0.7976  1.5798
 0.8943  0.2229  0.4182
 0.8277  1.7598  1.7178
 0.8377  0.9944  0.0811
 0.9289  1.3111  0.7678
[torch.FloatTensor of size 5x3]

Addition:語法二

print(torch.add(x,y))
 1.5705  0.7976  1.5798
 0.8943  0.2229  0.4182
 0.8277  1.7598  1.7178
 0.8377  0.9944  0.0811
 0.9289  1.3111  0.7678
[torch.FloatTensor of size 5x3]

Addition:給一個輸出的Tensor

result = torch.Tensor(5,3)
torch.add(x,y,out=result)
print(result)
 1.5705  0.7976  1.5798
 0.8943  0.2229  0.4182
 0.8277  1.7598  1.7178
 0.8377  0.9944  0.0811
 0.9289  1.3111  0.7678
[torch.FloatTensor of size 5x3]

Addition:替換

#把x的值加到y上
y.add_(x)
print(y)
 1.5705  0.7976  1.5798
 0.8943  0.2229  0.4182
 0.8277  1.7598  1.7178
 0.8377  0.9944  0.0811
 0.9289  1.3111  0.7678
[torch.FloatTensor of size 5x3]

注意,每個用於替換的操作其後都會接一個 _。例如x.copy_(y),x.t_(),都會改變x的值。

你可以使用標準的numpy的操作來使用資料:

print(x[:,1])#列印矩陣的所有行及第二列
 0.5597
 0.0467
 0.9863
 0.6273
 0.3872
[torch.FloatTensor of size 5]

Numpy Bridge:Tensor與Numpy的相互轉換

Tensor和Numpy在轉換後會共享資料所佔的記憶體區域,改變兩者的任何一個的值,兩者的值都會同時發生改變。

將Torch的Tensor轉換為Numpy的Array

a = torch.ones(5)
print(a)
 1
 1
 1
 1
 1
[torch.FloatTensor of size 5]
b = a.numpy()
print(b)
[ 1.  1.  1.  1.  1.]

驗證:共享記憶體的機制

a.add_(1)
print('a is :\n',a)
print('b is :\n',b)
a is :

 2
 2
 2
 2
 2
[torch.FloatTensor of size 5]

b is :
 [ 2.  2.  2.  2.  2.]

將numpy陣列轉換為torch的Tensor

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)

np.add(a,1,out=a)
print(a)
print(b)
[ 2.  2.  2.  2.  2.]

 2
 2
 2
 2
 2
[torch.DoubleTensor of size 5]

除了CharTensor外,運算在CPU模式的所有Tensor都支援與Numpy的相互轉換。

Autograd: 自動求導數

PyTorch的所有神經網路的核心功能都在於autograd類。該類提供所有對Tensor操作的求導。它是一個define-by-run的框架,這意味著你的反向傳播是有你的程式碼在執行時決定的,因而每個單一的迭代可以不同。

Variable

autograd.Variable是該類的一個核心類,它包含著Tensor並且支援幾乎所有相關的操作。一旦你完成了前向傳播的計算任務,你可以呼叫backward()使得所有的梯度自動的進行計算。

你可以使用Tensor.data訪問原始Tensor的資料,與此同時,梯度的值貝類加到屬性Tensor.grad中。
這裡寫圖片描述
圖示為Variable的內部組成

import torch
from torch.autograd import Variable

建立一個Variable

x = Variable(torch.ones(2,2),requires_grad=True)
print(x)
Variable containing:
 1  1
 1  1
[torch.FloatTensor of size 2x2]

對變數進行操作:

y = x + 2
print(y)
Variable containing:
 3  3
 3  3
[torch.FloatTensor of size 2x2]

y是由一個操作的結果所生成的,因此它擁有一個creator。

print(y.creator)
<torch.autograd._functions.basic_ops.AddConstant object at 0x7f31d7e47828>

對y進行更多的操作:

z = y * y * 3
out = z.mean()

print(z,out)
Variable containing:
 27  27
 27  27
[torch.FloatTensor of size 2x2]
 Variable containing:
 27
[torch.FloatTensor of size 1]

梯度

讓反向傳播 out.backward() 等價於操作out.backward(torch.Tensor([1.0]))

out.backward()

列印求導結果d(out)dx

print(x.grad)
Variable containing:
 4.5000  4.5000
 4.5000  4.5000
[torch.FloatTensor of size 2x2]

得出上面結果的原因:

out=3(x+2)2d(out)dx=32(x+2)x=[1111]d(out)dx=[4.54.54.54.5]

你可以利用autograd做很多瘋狂的事情。

x = torch.rand(3)
x = Variable(x,requires_grad=True)
y = x*2
count = 0
while y.data.norm() < 1000:
    y = y * 2#執行好多次,導致x有好多次的累加
    count = count + 1

print(y)
print(count)
Variable containing:
  279.9745
 1013.4999
  346.2041
[torch.FloatTensor of size 3]

10
gradients = torch.FloatTensor([0.1,1.0,0.0001])
y.backward(gradients)
print(x.grad)
Variable containing:
  204.8000
 2048.0000
    0.2048
[torch.FloatTensor of size 3]

神經網路

神經網路可以通過torch.nn包來實現。
現在我們對於autograd有了一個大概的印象,nn依賴於autograd來定義模型以及對它們求導數。一個nn.Module包含layers和方法forward(input),返回的結果為output

例如,觀看下面分類數字手寫識別的卷積神經網路:
<img src="mnist.png">
它是一個簡單的feed-forward網路。他將輸入一層一層的傳遞下去,最終給出一個輸出結果。
一個典型的神經網路的訓練流程如下所下:

  • 定義一個擁有可學習引數的神經網路
  • 遍歷資料集,將其內容作為輸入
  • 處理輸入資料
  • 計算代價(神經網路的輸出與正確值的距離)
  • 反向傳播
  • 更新神經網路中的引數,一般形式如下:
    weight=weight+learningrategradient

定義神經網路

讓我們來定義一個神經網路:

import torch
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        #輸入影象通道為1,6個輸出通道,5×5的卷積核
        self.conv1 = nn.Conv2d(1,6,5)
        # 輸入影象通道為6,16個輸出通道,5×5的卷積核
        self.conv2 = nn.Conv2d(6,16,5)
        #y = Wx + b 的仿射變換
        self.fc1 = nn.Linear(16*5*5,120)
        self.fc2 = nn.Linear(120,84)
        self.fc3 = nn.Linear(84,10)

    def forward(self,x):
        # 2×2的最大池化視窗
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
        # 若是一個正方形,可以用一個數來表示
        x = F.max_pool2d(F.relu(self.conv2(x)),2)
        x = x.view(-1,self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
    def num_flat_features(self,x):
        size = x.size()[1:]# 取得出batch外的所有維度
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

net = Net()
print(net)
Net (
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear (400 -> 120)
  (fc2): Linear (120 -> 84)
  (fc3): Linear (84 -> 10)
)

我們必須自定義forward函式,backward函式(用於計算梯度)使用autograd自動定義,你可以在forward函式中使用任何Tensor的操作

用於學習的網路引數可以通過net.parameters()獲取。

params = list(net.parameters())
print(len(params))
print(params[0].size())# 卷積層1的引數
print(params[0][1,:])
10
torch.Size([6, 1, 5, 5])
Variable containing:
(0 ,.,.) = 
 -0.1029  0.0501 -0.1202  0.0168 -0.0679
 -0.1803  0.1550 -0.1125 -0.0424 -0.0865
  0.0836  0.0452 -0.0619  0.1352  0.0567
  0.1265 -0.1011 -0.0643 -0.1561 -0.1950
 -0.0530  0.1887  0.1961 -0.0866  0.0307
[torch.FloatTensor of size 1x5x5]

forward函式的輸入是一個autograd.Variable,輸出同樣也是。

input = Variable(torch.randn(1,1,32,32))
out = net(input)
print(out)
Variable containing:
-0.1454  0.0262  0.0150 -0.0715 -0.1060  0.0319 -0.0391 -0.0759 -0.0447 -0.1261
[torch.FloatTensor of size 1x10]

清空所有引數的梯度的快取,並且使用隨機梯度進行反步操作:

net.zero_grad()
out.backward(torch.randn(1,10))

注意:torch.nn只支援mini-batches,完整的torch.nn包只支援樣本的mini-batch作為輸入,而不是單個的樣本。
例如,nn.Conv2d會接收一個4維的Tensor作為輸入,包括nSamples x nChannels x Height x Width
如果只有一個樣本,可以通過使用 input.unsqueeze(0)來新增一個偽造的batch維度。

在進行更深入的研究之前,讓我們概括一下目前所見到的一些類。

torch.Tensor:一個多維陣列
autograd.Variable:包含一個Tensor並接受應用在其上的歷史操作。擁有和Tensor一樣的API,額外的有像是backward()。同樣包含著Tensor中的梯度。
nn.Mosule:神經網路模型,便於封裝引數,易於轉到GPU進行計算,匯出,載入等。
nn.Parameter:一種型別的Variable,當作為Module的一個屬性時,自動的註冊為一個引數。
autograd.Function:實現用於自動求導操作的前向傳播和反向傳播。每一個Variable操作,創造至少一個單一的Function節點,用於建立一個Variable並編碼其歷史。

目前為止我們學習了:

  • 定義一個神經網路
  • 處理輸入,呼叫反向傳播

接下來我們還要學習:

  • 計算代價函式
  • 更新神經網路權重

代價函式(Loss Function)

代價函式通過計算來自於input的(output,target),計算一個值然後估計輸出值偏離於目標值的程度。
nn包中有很多種loss function,一個簡單的loss是:nn.MSELoss,該loss用於計算輸出和目標之間的均方誤差。
例如:

output = net(input)
target = Variable(torch.arange(1,11))#假設的target :1,2,3,4,5,6,7,8,9,10
criterion = nn.MSELoss()

loss = criterion(output,target)
print(loss)
Variable containing:
 39.1498
[torch.FloatTensor of size 1]

現在,如果你沿著反向傳播的方向,使用其creator屬性,你會看到一個計算圖:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

因此,當我們呼叫loss.backward()的時候。完整的圖就是對loss求導後的結果,每一個圖中的Variable將會擁有使用gradient累加的它們自己的.grad Variable。
為了更好的說明,進行一些反向傳播的操作:

print(loss.creator) # MSELoss
print(loss.creator.previous_functions[0][0])# Linear
print(loss.creator.previous_functions[0][0].previous_functions[0][0])# Relu
<torch.nn._functions.thnn.auto.MSELoss object at 0x7f319130cf28>
<torch.nn._functions.linear.Linear object at 0x7f319130cd68>
<torch.nn._functions.thnn.auto.Threshold object at 0x7f319130cc88>

Backprop(反向傳播)

為了將誤差進行反向傳播,我們只需要執行loss.backward()。你需要清空已經存在的梯度資訊,否則現存的梯度值會被累加到下一步要計算的梯度當中去。
現在,我們呼叫一下loss.backward(),然後看一下conv1的偏置梯度在反向傳播前後的變化。

net.zero_grad() # 清空梯度的快取
print("反向傳播前的conv1.bias.grad:")
print(net.conv1.bias.grad)

loss.backward()

print("反向傳播後的conv1.bias.grad:")
print(net.conv1.bias.grad)
反向傳播前的conv1.bias.grad:
Variable containing:
 0
 0
 0
 0
 0
 0
[torch.FloatTensor of size 6]

反向傳播後的conv1.bias.grad:
Variable containing:
1.00000e-02 *
  1.6163
 -1.4633
 -1.4045
 -0.7913
  4.5722
  1.9762
[torch.FloatTensor of size 6]

至此,我們明白瞭如何使用loss functions

Update the weights

最簡單的權重更新規則被稱為Stochastic Gradient Descent (SGD):

weight=weightlearningrategradient
使用簡單的python程式碼實現上述功能:
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

當然,PyTorch同樣提供了很多的類似函式包括SGD,Nesterov-SGD, Adam, RMSProp等等。所有的這些方法都被封裝到包torch.optim中。

import torch.optim as optim
# 建立自己的optimizer
optimizer = optim.SGD(net.parameters(),lr=0.01)
# 在訓練的迴圈中
optimizer.zero_grad() # 清空梯度快取
output = net(input)
loss = criterion(output,target)
loss.backward()
optimizer.step() # 更新操作

訓練一個影象分類器

我們將從以下幾個步驟來進行:

  • 使用torchvision載入並且正則化資料集CIFAR10
  • 定義一個卷積神經網路
  • 定義一個損失函式
  • 在training data 上訓練網路
  • 在test data上測試神經網路

載入並正則化CIFAR10

使用torchvision,它用於載入CIFAR10時十分方便

import torch
import torchvision
import torchvision.transforms as transforms

輸出的torchvision資料集是PILImages的格式,範圍為[0,1],我們需要將它轉化為正則化的Tensor,範圍是[-1,1]

transform = transforms.Compose(
[
    transforms.ToTensor(),
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))   
])
trainset = torchvision.datasets.CIFAR10(root='./data',train=True,
                                       download=True,transform=transform)
trainloader = torch.utils.data.DataLoader(trainset,batch_size=4,
                                         shuffle=True,num_workers=2)
testset = torchvision.datasets.CIFAR10('./data',train=False,
                                      download=True,transform=transform)
testloader = torch.utils.data.DataLoader(testset,batch_size=4,
                                        shuffle=False,num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
Files already downloaded and verified
Files already downloaded and verified

選幾張圖片做展示:

import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
#用於顯示影象的函式
def imshow(img):
    img = img / 2 + 0.5
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg,(1,2,0)))

# 隨機的獲取一些訓練影象
dataiter = iter(trainloader)
images,labels = dataiter.next()
#顯示影象
imshow(torchvision.utils.make_grid(images))
#列印標籤
print(' '.join('%10s'%classes[labels[j]] for j in range(4)))
      frog        dog      plane        car

png這裡寫圖片描述

定義一個卷積神經網路

複製之前神經網路的定義,修改網路的1個通道輸入為3個通道

from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.conv1 = nn.Conv2d(3,6,5)
        self.pool = nn.MaxPool2d(2,2)
        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 = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1,16*5*5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

定義Loss函式和優化器(optimizer)

這裡我們使用交叉熵損失函式和SGD做為優化器

import torch.optim as optim
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
        # 使用Variable包裝這些資料
        inputs,labels = Variable(inputs),Variable(labels)
        # 清空快取的梯度資訊
        optimizer.zero_grad()
        #forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs,labels)
        loss.backward()
        optimizer.step()
        #列印統計資訊
        running_loss += loss.data[0]
        if i%2000 == 1999:# 列印沒2000 mini-batches
            print('[%d,%5d] loss :%.3f'%
                  (epoch+1,i+1,running_loss / 2000))
            running_loss = 0.0

print("結束訓練")
[1, 2000] loss :2.184
[1, 4000] loss :1.821
[1, 6000] loss :1.644
[1, 8000] loss :1.548
[1,10000] loss :1.498
[1,12000] loss :1.456
[2, 2000] loss :1.380
[2, 4000] loss :1.345
[2, 6000] loss :1.323
[2, 8000] loss :1.329
[2,10000] loss :1.297
[2,12000] loss :1.266
結束訓練

在測試集上測試神經網路

我們已經把神經網路在訓練集上訓練了2遍,現在我們需要檢驗一下神經網路是否學到了什麼。
我們根據神經網路輸出的類標籤與實際的類標籤進行比較,將神經網路預測準確的樣本歸類到correct predictions中去。
首先,我們先是一張測試及中的圖片來熟悉一下資料:

dataiter = iter(testloader)
images,labels = dataiter.next()

# 顯示圖片
imshow(torchvision.utils.make_grid(images))
print("GroundTruth:",''.join('%10s'%classes[labels[j]] for j in range(4)))
GroundTruth:        cat      ship      ship     plane

這裡寫圖片描述

好了,讓我們看一下神經網路是如何識別上面的圖片的:

outputs = net(Variable(images))

輸出是10個類的能量。高的能量集中到一個類標籤中,則神經網路則會將資料歸類為該類。因此,讓我們找出最高的能量的索引。

_,predicted = torch.max(outputs.data,1)
print('Predicted:',' '.join('%5s'%classes[predicted[j][0]]
                           for j in range(4)))
Predicted:  ship   car   car  ship

接下來看看該神經網路在整個資料集上的表現如何。

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

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))
Accuracy of the network on the 10000 test images: 55 %

看起來不錯哦,因為它比隨機的結果好很多(隨機的準確率為10%)
那麼,那些類別可以被很好的區分,那些類別卻又不能被很好的區分呢?

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
    images, labels = data
    outputs = net(Variable(images))
    _, predicted = torch.max(outputs.data, 1)
    c = (predicted == labels).squeeze()
    for i in range(4):
        label = labels[i]
        class_correct[label] += c[i]
        class_total[label] += 1


for i in range(10):
    print('Accuracy of %5s : %2d %%' % (
        classes[i], 100 * class_correct[i] / class_total[i]))
Accuracy of plane : 66 %
Accuracy of   car : 81 %
Accuracy of  bird : 33 %
Accuracy of   cat : 44 %
Accuracy of  deer : 36 %
Accuracy of   dog : 54 %
Accuracy of  frog : 59 %
Accuracy of horse : 55 %
Accuracy of  ship : 73 %
Accuracy of truck : 44 %

目標達成

  • Understanding PyTorch’s Tensor library and neural networks at a high level.
  • Train a small neural network to classify images