0401-Tensor的基礎操作
pytorch完整教程目錄:https://www.cnblogs.com/nickchen121/p/14662511.html
一、tensor引入
tensor,也可以叫做張量,學過線性代數的你,其實早就接觸了張量,只不過我們一直把它叫做向量和矩陣,而向量就是一維張量、矩陣是二維張量,只不過張量還可以是三維的、四維的,只是維數高了之后,我們難以理解,因此統一把它都叫做張量。
在torch中,張量是一個數據類型,也就是tensor,它和numpy中的ndarray這個數據類型很像,以及和它的操作方法也很類似,其實你可以發現,ndarray不就是一維和二維張量嗎?
如果你看過我的Python博客,可以發現我把python的所有的基礎類型和其對應的操作方法都講到了,那是因為任何框架的基礎都是python,python的所有操作方法都學習全面了,你自己也可以造框架。而對於框架的各種操作方法,底層無非就是一堆python代碼的堆疊,也就是有些操作方法你不學,你也可以自己造出來,所以對於torch的很多不常用的內容我們可能會一筆概之或者直接不講。
而對於tensor的基礎操作,我們可以從兩個方面來講。
如果從接口的角度,對tensor的操作可以分為兩類:
torch.function
,如torch.save
tensor.function
,如tensor.view
注:對於這兩種接口方法,大多數時候都是等價的,如torch.sum(a,b)
和a.sum(b)
如果從存儲的角度講,對tensor的操作也可以分為兩類:
a.add(b)
,不會修改a自身的數據,加法的結果會返回一個新的tensora.add_(b)
,會修改a自身的數據,也就是說加法的結果存在a中
注:函數名以_結尾的都是修改調用者自身的數據。
二、創建tensor
此處我只列出表格,不給出詳細介紹和代碼打印結果,只給出一些細節上需要注意的東西,因為它除了支持多維,其他和numpy簡直一模一樣
函數 | 功能 |
---|---|
Tensor(*size) |
基礎構造函數 |
ones(*sizes) |
全1Tensor |
zeros(*sizes) |
全0Tensor |
eye(*sizes) |
對角矩陣(對角線為1,其他為0,不要求行列一致) |
arrange(s,e,step) |
從s到e,步長為step |
linspace(s,e,steps) |
從s到e,均勻分成steps份 |
rand/randn(*sizes) |
均勻/標准分布 |
normal(mean,std)/uniform(from,tor) |
正態分布/均勻分布 |
randperm(m) |
隨機排列 |
import torch as t
如果*size
為列表,則按照列表的形狀生成張量,否則傳入的參數看作是張量的形狀
a = t.Tensor(2, 3) # 指定形狀構建2*3維的張量
a
tensor([[ 0.0000e+00, -2.5244e-29, 0.0000e+00],
[-2.5244e-29, 6.7294e+22, 1.8037e+28]])
b = t.tensor([[1, 2, 3], [2, 3, 4]]) # 通過傳入列表構建2*3維的張量
b
tensor([[1, 2, 3],
[2, 3, 4]])
b.tolist() # 把b轉化為列表,但是b的實際數據類型仍是tensor
[[1, 2, 3], [2, 3, 4]]
print(f'type(b): {type(b)}')
type(b): <class 'torch.Tensor'>
b.size() # 返回b的大小,等價於b.shape()
torch.Size([2, 3])
b.numel() # 計算b中的元素個數,等價於b.nelement()
6
c = t.Tensor(b.size()) # 創建一個和b一樣形狀的張量
c
tensor([[0.0000e+00, 3.6013e-43, 1.8754e+28],
[2.0592e+23, 1.3003e+22, 1.0072e-11]])
注:t.Tensor(*size)創建tensor時,系統不會馬上分配空間,只有使用到tensor時才會分配內存,而其他操作都是在創建tensor后馬上進行空間分配
三、常用tensor操作
3.1 調整tensor的形狀
view()
方法調整tensor的形狀,但是必須得保證調整前后元素個數一致,但是view方法不會修改原tensor的形狀和數據
a = t.arange(0, 6)
a
tensor([0, 1, 2, 3, 4, 5])
b = a.view(2, 3)
print(f'a: {a}\n\n b:{b}')
a: tensor([0, 1, 2, 3, 4, 5])
b:tensor([[0, 1, 2],
[3, 4, 5]])
c = a.view(-1, 3) # -1會自動計算大小。注:我已經知道你在想什么了,兩個-1你就上天吧,鬼知道你想改成什么形狀的
print(f'a: {a}\n\n b:{c}')
a: tensor([0, 1, 2, 3, 4, 5])
b:tensor([[0, 1, 2],
[3, 4, 5]])
a[1] = 0 # view方法返回的tensor和原tensor共享內存,修改一個,另外一個也會修改
print(f'a: {a}\n\n b:{b}')
a: tensor([0, 0, 2, 3, 4, 5])
b:tensor([[0, 0, 2],
[3, 4, 5]])
resize()
是另一種用來調整size的方法,但是它相比較view,可以修改tensor的尺寸,如果尺寸超過了原尺寸,則會自動分配新的內存,反之,則會保留老數據
b.resize_(1, 3)
tensor([[0, 0, 2]])
b.resize_(3, 3)
tensor([[0, 0, 2],
[3, 4, 5],
[0, 0, 0]])
b.resize_(2, 3)
tensor([[0, 0, 2],
[3, 4, 5]])
3.2 添加或壓縮tensor維度
unsqueeze()
可以增加tensor的維度;squeeze()
可以壓縮tensor的維度
# 過於抽象,無法理解就跳過。
d = b.unsqueeze(
1) # 在第1維上增加“1”,也就是2*3的形狀變成2*1*3。如果是b.unsqueeze(0)就是在第0維上增加1,形狀變成1*2*3。
d, d.size()
(tensor([[[0, 0, 2]],
[[3, 4, 5]]]), torch.Size([2, 1, 3]))
b.unsqueeze(-1) # 在倒數第1維上增加“1”,也就是2*3的形狀變成2*3*1。
tensor([[[0],
[0],
[2]],
[[3],
[4],
[5]]])
e = b.view(1, 1, 2, 1, 3)
f = e.squeeze(0) # 壓縮第0維的“1”,某一維度為“1”才能壓縮,如果第0維的維度是“2”如(2,1,1,1,3)則無法亞索第0維
f, f.size()
(tensor([[[[0, 0, 2]],
[[3, 4, 5]]]]), torch.Size([1, 2, 1, 3]))
e.squeeze() # 把所有維度為“1”的壓縮。
tensor([[0, 0, 2],
[3, 4, 5]])
四、索引操作
tensor的索引操作和ndarray的索引操作類似,並且索引出來的結果與原tensor共享內存。因此在這里普通的切片操作我們就不多介紹,我們只講解tensor一些特有的選擇函數。
函數 | 功能 |
---|---|
index_select(input,dim,index) |
在指定維度dim上選取,例如選取某些行、某些列 |
masked_select(inpu,mask) |
a[a>1] 等價於a.masked_select(a>1) |
non_zero(input) |
獲取非0元素的下標 |
gather(input,dim,index) |
根據index,在dim維度上選取數據,輸出的size與index一樣 |
import torch as t
對於上述選擇函數,我們講解一下比較難的gather函數,對於一個二維tensor,gather的輸出如下所示:
out[i][j] = input[index[i][j]][j] # dim=0
out[i][j] = input[i][index[i][j]] # dim=1
a = t.arange(0, 16).view(4, 4)
a
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
# 選取對角線的元素
index = t.LongTensor([[0, 1, 2, 3]])
print(f'index: {index}')
a.gather(0, index) # dim=0
index: tensor([[0, 1, 2, 3]])
tensor([[ 0, 5, 10, 15]])
對於上述實例,可以做出如下解釋:
i=0,j=0 -> index[0,0]=0 -> input[index[0,0]][0]=input[0][0] = 0
i=0,j=1 -> index[0,1]=1 -> input[index[0,1]][1]=input[1][1] = 5
i=0,j=2 -> index[0,2]=2 -> input[index[0,2]][2]=input[2][2] = 10
i=0,j=3 -> index[0,3]=3 -> input[index[0,3]][3]=input[3][3] = 15
下述實例,自行判斷。
# 選取反對角線上的元素
index = t.LongTensor([[3, 2, 1, 0]]).t() # .t()是轉置
print(f'index: {index}')
a.gather(1, index)
index: tensor([[3],
[2],
[1],
[0]])
tensor([[ 3],
[ 6],
[ 9],
[12]])
# 選取反對角線上的元素
index = t.LongTensor([[3, 2, 1, 0]]) # .t()是轉置
a.gather(0, index)
tensor([[12, 9, 6, 3]])
# 選取兩個對角線上的元素
index = t.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]]).t() # .t()是轉置
print(f'index: {index}')
b = a.gather(1, index)
b
index: tensor([[0, 3],
[1, 2],
[2, 1],
[3, 0]])
tensor([[ 0, 3],
[ 5, 6],
[10, 9],
[15, 12]])
與gather函數相應的逆操作則是scatter_,scatter_可以把gather取出的元素放回去。
out = input.gather(dim, index)
out = Tensor()
out.scatter_(dim, index)
# 把兩個對角線元素放回到指定位置里
c = t.zeros(4, 4, dtype=t.int64)
c.scatter_(1, index, b)
tensor([[ 0, 0, 0, 3],
[ 0, 5, 6, 0],
[ 0, 9, 10, 0],
[12, 0, 0, 15]])
五、高級索引
torch的高級索引和numpy的高級索引也很類似,因此照例,只講一些復雜的高級索引方法。
注:高級索引操作的結果和原tensor不共享內存
x = t.arange(0, 27).view(3, 3, 3)
x
tensor([[[ 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], [1, 2], [2, 0]] # x[1,1,2] 和 x[2,2,0]
tensor([14, 24])
x[[2, 1, 0], [0], [1]] # x[2,0,1],x[1,0,1],x[0,0,1]
tensor([19, 10, 1])
x[[0, 2], ...] # x[0] 和 x[2]
tensor([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
可以從上述三個例子看出高級索引的本質就是先循環第一個列表中的元素,然后與后面列表的元素配對,配對滿足維數要求則停止,否則繼續往后搜索。
對於第一個例子:
- 先從
[1,2]
中取出1 - 1和第二個列表
[1,2]
配對,滿足三維要求,即[1,1,2]
,停止配對,循環步驟一取出2 - 2和第三個列表
[2,0]
配對,滿足三維要求,即[2,2,0]
,停止配對
對於第二例子:
- 先從
[2,1,0]
中取出2 - 2和第二個列表
[0]
配對,不滿足三維要求,繼續往后搜索,和第三個列表[1]
配對,滿足三維要求,即[2,0,1]
- ……
六、Tensor類型
6.1 Tensor數據類型
數據類型 | CPU tensor | GPU tensor |
---|---|---|
32bit浮點 | torch.FloatTensor | torch.cuda.FloatTensor |
64bit浮點 | torch.DoubleTensor | torch.cuda.DoubleTensor |
16bit半精度浮點 | torch.HalfTensor | torch.cuda.HalfTensor |
8bit無符號整型(0~255) | torch.ByteTensor | torch.cuda.ByteTensor |
8bit有符號整型(-128~127) | torch.CharTensor | torch.cuda.CharTensor |
16bit有符號整型 | torch.ShortTensor | torch.cuda.ShortTensor |
32bit有符號整型 | torch.IntTensor | torch.cuda.IntTensor |
64bit有符號整型 | torch.LongTensor | torch.cuda.LongTensor |
上表中只有HalfTensor值得一提,它是gpu獨有的數據類型,使用該數據類型,gpu在存儲該類型數據時,內存占用會減少一半,可以解決gpu顯存不足的問題,但是由於它所能表示的數值大小和精度有限,所以可能存在溢出問題。
6.2 數據類型轉換
# 設置默認tensor,系統默認tensor是FloatTensor,也僅支持浮點數類型為默認數據類型,設置成IntTensor會報錯
t.set_default_tensor_type('torch.DoubleTensor')
a = t.Tensor(2, 3)
a, a.type() # a現在是DoubleTensor
(tensor([[0., 0., 0.],
[0., 0., 0.]]), 'torch.DoubleTensor')
b = a.int() # 可通過`float(), int(), double(), char(), long(), int()`更換數據類型
b.type()
'torch.IntTensor'
c = a.type_as(b) # 對a進行數據類型轉換
c, c.type()
(tensor([[0, 0, 0],
[0, 0, 0]], dtype=torch.int32), 'torch.IntTensor')
d = a.new(2, 3) # 生成與a數據類型一致的tensor
d, d.type()
(tensor([[ 2.0000e+00, 2.0000e+00, 3.9525e-323],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]]), 'torch.DoubleTensor')
a.new?? # 查看new的源碼
t.set_default_tensor_type('torch.FloatTensor') # 恢復之前的默認設置
6.3 cpu和gpu間數據類型轉換
cpu和gpu的數據類型通常有tensor.cpu()
和tensor.gpu()
互相轉換,由於我的電腦沒有gpu,從網上摘抄一段供大家參考:
In [115]: a = t.ones(2,3)
In [116]: a.type()
Out[116]: 'torch.FloatTensor'
In [117]: a
Out[117]:
tensor([[1., 1., 1.],
[1., 1., 1.]])
In [118]: a.cuda()
Out[118]:
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:0')
In [119]: b = a.cuda()
In [120]: b
Out[120]:
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:0')
In [121]: b.type()
Out[121]: 'torch.cuda.FloatTensor'
In [122]: b.cpu()
Out[122]:
tensor([[1., 1., 1.],
[1., 1., 1.]])
In [123]: b.cpu().type()
Out[123]: 'torch.FloatTensor'
七、逐元素操作
通俗點講,就是對tensor進行數學操作,只不過是對tensor的每個元素都進行相對應的操作,因此叫做逐元素操作,也因此該類操作的輸出形狀與原tensor形狀一致。常見的逐元素操作如下表:
函數 | 功能 |
---|---|
mul/abs/sqrt/exp/fmod/log/pow…… | 乘法(*)/絕對值/平方根/除法(/)/指數/求余(%)/求冪(**) |
cos/sin/asin/atan2/cosh | 三角函數 |
ceil/round/floor/trunc | 上取整/四舍五入/下去整/只保留整數部分 |
clamp(input,min,max) |
超過min和max部分截斷 |
sigmod/tanh/... | 激活函數 |
針對上述一些運算符,torch實現了運算符重載,例如a**2
等價於torch.pow(a,2)
。
針對clamp函數,它的輸出滿足下述公式:
a = t.arange(0, 6).view(2, 3)
a
tensor([[0, 1, 2],
[3, 4, 5]])
a.clamp(min=3)
tensor([[3, 3, 3],
[3, 4, 5]])
八、歸並操作
該類操作可以沿着某一維度進行指定操作,因此它們的輸出形狀一般小於元tensor形狀。如加法sum,可以計算正整個tensor的和,也可以計算某一行或某一列的和。常用的歸並操作如下表所示:
函數 | 功能 |
---|---|
mean/sum/median/mode | 均值/和/中位數/眾數 |
norm/dist | 范數/距離 |
std/var | 標准差/方差 |
cunsum/cumprod | 累加/累乘 |
cat(tensor1,tensor2,dim) |
讓tensor1按照第dim個維度與tensor2進行連接 |
以上函數大多都有一個dim參數(對應numpy中的axis參數),它的使用如下所示(假設輸入的形狀是(a,b,c)):
- 如果指定dim=0,輸出形狀是(1,b,c)或(b,c)
- 如果指定dim=1,輸出形狀是(a,1,c)或(a,c)
- 如果指定dim=2,輸出形狀是(a,b,1)或(a,b)
對於上述操作是否保留輸出形狀中的“1”,取決於參數keepdim,如果keepdim=True
則保留,反之不保留。但是從torch0.2.0版本開始,統一不保留。雖然以上總結適用於大多數函數,但是對於cumsum函數,則不適用該規則。
b = t.ones(2, 3)
b.sum(dim=0), b.sum(dim=0, keepdim=True) # 前者輸出形狀是(3),后者輸出形狀是(1,3)
(tensor([2., 2., 2.]), tensor([[2., 2., 2.]]))
a = t.arange(0, 6).view(2, 3)
a
tensor([[0, 1, 2],
[3, 4, 5]])
a.cumsum(dim=1) # 對第二個維度行的元素按照索引順序進行累加
tensor([[ 0, 1, 3],
[ 3, 7, 12]])
九、比較
對於比較函數中,有些函數逐元素操作,有些函數則類似於歸並不逐元素操作。常用的比較函數有:
函數 | 功能 |
---|---|
gt/lt/le/eq/ne | 大於(>)/小於(<)/大於等於(>=)/小於等於(<=)/等於)(=)/不等(!=) |
topk | 最大的k個數 |
sort | 排序 |
max/min | 比較兩個tensor的最大值和最小值 |
其中max和min兩個函數有點特殊,它們有以下三種情況:
t.max(tensor)
:返回tensor中最大的一個數t.max(tensor,dim)
:指定維上最帶的數,返回tensor和下標t.max(tensor1,tensor2)
:比較兩個tensor相比較大的元素
a = t.linspace(0, 15, 6).view(2, 3)
a
tensor([[ 0., 3., 6.],
[ 9., 12., 15.]])
b = t.linspace(15, 0, 6).view(2, 3)
b
tensor([[15., 12., 9.],
[ 6., 3., 0.]])
t.max(a)
tensor(15.)
t.max(a, 1) # 返回第0行和第1行的最大的元素
torch.return_types.max(
values=tensor([ 6., 15.]),
indices=tensor([2, 2]))
t.max(a, b)
tensor([[15., 12., 9.],
[ 9., 12., 15.]])
十、線性代數運算
常用的線性代數函數如下表所示:
函數 | 功能 |
---|---|
trace | 對角線元素之和(矩陣的跡) |
diag | 對角線元素 |
triu/tril | 矩陣的上三角/下三角,可指定偏移量 |
mv/mm/bmm | 矩陣和向量的乘法/矩陣的乘法/兩個batch內的矩陣進行批矩陣乘法 |
addmm | 兩個矩陣進行乘法操作的結果與另一向量相加 |
addmv | 將矩陣和向量的結果與另一向量相加 |
addbmm | 將兩個batch內的矩陣進行批矩陣乘法操作並累加,其結果與另一矩陣相加 |
baddbmm | 將兩個batch內的矩陣進行批矩陣乘法操作,其結果與另一batch內的矩陣相加 |
eig | 計算方陣的特征值和特征向量 |
t | 轉置 |
dot/cross | 內積/外積 |
inverse | 求逆矩陣 |
svd | 奇異值分解 |
其中矩陣的轉置會導致存儲空間不連續,需調用它的.contiguous方法讓它連續
b = a.t()
b, b.is_contiguous()
(tensor([[ 0., 9.],
[ 3., 12.],
[ 6., 15.]]), False)
b = b.contiguous()
b, b.is_contiguous()
(tensor([[ 0., 9.],
[ 3., 12.],
[ 6., 15.]]), True)