DataCamp的Sayak Paul帶你入門張量、PyTorch。
在深度學(xué)習(xí)中,常??吹綇埩渴菙?shù)據(jù)結(jié)構(gòu)的基石這一說(shuō)法。Google的機(jī)器學(xué)習(xí)庫(kù)TensorFlow甚至都以張量(tensor)命名。張量是線性代數(shù)中用到的一種數(shù)據(jù)結(jié)構(gòu),類似向量和矩陣,你可以在張量上進(jìn)行算術(shù)運(yùn)算。
PyTorch(Facebook創(chuàng)建的python包,提供兩個(gè)高層特性:1) 類似Numpy的基于GPU加速的張量運(yùn)算 2) 在基于回放(tape-based)的自動(dòng)微分系統(tǒng)之上構(gòu)建的深度神經(jīng)網(wǎng)絡(luò)。
本教程將介紹什么是張量,如何在PyTorch中操作張量:
張量介紹
PyTorch介紹
PyTorch安裝步驟
PyTorch下的一些張量操作
基于PyTorch實(shí)現(xiàn)一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)
閑話少敘,讓我們開(kāi)始介紹張量吧。
張量介紹
張量是向量和矩陣的推廣,可以理解為多維數(shù)組。知名的《深度學(xué)習(xí)》(Goodfellow等編寫)是這樣介紹張量的:
在一般意義上,以基于可變數(shù)目的軸的規(guī)則網(wǎng)格組織的一組數(shù)字稱為張量。
標(biāo)量是零階張量。向量是一階張量,矩陣是二階張量。
下面是張量的示意圖:
現(xiàn)在讓我們以更清晰易懂的方式構(gòu)建張量背后的直覺(jué)。
張量是現(xiàn)代機(jī)器學(xué)習(xí)的基本構(gòu)建。它是一個(gè)數(shù)據(jù)容器,大多數(shù)情況下包含數(shù)字,有時(shí)可能包含字符串(不過(guò)這罕見(jiàn))。所以可以把張量想象成一桶數(shù)字。
人們經(jīng)?;煊脧埩亢投嗑S數(shù)組。不過(guò)有時(shí)需要嚴(yán)格區(qū)分兩者,如StackExchange指出:
張量和多維數(shù)組是不同類型的對(duì)象。前者是一種函數(shù),后者是適宜在坐標(biāo)系統(tǒng)中表示張量的一種數(shù)據(jù)結(jié)構(gòu)。
在數(shù)學(xué)上,張量由多元線性函數(shù)定義。一個(gè)多元線性函數(shù)包含多個(gè)向量變量。張量域是張量值函數(shù)。更嚴(yán)謹(jǐn)?shù)臄?shù)學(xué)解釋,可以參考https://math.stackexchange.com/q/10282
所以,張量是需要定義的函數(shù)或容器。實(shí)際上,當(dāng)數(shù)據(jù)傳入時(shí),計(jì)算才真正發(fā)生。當(dāng)不需要嚴(yán)格區(qū)分?jǐn)?shù)組和張量的時(shí)候,數(shù)組或多維數(shù)組(1D, 2D, …, ND)一般可以視作張量。
現(xiàn)在我們稍微講下張量表述(Tensor notation)。
張量表述和矩陣類似,一般用大寫字母表示張量,帶整數(shù)下標(biāo)的小寫字母表示張量中的標(biāo)量值。
標(biāo)量、向量、矩陣的許多運(yùn)算同樣適用于張量。
張量和張量代數(shù)是物理和工程領(lǐng)域廣泛使用的工具。機(jī)器學(xué)習(xí)的許多技術(shù),深度學(xué)習(xí)模型的訓(xùn)練和操作,常常使用張量這一術(shù)語(yǔ)進(jìn)行描述。
PyTorch介紹
PyTorch是一個(gè)非常靈活的基于Python的深度學(xué)習(xí)研究平臺(tái)。
PyTorch特性
提供各種張量的常規(guī)操作。
基于回放的自動(dòng)微分系統(tǒng)。
不同于TensorFlow、Theano、Caffe、CNTK等大多數(shù)框架采用的靜態(tài)圖系統(tǒng),PyTorch采用動(dòng)態(tài)圖系統(tǒng)。
最小化框架開(kāi)銷,可基于GPU加速。
相比Torch等替代品,PyTorch的內(nèi)存使用非常高效。這讓你可以訓(xùn)練比以往更大的深度學(xué)習(xí)模型。
Kirill Dubovikov寫的PyTorch vs TensorFlow?—?spotting the difference比較了PyTorch和TensorFlow這兩個(gè)框架。如果你想了解TensorFlow,可以看看Karlijn Willems寫的教程TensorFlow Tutorial For Beginners。
PyTorch安裝步驟
PyTorch的安裝很簡(jiǎn)單。如果你的顯卡支持,可以安裝GPU版本的PyTorch。
你可以使用pip安裝torch、torchvision這兩個(gè)包,也可以使用conda安裝pytorch torchvision這兩個(gè)包。注意,Windows平臺(tái)上,PyTorch不支持Python 2.7,需要基于Python 3.5以上的版本安裝。
具體的安裝命令可以通過(guò)PyTorch官網(wǎng)查詢: https://pytorch.org/get-started/locally/
好了,下面讓我們直接深入PyTorch下的一些張量算術(shù)。
PyTorch下的一些張量操作
首先,導(dǎo)入所需的庫(kù):
import torch
如果出現(xiàn)報(bào)錯(cuò),說(shuō)明PyTorch沒(méi)有安裝成功,請(qǐng)參考上一節(jié)重新安裝。
現(xiàn)在,我們構(gòu)造一個(gè)5×3的矩陣:
x = torch.rand(5, 3)
print(x)
輸出:
tensor([[ 0.5991, 0.9365, 0.6120],
[ 0.3622, 0.1408, 0.8811],
[ 0.6248, 0.4808, 0.0322],
[ 0.2267, 0.3715, 0.8430],
[ 0.0145, 0.0900, 0.3418]])
再構(gòu)造一個(gè)5×3的矩陣,不過(guò)這次用零初始化,并指定數(shù)據(jù)類型為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]])
構(gòu)造張量時(shí)直接提供數(shù)據(jù):
x = torch.tensor([5.5, 3])
print(x)
輸出:
tensor([ 5.5000, 3.0000])
如果你想檢驗(yàn)下自己是否理解了PyTorch中的張量,那可以思考下上面的張量x是什么類別的。
基于已有張量,可以創(chuàng)建新張量——新張量會(huì)復(fù)用輸入張量的屬性,比如dtype(數(shù)據(jù)類型),除非另外給出新值:
x = x.new_ones(5, 3, dtype=torch.double)
print(x)
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([[-1.2174, 1.1807, 1.4249],
[-1.1114, -0.8098, 0.4003],
[ 0.0780, -0.5011, -1.0985],
[ 1.8160, -0.3778, -0.8610],
[-0.7109, -2.0509, -1.2079]])
獲取張量的尺寸:
print(x.size())
輸出:
torch.Size([5, 3])
注意,torch.Size事實(shí)上是一個(gè)元組,支持所有元組操作。
現(xiàn)在,讓我們看下張量的加法。
張量加法
兩個(gè)張量分素相加,得到維度一致的張量,結(jié)果張量中每個(gè)標(biāo)量的值是相應(yīng)標(biāo)量的和。
y = torch.rand(5, 3)
print(x)
print(y)
print(x + y)
輸出:
tensor([[-1.2174, 1.1807, 1.4249],
[-1.1114, -0.8098, 0.4003],
[ 0.0780, -0.5011, -1.0985],
[ 1.8160, -0.3778, -0.8610],
[-0.7109, -2.0509, -1.2079]])
tensor([[ 0.8285, 0.7619, 0.1147],
[ 0.1624, 0.8994, 0.6119],
[ 0.2802, 0.2950, 0.7098],
[ 0.8132, 0.3382, 0.4383],
[ 0.6738, 0.2022, 0.3264]])
tensor([[-0.3889, 1.9426, 1.5396],
[-0.9490, 0.0897, 1.0122],
[ 0.3583, -0.2061, -0.3887],
[ 2.6292, -0.0396, -0.4227],
[-0.0371, -1.8487, -0.8815]])
除了使用+運(yùn)算符外,也可以調(diào)用torch.add方法(兩者是等價(jià)的):
print(torch.add(x, y))
下面我們來(lái)看張量減法。
張量減法
兩個(gè)張量分素相減,得到維度一致的張量,結(jié)果張量中每個(gè)標(biāo)量的值是相應(yīng)標(biāo)量之差。
接著我們將討論張量相乘。
張量乘法
假設(shè)mat1是一個(gè)(n×m)的張量,mat2是一個(gè)(m×p)的張量,兩者相乘,將得到一個(gè)(n×p)的張量。
mat1 = torch.randn(2, 3)
mat2 = torch.randn(3, 3)
print(mat1)
print(mat2)
print(torch.mm(mat1, mat2))
輸出:
tensor([[ 1.9490, -0.6503, -1.9448],
[-0.7126, 1.0519, -0.4250]])
tensor([[ 0.0846, 0.4410, -0.0625],
[-1.3264, -0.5265, 0.2575],
[-1.3324, 0.6644, 0.3528]])
tensor([[ 3.6185, -0.0901, -0.9753],
[-0.8892, -1.1504, 0.1654]])
注意,torch.mm()不支持廣播(broadcast)。
廣播
“廣播”這一術(shù)語(yǔ)用于描述如何在形狀不一的數(shù)組上應(yīng)用算術(shù)運(yùn)算。在滿足特定限制的前提下,較小的數(shù)組“廣播至”較大的數(shù)組,使兩者形狀互相兼容。廣播提供了一個(gè)向量化數(shù)組操作的機(jī)制,這樣遍歷就發(fā)生在C層面,而不是Python層面。廣播可以避免不必要的數(shù)據(jù)復(fù)制,通常導(dǎo)向高效的算法實(shí)現(xiàn)。不過(guò),也存在不適用廣播的情形(可能導(dǎo)致拖慢計(jì)算過(guò)程的低效內(nèi)存使用)。
可廣播的一對(duì)張量需滿足以下規(guī)則:
每個(gè)張量至少有一個(gè)維度。
迭代維度尺寸時(shí),從尾部的維度開(kāi)始,維度尺寸或者相等,或者其中一個(gè)張量的維度尺寸為一,或者其中一個(gè)張量不存在這個(gè)維度。
讓我們通過(guò)幾段代碼來(lái)理解PyTorch的廣播機(jī)制。
x=torch.empty(5,7,3)
y=torch.empty(5,7,3)
相同形狀的張量總是可廣播的,因?yàn)榭偰軡M足以上規(guī)則。
x=torch.empty((0,))
y=torch.empty(2,2)
不可廣播(x不滿足第一條規(guī)則)。
# 為了清晰易讀,可以對(duì)齊尾部
x=torch.empty(5,3,4,1)
y=torch.empty( 3,1,1)
x和y可廣播:
倒數(shù)第一個(gè)維度:兩者的尺寸均為1
倒數(shù)第二個(gè)維度:y尺寸為1
倒數(shù)第三個(gè)維度:兩者尺寸相同
倒數(shù)第四個(gè)維度:y該維度不存在
但下面一對(duì)就不可廣播了:
x=torch.empty(5,2,4,1)
y=torch.empty( 3,1,1)
這是因?yàn)榈箶?shù)第三個(gè)維度:2 != 3
現(xiàn)在你對(duì)“可廣播”這一概念已經(jīng)有所了解了,讓我們看下,廣播后的張量是什么樣的。
如果張量x和張量y是可廣播的,那么廣播后的張量尺寸按照如下方法計(jì)算:
如果x和y的維數(shù)不等,在維數(shù)較少的張量上添加尺寸為1的維度。結(jié)果維度尺寸是x和y相應(yīng)維度尺寸的較大者。例如:
x=torch.empty(5,1,4,1)
y=torch.empty( 3,1,1)
(x+y).size()
輸出:
torch.Size([5, 3, 4, 1])
再如:
x=torch.empty(1)
y=torch.empty(3,1,7)
(x+y).size()
輸出:
torch.Size([3, 1, 7])
再看一個(gè)不可廣播的例子:
x=torch.empty(5,2,4,1)
y=torch.empty(3,1,1)
(x+y).size()
報(bào)錯(cuò):
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
1 x=torch.empty(5,2,4,1)
2 y=torch.empty(3,1,1)
----> 3 (x+y).size()
RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 1
你現(xiàn)在應(yīng)該已經(jīng)掌握了廣播這個(gè)概念了!
張量乘積是最常見(jiàn)的張量乘法,但也存在其他種類的張量乘法,例如張量點(diǎn)積和張量縮并。
借助Numpy橋,PyTorch張量和NumPy數(shù)組之間的互相轉(zhuǎn)換極其迅速。下面就讓我們來(lái)了解一下這個(gè)概念。
NumPy橋
NumPy橋使得PyTorch張量和NumPy數(shù)組共享底層內(nèi)存地址,對(duì)其中之一的修改會(huì)反映到另一個(gè)上。
轉(zhuǎn)換PyTorch張量至NumPy數(shù)組。
a = torch.ones(5)
b = a.numpy()
print(a)
print(b)
輸出:
tensor([ 1., 1., 1., 1., 1.])
[1.1.1.1.1.]
在這一節(jié)中,我們討論了一些基本的張量算術(shù),例如加法、減法、張量乘積。下一節(jié)我們將使用PyTorch實(shí)現(xiàn)一個(gè)基本的神經(jīng)網(wǎng)絡(luò)。
基于PyTorch實(shí)現(xiàn)一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)
如果你想要溫習(xí)一下神經(jīng)網(wǎng)絡(luò)的概念,可以參考以下文章:
初窺神經(jīng)網(wǎng)絡(luò)內(nèi)部機(jī)制
從頭開(kāi)始搭建三層神經(jīng)網(wǎng)絡(luò)
基于Numpy實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò):反向傳播、梯度下降
在實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)之前,我們先來(lái)討論一下自動(dòng)微分,這是PyTorch下所有神經(jīng)網(wǎng)絡(luò)的核心,在進(jìn)行反向傳播計(jì)算梯度時(shí)尤其有用。
PyTorch的autograd模塊為張量的所有運(yùn)算提供了自動(dòng)微分。這是一個(gè)define-by-run框架,也就是說(shuō),反向傳播由代碼如何運(yùn)行定義,每個(gè)迭代都可以不一樣。
讓我們直接用代碼展示自動(dòng)微分是如何工作的。
x = torch.ones(2, 2, requires_grad=True)
print(x)
輸出:
tensor([[ 1., 1.],
[ 1., 1.]])
進(jìn)行加法運(yùn)算:
y = x + 2
print(y)
輸出:
tensor([[ 3., 3.],
[ 3., 3.]])
再進(jìn)行一些運(yùn)算:
z = y * y * 3
out = z.mean()
print(z)
print(out)
輸出:
tensor([[ 27., 27.],
[ 27., 27.]])
tensor(27.)
現(xiàn)在讓我們進(jìn)行反向傳播:
out.backward()
print(x.grad)
自動(dòng)微分給出的梯度為:
tensor([[ 4.5000, 4.5000],
[ 4.5000, 4.5000]])
感興趣的讀者可以手工驗(yàn)證下梯度。
了解了PyTorch如何進(jìn)行自動(dòng)微分之后,讓我們使用PyTorch編碼一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)。
我們將創(chuàng)建一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò),包括一個(gè)隱藏層,一個(gè)輸出層。隱藏層使用ReLU激活,輸出層使用sigmoid激活。
構(gòu)建神經(jīng)網(wǎng)絡(luò)需要引入torch.nn模塊:
import torch.nn as nn
接著定義網(wǎng)絡(luò)層尺寸和batch尺寸:
n_in, n_h, n_out, batch_size = 10, 5, 1, 10
現(xiàn)在生成一些輸入數(shù)據(jù)x和目標(biāo)數(shù)據(jù)y,并使用PyTorch張量存儲(chǔ)這些數(shù)據(jù)。
x = torch.randn(batch_size, n_in)
y = torch.tensor([[1.0], [0.0], [0.0], [1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]])
接下來(lái),只需一行代碼就可以定義我們的模型:
model = nn.Sequential(nn.Linear(n_in, n_h),
nn.ReLU(),
nn.Linear(n_h, n_out),
nn.Sigmoid())
我們創(chuàng)建了一個(gè)輸入 -> 線性 -> relu -> 線性 -> sigmoid的模型。對(duì)于需要更多自定義功能的更加復(fù)雜的模型,可以定義一個(gè)類,具體請(qǐng)參考PyTorch文檔。
現(xiàn)在,我們需要構(gòu)造損失函數(shù)。我們將使用均方誤差:
criterion = torch.nn.MSELoss()
然后定義優(yōu)化器。我們將使用強(qiáng)大的隨機(jī)梯度下降算法,學(xué)習(xí)率定為0.01.model.parameters()會(huì)返回一個(gè)模型參數(shù)(權(quán)重、偏置)上的迭代器。
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
下面我們跑50個(gè)epoch,這依次包括前向傳播、損失計(jì)算、反向傳播和參數(shù)更新。
for epoch in range(50):
# 前向傳播
y_pred = model(x)
# 計(jì)算并打印損失
loss = criterion(y_pred, y)
print('epoch: ', epoch,' loss: ', loss.item())
# 梯度歸零
optimizer.zero_grad()
# 反向傳播
loss.backward()
# 更新參數(shù)
optimizer.step()
輸出:
epoch: 0 loss: 0.2399429827928543
epoch: 1 loss: 0.23988191783428192
epoch: 2 loss: 0.23982088267803192
epoch: 3 loss: 0.2397598922252655
epoch: 4 loss: 0.23969893157482147
epoch: 5 loss: 0.23963800072669983
epoch: 6 loss: 0.23957709968090057
epoch: 7 loss: 0.23951618373394012
epoch: 8 loss: 0.23945537209510803
epoch: 9 loss: 0.23939454555511475
epoch: 10 loss: 0.23933371901512146
epoch: 11 loss: 0.23927298188209534
epoch: 12 loss: 0.23921218514442444
epoch: 13 loss: 0.23915143311023712
epoch: 14 loss: 0.2390907108783722
epoch: 15 loss: 0.23903003334999084
epoch: 16 loss: 0.23896940052509308
epoch: 17 loss: 0.23890872299671173
epoch: 18 loss: 0.23884813487529755
epoch: 19 loss: 0.23878750205039978
epoch: 20 loss: 0.23872694373130798
epoch: 21 loss: 0.2386663407087326
epoch: 22 loss: 0.2386058121919632
epoch: 23 loss: 0.23854532837867737
epoch: 24 loss: 0.23848481476306915
epoch: 25 loss: 0.23842433094978333
epoch: 26 loss: 0.2383638620376587
epoch: 27 loss: 0.23830339312553406
epoch: 28 loss: 0.2382429838180542
epoch: 29 loss: 0.23818258941173553
epoch: 30 loss: 0.2381247729063034
epoch: 31 loss: 0.2380656749010086
epoch: 32 loss: 0.23800739645957947
epoch: 33 loss: 0.2379491776227951
epoch: 34 loss: 0.2378900945186615
epoch: 35 loss: 0.23783239722251892
epoch: 36 loss: 0.23777374625205994
epoch: 37 loss: 0.23771481215953827
epoch: 38 loss: 0.23765745759010315
epoch: 39 loss: 0.23759838938713074
epoch: 40 loss: 0.23753997683525085
epoch: 41 loss: 0.2374821901321411
epoch: 42 loss: 0.23742322623729706
epoch: 43 loss: 0.23736533522605896
epoch: 44 loss: 0.23730707168579102
epoch: 45 loss: 0.23724813759326935
epoch: 46 loss: 0.23719079792499542
epoch: 47 loss: 0.23713204264640808
epoch: 48 loss: 0.23707345128059387
epoch: 49 loss: 0.2370160073041916
PyTorch的寫法很清晰,配上注釋,應(yīng)該不難理解。如果仍有不解之處,可以參考下面的講解:
y_pred獲取模型一次前向傳播的預(yù)測(cè)值。y_pred和目標(biāo)變量y一起傳給criterion以計(jì)算損失。
接著,optimizer.zero_grad()清空上一次迭代的梯度。
接下來(lái)的loss.backward()集中體現(xiàn)了PyTorch的神奇之處——這里用到了PyTorch的Autograd(自動(dòng)計(jì)算梯度)特性。Autograd基于動(dòng)態(tài)創(chuàng)建的計(jì)算圖自動(dòng)計(jì)算所有參數(shù)上的梯度。總的來(lái)說(shuō),這一步進(jìn)行的是梯度下降和反向傳播。
最后,我們調(diào)用optimizer.step(),使用新的梯度更新一次所有參數(shù)。
恭喜你讀到了這篇長(zhǎng)文的結(jié)尾。這篇文章從張量講到了自動(dòng)微分,同時(shí)基于PyTorch及其張量系統(tǒng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)。
如果你想了解更多關(guān)于PyTorch的內(nèi)容,或想進(jìn)一步深入,請(qǐng)閱讀PyTorch的官方文檔和教程,這些文檔和教程寫得非常好。你可以從PyTorch官網(wǎng)找到這些文檔和教程。
撰寫這篇教程的時(shí)候,我參考了以下內(nèi)容:
Daniel A. Fleisch的《A Student's Guide to Vectors and Tensors》
PyTorch官方文檔
Jason Brownlee寫的A Gentle Introduction to Tensors for Machine Learning with NumPy
如果有問(wèn)題要問(wèn),或者有想法要討論,歡迎留言!
如果你打算進(jìn)一步學(xué)習(xí)Python,可以參加DataCamp的Statistical Thinking in Python課程。
-
張量
+關(guān)注
關(guān)注
0文章
7瀏覽量
2562 -
pytorch
+關(guān)注
關(guān)注
2文章
807瀏覽量
13198
原文標(biāo)題:從張量到自動(dòng)微分:PyTorch入門教程
文章出處:【微信號(hào):jqr_AI,微信公眾號(hào):論智】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論