pytorch學(xué)習(xí)

本文全出自于:《動手學(xué)深度學(xué)習(xí)》(PyTorch版)
純屬學(xué)習(xí)筆記


許多函數(shù)

"tensor"這個單詞一般可譯作“張量”,張量可以看作是一個多維數(shù)組。標(biāo)量可以看作是0維張量,向量可以看作1維張量,矩陣可以看作是二維張量。

首先導(dǎo)入PyTorch:

import torch
然后我們創(chuàng)建一個5x3的未初始化的Tensor:

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

創(chuàng)建一個5x3的隨機初始化的Tensor:

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

創(chuàng)建一個5x3的long型全0的Tensor:

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

還可以直接根據(jù)數(shù)據(jù)創(chuàng)建:

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

還可以通過現(xiàn)有的Tensor來創(chuàng)建,此方法會默認(rèn)重用輸入Tensor的一些屬性,例如數(shù)據(jù)類型,除非自定義數(shù)據(jù)類型。

x = x.new_ones(5, 3, dtype=torch.float64)  # 返回的tensor默認(rèn)具有相同的torch.dtype和torch.device
print(x)

x = torch.randn_like(x, dtype=torch.float) # 指定新的數(shù)據(jù)類型
print(x) 

操作:
加法:

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

print(torch.add(x, y))

還可指定輸出:
  result = torch.empty(5, 3)
  torch.add(x, y, out=result)
  print(result)

加法形式三、inplace
  # adds x to y
  y.add_(x)
  print(y)

索引

我們還可以使用類似NumPy的索引操作來訪問Tensor的一部分,需要注意的是:索引出來的結(jié)果與原數(shù)據(jù)共享內(nèi)存,也即修改一個,另一個會跟著修改。

y = x[0, :]
y += 1
print(y)
print(x[0, :]) # 源tensor也被改了
常用的函數(shù)

改變形狀:

用view()來改變Tensor的形狀:

y = x.view(15)
z = x.view(-1, 5)  # -1所指的維度可以根據(jù)其他維度的值推出來
print(x.size(), y.size(), z.size())

注意view()返回的新Tensor與源Tensor雖然可能有不同的size,但是是共享data的,也即更改其中的一個,另外一個也會跟著改變。(顧名思義,view僅僅是改變了對這個張量的觀察角度,內(nèi)部數(shù)據(jù)并未改變)

如果我們想返回一個真正新的副本(即不共享data內(nèi)存)該怎么辦呢?Pytorch還提供了一個reshape()可以改變形狀,但是此函數(shù)并不能保證返回的是其拷貝,所以不推薦使用。推薦先用clone創(chuàng)造一個副本然后再使用view。

x_cp = x.clone().view(15)
x -= 1
print(x)
print(x_cp)

使用clone還有一個好處是會被記錄在計算圖中,即梯度回傳到副本時也會傳到源Tensor。
另外一個常用的函數(shù)就是item(), 它可以將一個標(biāo)量Tensor轉(zhuǎn)換成一個Python number:

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


PyTorch中的Tensor支持超過一百種操作,包括轉(zhuǎn)置、索引、切片、數(shù)學(xué)運算、線性代數(shù)、隨機數(shù)等等,可參考官方文檔。

2.2.3 廣播機制

兩個形狀相同的Tensor做按元素運算。當(dāng)對兩個形狀不同的Tensor按元素運算時,可能會觸發(fā)廣播(broadcasting)機制:先適當(dāng)復(fù)制元素使這兩個Tensor形狀相同后再按元素運算。

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)

輸出:
tensor([[1, 2]])
tensor([[1],
        [2],
        [3]])
tensor([[2, 3],
        [3, 4],
        [4, 5]])
由于x和y分別是1行2列和3行1列的矩陣,如果要計算x + y,那么x中第一行的2個元素被廣播(復(fù)制)到了第二行和第三行,而y中第一列的3個元素被廣播(復(fù)制)到了第二列。如此,就可以對2個3行2列的矩陣按元素相加。


## 2.2.4 運算的內(nèi)存開銷

前面說了,索引操作是不會開辟新內(nèi)存的,而像`y = x + y`這樣的運算是會新開內(nèi)存的,然后將`y`指向新內(nèi)存。為了演示這一點,我們可以使用Python自帶的`id`函數(shù):如果兩個實例的ID一致,那么它們所對應(yīng)的內(nèi)存地址相同;反之則不同。

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y = y + x
print(id(y) == id_before) # False


如果想指定結(jié)果到原來的`y`的內(nèi)存,我們可以使用前面介紹的索引來進行替換操作。在下面的例子中,我們把`x + y`的結(jié)果通過`[:]`寫進`y`對應(yīng)的內(nèi)存中。

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y[:] = y + x
print(id(y) == id_before) # True


我們還可以使用運算符全名函數(shù)中的`out`參數(shù)或者自加運算符`+=`(也即`add_()`)達到上述效果,例如`torch.add(x, y, out=y)`和`y += x`(`y.add_(x)`)。

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
torch.add(x, y, out=y) # y += x, y.add_(x)
print(id(y) == id_before) # True


> 注:雖然`view`返回的`Tensor`與源`Tensor`是共享`data`的,但是依然是一個新的`Tensor`(因為`Tensor`除了包含`data`外還有一些其他屬性),二者id(內(nèi)存地址)并不一致。

Tensor`和NumPy相互轉(zhuǎn)換

我們很容易用numpy()from_numpy()Tensor和NumPy中的數(shù)組相互轉(zhuǎn)換。但是需要注意的一點是: 這兩個函數(shù)所產(chǎn)生的的Tensor和NumPy中的數(shù)組共享相同的內(nèi)存(所以他們之間的轉(zhuǎn)換很快),改變其中一個時另一個也會改變?。?!

還有一個常用的將NumPy中的array轉(zhuǎn)換成Tensor的方法就是torch.tensor(), 需要注意的是,此方法總是會進行數(shù)據(jù)拷貝(就會消耗更多的時間和空間),所以返回的Tensor和原來的數(shù)據(jù)不再共享內(nèi)存。

Tensor轉(zhuǎn)NumPy

使用numpy()Tensor轉(zhuǎn)換成NumPy數(shù)組:

a = torch.ones(5)
b = a.numpy()
print(a, b)

a += 1
print(a, b)
b += 1
print(a, b)

NumPy數(shù)組轉(zhuǎn)Tensor

使用from_numpy()將NumPy數(shù)組轉(zhuǎn)換成Tensor:

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)

a += 1
print(a, b)
b += 1
print(a, b)

Tensor on GPU

用方法to()可以將Tensor在CPU和GPU(需要硬件支持)之間相互移動。

# 以下代碼只有在PyTorch GPU版本上才會執(zhí)行
if torch.cuda.is_available():
    device = torch.device("cuda")          # GPU
    y = torch.ones_like(x, device=device)  # 直接創(chuàng)建一個在GPU上的Tensor
    x = x.to(device)                       # 等價于 .to("cuda")
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # to()還可以同時更改數(shù)據(jù)類型

后面不知所云

自動梯度計算

Tensor是這個包的核心類,如果將其屬性.requires_grad設(shè)置為True,它將開始追蹤(track)在其上的所有操作(這樣就可以利用鏈?zhǔn)椒▌t進行梯度傳播了)。完成計算后,可以調(diào)用.backward()來完成所有梯度計算。此Tensor的梯度將累積到.grad屬性中。
如果不想要被繼續(xù)追蹤,可以調(diào)用.detach()將其從追蹤記錄中分離出來,這樣就可以防止將來的計算被追蹤,這樣梯度就傳不過去了。此外,還可以用with torch.no_grad()將不想被追蹤的操作代碼塊包裹起來,這種方法在評估模型的時候很常用,因為在評估模型時,我們并不需要計算可訓(xùn)練參數(shù)(requires_grad=True)的梯度。
Function是另外一個很重要的類。Tensor和Function互相結(jié)合就可以構(gòu)建一個記錄有整個計算過程的有向無環(huán)圖(DAG)。每個Tensor都有一個.grad_fn屬性,該屬性即創(chuàng)建該Tensor的Function, 就是說該Tensor是不是通過某些運算得到的,若是,則grad_fn返回一個與這些運算相關(guān)的對象,否則是None。(我不知所云)

創(chuàng)建一個Tensor并設(shè)置requires_grad=True:
創(chuàng)建一個Tensor并設(shè)置requires_grad=True:

x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)
Copy to clipboardErrorCopied
#輸出:

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

print(x.is_leaf, y.is_leaf) # True False
查看是否是張量

再來點復(fù)雜度運算操作:

z = y * y * 3
out = z.mean()
print(z, out)
Copy to clipboardErrorCopied
#輸出:

tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward>)  tensor(27., grad_fn=<MeanBackward1>)

通過.requires_grad_()來用in-place的方式改變requires_grad屬性:

a = torch.randn(2, 2) # 缺失情況下默認(rèn) requires_grad = False
a = ((a * 3) / (a - 1))
print(a.requires_grad) # False
a.requires_grad_(True)
print(a.requires_grad) # True
b = (a * a).sum()
print(b.grad_fn)

2.3.3 梯度

因為out是一個標(biāo)量,所以調(diào)用backward()時不需要指定求導(dǎo)變量:

out.backward() # 等價于 out.backward(torch.tensor(1.))

不理解的東西:



注意:grad在反向傳播過程中是累加的(accumulated),這意味著每一次運行反向傳播,梯度都會累加之前的梯度,所以一般在反向傳播之前需把梯度清零。

# 再來反向傳播一次,注意grad是累加的
out2 = x.sum()
out2.backward()
print(x.grad)

out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)

現(xiàn)在我們解釋2.3.1節(jié)留下的問題,為什么在y.backward()時,如果y是標(biāo)量,則不需要為backward()傳入任何參數(shù);否則,需要傳入一個與y同形的Tensor? 簡單來說就是為了避免向量(甚至更高維張量)對張量求導(dǎo),而轉(zhuǎn)換成標(biāo)量對張量求導(dǎo)。舉個例子,假設(shè)形狀為 m x n 的矩陣 X 經(jīng)過運算得到了 p x q 的矩陣 Y,Y 又經(jīng)過運算得到了 s x t 的矩陣 Z。那么按照前面講的規(guī)則,dZ/dY 應(yīng)該是一個 s x t x p x q 四維張量,dY/dX 是一個 p x q x m x n的四維張量。問題來了,怎樣反向傳播?怎樣將兩個四維張量相乘???這要怎么乘???就算能解決兩個四維張量怎么乘的問題,四維和三維的張量又怎么乘?導(dǎo)數(shù)的導(dǎo)數(shù)又怎么求,這一連串的問題,感覺要瘋掉…… 為了避免這個問題,我們不允許張量對張量求導(dǎo),只允許標(biāo)量對張量求導(dǎo),求導(dǎo)結(jié)果是和自變量同形的張量。所以必要時我們要把張量通過將所有張量的元素加權(quán)求和的方式轉(zhuǎn)換為標(biāo)量,舉個例子,假設(shè)y由自變量x計算而來,w是和y同形的張量,則y.backward(w)的含義是:先計算l = torch.sum(y * w),則l是個標(biāo)量,然后求l對自變量x的導(dǎo)數(shù)。 參考

如果我們想要修改tensor的數(shù)值,但是又不希望被autograd記錄(即不會影響反向傳播),那么我么可以對tensor.data進行操作。

x = torch.ones(1,requires_grad=True)

print(x.data) # 還是一個tensor
print(x.data.requires_grad) # 但是已經(jīng)是獨立于計算圖之外

y = 2 * x
x.data *= 100 # 只改變了值,不會記錄在計算圖,所以不會影響梯度傳播

y.backward()
print(x) # 更改data的值也會影響tensor的值
print(x.grad)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容