小白學PyTorch | 9 tensor資料結構與儲存結構

上一節課,講解了MNIST影象分類的一個小實戰,現在我們繼續深入學習一下pytorch的一些有的沒的的小知識來作為只是儲備。

參考目錄:

1 pytorch資料結構

1。1 預設整數與浮點數

1。2 dtype修改變數型別

1。3 變數型別有哪些

1。4 資料型別轉換

2 torch vs numpy

2。1 兩者轉換

2。2 兩者區別

3 張量

3。1 張量修改尺寸

3。2 張量記憶體儲存結構

3。3 儲存區

3。4 頭資訊區

1 pytorch資料結構

1.1 預設整數與浮點數

【pytorch預設的整數是int64】

pytorch的預設整數是用64個位元儲存,也就是8個位元組(Byte)儲存的。

【pytorch預設的浮點數是float32】

pytorch的預設浮點數是用32個位元儲存,也就是4個位元組(Byte)儲存的。

import torchimport numpy as np# ————————————print(‘torch的浮點數與整數的預設資料型別’)a = torch。tensor([1,2,3])b = torch。tensor([1。,2。,3。])print(a,a。dtype)print(b,b。dtype)

輸出:

torch的浮點數與整數的預設資料型別tensor([1, 2, 3]) torch。int64tensor([1。, 2。, 3。]) torch。float32

1.2 dtype修改變數型別

print(‘torch的浮點數與整數的預設資料型別’)a = torch。tensor([1,2,3],dtype=torch。int8)b = torch。tensor([1。,2。,3。],dtype = torch。float64)print(a,a。dtype)print(b,b。dtype)

輸出結果:

torch的浮點數與整數的預設資料型別tensor([1, 2, 3], dtype=torch。int8) torch。int8tensor([1。, 2。, 3。], dtype=torch。float64) torch。float64

1.3 變數型別有哪些

張量的資料型別其實和numpy。array基本一一對應,除了不支援str,主要有下面幾種形式:

torch。float64 # 等同於(torch。double)torch。float32 # 預設,FloatTensortorch。float16torch。int64   # 等同於torch。longtorch。int32   # 預設torch。int16torch。int8torch。uint8   # 二進位制碼,表示0-255torch。bool

在建立變數的時候,想要建立指定的變數型別,上文中提到了用dtype關鍵字來控制,但是我個人更喜歡使用特定的建構函式:

print(‘torch的建構函式’)a = torch。IntTensor([1,2,3])b = torch。LongTensor([1,2,3])c = torch。FloatTensor([1,2,3])d = torch。DoubleTensor([1,2,3])e = torch。tensor([1,2,3])f = torch。tensor([1。,2。,3。])print(a。dtype)print(b。dtype)print(c。dtype)print(d。dtype)print(e。dtype)print(f。dtype)

輸出結果:

torch的建構函式torch。int32torch。int64torch。float32torch。float64torch。int64torch。float32

因此我們可以得到結果:

torch。IntTensor對應torch。int32

torch。LongTensor對應torch。int64,

LongTensor常用在深度學習中的標籤值

,比方說分類任務中的類別標籤0,1,2,3等,要求用ing64的資料型別;

torch。FloatTensor對應torch。float32。

FloatTensor常用做深度學習中可學習引數或者輸入資料的型別

torch。DoubleTensor對應torch。float64

torch。tensor則有一個推斷的能力,加入輸入的資料是整數,則預設int64,相當於LongTensor;假如輸入資料是浮點數,則預設float32,相當於FLoatTensor。

剛好對應深度學習中的標籤和引數的資料型別,所以一般情況下,直接使用tensor就可以了,但是假如出現報錯的時候,也要學會使用dtype或者建構函式來確保資料型別的匹配

1.4 資料型別轉換

【使用torch.float()方法】

print(‘資料型別轉換’)a = torch。tensor([1,2,3])b = a。float()c = a。double()d = a。long()print(b。dtype)print(c。dtype)print(d。dtype)>>> 資料型別轉換>>> torch。float32>>> torch。float64>>> torch。int64

我個人比較習慣這個的方法。

【使用type方法】

b = a。type(torch。float32)c = a。type(torch。float64)d = a。type(torch。int64)print(b。dtype) # torch。float32print(c。dtype) # torch。float64print(d。dtype) # torch。int64

2 torch vs numpy

PyTorch是一個python包,目的是

加入深度學習應用

, torch基本上是實現了numpy的大部分必要的功能,並且tensor是可以利用GPU進行加速訓練的。

2.1 兩者轉換

轉換時非常非常簡單的:

import torchimport numpy as npa = np。array([1。,2。,3。])b = torch。tensor(a)c = b。numpy()print(a)print(b)print(c)

輸出結果:

[1。 2。 3。]tensor([1。, 2。, 3。], dtype=torch。float64)[1。 2。 3。]

下面的內容就變得有點意思了,是記憶體複製相關的。假如a和b兩個變數共享同一個記憶體,那麼改變a的話,b也會跟著改變;如果a和b變數的記憶體複製了,那麼兩者是兩個記憶體,所以改變a是不會改變b的。

下面是講解numpy和torch互相轉換的時候,什麼情況是共享記憶體,什麼情況下是記憶體複製

(其實這個問題,也就是做個瞭解罷了,無用的小知識)

【Tensor()轉換】

當numpy的資料型別和torch的資料型別相同時,共享記憶體;不同的時候,記憶體複製

import torchimport numpy as np#————————————print(‘torch的浮點數與整數的預設資料型別’)a = torch。tensor([1,2,3])b = torch。tensor([1。,2。,3。])print(a,a。dtype)print(b,b。dtype)

因為np。float64和torch。float32資料型別不同

torch的浮點數與整數的預設資料型別tensor([1, 2, 3]) torch。int64tensor([1。, 2。, 3。]) torch。float32

因為np。float32和torch。float32資料型別相同

【from_numpy()轉換】

print(‘from_numpy()’)a = np。array([1,2,3],dtype=np。float64)b = torch。from_numpy(a)b[0] = 999print(‘共享記憶體’ if a[0]==b[0] else ‘不共享記憶體’)>>> 共享記憶體a = np。array([1,2,3],dtype=np。float32)b = torch。from_numpy(a)b[0] = 999print(‘共享記憶體’ if a[0]==b[0] else ‘不共享記憶體’)>>> 共享記憶體

如果你使用from_numpy()的時候,不管是什麼型別,都是共享記憶體的。

【tensor()轉換】

更常用的是這個tensor(),注意看T的大小寫

, 如果使用的是tensor方法,那麼不管輸入型別是什麼,torch。tensor都會進行資料複製,不共享記憶體。

【.numpy()】

tensor轉成numpy的時候,。numpy方法是記憶體共享的哦。如果想改成記憶體複製的話,可以使用。numpy()。copy()就不共享記憶體了。或者使用。clone()。numpy()也可以實現同樣的效果。clone是tensor的方法,copy是numpy的方法。

【總結】

記不清的話,就記住,

tensor()資料複製了,.numpy()共享記憶體就行了。

2.2 兩者區別

【命名】

雖然PyTorch實現了Numpy的很多功能,但是

相同的功能卻有著不同的命名方式,這讓使用者迷惑。

例如建立隨機張量的時候:

print(‘torch的浮點數與整數的預設資料型別’)a = torch。tensor([1,2,3],dtype=torch。int8)b = torch。tensor([1。,2。,3。],dtype = torch。float64)print(a,a。dtype)print(b,b。dtype)

【張量重塑】

這部分會放在下一章節詳細說明~

3 張量

標量

:資料是一個數字

向量

:資料是一串數字,也是一維張量

矩陣

:資料二維陣列,也是二維張量

張量

:資料的維度超過2的時候,就叫多維張量

3.1 張量修改尺寸

pytorch常用reshape和view

numpy用resize和reshape

pytorch也有resize但是不常用

【reshape和view共享記憶體(常用)】

torch的浮點數與整數的預設資料型別tensor([1, 2, 3], dtype=torch。int8) torch。int8tensor([1。, 2。, 3。], dtype=torch。float64) torch。float64

輸出結果:

tensor([[0, 1, 2],        [3, 4, 5]])tensor([[0, 1, 2],        [3, 4, 5]])tensor([[999,   1,   2],        [  3,   4,   5]])tensor([[999,   1,   2],        [  3,   4,   5]])

上面的a,b,c三個變數其實是共享同一個記憶體,遷一而動全身。而且要求遵旨規則:

原始資料有6個元素,所以可以修改成的形式,但是無法修改成的形式

,我們來試試:

a = torch。arange(0,6)b = a。reshape((2,4))

會丟擲這樣的錯誤:

小白學PyTorch | 9 tensor資料結構與儲存結構

【torch的resize_(不常用)】

但是pytorch有一個不常用的函式(對我來說用的不多),resize,這個方法可以不遵守這個規則:

a = torch。arange(0,6)a。resize_(2,4)print(a)

輸出結果為:

小白學PyTorch | 9 tensor資料結構與儲存結構

自動的補充了兩個元素。雖然不知道這個函式有什麼意義……

這裡可以看到函式resize後面有一個_,這個表示inplace=True的意思,當有這個_或者引數inplace的時候,就是表示所作的修改是在原來的資料變數上完成的,也就不需要賦值給新的變量了。

【numpy的resize與reshape(常用)】

import numpy as npa = np。arange(0,6)a。resize(2,3)print(a)

import numpy as npa = np。arange(0,6)b = a。reshape(2,3)print(b)

兩個程式碼塊的輸出都是下面的,區別在於numpy的resize是沒有返回值的,相當於inplace=True了,直接在原變數的進行修改,而reshape是有返回值的,不在原變數上修改(但是呢reshape是共享記憶體的):

[[0 1 2] [3 4 5]]

3.2 張量記憶體儲存結構

tensor的資料結構包含兩個部分:

頭資訊區Tensor:儲存張量的形狀size,步長stride,資料型別等資訊

儲存區Storage:儲存真正的資料

頭資訊區Tensor的佔用記憶體較小,主要的佔用記憶體是Storate。

每一個tensor都有著對應的storage,一般不同的tensor的頭資訊可能不同,但是卻可能使用相同的storage

。(這裡就是之前共享記憶體的view、reshape方法,雖然頭資訊的張量形狀size發生了改變,但是其實儲存的資料都是同一個storage)

3.3 儲存區

我們來檢視一個tensor的儲存區:

torch。float64 # 等同於(torch。double)torch。float32 # 預設,FloatTensortorch。float16torch。int64   # 等同於torch。longtorch。int32   # 預設torch。int16torch。int8torch。uint8   # 二進位制碼,表示0-255torch。bool

輸出為:

print(‘torch的建構函式’)a = torch。IntTensor([1,2,3])b = torch。LongTensor([1,2,3])c = torch。FloatTensor([1,2,3])d = torch。DoubleTensor([1,2,3])e = torch。tensor([1,2,3])f = torch。tensor([1。,2。,3。])print(a。dtype)print(b。dtype)print(c。dtype)print(d。dtype)print(e。dtype)print(f。dtype)

然後對tensor變數做一個view的變換:

torch的建構函式torch。int32torch。int64torch。float32torch。float64torch。int64torch。float32

這個b。storage()輸出出來時和a。storate(),相同的,這也是為什麼view變換是記憶體共享的了。

# id()是獲取物件的記憶體地址print(id(a)==id(b)) # Falseprint(id(a。storage)==id(b。storage)) # True

可以發現,其實a和b雖然儲存區是相同的,但是其實a和b整體式不同的。自然,這個不同就不同在頭資訊區,應該是尺寸size改變了。

這也就是頭資訊區不同,但是儲存區相同,從而節省大量記憶體

我們更進一步,假設對tensor切片了,那麼切片後的資料是否共享記憶體,切片後的資料的storage是什麼樣子的呢?

print(‘研究tensor的切片’)a = torch。arange(0,6)b = a[2]print(id(a。storage)==id(b。storage))

輸出結果為:

>>> True

沒錯,就算切片之後,兩個tensor依然使用同一個儲存區,所以相比也是共享記憶體的,修改一個另一個也會變化。

#。data_ptr(),返回tensor首個元素的記憶體地址。print(a。data_ptr(),b。data_ptr())print(b。data_ptr()-a。data_ptr())

輸出為:

2080207827328 208020782734416

這是因為b的第一個元素和a的第一個元素記憶體地址相差了16個位元組,因為預設的tesnor是int64,也就是8個位元組一個元素,所以這裡相差了2個整形元素

3.4 頭資訊區

依然是上面那兩個tensor變數,a和b

a = torch。arange(0,6)b = a。view(2,3)print(a。stride(),b。stride())

輸出為:

(1,) (3, 1)

變數a是一維陣列,並且就是[0,1,2,3,4,5],所以步長stride是1;而b是二維陣列,是[[0,1,2],[3,4,5]],所以就是先3個3個分成第一維度的,然後再1個1個的作為第二維度。

由此可見,絕大多數操作並不修改 tensor 的資料,只是修改了 tensor 的頭資訊,這種做法更節省記憶體,同時提升了處理速度。

- END -