1. 程式人生 > >PyTorch 深度學習:60分鐘快速入門

PyTorch 深度學習:60分鐘快速入門

此教程為翻譯官方地址

本教程的目標:

  • 深入理解PyTorch張量庫和神經網路
  • 訓練一個小的神經網路來分類圖片

這個教程假設你熟悉numpy的基本操作。

注意

請確保torchtorchvision包已經安裝。

一、PyTorch 是什麼

他是一個基於Python的科學計算包,目標使用者有兩類

  • 為了使用GPU來替代numpy
  • 一個深度學習援救平臺:提供最大的靈活性和速度

開始

張量(Tensors)

張量類似於numpy的ndarrays,不同之處在於張量可以使用GPU來加快計算。

from __future__ import print_function
import
torch

構建一個未初始化的5*3的矩陣:

x = torch.Tensor(5, 3)
print(x)

輸出 :

1.00000e-10 *
 -1.1314  0.0000 -1.1314
  0.0000  0.0000  0.0000
  0.0000  0.0000  0.0000
  0.0000  0.0000  0.0000
  0.0000  0.0000  0.0000
[torch.FloatTensor of size 5x3]

構建一個隨機初始化的矩陣

x = torch.rand(5, 3)
print(x)

輸出:

0.2836  0.6710  0.5146
 0.8842  0.2821  0.7768
 0.3409  0.0428  0.6726
 0.1982  0.6950  0.6040
0.0272 0.6586 0.3555 [torch.FloatTensor of size 5x3]

獲取矩陣的大小:

print(x.size())

輸出:

torch.Size([5, 3])

注意

torch.Size實際上是一個元組,所以它支援元組相同的操作。

操作

張量上的操作有多重語法形式,下面我們一加法為例進行講解。

語法1

y = torch.rand(5, 3)
print(x + y)

輸出:

0.9842  1.5171  0.8148
 1.1334  1.6540  1.5739
 0.9804  1.1647  0.4759
 0.6232
0.2689 1.0596 1.0777 1.1705 0.3206 [torch.FloatTensor of size 5x3]

語法二

print(torch.add(x, y))

輸出:

0.9842  1.5171  0.8148
 1.1334  1.6540  1.5739
 0.9804  1.1647  0.4759
 0.6232  0.2689  1.0596
 1.0777  1.1705  0.3206
[torch.FloatTensor of size 5x3]

語法三:給出一個輸出向量

result = torch.Tensor(5, 3)
torch.add(x, y, out=result)
print(result)

輸出:

0.9842  1.5171  0.8148
 1.1334  1.6540  1.5739
 0.9804  1.1647  0.4759
 0.6232  0.2689  1.0596
 1.0777  1.1705  0.3206
[torch.FloatTensor of size 5x3]

語法四:原地操作(in-place)

# 把x加到y上
y.add_(x)
print(y)

輸出:

0.9842  1.5171  0.8148
 1.1334  1.6540  1.5739
 0.9804  1.1647  0.4759
 0.6232  0.2689  1.0596
 1.0777  1.1705  0.3206
[torch.FloatTensor of size 5x3]

注意

任何在原地(in-place)改變張量的操作都有一個’_’字尾。例如x.copy_(y), x.t_()操作將改變x.

你可以使用所有的numpy索引操作。

print(x[:, 1])

輸出:

1.5171
1.6540
1.1647
0.2689
1.1705
[torch.FloatTensor of size 5]

稍後閱讀

這裡描述了一百多種張量操作,包括轉置,索引,數學運算,線性代數,隨機數等。

numpy橋

把一個torch張量轉換為numpy陣列或者反過來都是很簡單的。

Torch張量和numpy陣列將共享潛在的記憶體,改變其中一個也將改變另一個。

把Torch張量轉換為numpy陣列

a = torch.ones(5)
print(a)

輸出:

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

輸出:

[ 1.  1.  1.  1.  1.]
<class 'numpy.ndarray'>

通過如下操作,我們看一下numpy陣列的值如何在改變。

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

[ 2.  2.  2.  2.  2.]

把numpy陣列轉換為torch張量

看看改變numpy陣列如何自動改變torch張量。

[ 2.  2.  2.  2.  2.]

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

所有在CPU上的張量,除了字元張量,都支援在numpy之間轉換。

CUDA張量

使用.cuda函式可以將張量移動到GPU上。

# let us run this cell only if CUDA is available
if torch.cuda.is_available():
    x = x.cuda()
    y = y.cuda()
    x + y

**指令碼總執行時間:**0.003秒

二、Autograd: 自動求導(automatic differentiation)

PyTorch 中所有神經網路的核心是autograd包.我們首先簡單介紹一下這個包,然後訓練我們的第一個神經網路.

autograd包為張量上的所有操作提供了自動求導.它是一個執行時定義的框架,這意味著反向傳播是根據你的程式碼如何執行來定義,並且每次迭代可以不同.

接下來我們用一些簡單的示例來看這個包

變數(Variable)

autograd.Variableautograd包的核心類.它包裝了張量(Tensor),支援幾乎所有的張量上的操作.一旦你完成你的前向計算,可以通過.backward()方法來自動計算所有的梯度.

你可以通過.data屬性來訪問變數中的原始張量,關於這個變數的梯度被計算放入.grad屬性中

Variable

對自動求導的實現還有一個非常重要的類,即函式(Function).

變數(Variable)和函式(Function)是相互聯絡的,並形成一個非迴圈圖來構建一個完整的計算過程.每個變數有一個.grad_fn屬性,它指向建立該變數的一個Function,使用者自己建立的變數除外,它的grad_fn屬性為None.

如果你想計算導數,可以在一個變數上呼叫.backward().如果一個Variable是一個標量(它只有一個元素值),你不必給該方法指定任何的引數,但是該Variable有多個值,你需要指定一個和該變數相同形狀的的grad_output引數(檢視API發現實際為gradients引數).

import torch
from torch.autograd import 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是通過一個操作建立的,所以它有grad_fn,而x是由使用者建立,所以它的grad_fn為None.

print(y.grad_fn)
print(x.grad_fn)

輸出:

<torch.autograd.function.AddConstantBackward object at 0x7faa6f3bdd68>
None

在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]

梯度(Gradients)

現在我們來執行反向傳播,out.backward()相當於執行out.backward(torch.Tensor([1.0]))

out.backward()

輸出outx的梯度d(out)/dx:

print(x.grad)

輸出:

Variable containing:
 4.5000  4.5000
 4.5000  4.5000
[torch.FloatTensor of size 2x2]

你應該得到一個值全為4.5的矩陣,我們把變數out稱為o,則o=14izi,zi=3(xi+2)2,zi|xi=1=27,因此oxi=32(xi+2),oxi=92=4.5

我們還可以用自動求導做更多有趣的事!

x = torch.randn(3)
x = Variable(x, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)

輸出:

Variable containing:
 682.4722
-598.8342
 692.9528
[torch.FloatTensor of size 3]
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)

print(x.grad)

輸出:

Variable containing:
  102.4000
 1024.0000
    0.1024
[torch.FloatTensor of size 3]

稍後閱讀:

以上指令碼的總的執行時間為0.003秒.

三、神經網路

可以使用torch.nn包來構建神經網路.

你已知道autograd包,nn包依賴autograd包來定義模型並求導.一個nn.Module包含各個層和一個faward(input)方法,該方法返回output.

例如,我們來看一下下面這個分類數字影象的網路.

mnist

convnet

他是一個簡單的前饋神經網路,它接受一個輸入,然後一層接著一層的輸入,知道最後得到結果.

神經網路的典型訓練過程如下:
1. 定義神經網路模型,它有一些可學習的引數(或者權重);
2. 在資料集上迭代;
3. 通過神經網路處理輸入;
4. 計算損失(輸出結果和正確值的差距大小)
5. 將梯度反向傳播會網路的引數;
6. 更新網路的引數,主要使用如下簡單的更新原則:

weight = weight - learning_rate * gradient

定義網路

我們先定義一個網路

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

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 5*5 square convolution
        # kernel

        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: 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):
        # max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If size is a square you can only specify a single number
        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:] # all dimensions except the batch dimension
        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))
for param in params:
    print(param.size())

輸出:

10
torch.Size([6, 1, 5, 5])
torch.Size([6])
torch.Size([16, 6, 5, 5])
torch.Size([16])
torch.Size([120, 400])
torch.Size([120])
torch.Size([84, 120])
torch.Size([84])
torch.Size([10, 84])
torch.Size([10])

forward的輸入和輸出都是autograd.Variable.注意:這個網路(LeNet)期望的輸入大小是32*32.如果使用MNIST資料集來訓練這個網路,請把圖片大小重新調整到32*32.

input = Variable(torch.randn(1, 1, 32, 32))
out = net(input)
print(out)

輸出:

Variable containing:
-0.0536 -0.0548 -0.1079  0.0030  0.0521 -0.1061 -0.1456 -0.0095  0.0704  0.0259
[torch.FloatTensor of size 1x10]

將所有引數的梯度快取清零,然後進行隨機梯度的的反向傳播.

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

注意

  1. torch.nn 只支援小批量輸入,整個torch.nn包都只支援小批量樣本,而不支援單個樣本
  2. 例如,nn.Conv2d將接受一個4維的張量,每一維分別是sSamples * nChannels * Height * Width(樣本數*通道數*高*寬).
  3. 如果你有單個樣本,只需使用input.unsqueeze(0)來新增其它的維數.

在繼續之前,我們回顧一下到目前為止見過的所有類.

回顧

  • torch.Tensor-一個多維陣列
  • autograd.Variable-包裝一個Tensor,記錄在其上執行過的操作.除了擁有Tensor擁有的API,還有類似backward()的API.也儲存關於這個向量的梯度.
  • nn.Module-神經網路模組.封裝引數,移動到GPU上執行,匯出,載入等
  • nn.Parameter-一種變數,當把它賦值給一個Module時,被自動的註冊為一個引數.
  • autograd.Function-實現一個自動求導操作的前向和反向定義,每個變數操作至少建立一個函式節點,(Every Variable operation, creates at least a single Function node, that connects to functions that created a Variable and encodes its history.)

現在,我們包含了如下內容:

  • 定義一個神經網路
  • 處理輸入和呼叫backward

剩下的內容:

  • 計算損失值
  • 更新神經網路的權值

損失函式

一個損失函式接受一對(output, target)作為輸入(output為網路的輸出,target為實際值),計算一個值來估計網路的輸出和目標值相差多少.

nn包中有幾種不同的損失函式.一個簡單的損失函式是:nn.MSELoss,他計算輸入(個人認為是網路的輸出)和目標值之間的均方誤差.

例如:

out = net(input)
target = Variable(torch.arange(1, 11))  # a dummy target, for example
criterion = nn.MSELoss()

loss = criterion(out, target)
print(loss)

輸出:

Variable containing:
 38.1365
[torch.FloatTensor of size 1]

現在,你反向跟蹤loss,使用它的.grad_fn屬性,你會看到向下面這樣的一個計算圖:

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

所以, 當你呼叫loss.backward(),整個圖關於損失被求導,圖中所有變數將擁有.grad變數來累計他們的梯度.

為了說明,我們反向跟蹤幾步:

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU

輸出:

<torch.autograd.function.MSELossBackward object at 0x7fb3c0dcf4f8>
<torch.autograd.function.AddmmBackward object at 0x7fb3c0dcf408>
<AccumulateGrad object at 0x7fb3c0db79e8>

反向傳播

為了反向傳播誤差,我們所需做的是呼叫loss.backward().你需要清除已存在的梯度,否則梯度將被累加到已存在的梯度.

現在,我們將呼叫loss.backward(),並檢視conv1層的偏置項在反向傳播前後的梯度.

輸出(官網的例子)

conv1.bias.grad before backward
Variable containing:
 0numpy
 0
 0
 0
 0
 0
[torch.FloatTensor of size 6]

conv1.bias.grad after backward
Variable containing:
-0.0317
-0.1682
-0.0158
 0.2276
-0.0148
-0.0254
[torch.FloatTensor of size 6]

本人執行輸出

conv1.bias.grad before backward
None
conv1.bias.grad after backward
Variable containing:
 0.0011
 0.1170
-0.0012
-0.0204
-0.0325
-0.0648
[torch.FloatTensor of size 6]

不同之處在於backward之前不同,官網示例的梯度為0,而實際執行出來卻是None.

現在我們已知道如何使用損失函式.

稍後閱讀

神經網路包包含了各種用來構成深度神經網路構建塊的模組和損失函式,一份完整的文件檢視這裡

唯一剩下的內容:

  • 更新網路的權重

更新權重

實踐中最簡單的更新規則是隨機梯度下降(SGD).

weight=weightlearning_rategradient
![]

我們可以使用簡單的Python程式碼實現這個規則.

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

然而,當你使用神經網路是,你想要使用各種不同的更新規則,比如SGD,Nesterov-SGD,Adam, RMSPROP等.為了能做到這一點,我們構建了一個包torch.optim實現了所有的這些規則.使用他們非常簡單:

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

#in your trainning loop:
optimizer.zero_grad()  # zero the gradient buffers
output = net(input)
loss = criter(output, target)
loss.backward()
optimizer.setp() # does the update

指令碼總執行時間: 0.367秒

四、訓練一個分類器

你已經看到如何去定義一個神經網路,計算損失值和更新網路的權重.

你現在可能在思考.

關於資料

通常,當你處理影象,文字,音訊和視訊資料時,你可以使用標準的Python包來載入資料到一個numpy陣列中.然後把這個陣列轉換成torch.*Tensor.

  • 對於影象,有諸如Pillow,OpenCV包.
  • 對於音訊,有諸如scipy和librosa包
  • 對於文字,原始Python和Cython來載入,或者NLTK和SpaCy是有用的.

對於視覺,我們建立了一個torchvision包,包含常見資料集的資料載入,比如Imagenet,CIFAR10,MNIST等,和影象轉換器,也就是torchvision.datasetstorch.utils.data.DataLoader.

這提供了巨大的便利,也避免了程式碼的重複.

在這個教程中,我們使用CIFAR10資料集,它有如下10個類別:’airplane’,’automobile’,’bird’,’cat’,’deer’,’dog’,’frog’,’horse’,’ship’,’truck’.這個資料集中的影象大小為3*32*32,即,3通道,32*32畫素.

cifar10

訓練一個影象分類器

我們將一次按照下列順序進行:

  1. 使用torchvision載入和歸一化CIFAR10訓練集和測試集.
  2. 定義一個卷積神經網路
  3. 定義損失函式
  4. 在訓練集上訓練網路
  5. 在測試機上測試網路

1. 載入和歸一化CIFAR0

使用torchvision載入CIFAR10是非常容易的.

import torch
import torchvision
import torchvision.transforms as transforms

torchvision的輸出是[0,1]的PILImage影象,我們把它轉換為歸一化範圍為[-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(root='./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

# functions to show an image

def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()

# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

輸出:

truck   cat   car plane

sphx_glr_cifar10_tutorial_001

2. 定義一個卷積神經網路

從之前的神經網路一節複製神經網路程式碼,並修改為接受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()

3. 定義損失函式和優化器