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)的輸出公式為:

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入門與實戰