Tensor:張量,可以是標量(一個數)、向量(一維數組)、矩陣(二維數組)或者更高維的數組。
它與numpy和ndarrays相似,但是PyTorch的tensor支持GPU加速。
一、tensor分類
- 從接口的角度分類
torch.function | 如torch.save() |
tensor.function | 如tensor.view() |
- 從存儲的角度分類
不會修改自身的數據 | 如a.add(b),加法的結果會返回一個新的tensor |
會修改自身的數據 | 如a.add_(b),加法的結果仍然存儲在a中,a被修改了 |
二、創建Tensor
在PyTorch中新建tensor的方法有很多,具體如下:
函數 | 功能 |
Tensor(*sizes) | 基礎構造函數 |
ones(*sizes) | 全1Tensor |
zeros(*sizes) | 全0Tensor |
eye(*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操作
通過tensor.view方法調整tensor的形狀,但必須保證調整前後元素總數一致。view不會修改自身的數據,
返回的新tensor與源tensor共享內存,即更改其中一個,另一個也隨之改變。
主要用在經常需要添加或者減少某一維度,這時就需要squeeze和unsqueeze這兩個函數。
resize是另一種可以用來調整size的方法,但與view不同,它可以修改tensor的尺寸。如果新尺寸超過了原尺寸,會自動分配新的內存空間,但如果新尺寸小於原尺寸,則之前的數據依舊會被保存。
Tensor支持numpy.ndarray類似的索引操作,語法也類似。如無特殊說明,索引出來的結果與原tensor共享內存。
from __future__ import print_function
import torch as t
a = t.randn(3, 4)
print(a)
print(a[0]) # 第0行(下標從0開始)
print(a[:, 0]) # 第0列
結果如下:
四、常用的選擇函數
函數 | 功能 |
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一樣 |
五、常見的逐元素操作
函數 | 功能 |
abs//sqrt/div/exp/fmod/log/pow | 絕對值/平方根/除法/指數/求餘/求冪 |
cos/sin/asin/atan2/cosh | 三角函數 |
clamp(input,min,max) | 超過min和max部分截斷 |
ceil/round/floor/trunc | 上取整/四捨五入/下取整/只保留整數部分 |
sigmod/tanh | 激活函數 |
clamp常用在某些需要比較大小的地方,如取一個tensor的每個元素與另一個數的較大值。
a = t.arange(0, 6).view(2, 3)
print(a)
# 不轉換爲float,則會報錯:RuntimeError: cos_vml_cpu not implemented for 'Long'
print(t.cos(a.float()))
a = t.arange(0, 6).view(2, 3)
print(a)
print(a % 3) # 等價於t.fmod(a, 3)
print(a ** 2) # 等價於t.pow(a, 2)
a = t.arange(0, 6).view(2, 3)
print(a)
# a中每個元素都與3相比,取較大的一個
print(t.clamp(a, min=3))
六、歸併操作
歸併操作會是輸出形狀小於輸入形狀,並可以沿着某一維度進行指定操作。
常用的歸併操作:
函數 | 功能 |
mean/sum/median/mode | 均值/和/中位數/衆數 |
norm/dist | 範數/距離 |
std/var | 標準差/方差 |
cumsum/cumprod | 累加/累乘 |
以上大多數函數都有一個參數dim,用來指定這些操作是在哪個維度上執行的。
假設輸入的形狀是(m, n, k)——m行n列:
輸入dim | 輸出的形狀 |
0 | (1, n, k)或者(n, k)——沿着列,變成一行 |
1 | (m, 1, k)或者(m, k)——沿着行,變成一列 |
2 | (m, n, 1)或者(m, n) |
注意: size中是否有“1”,取決於參數keepdim,keepdim=True會保留維度1。默認爲false
七、常用的比較函數
函數 | 功能 |
gt/lt/ge/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相比較的元素 |
八、常用的線性代數函數
函數 | 功能 |
trace | 對角線元素之和(矩陣的跡) |
diag | 對角線元素 |
triu/tril | 矩陣的上三角/下三角,可指定偏移量 |
mm/bmm | 矩陣乘法/batch的矩陣乘法 |
addmm/addbmm/addmv | 矩陣運算 |
t | 轉置 |
dot/cross | 內積/外積 |
inverse | 求逆矩陣 |
svd | 奇異值分解 |
注意:矩陣的轉置會導致存儲空間不連續,需調用它的.contiguous方法將其轉爲連續
a = t.ones(2, 3)
print(a)
# 求a的轉置
b = a.t()
# 查看a的轉置矩陣b是否連續
print(b.is_contiguous())
# 將其轉爲連續
print(b.contiguous())
結果:
九、對於Tensor和Numpy之間遇到的問題
需要注意的是:Numpy和Tensor共享內存。PyTorch已經支持了自動廣播法則。
當遇到Tensor不支持的操作時,可以先轉成Numpy數組,處理後再轉爲tensor,其轉換開銷很小。
- 當輸入數組的某個維度的長度爲1時,計算時沿此維度複製擴充成一樣的形狀。
- unsqueeze或view:爲數據某一維的形狀補1,實現法則1.
- expand或expand_as,重複數組,實現法則3;該操作不會複製數組,所以不會佔用額外的空間
9.1、持久化
tensor的保存:t.save
tensor的加載:t.load
在load時還可以將GPU tensor映射到CPU或其他GPU上。
if t.cuda.is_available():
a = a.cuda(1) # 把a轉爲GPU1上的tensor
t.save(a, 'a.path')
# 加載爲b,存儲於GPU1上(因爲保存時tensor就在GPU1上)
b = t.load('a.path')
# 加載爲c,存儲於CPU
c = t.load('a.path', map_location=lambda storage, loc: storage)
# 加載爲d,存儲於GPU0上
d = t.load('a.path', map_location={'cuda:1':'cuda: 0'})
9.2、向量化
向量化計算是一種特殊的並行計算方式,一般程序在同一時間只執行一個操作的方式,它可以在同一時間執行多個操作,通常是對不同的數據執行同樣的一個或一批指令,或者說把指令應用於一個數組/向量上。向量化可極大地提高科學運算的效率。
在科學計算程序中應當極力避免使用Python原生的for循環,儘量使用向量化的數值計算。
還需要注意:
- 大多數t.function都有一個參數out,這時產生的結果將保存在out指定的tensor之中
a = t.arange(0, 20000000)
b = t.LongTensor()
t.arange(0, 20000000, out=b) # 64bit的LongTensor不會溢出
- t.set_num_threads可以設置PyTorch進行CPU多線程並行計算時所佔用的線程數,用來限制PyTorch所佔用的CPU數目。
- t.set_printoptions可以用來設置打印tensor時的數值精度和格式。
十、View的用法
a=torch.Tensor([[[1,2,3],[4,5,6]]])
b=torch.Tensor([1,2,3,4,5,6])
print(a.view(1,6))
print(b.view(1,6))
輸出結果:tensor([[1., 2., 3., 4., 5., 6.]])
tensor([[1., 2., 3., 4., 5., 6.]])
# -------------------------------------------------------
a=torch.Tensor([[[1,2,3],[4,5,6]]])
print(a.view(3,2))
輸出結果:
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
相當於就是從1,2,3,4,5,6順序的拿數組來填充需要的形狀
# --------------------------------------------------------
但是如果你想得到:
tensor([[1., 4.],
[2., 5.],
[3., 6.]])
就需要用到permute()
# -------------------------------------------------------
另外,參數不可爲空。
參數中的-1就代表這個位置由其他位置的數字來推斷,只要在不致歧義的情況的下,view參數就可以推斷出來,也就是人可以推斷出形狀的情況下,view函數也可以推斷出來。
比如a tensor的數據個數是6個,如果view(1,-1),我們就可以根據tensor的元素個數推斷出-1代表6。而如果是view(-1,-1,2),人不知道怎麼推斷,機器也不知道。
還有一種情況是人可以推斷出來,但是機器推斷不出來的:view(-1,-1,6),人可以知道-1都代表1,但是機器不允許同時有兩個負1。
如果沒有-1,那麼所有參數的乘積就要和tensor中元素的總個數一致了,否則就會出現錯誤。
十一、sequeeze和unsqueeze
簡介和用法
squeeze和unsqueeze的作用與其翻譯基本一致,被作用維度壓縮和解壓縮.用法相對簡單,具體如下:
tensor_unsqueeze = tensor.unsqueeze(dim)
若tensor存在n個維度,則dim的取值爲[-n+1,n]區間的整數,且dim的取值不能爲空.
tensor_squeeze = tensor.squeeze(dim)
若tensor存在n個維度,則dim的取值爲[-n,n-1]區間的整數,但dim的值可以爲空
具體示例:
首先看unsqueeze,其中的參數dim不能爲空。
f = torch.arange(0, 6).view(3, 2)
print(f, f.size())
# 參數表示在哪一維的前面增加一個維度
print(f.unsqueeze(0), f.unsqueeze(0).size()) # 1×3×2
print(f.unsqueeze(1), f.unsqueeze(1).size()) # 3×1×2
print(f.unsqueeze(2), f.unsqueeze(2).size()) # 3×2×1
# --------------------------------------------------
tensor([[0, 1],
[2, 3],
[4, 5]]) torch.Size([3, 2])
tensor([[[0, 1],
[2, 3],
[4, 5]]]) torch.Size([1, 3, 2])
tensor([[[0, 1]],
[[2, 3]],
[[4, 5]]]) torch.Size([3, 1, 2])
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
再看squeeze,它是將被操作目標中維度爲1的部分去除。
其中dim表示需要在哪一維去掉一個維度,如果不指定則自動尋找,如果指定則當指定的維度爲1時去掉,如果不爲1則不改變。
f = torch.arange(0, 6).view(3, 2, 1)
print(f, f.size()) # 3*2*1
print(f.squeeze(0), f.squeeze(0).size()) # 3*2*1,維度爲3,不能去掉,所以不變
print(f.squeeze(1), f.squeeze(1).size()) # 3*2*1,維度爲2,不能去掉,所有不變
print(f.squeeze(2), f.squeeze(2).size()) # 3*2 ,維度爲1,可以去掉,所有改變
print(f.squeeze(), f.squeeze().size()) # 如果無參數,則自動尋找,找到則刪除。
# -------------------------------------------------------------------------------
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
tensor([[0, 1],
[2, 3],
[4, 5]]) torch.Size([3, 2])
tensor([[0, 1],
[2, 3],
[4, 5]]) torch.Size([3, 2])
十二、cat的用法
cat是用於將兩個矩陣進行拼接,0表示按行拼接,1表示按列拼接
a = torch.ones(2, 3)
b = 2 * torch.ones(4, 3)
c = 4 * torch.ones(2, 4)
print(a)
print(b)
print(c)
d = torch.cat((a, b), 0) # 0表示按行拼接
e = torch.cat((a, c), 1) # 1表示按列拼接
print(d)
print(e)
# -----------------------------------------------
tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]])
tensor([[4., 4., 4., 4.],
[4., 4., 4., 4.]])
tensor([[1., 1., 1.],
[1., 1., 1.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]])
tensor([[1., 1., 1., 4., 4., 4., 4.],
[1., 1., 1., 4., 4., 4., 4.]])