深度學習框架PyTorch一書的學習-第一/二章


參考https://github.com/chenyuntc/pytorch-book/tree/v1.0

希望大家直接到上面的網址去查看代碼,下面是本人的筆記

 

pytorch的設計遵循tensor-> variable(autograd)-> nn.Module三個由低到高的抽象層次,分別代表高維數組(張量)、自動求導(變量)和神經網絡(層/模塊)。這三個抽象之間聯系緊密,可以同時進行修改和操作

 

在IPython和Jupyter notebook兩個工具中使用了Jupyter notebook

更多安裝內容可見:深度學習PyTorch環境安裝——mac

 

1.tensor

Tensor是pytorch中的重要數據結構,可認為是一個高維數組。它可以是一個數(標量)、一維數組(向量)、二維數組(矩陣)或更高維的數組

其簡單的用法可見pytorch學習-WHAT IS PYTORCH

1)導入包

from __future__ import print_function
import torch as t

 

2)僅分配空間,不初始化

#構建5*3矩陣,只是分配了空間,未初始化
x = t.Tensor(5,3)
x

返回:

tensor([[ 0.0000e+00,  2.0000e+00, -1.0696e+12],
        [-1.5849e+29,  2.4158e-12,  1.1625e+33],
        [ 8.9605e-01,  1.1632e+33,  5.6003e-02],
        [ 7.0374e+22,  5.7453e-44,  0.0000e+00],
        [ 0.0000e+00,  2.0000e+00, -9.4044e+11]])

 

3)初始化

#使用[0,1]均勻分布隨機初始化二維數組
x = t.rand(5, 3)
x

返回:

tensor([[0.0136, 0.1579, 0.7545],
        [0.0127, 0.3194, 0.0126],
        [0.7604, 0.0289, 0.4808],
        [0.3374, 0.8478, 0.4047],
        [0.2441, 0.4211, 0.5480]])

 

4)獲得size

print(x.size())
#兩種寫法,得到二維數組的行列數
#支持該寫法的原因是因為torch.Size是tuple對象的子類,因此其支持tuple的所有操作,如索引等
x.size()[0], x.size(1)

返回:

torch.Size([5, 3])
(5, 3)

 

5)加法的幾種寫法

y = t.rand(5,3)
y

返回:

tensor([[0.6840, 0.5716, 0.3848],
        [0.8492, 0.5449, 0.3555],
        [0.4052, 0.5689, 0.1948],
        [0.2308, 0.6252, 0.8855],
        [0.0487, 0.2880, 0.9232]])

1>第一種

x + y

返回:

tensor([[0.6975, 0.7295, 1.1394],
        [0.8619, 0.8643, 0.3681],
        [1.1656, 0.5978, 0.6756],
        [0.5681, 1.4729, 1.2902],
        [0.2928, 0.7091, 1.4712]])

2>第二種

t.add(x,y)

結果相同

3》第三種:指定加法結果的輸出目標為result

result = t.Tensor(5,3) #預分配空間存儲結果
t.add(x, y, out=result)
result

結果相同

4》第四種:改變y的值

y.add_(x)
y

y變為上面的加法結果

函數名后面帶下划線_的函數會修改tensor本身

 

2.自動微分

深度學習算法的本質是通過反向函數求導數,pytorch的Autograd模塊實現了此功能。在Tensor上的所有操作,Autograd都能夠為他們自動提供微分,避免手動計算的復雜過程

其中Autograd.Variable是Autograd的核心類。Tensor被封裝成Variable后,可以調用它的.backward實現反向傳播,自動計算所有梯度

 

  • data : 保存Variable所包含的Tensor
  • grad : 保存data對應的梯度,grad也是一個Variable(即也有data\grad\grad_fn三個值),而不是Tensor,它和data的形狀一樣
  • grad_fn : 指向一個Function對象,這個Function用來反向傳播計算輸入的梯度

1)導入包

from __future__ import print_function 
import torch as t

from
torch.autograd import Variable

 

2)新建

#使用Tensor新建一個Variable
x = Variable(t.ones(2, 2),requires_grad = True)
x

返回:

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

此時查看該值的grad和grad_fn是沒有返回值的,因為沒有進行任何操作

x.grad_fn
x.grad

 

3)進行求和操作,查看梯度

y = x.sum()
y

返回:

tensor(4., grad_fn=<SumBackward0>)

這時候可查看:

y.grad_fn

返回:

<SumBackward0 at 0x122782978>

可知y是變量Variable x進行sum操作求來的,但是這個時候y.grad是沒有返回值的,因為沒有使用y進行別的操作

這個時候的x.grad也是沒有值的,雖然使用x進行了sum操作,但是還沒有對y反向傳播來計算梯度

y.backward()#反向傳播,計算梯度

然后再查看:

#因為y = x.sum() = (x[0][0] + x[0][1] + x[1][0] + x[1][1])
#每個值的梯度都為1
x.grad

返回:

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

 

但是需要注意的一點就是grad在反向求和的過程中是累加的,所以反向傳播前要把梯度清零

比如再運行一遍反向傳播:

y.backward()
x.grad

可見返回:

tensor([[2., 2.],
        [2., 2.]])

清零操作:

x.grad.data.zero_()#清零

再運行即可:

y.backward()
x.grad

返回:

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

 

在這里簡單解釋一下梯度的計算,如圖示:

下面我們使用代碼實現一下看看是不是這樣:

x = Variable(t.ones(2,2), requires_grad = True)
y = x +1
z = y*y
k = z.mean()

然后進行反向傳播,獲得x的梯度:

k.backward()
x.grad

返回果然是:

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

 

4)Variable和Tensor有近乎一致的接口,在實際中可以無縫切換

x = Variable(t.ones(4,5))
y = t.cos(x)#傳入的是Variable
x_tensor_cos = t.cos(x.data)#傳入的是Tensor
print(y)
x_tensor_cos

返回:

tensor([[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
        [0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
        [0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
        [0.5403, 0.5403, 0.5403, 0.5403, 0.5403]])
tensor([[0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
        [0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
        [0.5403, 0.5403, 0.5403, 0.5403, 0.5403],
        [0.5403, 0.5403, 0.5403, 0.5403, 0.5403]])

可見這樣子得到的值並沒有什么區別,返回的都是Tensor,而不是Variable

 

3.神經網絡

torch.nn是專門為神經網絡設計的模塊化接口

nn構建於Autograd之上,可用來定義和運行神經網絡。nn.Module是nn中最重要的類,可以把它看作一個網絡的封裝,包含網絡各層定義和forward方法,調用forward(input)可返回前向傳播的結果

 

舉例最早的卷積神經網絡LeNet(判斷圖片中的字符),來看看如何使用nn.Module實現

1)定義網絡

繼承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,參數1表示輸入圖片為單通道,即灰度圖像,為1*32*32,6表示輸出通道數,即過濾器的通道數,5表示過濾器為5*5
        #所以下一層得到的值為6*28*28,作為輸入
        self.conv1 = nn.Conv2d(1, 6, 5)
        #卷積層2
        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) #最后的輸出是10,是因為判斷的數值分成了10類
    
    def forward(self, x):
        #先conv1卷積->再relu激活->再max_pool池化
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) #(2, 2)相當於池化將高度和寬度縮減一半,得到結果為6*14*14
        x = F.max_pool2d(F.relu(self.conv2(x)), 2) #激活后為16*10*10,池化后16*5*5,因為大小是正方形,所以第二個參數可以只有一個值,效果和上面的(2,2)是相同的
        
        #reshape,將圖形扁平化,使其由16*5*5的三維立體變成1*(16*5*5)的二維矩陣
        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)
        

返回:

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(in_features=400, 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)
)

只要在nn.Module的子類中定義了forward函數,backward函數就會被自動實現(利用Autograd)

在forward函數中可以使用任何Variable支持的函數

上面定義的網絡的可學習參數可通過net.Parameters()返回,net,named_parameters()可同時返回可學習的參數及名稱

params = list(net.parameters())
print(len(params)) #返回10

如果想要查看參數的名字等信息,可見:

for name, parameter in net.named_parameters():
    print(name, ':', parameter.size())

返回:

conv1.weight : torch.Size([6, 1, 5, 5])
conv1.bias : torch.Size([6])
conv2.weight : torch.Size([16, 6, 5, 5])
conv2.bias : torch.Size([16])
fc1.weight : torch.Size([120, 400])
fc1.bias : torch.Size([120])
fc2.weight : torch.Size([84, 120])
fc2.bias : torch.Size([84])
fc3.weight : torch.Size([10, 84])
fc3.bias : torch.Size([10])

 

forward函數的輸入和輸出都是Variable,只有Variable才具有自動求導功能,Tensor是沒有的。所以在輸入時,需要把Tensor封裝成Variable

這里nn.Conv2d的輸入必須是四維的,即樣本數*通道數*高*寬

input = Variable(t.randn(1, 1, 32, 32)) #即輸入是一張灰度的32*32大小的圖片
output = net(input)
output.size()

返回:

torch.Size([1, 10])

 

net.zero_grad() #所有參數的梯度清零
output.backward(Variable(t.ones(1, 10))) #反向傳播

這里的backward()中為什么需要傳入參數Variable(t.ones(1, 10))呢?沒有傳入就會報錯:

RuntimeError: grad can be implicitly created only for scalar outputs

原因可見:pytorch的backward

 

這時候就可以查看參數的梯度了:

params = list(net.parameters())
print(params[0].grad)

返回:

tensor([[[[ 1.0658e-01, -3.3146e-02,  3.7709e-03, -3.0634e-02,  1.1791e-02],
          [ 3.1975e-02,  4.4254e-02, -1.1230e-02,  8.9412e-02,  7.1382e-02],
          [-5.6357e-02,  9.1797e-05,  1.8966e-02, -4.4844e-03,  5.7771e-02],
          [-1.2467e-03, -6.6853e-02, -6.9389e-02,  1.6258e-02,  7.9231e-02],
          [-3.1562e-02,  2.0557e-02,  2.1569e-02,  8.2486e-02, -1.1457e-01]]],


        [[[-4.3394e-02, -4.5307e-03,  2.7980e-02,  1.8965e-03,  1.0381e-01],
          [-2.9320e-02,  2.8249e-02, -3.9046e-02,  1.8078e-02, -4.6308e-02],
          [-3.2397e-03, -1.8470e-02, -6.8865e-03,  5.4334e-03, -6.9593e-02],
          [-3.7685e-02, -1.2786e-01, -5.6480e-03, -5.7637e-02, -2.2518e-02],
          [ 6.1855e-03,  2.5202e-02,  3.0603e-02,  2.8058e-02, -6.9573e-02]]],


        [[[-9.2159e-02,  8.4124e-02, -7.6889e-02, -4.0251e-03, -2.3461e-02],
          [ 1.3258e-01, -4.1542e-02,  1.5377e-02, -2.0014e-02, -1.3080e-02],
          [-1.8869e-02,  7.1884e-02, -5.8952e-02,  2.0899e-02,  1.5558e-02],
          [ 2.7937e-02,  7.8165e-02, -4.8216e-02, -1.6983e-02,  4.5583e-02],
          [-7.2376e-02,  5.4218e-03,  7.5949e-02, -6.5286e-02,  3.4859e-03]]],


        [[[ 9.8840e-02, -9.1584e-02,  4.7770e-02,  6.2509e-03,  8.5981e-03],
          [ 4.2380e-02,  1.8999e-02,  2.4990e-02,  8.1884e-03, -3.5326e-02],
          [-1.2791e-02,  9.0136e-02,  8.5160e-06,  2.5163e-02,  7.3440e-03],
          [-8.7427e-02,  4.0755e-02, -1.1036e-02,  5.3797e-02,  7.8865e-03],
          [-3.1478e-02,  6.5078e-02,  3.5277e-02, -3.8831e-02, -8.0675e-02]]],


        [[[-7.8461e-03,  5.7134e-02,  5.0355e-02, -1.2885e-02, -1.3467e-02],
          [-1.3372e-02, -1.9114e-02, -9.7893e-02, -8.6965e-03,  1.4722e-02],
          [ 1.9918e-02,  2.6398e-02,  1.5144e-02,  4.7445e-02,  6.1139e-03],
          [-4.0234e-02, -3.6375e-02,  5.7755e-02, -1.3112e-02, -6.8822e-02],
          [-2.2237e-02, -3.2318e-02,  1.3830e-03, -1.1355e-02, -2.0060e-02]]],


        [[[-9.9372e-02, -6.5939e-02,  5.9860e-02,  1.4672e-02, -3.6069e-02],
          [ 3.5409e-02, -8.1433e-02,  3.1477e-03, -2.8791e-02,  5.8678e-02],
          [ 3.1001e-02,  5.9960e-02, -7.9541e-02, -1.0646e-01,  1.2691e-02],
          [ 2.8374e-02, -1.0153e-01,  4.3227e-02,  7.5082e-02, -1.0772e-02],
          [-4.9453e-02, -9.9464e-03, -4.2608e-02, -4.2708e-02,  5.5077e-02]]]])

⚠️torch.nn只支持mini_batches,不支持一次只輸入一個樣本,即一次必須是一個batch

如果只想輸入一個樣本,則使用input.unsqueeze(0)將batch_size設為1

 

4.損失函數

nn實現了神經網絡中大多數的損失函數,例如nn.MSELoss用來計算均方誤差,nn.CrossEntropyLoss用來計算交叉熵損失

output = net(input)
print(output)
target = Variable(t.arange(0, 10))
print(target) criterion
= nn.MSELoss() loss = criterion(output, target) loss

運行時遇到下面的錯誤:

RuntimeError: Expected object of scalar type Float but got scalar type Long for argument #2 'target'

解決辦法就是轉換target類別為float即可:

output = net(input)
print(output)
target = Variable(t.arange(0, 10)).float() #更改
print(target) criterion
= nn.MSELoss() loss = criterion(output, target) loss

返回:

tensor([[-0.0552, -0.0790,  0.0176, -0.1695, -0.1261, -0.0572,  0.0072, -0.1686,
          0.0739, -0.0895]], grad_fn=<AddmmBackward>)
tensor([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
tensor(29.0489, grad_fn=<MseLossBackward>)

 

如果對該loss進行反向傳播溯源,即使用grad_fn屬性

首先從之前的網絡可知其整個計算圖為:

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

當我們使用loss.backward()時,該圖會動態生成並自動微分,也會自動計算途中參數(parameter)的導數

net.zero_grad() #把net中所有可學習的參數的梯度清零
print("反向傳播前conv1.bias的梯度:")
print(net.conv1.bias.grad)
loss.backward()
print("反向傳播后conv1.bias的梯度:")
print(net.conv1.bias.grad)

返回:

反向傳播前conv1.bias的梯度:
tensor([0., 0., 0., 0., 0., 0.])
反向傳播后conv1.bias的梯度:
tensor([-0.0503,  0.0551,  0.0052,  0.0081,  0.0084,  0.0665])

 

5.優化器

torch.optim中實現了深度學習中絕大多數的優化方法,例如RMSProp,Adam,SGD等

import torch.optim as optim
#新建一個優化器,指定要調整的參數和學習率
optimizer = optim.SGD(net.parameters(), lr = 0.01)

#在訓練的過程中
#先將梯度清零(和net.zero_grad()效果一樣)
optimizer.zero_grad()

#計算損失
output = net(input)
loss = criterion(output, target)

#反向傳播
loss.backward()

#更新參數
optimizer.step()

 

6.數據加載和預處理

torchvision實現了常用的圖像數據加載功能,例如Imagenet,CIFAR10,MNIST等,以及常用的數據轉換操作,這極大方便了數據加載

 

小試牛刀:CIFAR-10分類

可見pytorch例子學習——TRAINING A CLASSIFIER

 


免責聲明!

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



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