Tensor fundamentals
讀者已經(jīng)了解張量是PyTorch中的基本數(shù)據(jù)結(jié)構(gòu)。所謂的張量就是數(shù)組,即一種存儲(chǔ)數(shù)字集合的數(shù)據(jù)結(jié)構(gòu),這些數(shù)字可通過單個(gè)或多個(gè)索引訪問。請(qǐng)讀者觀察一下list中的索引,以便將其與張量中的索引進(jìn)行比較。以下展示了一個(gè)包含三個(gè)數(shù)字的list。
>>> a = [1.0, 2.0, 1.0]
讀者可以使用對(duì)應(yīng)的從0開始的索引來訪問列表的第一個(gè)元素:
>>> a[0]
1.0
>>> a[2] = 3.0
>>> a
[1.0, 2.0, 3.0]
對(duì)于簡單的Python程序來說,使用Python list存儲(chǔ)并處理矢量(例如2D直線的坐標(biāo))是很常見的。但是,由于以下幾個(gè)原因,這種做法可能不是最佳選擇:
Python中的數(shù)值是成熟的對(duì)象。浮點(diǎn)數(shù)可能只需要32位就可以在計(jì)算機(jī)上表示,而Python將它們封裝在功能齊全的Python對(duì)象中,并帶有引用計(jì)數(shù)等功能。如果讀者僅需要存儲(chǔ)少量的數(shù)值那問題不大,但是分配數(shù)百萬個(gè)這樣的數(shù)值就會(huì)效率低下。
Python中的lists適用于對(duì)象的有序集合。沒有定義用于有效地獲取兩個(gè)向量的點(diǎn)積或向量求和的操作。此外,Python lists無法優(yōu)化其在內(nèi)存中的布局,因?yàn)樗鼈兪侵赶騊ython對(duì)象(任何類型,而不僅僅是數(shù)值)的可索引指針集合。最后,Python lists是一維的,盡管讀者可以創(chuàng)建包含lists對(duì)象的lists,但是這種做法效率低下。
與經(jīng)過優(yōu)化的編譯代碼相比,Python解釋器速度較慢。使用編譯型低級(jí)語言(例如C)編寫的經(jīng)過優(yōu)化后的代碼,能夠更快地對(duì)大量數(shù)值執(zhí)行數(shù)學(xué)運(yùn)算。
由于這些原因,數(shù)據(jù)科學(xué)庫依賴于NumPy或引入專用數(shù)據(jù)結(jié)構(gòu)(例如PyTorch張量),這些數(shù)據(jù)結(jié)構(gòu)提供了數(shù)值數(shù)據(jù)結(jié)構(gòu)及其相關(guān)操作的高效率低級(jí)別實(shí)現(xiàn)并將其封裝在高級(jí)API里。
張量可以表示許多類型的數(shù)據(jù),包括圖像、時(shí)間序列、音頻甚至句子。通過定義張量上的操作(本章中將探討其中的一些操作),即使使用高級(jí)(不是特別快速)的語言(例如Python),也可以同時(shí)高效地切片和操作數(shù)據(jù)。
現(xiàn)在讀者可以構(gòu)建第一個(gè)PyTorch張量,具體地看看它的結(jié)構(gòu)。這個(gè)張量有一列三行:
>>> import torch
>>> a = torch.ones(3)
>>> a
tensor([1., 1., 1.])
>>> a[1]
tensor(1.)
>>> float(a[1])
1.0
>>> a[2] = 2.0
>>> a
tensor([1., 1., 2.])
現(xiàn)在看看我們都執(zhí)行了什么操作。導(dǎo)入torch模塊后調(diào)用了一個(gè)函數(shù),該函數(shù)創(chuàng)建size為3的(一維)張量,并填充值為1.0。我們可以使用基于0的索引來訪問元素,也可以為其分配新的值。
盡管從表面上看,此示例與list并沒有太大區(qū)別,但實(shí)際上情況完全不同。Python list或tuple中的數(shù)值是在內(nèi)存中單獨(dú)分配的Python對(duì)象的集合,如下圖左側(cè)所示。與之不同的是,PyTorch張量或NumPy數(shù)組是(通常)占用連續(xù)的內(nèi)存塊,包含未封裝的C數(shù)值類型而不是Python對(duì)象。在這種情況下,32位浮點(diǎn)數(shù)(4字節(jié))存儲(chǔ)方式如下圖右側(cè)所示。因此,具有一百萬個(gè)浮點(diǎn)數(shù)的一維張量需要存儲(chǔ)400萬個(gè)連續(xù)字節(jié),再加上少量的元數(shù)據(jù)開銷(維度,數(shù)據(jù)類型等)。
假設(shè)我們要管理一個(gè)包含2D坐標(biāo)的list,以表示一個(gè)幾何對(duì)象,例如三角形。該示例與深度學(xué)習(xí)相關(guān)不大,但易于理解。我們可以使用一維張量,將xs存儲(chǔ)在偶數(shù)索引中并且將ys存儲(chǔ)在奇數(shù)索引中,而不是在Python list中將坐標(biāo)作為數(shù)值來使用,就像這樣:
>>> points = torch.zeros(6) #torch.zeros是一種定義相同尺寸數(shù)組的方法
>>> points[0] = 1.0 #重新賦值
>>> points[1] = 4.0
>>> points[2] = 2.0
>>> points[3] = 1.0
>>> points[4] = 3.0
>>> points[5] = 5.0
我們還可以傳入一個(gè)Python list來構(gòu)造對(duì)象:
>>> points = torch.tensor([1.0, 4.0, 2.0, 1.0, 3.0, 5.0])
>>> points
tensor([1., 4., 2., 1., 3., 5.])
獲取第一個(gè)點(diǎn)的坐標(biāo):
>>> float(points[0]), float(points[1])
(1.0, 4.0)
這項(xiàng)技術(shù)只能說合格,盡管讓第一個(gè)索引引用單個(gè)2D點(diǎn)而不是點(diǎn)坐標(biāo)的做法是可行的。為了改進(jìn),我們可以使用2D張量:
>>> points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])
>>> points
tensor([[1., 4.],
[2., 1.],
[3., 5.]])
我們傳入一個(gè)包含list對(duì)象的list來構(gòu)造張量,現(xiàn)在可以獲取這個(gè)張量的形狀:
>>> points.shape
torch.Size([3, 2])
它會(huì)告知我們張量每個(gè)維度的大小。我們還可以使用0或1來初始化張量,僅傳入一個(gè)包含維度信息的tuple:
>>> points = torch.zeros(3, 2)
>>> points
tensor([[0., 0.],
[0., 0.],
[0., 0.]])
現(xiàn)在我們可以使用兩個(gè)索引來訪問單個(gè)元素:
>>> points = torch.FloatTensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])
>>> points
tensor([[1., 4.],
[2., 1.],
[3., 5.]])
>>> points[0, 1]
tensor(4.)
這段代碼返回?cái)?shù)據(jù)集中第1個(gè)點(diǎn)的y坐標(biāo)。我們也可以按照之前的做法來獲取第一個(gè)點(diǎn)的2D坐標(biāo):
>>> points[0]
tensor([1., 4.])
請(qǐng)注意,輸出結(jié)果是另一個(gè)張量。它是尺寸大小為2的一維張量,包含points張量第一行中的值。此輸出是否意味著分配了新的內(nèi)存塊,將值復(fù)制到其中并封裝進(jìn)一個(gè)新的張量,將新的內(nèi)存返回了?答案是No,因?yàn)樵撨^程效率不高,尤其是處理數(shù)百萬個(gè)點(diǎn)的時(shí)候。相反,我們獲得的新張量和points共享同一塊內(nèi)存,當(dāng)然僅限于第一行(因?yàn)槲覀冋{(diào)用的是第一行)。
聯(lián)系客服