PyTorch是一個基於python的科學計算包,除了可以替代NumPy構建數組張量來利用GPU進行計算之外,還是一個高靈活性、速度快的深度學習平臺
張量
張量(Tensor)實際上是一個多維數組,使用GPU可以加速張量的運算。
創建tensor:可以通過torch.tensor()
將普通數組轉化爲tensor,torch.empty()
或rand()
可以創建隨機tensor,torch.zeros()
、ones()
創建數據都是0或1的tensor。t.clone()
可以克隆一個張量,t.size()
可以獲取張量的形狀。
import torch
t = torch.tensor([[5, 6], [7, 8]]) # 轉化爲tensor
t1 = torch.empty(5, 3) # 創建隨機5×3的tensor
t2 = torch.rand(5, 3)
t3 = torch.zeros(5, 3, dtype=torch.float16) # 創建全爲0的5×3數組
t4 = torch.ones(5, 3) # 創建全爲1的
t5 = torch.ones_like(t3) # 創建形狀和t3一樣、全爲1的數組
print(t5.size()) # 輸出:torch.Size([5, 3])
操作tensor:tensor可以像Numpy一樣直接對數組進行索引、截取、切片等操作。
view(-1,2)
可以改變tensor的形狀,類似於numpy.reshape(),這裏修改爲第一維長度爲2,第二維-1代表自動計算維度。
permute()
可以交換數組維度位置,類似於numpy.transpose()
squeeze()
可以壓縮張量中長度爲1的維度,比如數組[[1,2,3]]形狀爲(1,3),經過壓縮後變成長度爲3的數組[1,2,3]。反之,unsqueeze()
可以在指定位置將維度增加長度爲1
expand()
可以按原數據爲元素擴大維度,即將原來的數據複製多份並一起組成更高維度,擴大tensor不需要分配新內存,只是僅僅新建一個tensor的視圖。使用時注意保持新維度與原維度的統一。
t = torch.tensor([[1, 2, 3],
[4, 5, 6]])
print(t[0, :2]) # 選取首行前兩個元素,輸出tensor([1, 2])
print(t.view(-1, 2)) # 改變形狀
'''
tensor([[1, 2],
[3, 4],
[5, 6]])
'''
t = torch.unsqueeze(t, dim=0) # 給張量第一維增加一個維度
print(t)
'''
tensor([[[1, 2, 3],
[4, 5, 6]]])
'''
print(t.permute(2, 1, 0)) # 交換維度位置
'''
tensor([[1, 3, 5],
[2, 4, 6]])
'''
print(t.expand(2, 2, 3)) # 將(2,3)的tensor擴維成爲(2,2,3)
'''
tensor([[[1, 2, 3],
[4, 5, 6]],
[[1, 2, 3],
[4, 5, 6]]])
'''
通過tensor進行四則運算也十分方便,下面以加法爲例記錄幾種使用的形式:
x = torch.tensor([[1, 2, 3],
[4, 5, 6]])
y = torch.tensor([[7, 8, 9],
[10, 11, 12]])
print(x + y) # 用運算符
print(torch.add(x, y)) # 用add()函數
z = torch.empty(2, 3)
torch.add(x, y, out=z) # 指明輸出對象
print(z)
y.add_(x) # 在原數組上操作
print(y)
值得注意的是,pytorch中在原張量上進行的操作函數後面都跟有一個
_
,例如其他的還有在原張量上覆制x.copy_()。
和NumPy數組相互轉化:通過tensor.numpy()
可以將tensor轉爲numpy數組,但二者會共享一個內存空間,即對tensor進行的修改操作之後,輸出numpy的結果也會隨之改變。如果希望產生一個新的可以通過clone()函數。通過torch.from_numpy()
可以從numpy數組創建tensor,同樣地二者共享內存。
t = torch.ones(5)
print(t)
n1 = t.numpy() # 轉化爲numpy
n2 = t.clone().numpy()
t.add_(1) # 對原來的tensor進行操作
print(n1)
print(n2)
n = np.ones(5)
t2 = torch.from_numpy(n) # 從numpy創建tensor
'''
tensor([1., 1., 1., 1., 1.]) 原來的t
[2. 2. 2. 2. 2.] 操作t後對應的n1也隨之改變
[1. 1. 1. 1. 1.] 但是克隆產生的n2不會改變
'''
使用不同設備計算tensor,例如下面使用to()
來將tensor移入和移出GPU
if torch.cuda.is_available():
device = torch.device("cuda") # a CUDA device object
y = torch.ones_like(x, device=device) # 直接在GPU上創建tensor
x = x.to(device) # 或者使用.to("cuda"),將tensor移入GPU
z = x + y
print(z)
print(z.to("cpu", torch.double)) # 將tensor移出GPU,也能在移動時改變dtype
'''
tensor([2., 2., 2., 2., 2.], device='cuda:0')
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
'''
自動求導
PyTorch中,所有神經網絡的核心是 autograd
包,它爲張量上的所有操作提供了自動求導機制。Pytorch通過Tensor
來儲存數據,我們可以把它看作一個節點,通過Function
實現數據操作,即節點之間轉換的路徑。如下所示爲一個訓練過程,輸入張量x,經過一系列操作之後得到輸出y。
如下所示爲實現上面過程的代碼,首先通過tensor定義張量,通過屬性requires_grad
指定是否需要自動求導。之後執行正向操作*w1、+b、*w2,最後求平均得到輸出y。接着通過backward()
執行反向傳播,就可以得到張量的grad值了。
# 定義張量
x = torch.ones(5, requires_grad=True)
w1 = torch.tensor(2.0, requires_grad=True)
w2 = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(4.0, requires_grad=False)
# 執行正向操作
l1 = x * w1
l2 = l1 + b
l3 = l2 * w2
y = l3.mean()
# 反向傳播
y.backward()
print(l1.data, l1.grad, l1.grad_fn)
# tensor([2., 2., 2., 2., 2.]) None <MulBackward0 object at 0x0000024D8E921BE0>
print(l2.data, l2.grad, l2.grad_fn)
# tensor([6., 6., 6., 6., 6.]) None <AddBackward0 object at 0x000001B960FC0F98>
print(y)
# tensor(18., grad_fn=<MeanBackward0>)
print(w1.grad, w2.grad)
# tensor(3.) tensor(6.)
print(x.grad)
# tensor([1.2000, 1.2000, 1.2000, 1.2000, 1.2000])
從上面的輸出可以看到x一開始爲[1,1,1,1,1],乘以w1後爲[2., 2., 2., 2., 2.],+b之後爲[6., 6., 6., 6., 6.],再乘以3爲[18., 18., 18., 18., 18.],求均值得到y爲18。
Tensor的grad_fn
屬性用於記錄上一步是經過怎樣的操作得到自己的,用於反向傳播時進行求導。例如l1.grad_fn爲MulBackward0代表其反向傳播操作爲乘法
反向傳播後通過grade
屬性可以獲得最後輸出對本張量的導數值。例如上面的操作中輸出爲y,反向傳播後,w2.grad獲得的就是dy/dw2。由鏈式求導法則可得dy/dw2=dy/dl3 × dl3/dw2。由於y由l3求均值得到,即y=1/5(Σl3),所以dy/dl3=1/5,又l3=l2 * w2,所以dl3/dw2=l2,所以dy/dw2=1/5*l2=1/5(6., 6., 6., 6., 6.)=6。同理通過反向鏈式求導得到w1、x的grad
注意到l1、l2的grade值爲None,這是由於張量l1、l2、l3都是中間計算結果,它們被稱爲非葉張量,相對地由用戶創建的x、w1、w2被稱爲葉張量(leaf tensor)。pytorch爲了節約內存並不會保存中間張量的導數值,只會用grad_fn來記錄是通過什麼操作產生的。如果我們希望查看的話,可以通過retain_grad()
來保存非葉張量的導數值。
...
l1.retain_grad()
l2.retain_grad()
y.backward()
print(l1.data, l1.grad, l1.grad_fn)
# tensor([2., 2., 2., 2., 2.]) tensor([0.6000, 0.6000, 0.6000, 0.6000, 0.6000]) <MulBackward0 object at 0x000001DF52100F60>
print(l2.data, l2.grad, l2.grad_fn)
# tensor([6., 6., 6., 6., 6.]) tensor([0.6000, 0.6000, 0.6000, 0.6000, 0.6000]) <AddBackward0 object at 0x000001DF52111048>
如果我們設置了張量requires_grad,但在某個訓練過程中不需要記錄梯度grad,可以把代碼塊包裹在with torch.no_grad():
中,這樣裏面的所有張量都不會保存grad
x = torch.ones(5, requires_grad=True)
print((x ** 2).requires_grad) # 輸出True,記錄grad
with torch.no_grad():
print((x ** 2).requires_grad) # 輸出False,不記錄