2021 PyTorch官方實(shí)戰(zhàn)教程(一)Tensor 詳解
點(diǎn)擊上方“AI算法與圖像處理”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時間送達(dá)
這個系列時pytorch官方實(shí)戰(zhàn)教程,后續(xù)會繼續(xù)更新。。
一、pytorch Tensor詳解
首先,讓我們導(dǎo)入PyTorch模塊。我們還將添加Python的math模塊來簡化一些示例。
import torch
import math
Creating Tensors
最簡單創(chuàng)建 tensor 的方法是調(diào)用torch.empty() :
x = torch.empty(3, 4)
print(type(x))
print(x)
輸出:
<class 'torch.Tensor'>
tensor([[0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
[0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
[0.0000e+00, 0.0000e+00, 2.8026e-45, 0.0000e+00]])
讓我們把剛才所做的整理一下:
我們創(chuàng)建了一個張量使用眾多的工廠方法之一附加到
torch模塊。這個張量本身是二維的,有3行4列。
返回的對象類型是
torch.Tensor,是torch.FloatTensor的別名;默認(rèn)情況下,PyTorch張量由32位浮點(diǎn)數(shù)填充(有關(guān)數(shù)據(jù)類型的詳細(xì)信息,請參見下文。)打印張量時,可能會看到一些隨機(jī)的值。
torch.empty()調(diào)用為張量分配內(nèi)存,但不使用任何值對其進(jìn)行初始化-因此您看到的是分配時內(nèi)存中的內(nèi)容。
關(guān)于張量及其維數(shù)和術(shù)語的簡要說明:
有時你會看到一個稱為向量的一維張量。
同樣,二維張量通常被稱為矩陣。
任何超過兩個維度的東西通常被稱為張量。
通常情況下,您需要用一些值初始化張量。常見的情況是全0、全1或隨機(jī)值,torch模塊為所有這些提供工廠方法:
zeros = torch.zeros(2, 3)
print(zeros)
ones = torch.ones(2, 3)
print(ones)
torch.manual_seed(1729)
random = torch.rand(2, 3)
print(random)
輸出:
tensor([[0., 0., 0.],
[0., 0., 0.]])
tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([[0.3126, 0.3791, 0.3087],
[0.0736, 0.4216, 0.0691]])
工廠方法都做你所期望的——我們有一個張量充滿了0,另一個充滿了1,還有一個是介于0和1之間隨機(jī)值。
Random Tensors and Seeding
說到隨機(jī)張量,你注意到它前面的 torch.manual_seed()調(diào)用了嗎?用隨機(jī)值初始化張量(例如模型的學(xué)習(xí)權(quán)重)是很常見的,但有時——特別是在研究環(huán)境中——你需要對結(jié)果的再現(xiàn)性有一些保證。手動設(shè)置隨機(jī)數(shù)生成器的種子是一種方法。讓我們更仔細(xì)地看一下:
torch.manual_seed(1729)
random1 = torch.rand(2, 3)
print(random1)
random2 = torch.rand(2, 3)
print(random2)
torch.manual_seed(1729)
random3 = torch.rand(2, 3)
print(random3)
random4 = torch.rand(2, 3)
print(random4)
輸出:
tensor([[0.3126, 0.3791, 0.3087],
[0.0736, 0.4216, 0.0691]])
tensor([[0.2332, 0.4047, 0.2162],
[0.9927, 0.4128, 0.5938]])
tensor([[0.3126, 0.3791, 0.3087],
[0.0736, 0.4216, 0.0691]])
tensor([[0.2332, 0.4047, 0.2162],
[0.9927, 0.4128, 0.5938]])
上面你應(yīng)該看到的是random1和random3攜帶相同的值,就像random2和random4一樣。手動設(shè)置RNG的種子會重置它,因此在大多數(shù)設(shè)置中,基于隨機(jī)數(shù)的相同計算應(yīng)該提供相同的結(jié)果。
更多相關(guān)內(nèi)容,可以參考:https://pytorch.org/docs/stable/notes/randomness.html
Tensor Shapes
通常,在對兩個或多個張量執(zhí)行操作時,它們需要具有相同的形狀—也就是說,在每個維度中具有相同數(shù)量的維度和相同數(shù)量的單元。為此,我們有torch.*_like()方法:
x = torch.empty(2, 2, 3)
print(x.shape)
print(x)
empty_like_x = torch.empty_like(x)
print(empty_like_x.shape)
print(empty_like_x)
zeros_like_x = torch.zeros_like(x)
print(zeros_like_x.shape)
print(zeros_like_x)
ones_like_x = torch.ones_like(x)
print(ones_like_x.shape)
print(ones_like_x)
rand_like_x = torch.rand_like(x)
print(rand_like_x.shape)
print(rand_like_x)
輸出:
torch.Size([2, 2, 3])
tensor([[[ 0.0000e+00, 1.0842e-19, 4.9628e-26],
[-2.5250e-29, 9.8091e-45, 0.0000e+00]],
[[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]]])
torch.Size([2, 2, 3])
tensor([[[ 2.5776e-33, 1.4013e-45, nan],
[ 0.0000e+00, 1.4013e-45, 0.0000e+00]],
[[ 4.9477e-26, -3.6902e+19, 2.6082e-33],
[ 1.4013e-45, 4.9633e-26, -8.5920e+09]]])
torch.Size([2, 2, 3])
tensor([[[0., 0., 0.],
[0., 0., 0.]],
[[0., 0., 0.],
[0., 0., 0.]]])
torch.Size([2, 2, 3])
tensor([[[1., 1., 1.],
[1., 1., 1.]],
[[1., 1., 1.],
[1., 1., 1.]]])
torch.Size([2, 2, 3])
tensor([[[0.6128, 0.1519, 0.0453],
[0.5035, 0.9978, 0.3884]],
[[0.6929, 0.1703, 0.1384],
[0.4759, 0.7481, 0.0361]]])
上面代碼單元中的第一個新內(nèi)容是使用張量的.shape屬性。這個屬性包含張量的每個維度的范圍的列表-在我們的例子中,x是一個形狀為2x2x3的三維張量。
在下面,我們調(diào)用.empty_like()、.zeros_like()、.one_like()和.rand_like()方法。使用.shape屬性,我們可以驗(yàn)證這些方法中的每一個都返回相同維度和范圍的張量。
創(chuàng)建要覆蓋的張量的最后一種方法是直接從PyTorch集合中指定其數(shù)據(jù):
some_constants = torch.tensor([[3.1415926, 2.71828], [1.61803, 0.0072897]])
print(some_constants)
some_integers = torch.tensor((2, 3, 5, 7, 11, 13, 17, 19))
print(some_integers)
more_integers = torch.tensor(((2, 4, 6), [3, 6, 9]))
print(more_integers)
輸出:
tensor([[3.1416, 2.7183],
[1.6180, 0.0073]])
tensor([ 2, 3, 5, 7, 11, 13, 17, 19])
tensor([[2, 4, 6],
[3, 6, 9]])
如果Python元組或列表中已有數(shù)據(jù),那么使用torch.tensor()是創(chuàng)建張量的最直接的方法。如上所示,嵌套集合將產(chǎn)生多維張量。
注意:torch.tensor()創(chuàng)建數(shù)據(jù)的副本。
Tensor Data Types
設(shè)置張量的數(shù)據(jù)類型有兩種方法:
a = torch.ones((2, 3), dtype=torch.int16)
print(a)
b = torch.rand((2, 3), dtype=torch.float64) * 20.
print(b)
c = b.to(torch.int32)
print(c)
輸出:
tensor([[1, 1, 1],
[1, 1, 1]], dtype=torch.int16)
tensor([[ 0.9956, 1.4148, 5.8364],
[11.2406, 11.2083, 11.6692]], dtype=torch.float64)
tensor([[ 0, 1, 5],
[11, 11, 11]], dtype=torch.int32)
設(shè)置張量的基礎(chǔ)數(shù)據(jù)類型的最簡單方法是在創(chuàng)建時使用可選參數(shù)。在上面單元格的第一行中,我們?yōu)閺埩?code style="font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size: 12px;padding: 0.2em 0.4em;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;">a設(shè)置dtype=torch.int16。當(dāng)我們打印a時,我們可以看到它充滿了1而不是1.Python微妙地暗示這是一個整數(shù)類型而不是浮點(diǎn)類型。
另一個要注意的是,與將dtype保留為默認(rèn)值(32位浮點(diǎn))不同,打印張量還指定了它的dtype。
您可能已經(jīng)注意到,我們已經(jīng)從將張量的形狀指定為一系列整數(shù)參數(shù),到將這些參數(shù)分組為一個元組。這并不是絕對必要的—Pytorch將把一系列初始的、未標(biāo)記的整數(shù)參數(shù)作為張量形狀—但是當(dāng)添加可選參數(shù)時,它可以使您的意圖更具可讀性。
另一種設(shè)置數(shù)據(jù)類型的方法是使用.to()方法。在上面的單元中,我們以通常的方式創(chuàng)建一個隨機(jī)浮點(diǎn)張量b。接下來,我們通過使用.to()方法將b轉(zhuǎn)換為32位整數(shù)來創(chuàng)建c。請注意,c包含與b相同的所有值,但被截斷為整數(shù)。
可用的數(shù)據(jù)類型包括:
torch.booltorch.int8torch.uint8torch.int16torch.int32torch.int64torch.halftorch.floattorch.doubletorch.bfloat
Math & Logic with PyTorch Tensors
現(xiàn)在你知道了一些創(chuàng)建張量的方法。。。你能用它們做什么?
讓我們先看一下基本的算術(shù),以及張量如何與簡單的標(biāo)量相互作用:
ones = torch.zeros(2, 2) + 1
twos = torch.ones(2, 2) * 2
threes = (torch.ones(2, 2) * 7 - 1) / 2
fours = twos ** 2
sqrt2s = twos ** 0.5
print(ones)
print(twos)
print(threes)
print(fours)
print(sqrt2s)
輸出:
tensor([[1., 1.],
[1., 1.]])
tensor([[2., 2.],
[2., 2.]])
tensor([[3., 3.],
[3., 3.]])
tensor([[4., 4.],
[4., 4.]])
tensor([[1.4142, 1.4142],
[1.4142, 1.4142]])
如上所述,張量和標(biāo)量之間的算術(shù)運(yùn)算(如加法、減法、乘法、除法和指數(shù)運(yùn)算)分布在張量的每個元素上。因?yàn)檫@樣一個操作的輸出將是一個張量,所以您可以用常用的操作符優(yōu)先規(guī)則將它們鏈接在一起,就像我們創(chuàng)建threes的那一行一樣。
兩個張量之間類似的運(yùn)算也會像你直覺預(yù)期的那樣:
powers2 = twos ** torch.tensor([[1, 2], [3, 4]])
print(powers2)
fives = ones + fours
print(fives)
dozens = threes * fours
print(dozens)
輸出:
tensor([[ 2., 4.],
[ 8., 16.]])
tensor([[5., 5.],
[5., 5.]])
tensor([[12., 12.],
[12., 12.]])
這里需要注意的是,前面代碼單元中的所有張量都是相同的形狀。當(dāng)我們嘗試對不同形狀的張量執(zhí)行二元運(yùn)算時會發(fā)生什么?
注意:下面的單元格拋出一個運(yùn)行時錯誤。這是故意的。
a = torch.rand(2, 3)
b = torch.rand(3, 2)
print(a * b)
輸出:
RuntimeError Traceback (most recent call last)
<ipython-input-10-fcc83145fe91> in <module>
2 b = torch.rand(3, 2)
3
----> 4 print(a * b)
RuntimeError: The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 1
在一般情況下,不能以這種方式對不同形狀的張量進(jìn)行操作,即使是在類似于上面單元的情況下,其中張量具有相同數(shù)量的元素。
In Brief: Tensor Broadcasting
(注意:如果您熟悉NumPy ndarrays中的broadcasting 語義,您會發(fā)現(xiàn)這里也適用相同的規(guī)則。)
相同形狀規(guī)則的例外是張量broadcasting。舉個例子:
rand = torch.rand(2, 4)
doubled = rand * (torch.ones(1, 4) * 2)
print(rand)
print(doubled)
輸出:
tensor([[0.2024, 0.5731, 0.7191, 0.4067],
[0.7301, 0.6276, 0.7357, 0.0381]])
tensor([[0.4049, 1.1461, 1.4382, 0.8134],
[1.4602, 1.2551, 1.4715, 0.0762]])
這里有什么訣竅?我們怎么把2x4張量乘以1x4張量?
廣播是在形狀相似的張量之間執(zhí)行操作的一種方式。在上面的例子中,一行四列張量乘以兩行四列張量的兩行。
這是深度學(xué)習(xí)的一個重要操作。常見的例子是將一個學(xué)習(xí)權(quán)重的張量乘以一批輸入張量,分別對該批中的每個實(shí)例應(yīng)用該操作,并返回一個形狀相同的張量,就像上面的(2,4)*(1,4)示例返回的形狀張量(2,4)一樣。
廣播規(guī)則如下:
每個張量必須至少有一個維度-沒有空張量。
比較兩個張量的尺寸,從最后到第一:
每個維度必須相等,或者
其中一個尺寸必須為1,或
維度不存在于其中一個張量中
當(dāng)然,形狀相同的張量是肯定可以的“可廣播的”,正如您前面看到的。
以下是一些遵守上述規(guī)則并允許廣播的情況的示例:
a = torch.ones(4, 3, 2)
b = a * torch.rand( 3, 2) # 3rd & 2nd dims identical to a, dim 1 absent
print(b)
c = a * torch.rand( 3, 1) # 3rd dim = 1, 2nd dim identical to a
print(c)
d = a * torch.rand( 1, 2) # 3rd dim identical to a, 2nd dim = 1
print(d)
輸出:
tensor([[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]],
[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]],
[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]],
[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]]])
tensor([[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]],
[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]],
[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]],
[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]]])
tensor([[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]],
[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]],
[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]],
[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]]])
仔細(xì)觀察上面每個張量的值:
產(chǎn)生
b的乘法運(yùn)算在a的每個“層”上廣播。對于
c來說,操作是在a的每一層和每一行上廣播的—每3個元素的列都是相同的。對于
d,我們改變了它-現(xiàn)在每一行都是相同的,跨層和跨列。
有關(guān)廣播的更多信息,請參閱有關(guān)該主題的PyTorch文檔。https://pytorch.org/docs/stable/notes/broadcasting.html
以下是一些廣播嘗試失敗的例子:
注意:下面的單元格拋出一個運(yùn)行時錯誤。這是故意的。
a = torch.ones(4, 3, 2)
b = a * torch.rand(4, 3) # dimensions must match last-to-first
c = a * torch.rand( 2, 3) # both 3rd & 2nd dims different
d = a * torch.rand((0, )) # can't broadcast with an empty tensor
輸出:
RuntimeError Traceback (most recent call last)
<ipython-input-13-591adab00a90> in <module>
1 a = torch.ones(4, 3, 2)
2
----> 3 b = a * torch.rand(4, 3) # dimensions must match last-to-first
4
5 c = a * torch.rand( 2, 3) # both 3rd & 2nd dims different
RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 2
More Math with Tensors
PyTorch張量有三百多個操作可以在它們上面執(zhí)行。
以下是一些主要業(yè)務(wù)類別的小案例:
# common functions
a = torch.rand(2, 4) * 2 - 1
print('Common functions:')
print(torch.abs(a))
print(torch.ceil(a))
print(torch.floor(a))
print(torch.clamp(a, -0.5, 0.5))
# trigonometric functions and their inverses
angles = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
sines = torch.sin(angles)
inverses = torch.asin(sines)
print('\nSine and arcsine:')
print(angles)
print(sines)
print(inverses)
# bitwise operations
print('\nBitwise XOR:')
b = torch.tensor([1, 5, 11])
c = torch.tensor([2, 7, 10])
print(torch.bitwise_xor(b, c))
# comparisons:
print('\nBroadcasted, element-wise equality comparison:')
d = torch.tensor([[1., 2.], [3., 4.]])
e = torch.ones(1, 2) # many comparison ops support broadcasting!
print(torch.eq(d, e)) # returns a tensor of type bool
# reductions:
print('\nReduction ops:')
print(torch.max(d)) # returns a single-element tensor
print(torch.max(d).item()) # extracts the value from the returned tensor
print(torch.mean(d)) # average
print(torch.std(d)) # standard deviation
print(torch.prod(d)) # product of all numbers
print(torch.unique(torch.tensor([1, 2, 1, 2, 1, 2]))) # filter unique elements
# vector and linear algebra operations
v1 = torch.tensor([1., 0., 0.]) # x unit vector
v2 = torch.tensor([0., 1., 0.]) # y unit vector
m1 = torch.rand(2, 2) # random matrix
m2 = torch.tensor([[3., 0.], [0., 3.]]) # three times identity matrix
print('\nVectors & Matrices:')
print(torch.cross(v2, v1)) # negative of z unit vector (v1 x v2 == -v2 x v1)
print(m1)
m3 = torch.matmul(m1, m2)
print(m3) # 3 times m1
print(torch.svd(m3)) # singular value decomposition
輸出:
Common functions:
tensor([[0.8447, 0.1992, 0.9755, 0.9295],
[0.8190, 0.1029, 0.7480, 0.4949]])
tensor([[-0., -0., 1., -0.],
[-0., -0., 1., -0.]])
tensor([[-1., -1., 0., -1.],
[-1., -1., 0., -1.]])
tensor([[-0.5000, -0.1992, 0.5000, -0.5000],
[-0.5000, -0.1029, 0.5000, -0.4949]])
Sine and arcsine:
tensor([0.0000, 0.7854, 1.5708, 2.3562])
tensor([0.0000, 0.7071, 1.0000, 0.7071])
tensor([0.0000, 0.7854, 1.5708, 0.7854])
Bitwise XOR:
tensor([3, 2, 1])
Broadcasted, element-wise equality comparison:
tensor([[ True, False],
[False, False]])
Reduction ops:
tensor(4.)
4.0
tensor(2.5000)
tensor(1.2910)
tensor(24.)
tensor([1, 2])
Vectors & Matrices:
tensor([ 0., 0., -1.])
tensor([[0.6923, 0.7545],
[0.7746, 0.2330]])
tensor([[2.0769, 2.2636],
[2.3237, 0.6990]])
torch.return_types.svd(
U=tensor([[-0.7959, -0.6054],
[-0.6054, 0.7959]]),
S=tensor([3.7831, 1.0066]),
V=tensor([[-0.8088, 0.5881],
[-0.5881, -0.8088]]))
這是一個很小的示例,用于獲取更多詳細(xì)信息和完整的數(shù)學(xué)函數(shù)清單 https://pytorch.org/docs/stable/torch.html#math-operations
Altering Tensors in Place
大多數(shù)關(guān)于張量的二元運(yùn)算都會返回第三個新的張量。當(dāng)我們說c=a*b(其中a和b是張量)時,新的張量c將占用不同于其他張量的內(nèi)存區(qū)域。
不過,有時您可能希望在適當(dāng)?shù)奈恢酶膹埩俊纾绻趫?zhí)行元素計算,您可以在其中丟棄中間值。為此,大多數(shù)數(shù)學(xué)函數(shù)都有一個附加下劃線(_)的版本,它將在適當(dāng)?shù)奈恢酶淖儚埩俊?/p>
例如:
a = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print('a:')
print(a)
print(torch.sin(a)) # this operation creates a new tensor in memory
print(a) # a has not changed
b = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print('\nb:')
print(b)
print(torch.sin_(b)) # note the underscore
print(b) # b has changed
輸出:
a:
tensor([0.0000, 0.7854, 1.5708, 2.3562])
tensor([0.0000, 0.7071, 1.0000, 0.7071])
tensor([0.0000, 0.7854, 1.5708, 2.3562])
b:
tensor([0.0000, 0.7854, 1.5708, 2.3562])
tensor([0.0000, 0.7071, 1.0000, 0.7071])
tensor([0.0000, 0.7071, 1.0000, 0.7071])
對于算術(shù)運(yùn)算,有些函數(shù)的行為類似:
a = torch.ones(2, 2)
b = torch.rand(2, 2)
print('Before:')
print(a)
print(b)
print('\nAfter adding:')
print(a.add_(b))
print(a)
print(b)
print('\nAfter multiplying')
print(b.mul_(b))
print(b)
輸出:
Before:
tensor([[1., 1.],
[1., 1.]])
tensor([[0.8441, 0.9004],
[0.3995, 0.6324]])
After adding:
tensor([[1.8441, 1.9004],
[1.3995, 1.6324]])
tensor([[1.8441, 1.9004],
[1.3995, 1.6324]])
tensor([[0.8441, 0.9004],
[0.3995, 0.6324]])
After multiplying
tensor([[0.7125, 0.8107],
[0.1596, 0.3999]])
tensor([[0.7125, 0.8107],
[0.1596, 0.3999]])
請注意,這些in-place算術(shù)函數(shù)是torch.Tensor對象上的方法,與許多其他函數(shù)(例如,torch.sin())不同,它們沒有附加到torch模塊。從a.add_(b)可以看出,調(diào)用的張量是在適當(dāng)位置改變的張量。
還有另一種方法可以將計算結(jié)果放入現(xiàn)有的分配張量中。到目前為止我們看到的許多方法和函數(shù)-包括創(chuàng)建方法有一個out參數(shù),可以指定一個張量來接收輸出。如果out張量的形狀和數(shù)據(jù)類型正確,則無需新的內(nèi)存分配即可實(shí)現(xiàn):
a = torch.rand(2, 2)
b = torch.rand(2, 2)
c = torch.zeros(2, 2)
old_id = id(c)
print(c)
d = torch.matmul(a, b, out=c)
print(c) # contents of c have changed
assert c is d # test c & d are same object, not just containing equal values
assert id(c), old_id # make sure that our new c is the same object as the old one
torch.rand(2, 2, out=c) # works for creation too!
print(c) # c has changed again
assert id(c), old_id # still the same object!
Copying Tensors
與Python中的任何對象一樣,將張量賦給變量會使變量成為張量的標(biāo)簽,而不會復(fù)制它。例如:
a = torch.ones(2, 2)
b = a
a[0][1] = 561 # we change a...
print(b) # ...and b is also altered
輸出:
tensor([[ 1., 561.],
[ 1., 1.]])
但是如果你想要一份單獨(dú)的數(shù)據(jù)拷貝呢?clone()方法就在這里:
a = torch.ones(2, 2)
b = a.clone()
assert b is not a # different objects in memory...
print(torch.eq(a, b)) # ...but still with the same contents!
a[0][1] = 561 # a changes...
print(b) # ...but b is still all ones
輸出:
tensor([[True, True],
[True, True]])
tensor([[1., 1.],
[1., 1.]])
使用clone()時需要注意一點(diǎn)。如果源張量已啟用 autograd,則克隆也將啟用autograd。這將在autograd上的視頻中進(jìn)行更深入的討論,但如果您想要詳細(xì)信息的light版本,請繼續(xù)。
在許多情況下,這將是你想要的。例如,如果您的模型在forward()方法中有多個計算路徑,并且原始張量及其克隆都有助于模型的輸出,那么要啟用模型學(xué)習(xí),您需要為這兩個張量啟用autograd。如果源張量啟用了autograd(如果它是一組學(xué)習(xí)權(quán)值或從包含權(quán)值的計算中導(dǎo)出,則通常會啟用autograd),那么您將得到所需的結(jié)果。
另一方面,如果你在做一個原始張量和克隆張量都不需要跟蹤梯度的計算,那么只要源張量的autograd已經(jīng)關(guān)閉,你就可以開始了。
不過,還有第三種情況:假設(shè)您正在模型的forward()函數(shù)中執(zhí)行一個計算,在這個函數(shù)中,默認(rèn)情況下對所有對象都啟用梯度,但是您希望在中間提取一些值來生成一些度量。在這種情況下,您不希望源張量的克隆副本跟蹤梯度-關(guān)閉autograd的歷史跟蹤后,性能會得到提高。為此,可以對源張量使用.detach()方法:
a = torch.rand(2, 2, requires_grad=True) # turn on autograd
print(a)
b = a.clone()
print(b)
c = a.detach().clone()
print(c)
print(a)
輸出:
tensor([[0.5461, 0.5396],
[0.3053, 0.1973]], requires_grad=True)
tensor([[0.5461, 0.5396],
[0.3053, 0.1973]], grad_fn=<CloneBackward>)
tensor([[0.5461, 0.5396],
[0.3053, 0.1973]])
tensor([[0.5461, 0.5396],
[0.3053, 0.1973]], requires_grad=True)
這里發(fā)生了什么?
我們創(chuàng)建一個啟用
requires_grad=True的a。我們還沒有討論這個可選的參數(shù),但將在autograd單元期間討論。當(dāng)我們打印
a時,它會通知我們屬性requires_grad=True—這意味著自動加載和計算歷史跟蹤已打開。我們克隆了
a,并給它貼上了b的標(biāo)簽。當(dāng)我們打印b時,我們可以看到它正在跟蹤它的計算歷史-它繼承了a的autograd設(shè)置,并添加到計算歷史中。我們將
a克隆到c中,但首先調(diào)用detach()。打印
c時,我們看不到任何計算歷史,也沒有requires_grad=True。
detach()方法將張量從其計算歷史中分離出來。它說,“做下一步要做的事情,就好像autograd關(guān)閉了一樣。”它這樣做時不改變a-你可以看到,當(dāng)我們在最后再次打印a時,它保留了它的requires_grad=True屬性。
Moving to GPU
PyTorch的一個主要優(yōu)點(diǎn)是在兼容CUDA的Nvidia GPU上具有強(qiáng)大的加速能力CUDA“代表計算統(tǒng)一設(shè)備架構(gòu) -Compute Unified Device Architecture,這是Nvidia的并行計算平臺。)到目前為止,我們所做的一切都是在CPU上完成的。我們?nèi)绾无D(zhuǎn)向更快的硬件?
首先,我們應(yīng)該使用is_available()方法檢查GPU是否可用。
注意:如果沒有安裝與CUDA兼容的GPU和CUDA驅(qū)動程序,本節(jié)中的可執(zhí)行單元將不會執(zhí)行任何與GPU相關(guān)的代碼。
if torch.cuda.is_available():
print('We have a GPU!')
else:
print('Sorry, CPU only.')
輸出:
Sorry, CPU only.
一旦我們確定一個或多個GPU可用,我們就需要把數(shù)據(jù)放在GPU可以看到的地方。你的CPU對你計算機(jī)內(nèi)存中的數(shù)據(jù)進(jìn)行計算。你的GPU有專用內(nèi)存。每當(dāng)您想在設(shè)備上執(zhí)行計算時,必須將計算所需的所有數(shù)據(jù)移動到該設(shè)備可訪問的內(nèi)存中(通俗地說,“將數(shù)據(jù)移動到GPU可訪問的內(nèi)存”簡稱為“將數(shù)據(jù)移動到GPU”。)
有多種方法可以將數(shù)據(jù)傳送到目標(biāo)設(shè)備上。您可以在創(chuàng)建時執(zhí)行此操作:
if torch.cuda.is_available():
gpu_rand = torch.rand(2, 2, device='cuda')
print(gpu_rand)
else:
print('Sorry, CPU only.')
輸出:
Sorry, CPU only.
默認(rèn)情況下,新的張量是在CPU上創(chuàng)建的,因此我們必須指定何時在GPU上創(chuàng)建帶有可選device參數(shù)的張量。你可以看到當(dāng)我們打印新的時態(tài)時,PyToch會通知我們它在哪個設(shè)備上(如果它不在CPU上)。
您可以使用torch.cuda.device_ucount()查詢GPU的數(shù)量。如果您有多個GPU,則可以通過索引設(shè)置例如:device='cuda:0',device='cuda:1'等等。
作為一種編碼實(shí)踐,用字符串常量指定我們的設(shè)備是非常脆弱的。在理想的世界中,無論您是在CPU還是GPU硬件上,您的代碼都會執(zhí)行強(qiáng)勁。您可以通過創(chuàng)建一個設(shè)備句柄來完成這一點(diǎn),該句柄可以傳遞給張量,而不是字符串:
if torch.cuda.is_available():
my_device = torch.device('cuda')
else:
my_device = torch.device('cpu')
print('Device: {}'.format(my_device))
x = torch.rand(2, 2, device=my_device)
print(x)
輸出:
Device: cpu
tensor([[0.3285, 0.5655],
[0.0065, 0.7765]])
如果一個設(shè)備上存在張量,可以使用to()方法將其移動到另一個設(shè)備上。下面的代碼行在CPU上創(chuàng)建一個張量,并將其移動到上一個單元中獲取的設(shè)備句柄。
y = torch.rand(2, 2)
y = y.to(my_device)
重要的是要知道,為了進(jìn)行涉及兩個或更多張量的計算,所有的張量必須在同一個設(shè)備上。無論您是否有可用的GPU設(shè)備,以下代碼都將引發(fā)運(yùn)行時錯誤:
x = torch.rand(2, 2)
y = torch.rand(2, 2, device='gpu')
z = x + y # exception will be thrown
Manipulating Tensor Shapes
有時,你需要改變張量的形狀。下面,我們來看看幾個常見的案例,以及如何處理它們。
Changing the Number of Dimensions
可能需要更改維度數(shù)的一種情況是將輸入的單個實(shí)例傳遞給模型。PyTorch模型通常需要成批(batch)的輸入。
例如,假設(shè)有一個模型可以處理3 x 226 x 226圖像-一個226像素的正方形,有3個顏色通道。當(dāng)你加載和變換它,你會得到一個張量的形狀(3,226,226)。不過,您的模型需要輸入shape(N,3,226,226),其中N是批處理中的圖像數(shù)。那你怎么做一批呢?
a = torch.rand(3, 226, 226)
b = a.unsqueeze(0)
print(a.shape)
print(b.shape)
輸出:
torch.Size([3, 226, 226])
torch.Size([1, 3, 226, 226])
unsqueze()方法添加范圍1的維度。unsqueze(0)將其添加為新的第零維-現(xiàn)在您有了第零維 batch!
所以如果這是 un*squeezing?squeezing 是什么意思?我們利用了一個事實(shí),范圍1*的任何維度都不會改變張量中元素的數(shù)量。
c = torch.rand(1, 1, 1, 1, 1)
print(c)
輸出:
tensor([[[[[0.8960]]]]])
繼續(xù)上面的例子,假設(shè)模型的輸出是每個輸入的20個元素的向量。然后您會期望輸出具有shape(N,20),其中N是輸入批處理中的實(shí)例數(shù)。這意味著對于單個輸入批,我們將得到一個shape(1,20)的輸出。
如果你想用這個輸出做一些非批處理的計算-一些只需要20個元素的向量的東西呢?
a = torch.rand(1, 20)
print(a.shape)
print(a)
b = a.squeeze(0)
print(b.shape)
print(b)
c = torch.rand(2, 2)
print(c.shape)
d = c.squeeze(0)
print(d.shape)
輸出:
torch.Size([1, 20])
tensor([[0.4887, 0.8625, 0.6191, 0.9935, 0.1844, 0.6138, 0.6854, 0.0438, 0.0636,
0.2884, 0.4362, 0.2368, 0.1394, 0.1721, 0.1751, 0.3851, 0.0732, 0.3118,
0.9180, 0.7293]])
torch.Size([20])
tensor([0.4887, 0.8625, 0.6191, 0.9935, 0.1844, 0.6138, 0.6854, 0.0438, 0.0636,
0.2884, 0.4362, 0.2368, 0.1394, 0.1721, 0.1751, 0.3851, 0.0732, 0.3118,
0.9180, 0.7293])
torch.Size([2, 2])
torch.Size([2, 2])
你可以從形狀上看到,我們的二維張量現(xiàn)在是一維的,如果你仔細(xì)觀察上面單元格的輸出,你會發(fā)現(xiàn),由于有一個額外的維度,打印a會顯示一組“額外的”方括號[]。
squeeze()只能處理額外的 1 個維度。請看上面,我們試圖在 c中壓縮一個尺寸為2的維度,并恢復(fù)到我們開始時的形狀。對調(diào)用squeeze() 和 unsqueeze()只能作用于額外的 1個維度,因?yàn)榉駝t會更改張量中的元素數(shù)。
另一個可以使用unsqueze()的地方是簡化廣播。回想一下上面的例子,我們有以下代碼:
a = torch.ones(4, 3, 2)
c = a * torch.rand( 3, 1) # 3rd dim = 1, 2nd dim identical to a
print(c)
這樣做的效果是在維度0和維度2上廣播操作,使得隨機(jī)的3×1張量按元素乘以a中的每一個3元素列。
如果隨機(jī)向量是三元素向量呢?我們將失去做廣播的能力,因?yàn)楦鶕?jù)廣播規(guī)則,最終尺寸將不匹配。unsqueze()來解決:
a = torch.ones(4, 3, 2)
b = torch.rand( 3) # trying to multiply a * b will give a runtime error
c = b.unsqueeze(1) # change to a 2-dimensional tensor, adding new dim at the end
print(c.shape)
print(a * c) # broadcasting works again!
輸出:
torch.Size([3, 1])
tensor([[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]],
[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]],
[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]],
[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]]])
squeeze() 和 unsqueeze() 方法也有 in-place 版本, squeeze_() 和unsqueeze_():
batch_me = torch.rand(3, 226, 226)
print(batch_me.shape)
batch_me.unsqueeze_(0)
print(batch_me.shape)
輸出:
torch.Size([3, 226, 226])
torch.Size([1, 3, 226, 226])
有時,您可能希望更徹底地更改張量的形狀,同時仍保留元素的數(shù)量及其內(nèi)容。發(fā)生這種情況的一種情況是在模型的卷積層和模型的線性層之間的接口處-這在圖像分類模型中很常見。卷積核將產(chǎn)生形狀 features x width x height 的輸出張量,但下面的線性層需要一維輸入。如果您請求的尺寸產(chǎn)生與輸入張量相同數(shù)量的元素,則reshape()將為您執(zhí)行此操作:
output3d = torch.rand(6, 20, 20)
print(output3d.shape)
input1d = output3d.reshape(6 * 20 * 20)
print(input1d.shape)
# can also call it as a method on the torch module:
print(torch.reshape(output3d, (6 * 20 * 20,)).shape)
輸出:
torch.Size([6, 20, 20])
torch.Size([2400])
torch.Size([2400])
(注意:上面單元格的最后一行中的(6 * 20 * 20,) 參數(shù)是因?yàn)镻yTorch在指定張量形狀時需要一個元組(tuple)-但是當(dāng)形狀是方法的第一個參數(shù)時,它允許我們作弊并只使用一系列整數(shù)。在這里,我們必須添加括號和逗號,以使方法相信這實(shí)際上是一個單元素元組。)
如果可以的話,reshape()將返回一個關(guān)于要更改的張量的視圖,即一個單獨(dú)的張量對象查看相同的內(nèi)存底層區(qū)域。這一點(diǎn)很重要:這意味著對源張量所做的任何更改都將反映在該張量的視圖中,除非您克隆它。
有些條件超出了本文的介紹范圍,在這些條件下,reshape()必須返回一個包含數(shù)據(jù)副本的張量。有關(guān)更多信息,請參閱文檔。https://pytorch.org/docs/stable/torch.html#torch.reshape
NumPy Bridge
在上面關(guān)于廣播的部分中,提到了PyTorch的廣播語義與NumPy的兼容,但是PyTorch和NumPy之間的親緣關(guān)系甚至更深。
如果您有存儲在NumPy ndarrays中的數(shù)據(jù)的現(xiàn)有ML或科學(xué)代碼,您可能希望將這些數(shù)據(jù)表示為PyTorch張量,無論是利用PyTorch的GPU加速,還是利用其高效的抽象來構(gòu)建ML模型。很容易在Ndarray和Pytork張量之間切換:
import numpy as np
numpy_array = np.ones((2, 3))
print(numpy_array)
pytorch_tensor = torch.from_numpy(numpy_array)
print(pytorch_tensor)
輸出:
[[1. 1. 1.]
[1. 1. 1.]]
tensor([[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
PyTorch創(chuàng)建了一個與NumPy數(shù)組相同形狀和包含相同數(shù)據(jù)的張量,甚至保留了NumPy默認(rèn)的64位浮點(diǎn)數(shù)據(jù)類型。
轉(zhuǎn)換可以很容易地轉(zhuǎn)到另一個方向:
pytorch_rand = torch.rand(2, 3)
print(pytorch_rand)
numpy_rand = pytorch_rand.numpy()
print(numpy_rand)
輸出:
tensor([[0.5647, 0.9160, 0.7783],
[0.8277, 0.4579, 0.6382]])
[[0.5646949 0.91600937 0.77828014]
[0.82769746 0.45785618 0.6381657 ]]
重要的是要知道,這些轉(zhuǎn)換后的對象使用的底層內(nèi)存與其源對象相同,這意味著對其中一個對象的更改將反映在另一個對象中:
print(pytorch_tensor)
pytorch_rand[1, 1] = 17
print(numpy_rand)
輸出:
tensor([[ 1., 1., 1.],
[ 1., 23., 1.]], dtype=torch.float64)
[[ 0.5646949 0.91600937 0.77828014]
[ 0.82769746 17. 0.6381657 ]]
個人微信(如果沒有備注不拉群!) 請注明:地區(qū)+學(xué)校/企業(yè)+研究方向+昵稱
下載1:何愷明頂會分享
在「AI算法與圖像處理」公眾號后臺回復(fù):何愷明,即可下載。總共有6份PDF,涉及 ResNet、Mask RCNN等經(jīng)典工作的總結(jié)分析
下載2:終身受益的編程指南:Google編程風(fēng)格指南
在「AI算法與圖像處理」公眾號后臺回復(fù):c++,即可下載。歷經(jīng)十年考驗(yàn),最權(quán)威的編程規(guī)范!
下載3 CVPR2021
在「AI算法與圖像處理」公眾號后臺回復(fù):CVPR,即可下載1467篇CVPR 2020論文 和 CVPR 2021 最新論文
點(diǎn)亮
,告訴大家你也在看
