天天看點

pytorch gather_Pytorch之Tensor超詳細了解(一)

Tensor,又名張量,幾乎所有的深度學習架構都有這種資料結構。下面對工程實作的方法進行詳細介紹,若能精通Tensor,以後想建立自己的深度學習模型時,速度必将得到巨大的提升,同時也能加強對模型細節的了解。

1. 分類

從接口的角度來講,對tensor的操作可分為兩類:

(1)torch.function,如torch.save(儲存模型)等。

(2)tensor.function,如tensor.view(修改形狀)等。

從存儲的角度來講,又可分為兩類:

(1)不會修改自身,如a.add(b),加法的結果會傳回一個新的tensor。

(2)會修改自身,如a.add_(b),加法的結果存儲在a中,a被修改了。

函數名以"_"結尾的都是inplace方式,即會修改調用者自己的資料,在實際寫代碼的時候應加以區分。

2. 建立Tensor

在Pytorch中建立Tensor的方法有很多,具體如下表所示:

函數 功能
Tensor(*sizes) 基礎構造函數
ones(*sizes) 全1的Tensor
zeros(*sizes) 全0的Tensor
eyes(*sizes) 對角線為1,其他為0
arange(s,e,step) 從s到e,步長為step
linspace(s,e,steps) 從s到e,均勻切分成steps
rand/randn(*sizes) 均勻/标準分布
normal(mean,std)/uniform(from,to) 正态/均勻分布
randperm(m) 随機排列

其中,使用Tensor()建立是最複雜的方式,它既可以接收一個list,将它變成Tensor;也可以根據指定的形狀建立Tensor。下面開始舉例:

>>> import torch as t>>> a=t.Tensor(2,3)>>> a   #a裡面的值取決于記憶體空間的狀态tensor([[-1.2628e-24,  4.5712e-41,  2.5193e-38],        [ 0.0000e+00,  2.8734e-38,  0.0000e+00]])>>> b=t.Tensor([[1,2,3],[3,2,5]])>>> btensor([[1., 2., 3.],        [3., 2., 5.]])
           
>>> b.tolist()  # 把Tensor--->list[[1.0, 2.0, 3.0], [3.0, 2.0, 5.0]]
           

b.size()傳回torch.size對象:

>>> b.size()torch.Size([2, 3])>>> b.numel() # b中元素的總個數6
           
# 建立一個和b形狀一樣的tensor>>> c=t.Tensor(b.size())>>> ctensor([[2.9370e-37, 0.0000e+00, 3.0000e+00],        [3.0000e+00, 2.0000e+00, 5.0000e+00]])# 建立一個元素為2和3的tensor>>> d = t.Tensor((2,3))>>> dtensor([2., 3.])
           

注意:t.Tensor(*sizes)建立之後,不會馬上配置設定記憶體空間,隻會計算剩餘的記憶體是否夠用,使用到Tensor才進行記憶體的配置設定,而其他操作都是建立時就立即配置設定空間,其他操作如下:

>>> t.ones(2,3)tensor([[1., 1., 1.],        [1., 1., 1.]])>>> t.zeros(2,3)tensor([[0., 0., 0.],        [0., 0., 0.]])>>> t.arange(1,6,2)tensor([1, 3, 5])>>> t.linspace(1,10,20)tensor([ 1.0000,  1.4737,  1.9474,  2.4211,  2.8947,  3.3684,  3.8421,  4.3158,         4.7895,  5.2632,  5.7368,  6.2105,  6.6842,  7.1579,  7.6316,  8.1053,         8.5789,  9.0526,  9.5263, 10.0000])>>> t.randn(2,3)tensor([[ 2.0619,  1.1582, -1.1900],        [-0.6511,  0.5418, -0.8803]])>>> t.rand(2,3)tensor([[0.4519, 0.5684, 0.9345],        [0.4053, 0.9160, 0.1647]])>>> t.eye(2,3) #對角線為1,不要求行列數一緻tensor([[1., 0., 0.],        [0., 1., 0.]])
           

3. 常用 Tensor 操作

通過tensor.view可以更改tensor的形狀,但是必須保證調整前後元素總數是一緻的。并且新的tensor和源tensor是共享記憶體的,即更改其中一個,另外一個也會跟着改變。而且,在實際應用中,可能會需要添加或者減少某一個次元,這時squeeze和unsqueeze這倆函數就派上用場了。

>>> a=t.arange(6)>>> a.view(2,3)tensor([[0, 1, 2],        [3, 4, 5]])>>> b=a.view(-1,3)>>> btensor([[0, 1, 2],        [3, 4, 5]])        >>> b.unsqueeze(1) #注意形狀,在第1維上(次元從0開始)增加1tensor([[[0, 1, 2]],        [[3, 4, 5]]])>>> b.size()torch.Size([2, 3])>>> b.shapetorch.Size([2, 3])>>> c=b.unsqueeze(1)>>> c.size()torch.Size([2, 1, 3])>>> c.squeeze(-2) tensor([[0, 1, 2],        [3, 4, 5]])>>> d=b.view(1,1,1,2,3)>>> d.squeeze(0) #壓縮第0維的1tensor([[[[0, 1, 2],          [3, 4, 5]]]])>>> d.squeeze() #将所有次元為1的進行壓縮tensor([[0, 1, 2],        [3, 4, 5]])>>> a[2]=10 # 共享記憶體,b也跟着變化了>>> btensor([[ 0,  1, 10],        [ 3,  4,  5]])
           

resize是另一種可以調整tensor形狀的方法,但是它與view最大的差別就是:它可以修改tensor的形狀,即如果新尺寸超過了原尺寸會自動配置設定新的記憶體空間;如果小于原尺寸,會保留新尺寸的部分資料。

>>> btensor([[0, 1, 2],        [3, 4, 5]])>>> b.resize_(1,3)tensor([[0, 1, 2]])>>> b.resize_(3,3)tensor([[                  0,                   1,                   2],        [                  3,                   4,                   5],        [4849316058747981391, 4995148752773008735, 5493058101194933581]])
           

3. 索引操作

>>> import torch as t>>> a=t.randn(3,4) #正态分布>>> atensor([[-1.7722, -1.5571,  1.0267,  1.4227],        [ 0.8671, -1.6075,  2.1283,  1.6735],        [ 0.9111, -1.1461, -0.1792, -0.3909]])>>> a[0] #第0行tensor([-1.7722, -1.5571,  1.0267,  1.4227])>>> a[:,0] #第0列tensor([-1.7722,  0.8671,  0.9111])>>> a[0][2] tensor(1.0267)>>> a[0,2] #和a[0][2]上面相等tensor(1.0267)>>> a[:2] #前兩行tensor([[-1.7722, -1.5571,  1.0267,  1.4227],        [ 0.8671, -1.6075,  2.1283,  1.6735]])>>> a[:2,0:2] #前兩行,前兩列tensor([[-1.7722, -1.5571],        [ 0.8671, -1.6075]])>>> a[0:1,:2]#注意和下面一個例子的差別,這是二維的tensor([[-1.7722, -1.5571]])>>> a[0,:2] #這是一維的tensor([-1.7722, -1.5571])>>> a > 1 tensor([[False, False,  True,  True],        [False, False,  True,  True],        [False, False, False, False]])>>> a[a>1] #等于masked_select(a,a>1)或者a.masked_select(a>1)tensor([1.0267, 1.4227, 2.1283, 1.6735])
           
函數 功能
index_select(input,dim,index) 在指定次元dim上選取,例如選取某些行、某些列
masked_select(input,mask) 例子如a[a>0],使用ByteTensor進行選取
non_zero(input) 非0元素的下标
gather(input,dim,index) 根據index,在dim次元上選取資料,輸出的size和index一樣
>>> a=t.arange(0,16).view(4,4)>>> atensor([[ 0,  1,  2,  3],        [ 4,  5,  6,  7],        [ 8,  9, 10, 11],        [12, 13, 14, 15]])>>> index=t.Tensor([[0,1,2,3]])>>> t.gather(a,0,index)Traceback (most recent call last):  File "", line 1, in <module>RuntimeError: gather_cpu(): Expected dtype int64 for index#可以看到這裡發生了錯誤,原因是索引index的資料類型不是LongTensor,# 是Tensor;至于為啥,作者也不知道,沒有百度出來,反正以後索引用LongTensor就對了>>> index=t.LongTensor([[0,1,2,3]])>>> a.gather(0,index)tensor([[ 0,  5, 10, 15]])>>> t.gather(a,0,index)tensor([[ 0,  5, 10, 15]])
           

4. 進階索引

進階索引可以看作是普通索引的擴充,但是不和原始的Tensor共享記憶體。

>>> x=t.arange(27).view(3,3,3)>>> xtensor([[[ 0,  1,  2],         [ 3,  4,  5],         [ 6,  7,  8]],        [[ 9, 10, 11],         [12, 13, 14],         [15, 16, 17]],        [[18, 19, 20],         [21, 22, 23],         [24, 25, 26]]])>>> x[[1,2],[2,2],[2,1]] # 相當于x[1,2,2]和x[2,2,1]tensor([17, 25])>>> x[[2,1,0],1,1] #相當于x[2,1,1],x[1,1,1]和x[0,1,1]tensor([22, 13,  4])>>> x[[0,2],...] tensor([[[ 0,  1,  2],         [ 3,  4,  5],         [ 6,  7,  8]],        [[18, 19, 20],         [21, 22, 23],         [24, 25, 26]]])
           

5. Tensor類型

Tensor有許多不同的資料類型,每種類型還有CPU和GPU版本(HalfTensor除外)。預設的tensor是FloatTensor,可通過t.set_default_tensor_type修改預設的tensor類型;HalfTensor是專門為GPU設計的,可以減少記憶體消耗,但是HalfTensor所能表示的資料大小有限,是以可能會出現溢出問題。

資料類型 CPU tensor GPU tensor
32bit 浮點 torch.FloatTensor torch.cuda.FloatTensor
64bit 浮點 torch.DoubleTensor torch.cuda.DoubleTensor
16bit 半精度浮點 N/A torch.cuda.HalfTensor
32bit 有符号整型 torch.IntTensor torch.cuda.IntTensor
64bit 有符号整型 torch.FloatTensor torch.cuda.FloatTensor

資料類型的互相轉換:

>>> t.set_default_tensor_type("torch.IntTensor")Traceback (most recent call last):  File "", line 1, in <module>  File "/usr/python3.8/lib/python3.8/site-packages/torch/__init__.py", line 206, in set_default_tensor_type    _C._set_default_tensor_type(t)TypeError: only floating-point types are supported as the default type# 說明不允許轉換預設類型,好吧,那就不換了>>> a=t.Tensor([1,2,3]) #是torch.FloatTensor類型>>> atensor([1., 2., 3.])>>> b=a.type(t.IntTensor)>>> btensor([1, 2, 3], dtype=torch.int32)>>> c=a.type_as(b)>>> ctensor([1, 2, 3], dtype=torch.int32)
           

5. 逐元素操作

這裡将的是對tensor的每一個元素進行操作的函數,這類函數的輸入和輸出一般都保持一緻,常見的操作如下表所示。

函數 功能
abs/sqrt/div/exp/fmod/log/pow/mul 絕對值/平方根/除法/指數/求餘/log/求幂/乘法(非矩陣相乘)
cos/sin 三角函數
ceil/round/floor/trunc 向上取整/四舍五入/向下取整/取整數部分
clamp(input,min,max) 超過min和max部分截斷
sigmod/tanh 激活函數

注意:矩陣相乘為torch.nn(a,b),這不是逐元素操作,要和mul區分開來

其中,有很多運算都實作了運算符重載,何為重載呢,比如a**2 等于torch.pow(a,2),a*2 等于torch.mul(a,2)。

其中 clamp(x,min,max)的輸出公式為:

pytorch gather_Pytorch之Tensor超詳細了解(一)

clamp常用在一些需要比較大小的地方,比如說取一個tensor的每個元素和另一個數的較大值。

>>> import torch as t>>> a=t.arange(0,6).view(2,3)>>> t.cos(a)tensor([[ 1.0000,  0.5403, -0.4161],        [-0.9900, -0.6536,  0.2837]])>>> a%3 tensor([[ 0.,  1.,  2.],        [ 0.,  1.,  2.]])>>> t.fmod(a,3)tensor([[ 0.,  1.,  2.],        [ 0.,  1.,  2.]])>>> a**2tensor([[  0.,   1.,   4.],        [  9.,  16.,  25.]])>>> t.pow(a,2)tensor([[  0.,   1.,   4.],        [  9.,  16.,  25.]])>>> a*2tensor([[  0.,   2.,   4.],        [  6.,   8.,  10.]])>>> t.mul(a,2)tensor([[  0.,   2.,   4.],        [  6.,   8.,  10.]])>>> t.clamp(a,min=3) # a中的元素和3比較,取較大的一個tensor([[ 3.,  3.,  3.],        [ 3.,  4.,  5.]])
           

6. 歸并操作

此類操作一般用作統計,可以沿着某一維進行操作。如求和操作sum,既可以計算整個Tensor的值,也可以計算Tensor中每一行或每一列的和。常用的歸并操作如下。

函數 功能
mean/sum/median/mode 均值/和/中位數/衆數
norm/dist 範數/距離
std/var 方差/标準差
cumsum/cumprod 累加/累乘

這些操作大多數都有一個參數dim,用來指定在哪個次元上進行這些操作。這裡提供一個簡單的記憶方法。

假設輸入的形狀是(x,y,k):

如果指定dim=0,輸出的形狀是(1,n,k)或(n,k)。

如果指定dim=1,輸出的形狀是(x,1,k)或(x,k)。

如果指定dim=2,輸出的形狀是(x,y,1)或(x,y)。

輸出的形狀裡是否有“1”,取決于這些操作的另一個參數keepdim,如果keepdim=1則會保留次元1,pytorch中預設是False。

>>> b=t.ones(2,3)>>> b.sum(dim=0,keepdim=True) # 注意次元,這是2維的tensor([[ 2.,  2.,  2.]])>>> b.sum(dim=0) # 而這是一維的tensor([ 2.,  2.,  2.])>>> b.sum(dim=1)tensor([ 3.,  3.])>>> c=t.arange(6).view(2,3)>>> ctensor([[ 0.,  1.,  2.],        [ 3.,  4.,  5.]])>>> c.cumsum(dim=1) # 沿着行累加tensor([[  0.,   1.,   3.],        [  3.,   7.,  12.]])
           

7. 比較操作

比較操作有些是逐元素進行操作,有些則類似于歸并操作。常用的比較函數如下所示。

函數 功能
gt/lt/ge/le/eq/ne 大于/小于/大于等于/小于等于/等于/不等
topk 最大的k個數
sort 排序
max/min 比較兩個tensor的最大值和最小值

表中的第一行操作已經實作了運算符重載,是以可以使用a<=b等來操作,傳回結果是一個ByteTensor,可以用來選取元素。max/min這兩個操作,有如下解釋:

t.max(a),傳回a中最大的一個值;

t.max(a,dim),傳回a中指定次元上最大的數和該數的下标;

t.max(a,b),比較兩個tensor中比較大的元素。

比較一個tensor和一個數,可以用clamp(x,min,max),下面開始看例子。

>>> a=t.linspace(0,15,6).view(2,3)>>> atensor([[  0.,   3.,   6.],        [  9.,  12.,  15.]])>>> b=t.linspace(15,0,6).view(2,3)>>> btensor([[ 15.,  12.,   9.],        [  6.,   3.,   0.]])>>> a>btensor([[ 0,  0,  0],        [ 1,  1,  1]], dtype=torch.uint8)>>> a[a>b] # a中大于b的元素,注意形狀改變了tensor([  9.,  12.,  15.])>>> t.max(a)tensor(15.)>>> t.max(b,dim=1) # 15和6是該行最大的元素,0和0是該行的第幾個元素(tensor([ 15.,   6.]), tensor([ 0,  0]))>>> t.max(a,b) # 取a和b中的最大值tensor([[ 15.,  12.,   9.],        [  9.,  12.,  15.]])>>> t.clamp(a,min=10) # 和10比較,取最大值tensor([[ 10.,  10.,  10.],        [ 10.,  12.,  15.]])
           

8. 線性代數

函數 功能
trace 對角線元素之和(迹)
diag 對角線元素
mm/bmm 矩陣乘法/batch的矩陣乘法
t 轉置
dot/cross 内積/外積
inverse 求逆矩陣
svd 奇異值分解
>>> a=t.arange(6).view(2,3)>>> atensor([[ 0.,  1.,  2.],        [ 3.,  4.,  5.]])>>> t.trace(a) # 對角線元素之和0+4tensor(4.)>>> t.diag(a) # 提取對角線元素tensor([ 0.,  4.])
           

其他函數的用法待後期慢慢介紹,今天先掌握到這裡為止哦,回去好好複習把,這些暫時夠用了!!!

參考書籍:深度學習架構:PyTorch入門與實戰

pytorch gather_Pytorch之Tensor超詳細了解(一)

繼續閱讀