學習筆記:《深度學習框架PyTorch入門與實踐》(陳雲)Part1
學習筆記:《深度學習框架PyTorch入門與實踐》(陳雲)Part1
2017年1月,FAIR團隊在GitHub上開源了PyTorch。
常見的深度學習框架:
(1)Theono(2008)已經停止開發,不建議學習;
(2)TensorFlow(2015,Google):使用資料流圖進行數值計算,圖中節點代表數學運算,而圖中的邊代表在這些節點之間傳遞的多維陣列(張量)。不完美但流行,社群強大,適合生產環境;
(3)Keras:高層神經網路API,由純Python編寫而成並使用TensorFlow、Theano及CNTK作為後端。過度封裝導致程式過於緩慢。入門最簡單,但是不夠靈活,使用受限;
(4)Caffe/Caffe2:核心語言是C++,支援命令列、PyThon和MATLAB介面,既可以在CPU上執行,也可在GPU上執行。文件不夠完善,但效能優異,幾乎全平臺支援(Caffe2),適合生產環境;
(5)MXNet:文件略混亂,但分散式效能強大,語言支援最多,適合AWS平臺使用;
(6)CNTK(2015,MicroSoft):社群不夠活躍,但是效能突出,擅長語言方面的研究。
計算圖:
幾乎所有的框架都是基於計算圖的,而計算圖又可以分為靜態計算圖和動態計算圖。PyTorch基於動態圖,TensorFlow基於靜態圖。
為什麼選擇Pytorch?
(1)簡潔:追求最少的封裝,避免重複造輪子。PyTorch設計遵循tensor->variable(autograd)->nn.module三個由低到高的抽象層次,分別代表高維陣列(張量)、自動求導(變數)和神經網路(層/模組),而且這三個抽象之間聯絡緊密,可同時進行修改和操作。
(2)速度:許多評測中,Pytorch速度表現勝過TensorFlow和Keras等框架。
(3)易用。
(4)活躍的社群。
目前,官方已經支援Windows PyTorch。
2 PyTorch安裝
需提前安裝:Python + Pip + CUDA + Numpy。
Anaconda對於Python相當於Ubuntu對於Linux,即Anaconda是Python的一個發行版,將Python和許多常用的package打包,方便Pythoners直接使用。
Pip是一個安裝和管理Python包的工具。
安裝Pip:sudo python get-pip.py
升級Pip:pip install -U pip
簡單來說,pip和pip3一樣,只是為了區別python2和python3之間的呼叫,避免衝突而進行的設定。如果你的電腦只安裝了Python,那麼你無法使用pip3。同時安裝了Python和Python3,使用pip命令,新安裝的庫會在python2.x/site-packages目錄下,使用pip3命令,會在python3.x/site-packages目錄下。
CUDA(Comupute Unified Device Architecture):英偉達公司推出的一種基於新的並行程式設計模型和指令集架構的通用計算架構。
cuDNN:用於深度神經網路的GPU加速庫。
安裝PyTorch需安裝2個模組:主模組torch用來搭建網路,輔模組torchvision用來生成圖片、視訊資料集和一些流行的模型類和預訓練模型。
安裝(PyTorch官網):
# Python 3.7
pip3 install http://download.pytorch.org/whl/cu80/torch-0.4.1-cp37-cp37m-win_amd64.whl
pip3 install torchvision
驗證是否安裝成功:
python (進入Python環境)
import python
import torchvision
#不報錯則成功
3 學習環境配置
在從事科學計算相關工作時,Ipython和Jupyter Notebook是兩個必不可少的工具。
Ipython是一個互動式計算系統,可認為增強版的Python Shell。
Jupyter是一個互動式筆記本,現支援執行40多種程式語言。
Jupyter:
(1)安裝:pip install jupyter。
(2)開啟:命令列輸入jupyter notebook即可啟動。或開啟瀏覽器,輸入http://127.0.0.1:8888訪問。
(3)使用:單擊頁面右上角“new”選項,選擇相應Notebook型別(python3/python2)可新建一個notebook,寫入程式碼,按“ctrl+enter”快捷鍵即可執行程式碼。
4 PyTorch入門第一步
4.1 Tensor
Tensor是PyTorch中重要的資料結構,可認為是一個高維陣列,它可以是一個數(標量)、一維陣列(向量)、二維陣列(矩陣)或更高維的陣列。Tensor和numpy的ndarrays類似,但Tensor可以使用GPU加速。Tensor的使用和numpy及MATLAB的介面十分相似。下面展示Tensor的基本使用方法。
from __future__ import print_function
import torch as t
#構建5*3矩陣,只是分配了空間,未初始化
x1=t.Tensor(5,3)
print(x1)
#使用[0,1]均勻分佈隨機初始化二維陣列
x2=t.rand(5,3)
print(x2)
print(x2.size()) #檢視x2的形狀
print(x2.size()[1]) #檢視列的個數,等價於x2.size(1)
Out:
tensor([[-386620439650957628105129136431824896.0000,
0.0000,
-386620439650957628105129136431824896.0000],
[ 0.0000,
0.0000,
0.0000],
[ 0.0000,
0.0000,
0.0000],
[ 0.0000,
0.0000,
0.0000],
[ 0.0000,
0.0000,
0.0000]])
tensor([[0.4898, 0.9725, 0.1614],
[0.2631, 0.0199, 0.9133],
[0.0881, 0.8390, 0.6090],
[0.7283, 0.7939, 0.4258],
[0.5043, 0.2234, 0.3374]])
torch.Size([5, 3])
3
from __future__ import print_function
import torch as t
x=t.ones(5,3)
y=t.ones(5,3)
#加法的第一種寫法
print(x + y)
#加法的第二種寫法
print(t.add(x,y))
#加法的第三種寫法
result=t.Tensor(5,3) #預先分配空間
t.add(x,y,out=result) #輸入到result
print(result)
Out:
tensor([[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]])
tensor([[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]])
tensor([[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]])
from __future__ import print_function
import torch as t
x=t.ones(5,3)
y=t.ones(5,3)
print("最初y")
print(y)
print("第一種加法,y的結果")
y.add(x) #普通加法,不改變y的內容
print(y)
print("第二種加法,y的結果")
y.add_(x) #inplace加法,改變y的內容
print(y)
Tensor還支援很多操作,包括數學運算、線性代數、選擇、切片等,其介面設計與numpy極為相似。詳細會在後續講解。
Tensor和numpy的陣列間的互操作非常容易且快速。Tensor不支援的操作,可以先轉為numpy陣列處理,之後再轉回Tensor。
from __future__ import print_function
import torch as t
a=t.ones(5) #新建一個全是1的Tensor
b=a.numpy() #Tensor->Numpy
print(a)
print(b)
Out:
tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]
import numpy as np
a=np.ones(5)
b=t.from_numpy(a) #Numpy->Tensor
print(a)
print(b)
Out:
[1. 1. 1. 1. 1.]
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
Tensor和numpy物件共享記憶體,所以它們之間的轉換很快,而且幾乎不會消耗資源。這也意味著,如果其中一個變了,另外一個也會隨之改變。
import numpy as np
a=np.ones(5)
b=t.from_numpy(a) #Numpy->Tensor
print(a)
print(b)
b.add_(1) #以_結尾的函式會修改自身
print(a)
print(b) #Tensor和Numpy共享記憶體
Out:
[1. 1. 1. 1. 1.]
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
Tensor可通過.cuda方法轉為GPU的Tensor,從而享受GPU帶來的加速運算。在此處可能會發現GPU運算的速度並未提升太多,這是因為x和y較小且運算簡單,而且將資料從記憶體轉移到視訊記憶體還需要花費額外的開銷。GPU的優勢需在大規模資料和複雜運算下才能體現出來。
#在不支援cuda的機器上,下一步不會執行
import torch as t
if t.cuda.is_available():
x=t.ones(5)
y=t.ones(5)
print(x.cuda() + y.cuda())
Out:
tensor([2., 2., 2., 2., 2.], device='cuda:0')
4.2 Autograd:自動微分
from torch.autograd import Variable
import torch as t
#使用Tensor新建一個Variable
x = Variable(t.ones(2,2),requires_grad = True)
print(x)
y = x.sum() # y=(x[0][0]+x[0][1]+x[1][0]+x[1][1])
print(y)
y.grad_fn
y.backward() #反向傳播,計算梯度
#每個值的梯度都為1
print(x.grad)
#注意:grad在反向傳播過程中是累加的,這意味著每次執行反向傳播,梯度都會累加之前的梯度,所以反向傳播前需把梯度清零
y.backward()
print(x.grad)
#清空
x.grad.data.zero_()
y.backward()
print(x.grad)
Out:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
tensor(4., grad_fn=<SumBackward0>)
tensor([[1., 1.],
[1., 1.]])
tensor([[2., 2.],
[2., 2.]])
tensor([[1., 1.],
[1., 1.]])
4.3 神經網路
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch as t
class Net(nn.Module):
def __init__(self):
#nn.Module子類的函式必須在建構函式中執行父類的建構函式
#下式等價於nn.Module.__init__()
super(Net,self).__init__()
#卷積層‘1’表示輸入圖片為單通道,‘6’表示輸出通道數,‘5’表示卷積核為5*5
self.conv1 = nn.Conv2d(1, 6, 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):
# 卷積->啟用->池化
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)
Out:
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)
)
params = list(net.parameters())
print(len(params))
for name, parameters in net.named_parameters():
print(name, ":", parameters.size())
Out:
10
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])
input = Variable(t.randn(1,1,32,32))
out = net(input)
out.size()
Out:
torch.Size([1, 10])
out = net(input)
target = Variable(t.arange(0,10))
criterion = nn.MSELoss()
loss = criterion(out,target)
#執行.backward,觀察呼叫之前和呼叫之後的grad
net.zero_grad() #把net中所有可學習引數的梯度清零
print("反向傳播之前conv1.bias的梯度")
print(net.conv1.bias.grad)
loss.backward()
print("反向傳播之後conv1.bias的梯度")
print(net.conv1.bias.grad)
Out:
報錯待解決