Pytorch【60天修煉計劃】之第一階段——入門

前言:
【之前學習深度學習過程中用到了簡單的Pytorch框架,但是幾乎是斷斷續續的學習,所以非常不熟悉它的使用,所以準備開啓一個60天修煉Pytorch的計劃,我相信只要天天練,天天寫,天天看,之後一定會熟悉它甚至能夠使用它產生大的突破。】

這篇文章是這次計劃中的第一階段——入門,使用的是官方的Pytorch指南:PyTorch Tutorials,我將一步步使用Jupyter notebook進行代碼的編寫。並將內容轉化爲Markdown貼到文章中,使之形成自己的東西。


DAY1 :DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ

WHAT IS PYTORCH

import torch

1 TENSORS

# 建立一個5*3的未初始化的矩陣

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

tensor([[1.6101e+19, 1.6255e-43, 4.4659e-42], [1.5900e-30, 6.2123e-33, 4.0718e-28], [6.3770e+11, 1.1576e+27, 1.7486e+25], [1.2567e+19, 2.8404e+29, 4.9599e-14], [4.5802e-14, 3.4388e-39, 2.0993e-32]])

# 建立一個隨機未初始化的5*3矩陣

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

tensor([[0.2438, 0.8465, 0.5957], [0.1865, 0.3274, 0.9300], [0.6530, 0.9168, 0.1984], [0.7865, 0.3302, 0.2258], [0.6449, 0.1740, 0.5631]])

# 建立一個元素全0且數據類型爲long的矩陣

x = torch.zeros(5, 3, dtype = torch.long)
print(x)

tensor([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])

# 直接從數據中建立一個tensor

x = torch.tensor([5.5, 3])
print(x)

tensor([5.5000, 3.0000])

# 基於一個已經存在的tensor創建一個tensor,這些方法將重用輸入張量的屬性,例如 dtype,除非用戶提供新值

x = x.new_ones(5, 3, dtype = torch.double)
print(x)

# 重寫類型,但是size不變
x = torch.randn_like(x, dtype = torch.float)
print(x)

tensor([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.]], dtype=torch.float64) tensor([[ 0.3361, -0.1183, -0.7414], [-0.0770, -0.6797, -1.1381], [-0.3244, -0.0458, -0.3434], [-0.2456, -0.5253, 0.2276], [ 0.5424, 1.1312, -1.2819]])

# torch.Size是一個元組,所以它支持所有元組操作

print(x.size())

torch.Size([5, 3])


2 OPERATIONS

# 加法--語法1

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

tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])

# 加法--語法2

print(torch.add(x, y))

tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])

# 加法--提供一個參數保存計算結果

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

tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])

# 加法--原地加

y.add_(x)
print(y)

tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])

任何可以改變tensor內容的操作都會在方法名後加一個下劃線’’,比如 x.copy(y), x.t_()都會改變x的值

# 索引

print(x)
print(x[:, 1])

tensor([[ 0.3361, -0.1183, -0.7414], [-0.0770, -0.6797, -1.1381], [-0.3244, -0.0458, -0.3434], [-0.2456, -0.5253, 0.2276], [ 0.5424, 1.1312, -1.2819]]) tensor([-0.1183, -0.6797, -0.0458, -0.5253, 1.1312])

# resize/reshape tensor,使用torch.view方法

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)
print(x.size(), y.size(), z.size())

torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

# 如果tensor中只有一個元素,那麼使用 .item()獲得一個python數字

x = torch.randn(1)
print(x)
print(x.item())

tensor([0.0417])
0.04168638959527016

還有很多的Tensor操作,見 https://www.pytorchtutorial.com/docs/package_references/torch/


3 Numpy 橋

3.1 將一個Torch Tensor 轉化爲一個 Numpy Array

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

# 使用tensor.numpy()轉化
b = a.numpy()
print(b)

tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]

3.2 將一個Numpy Array 轉化爲一個 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.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

當我們改變了np array的值,則自動改變 torch tensor的值


4 CUDA Tensors

Tensors 可以使用 .to方法移到任何設備上

# 只有CUDA空閒時纔可以運行

if torch.cuda.is_available():
    device = torch.device("cuda")
    
    # 直接在GPU上創建一個tensor
    y = torch.ones_like(x, device = device)
    
    # 或直接用.to
    x = x.to(device)
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))

tensor([1.0417], device='cuda:0')
tensor([1.0417], dtype=torch.float64)


AUTOGRAD: AUTOMATIC DIFFERENTIATION

Pytorch中所有的神經網絡最核心的部分就是 autograd包。

autograd包爲張量上的所有操作提供 自動微分。它是一個運行時定義的框架,這意味着你的反向傳播是根據你代碼運行的方式來定義的,因此每一輪迭代都可以各不相同。

import torch


# 創建一個tensor並設置其requires_grad=Ture來追蹤計算
x = torch.ones(2, 2, requires_grad = True)
print(x)
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
y = x + 2
print(y)

# 每個通過Function計算得到的變量都有一個.grad_fn屬性
print(y.grad_fn)
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x7fdf3c564438>
z = y * y * 3
out = z.mean()

print(z, out)
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)

# .requires_grad_( ... ) changes an existing Tensor’s requires_grad flag in-place.
a.requires_grad_(True)
print(a.requires_grad)

b = (a * a).sum()
print(b.grad_fn)
False
True
<SumBackward0 object at 0x7fdf3c132a20>

1 Gradients

out.backward()

print(x.grad)
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])
x = torch.randn(3, requires_grad=True)

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

print(y)
tensor([-943.6801, 1676.0422, -134.2314], grad_fn=<MulBackward0>)
v = torch.tensor([0.2, 1.0, 0.0001], dtype = torch.float)
y.backward(v)

print(x.grad)
tensor([4.0960e+02, 2.0480e+03, 2.0480e-01])
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)
True
True
False

3 - NEURAL NETWORKS

神經網絡可以使用 torch.nn 包進行構建

現在你對autograd有了初步的瞭解,而nn建立在autograd的基礎上來進行模型的定義和微分。
nn.Module中包含着神經網絡的層,同時forward(input)方法能夠將output進行返回。

一個典型的神經網絡的訓練過程是這樣的:

  • 定義一個有着可學習的參數(或者權重)的神經網絡
  • 對着一個輸入的數據集進行迭代:
    • 用神經網絡對輸入進行處理
    • 計算代價值 (對輸出值的修正到底有多少)
    • 將梯度傳播回神經網絡的參數中
    • 更新網絡中的權重
      • 通常使用簡單的更新規則: weight=weight+lrgradientweight = weight + lr * gradient

1 定義網絡

import torch
import torch.nn as 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, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 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 the size is 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=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

當定義了 forward 函數 , backward 函數將會自動生成。
可以在 forward函數中使用任何張量運算。

一個模型中可學習的參數通過 net.parameters() 返回

params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
10
torch.Size([6, 1, 3, 3])
# 嘗試一個隨機的32*32的輸入

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[-0.0575,  0.0170, -0.1036,  0.1189,  0.0374, -0.0866,  0.0576, -0.1601,
         -0.0319,  0.0746]], grad_fn=<AddmmBackward>)
# 對所有的參數的梯度緩衝區進行歸零
net.zero_grad()

# 使用隨機的梯度進行反向傳播
out.backward(torch.randn(1, 10))

Note:

整個torch.nn包只接受那種小批量樣本的數據,而非單個樣本。 例如,nn.Conv2d 能夠接受一個四維(nSamples x nChannels x Height x Width)的Tensor。
如果你拿的是單個樣本,使用 input.unsqueeze(0) 來加一個假維度就可以了。

複習一下前面我們學到的:

  • torch.Tensor - 一個支持自動求導操作的多維數組

  • nn.Module - 神經網絡模塊。便捷的數據封裝,能夠將運算移往GPU,還包括一些輸入輸出的東西。

  • nn.Parameter - 一種變量,當將任何值賦予Module時自動註冊爲一個參數。

  • autograd.Function - 實現了使用自動求導方法的前饋和後饋的定義。每個Variable的操作都會生成至少一個獨立的Function節點,與生成了Variable的函數相連之後記錄下操作歷史。

目前,我們已經明白的部分:

  • 定義了一個神經網絡。
  • 處理了輸入以及實現了反饋。

還剩下:

  • 計算代價。
  • 更新網絡中的權重。

2 代價函數

一個代價函數以(output, target)對作爲輸入,並計算輸出與目標值的差距

在nn包中有許多的代價函數。最簡單的是 nn.MSELoss計算輸出與目標之間的均方誤差。

output = net(input)
target = torch.randn(10)

#將輸出與目標值的shape相同
target = target.view(1, -1) 
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)
tensor(0.5986, grad_fn=<MseLossBackward>)

現在,如果你跟隨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
<MseLossBackward object at 0x7f2db03a0048>
<AddmmBackward object at 0x7f2db03a0160>
<AccumulateGrad object at 0x7f2db03b95f8>

3 反向傳播

爲了反向傳播偏差,我們需要運行 loss.backward(),不過還需要 清除現有的梯度,否則梯度將被累積爲現有梯度。

net.zero_grad()

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0007,  0.0011, -0.0035, -0.0100,  0.0030, -0.0035])

4 更新權值

SGD的更新權值公式爲: weight=weight+lrgradientweight = weight + lr * gradient
可使用簡單的python語句實現:

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

但是若使用神經網絡,就直接在 torch.optim運用這些方式(SGD,Nesterov-SGD, Adam, RMSProp等等)

import torch.optim as optim

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

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

4 - TRAINING A CLASSIFIER

數據

因爲需要處理像圖像、文字、音頻或視頻這類數據時,可以使用標準python包來導入數據生成一個numpy類型,然後在轉化成Tensor

  • For 【images】, packages such as 【Pillow, OpenCV】 are useful
  • For 【audio】, packages such as 【scipy and librosa】
  • For 【text】, either 【raw Python or Cython based loading, or NLTK and SpaCy】 are useful

特別地,對於圖像,可以使用 torchvision這個包,其中包含了一些現成的數據集如:Imagenet, CIFAR10, MNIST等等。同時還有一些轉換圖像用的工具,torchvision.datasetstorch.utils.data.DataLoader

這個指南使用的是CIFAR10的數據集。我們要進行的分類的類別有:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。 這個數據集中的圖像都是32x32x3的圖片。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cKHxp0DE-1569585287594)(attachment:image.png)]

我們要按順序做這幾個步驟:

  • 使用torchvision來讀取並預處理CIFAR10數據集
  • 定義一個卷積神經網絡
  • 定義一個代價函數
  • 在神經網絡中訓練訓練集數據
  • 使用測試集數據測試神經網絡

1 導入和標準化 CIFAR10

使用 torchvision

import torch
import torchvision
import torchvision.transforms as transforms

# torchvision數據集的輸出是在[0, 1]範圍內的PILImage圖片。
# 我們此處使用歸一化的方法將其轉化爲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(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
%matplotlib inline 
# 在jupyter中只有加上這行代碼才能顯示圖片
 
def imshow(img):
    img = img / 2 + 0.5
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()
    
# 獲得一些隨機圖片
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)))

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-x1N8lh9n-1569585287595)(output_5_0.png)]

  dog  deer   cat  deer

2 定義一個卷及神經網絡

x -> conv -> relu -> maxPool -> conv -> relu -> maxpool -> linear -> relu -> linear -> relu -> linear -> y

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 定義一個代價函數和優化器

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr = 0.001, momentum = 0.9)

4 訓練神經網絡

for epoch in range(2):
    
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 獲得輸入數據,data是一個[inputs, labels]的列表
        inputs, labels = data
        
        optimizer.zero_grad()
        
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
        if i % 2000 == 1999:
            print('[epoch: %d]  [i: %d]  [loss: %.3f]' %(epoch + 1, i + 1, running_loss/2000))
            running_loss = 0.0

print('Finished Training!')
[epoch: 1]  [i: 2000]  [loss: 2.195]
[epoch: 1]  [i: 4000]  [loss: 1.829]
[epoch: 1]  [i: 6000]  [loss: 1.644]
[epoch: 1]  [i: 8000]  [loss: 1.575]
[epoch: 1]  [i: 10000]  [loss: 1.504]
[epoch: 1]  [i: 12000]  [loss: 1.461]
[epoch: 2]  [i: 2000]  [loss: 1.359]
[epoch: 2]  [i: 4000]  [loss: 1.322]
[epoch: 2]  [i: 6000]  [loss: 1.340]
[epoch: 2]  [i: 8000]  [loss: 1.307]
[epoch: 2]  [i: 10000]  [loss: 1.298]
[epoch: 2]  [i: 12000]  [loss: 1.259]
Finished Training!

5 用測試集測試模型

# 先顯示一下測試集圖片

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

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

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7wygEJB6-1569585287596)(output_13_0.png)]

GroundTruth:    cat  ship  ship plane
outputs = net(images)

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))
Predicted:    cat   car   car plane
# 在整個測試集中測試

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        
        # torch.max(a, 1)返回a中每一行最大值的那個元素,且返回其索引
        _, predicted = torch.max(outputs.data, 1)
        
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    
print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))
Accuracy of the network on the 10000 test images: 56 %
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))

with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            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 : 57 %
Accuracy of   car : 57 %
Accuracy of  bird : 48 %
Accuracy of   cat : 40 %
Accuracy of  deer : 24 %
Accuracy of   dog : 42 %
Accuracy of  frog : 75 %
Accuracy of horse : 73 %
Accuracy of  ship : 70 %
Accuracy of truck : 71 %

6 在GPU上訓練

如果CUDA空閒,那麼我們首先定義我們的設備爲第一個可見的cuda設備

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
cuda:0
net.to(device)
# 記住,每一步都需要把輸入和目標傳給GPU。
inputs, labels = data[0].to(device), data[1].to(device)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章