Tensor操作

函數 功能 例子
Tensor(*sizes) 制定size構造函數 a = t.Tensor(2, 3)
Tensor(*list) 從list構造 a = t.Tensor([[1,2,3],[4,5,6]])
tensor(data,) 類似np.array的構造函數(需要數據) scalar = t.tensor(3.14159) ;vector = t.tensor([1, 2])
ones(*sizes) 全1Tensor
zeros(*sizes) 全0Tensor
eye(*sizes) 對角線爲1,其他爲0 t.eye(2, 3, dtype=t.int) # 對角線爲1, 不要求行列數一致
arange(s,e,step 從s到e,步長爲step t.arange(1, 6, 2)
linspace(s,e,steps) 從s到e,均勻切分成steps份 t.linspace(1, 10, 3)
rand/randn(*sizes) 均勻/標準分佈 t.randn(2, 3, device=t.device(‘cpu’))
normal(mean,std)/uniform(from,to) 正態分佈/均勻分佈
randperm(m) 隨機排列 t.randperm(5) # 長度爲5的隨機排列

這些創建方法都可以在創建的時候指定數據類型dtype和存放device(cpu/gpu).

t.tensor和t.Tensor的區別

一句話來講,torch.Tensor是由torch.tensortorch.empty組成的。torch.Tensor可以新建一個空tensor而torch.tensor不可以。

tensor_without_data = torch.Tensor() #對
  • 1

tensor_without_data = torch.tensor()  錯

基本操作

查看tensor 大小: t.size(), t.shape()

tensor轉list: t.tolist()

計算tensor中元素總個數: t.numel()

調整形狀:

t.view(), t.resize()

a = t.arange(0, 6)
a.view(2, 3)
a.shape()
  • 1
  • 2
  • 3

$ tensor([[ 0., 1., 2.],
[ 3., 4., 5.]])
torch.Size([2, 3])

resize與view不同的是它可以修改tensor的大小。

b.resize_(1, 3)
b.resize_(3, 3)
  • 1
  • 2

tensor([[ 0., 100., 2.]])
tensor([[ 0.0000, 100.0000, 2.0000],
[ 3.0000, 4.0000, 5.0000],
[ -0.0000, 0.0000, 0.0000]])

增減維度 t.squeeze(), t.unsqueeze()?

b.unsqueeze(1) # 注意形狀,在第1維(下標從0開始)上增加“1” 
#等價於 b[:,None]
b[:, None].shape
  • 1
  • 2
  • 3

b從torch.Size([2, 3])變成torch.Size([2, 1, 3])

索引操作

如無特殊說明,索引出來的結果與原tensor共享內存,也即修改一個,另一個會跟着修改。

a[0] # 第0行(下標從0開始)
a[:, 0] # 第0列
a[0][2] # 第0行第2個元素,等價於a[0, 2]
a[0, -1] # 第0行最後一個元素
a[:2] # 前兩行
a[:2, 0:2] # 前兩行,第0,1列
a[0:1, :2] # 第0行,前兩列 

None類似於np.newaxis, 爲a新增了一個軸

等價於a.view(1, a.shape[0], a.shape[1])

a[None].shape # 等價於a[None,:,:]
a[:,None,:].shape

a > 1 # 返回一個ByteTensor
a[a>1] # 等價於a.masked_select(a>1) # 選擇結果與原tensor不共享內存空間
a[t.LongTensor([0,1])] # 第0行和第1行

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

常用的選擇函數

函數 功能
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一樣

gather是一個比較複雜的操作,對一個2維tensor,輸出的每個元素如下:

out[i][j] = input[index[i][j]][j]  # dim=0
out[i][j] = input[i][index[i][j]]  # dim=1
  • 1
  • 2

三維tensor的gather操作同理,下面舉幾個例子。

a = t.arange(0, 16).view(4, 4)
tensor([[  0.,   1.,   2.,   3.],
        [  4.,   5.,   6.,   7.],
        [  8.,   9.,  10.,  11.],
        [ 12.,  13.,  14.,  15.]])

選取對角線的元素

index = t.LongTensor([[0,1,2,3]])
a.gather(0, index)
tensor([[ 0., 5., 10., 15.]])

選取反對角線上的元素

index = t.LongTensor([[3,2,1,0]]).t()
a.gather(1, index)
tensor([[ 3.],
[ 6.],
[ 9.],
[ 12.]])

選取反對角線上的元素,注意與上面的不同

index = t.LongTensor([[3,2,1,0]])
a.gather(0, index)

選取反對角線上的元素,注意與上面的不同

index = t.LongTensor([[3,2,1,0]])

a.gather(0, index)

tensor([[ 12., 9., 6., 3.]])

  • 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
  • 27
  • 28

gather相對應的逆操作是scatter_gather把數據從input中按index取出,而scatter_是把取出的數據再放回去。注意scatter_函數是inplace操作。

out = input.gather(dim, index)
-->近似逆操作
out = Tensor()
out.scatter_(dim, index)
  • 1
  • 2
  • 3
  • 4

把兩個對角線元素放回去到指定位置
c = t.zeros(4,4)
c.scatter_(1, index, b)
tensor([[ 0., 0., 0., 3.],
[ 0., 5., 6., 0.],
[ 0., 9., 10., 0.],
[ 12., 0., 0., 15.]])

對tensor的任何索引操作仍是一個tensor,想要獲取標準的python對象數值,需要調用tensor.item(), 這個方法只對包含一個元素的tensor適用
a[0,0] #依舊是tensor)
a[0,0].item() # python float

Tensor類型

Tensor有不同的數據類型,如表3-3所示,每種類型分別對應有CPU和GPU版本(HalfTensor除外)。默認的tensor是FloatTensor,可通過t.set_default_tensor_type 來修改默認tensor類型(如果默認類型爲GPU tensor,則所有操作都將在GPU上進行)。Tensor的類型對分析內存佔用很有幫助。例如對於一個size爲(1000, 1000, 1000)的FloatTensor,它有1000*1000*1000=10^9個元素,每個元素佔32bit/8 = 4Byte內存,所以共佔大約4GB內存/顯存。HalfTensor是專門爲GPU版本設計的,同樣的元素個數,顯存佔用只有FloatTensor的一半,所以可以極大緩解GPU顯存不足的問題,但由於HalfTensor所能表示的數值大小和精度有限1,所以可能出現溢出等問題。

tensor數據類型

Data type dtype CPU tensor GPU tensor
32-bit floating point torch.float32 or torch.float torch.FloatTensor torch.cuda.FloatTensor
64-bit floating point torch.float64 or torch.double torch.DoubleTensor torch.cuda.DoubleTensor
16-bit floating point torch.float16 or torch.half torch.HalfTensor torch.cuda.HalfTensor
8-bit integer (unsigned) torch.uint8 torch.ByteTensor torch.cuda.ByteTensor
8-bit integer (signed) torch.int8 torch.CharTensor torch.cuda.CharTensor
16-bit integer (signed) torch.int16 or torch.short torch.ShortTensor torch.cuda.ShortTensor
32-bit integer (signed) torch.int32 or torch.int torch.IntTensor torch.cuda.IntTensor
64-bit integer (signed) torch.int64 or torch.long torch.LongTensor torch.cuda.LongTensor

各數據類型之間可以互相轉換,type(new_type)是通用的做法,同時還有floatlonghalf等快捷方法。CPU tensor與GPU tensor之間的互相轉換通過tensor.cudatensor.cpu方法實現,此外還可以使用tensor.to(device)。Tensor還有一個new方法,用法與t.Tensor一樣,會調用該tensor對應類型的構造函數,生成與當前tensor類型一致的tensor。torch.*_like(tensora) 可以生成和tensora擁有同樣屬性(類型,形狀,cpu/gpu)的新tensor。 tensor.new_*(new_shape) 新建一個不同形狀的tensor。

數學運算

in place操作(是否修改自身數據)

對tensor的操作又可分爲兩類:

  1. 不會修改自身的數據,如 a.add(b), 加法的結果會返回一個新的tensor。
  2. 會修改自身的數據,如 a.add_(b), 加法的結果仍存儲在a中,a被修改了。

逐元素操作

這部分操作會對tensor的每一個元素(point-wise,又名element-wise)進行操作,此類操作的輸入與輸出形狀一致。常用的操作如表3-4所示。

表3-4: 常見的逐元素操作

函數 功能
abs/sqrt/div/exp/fmod/log/pow… 絕對值/平方根/除法/指數/求餘/求冪…
cos/sin/asin/atan2/cosh… 相關三角函數
ceil/round/floor/trunc 上取整/四捨五入/下取整/只保留整數部分
clamp(input, min, max) 超過min和max部分截斷
sigmod/tanh… 激活函數

對於很多操作,例如div、mul、pow、fmod等,PyTorch都實現了運算符重載,所以可以直接使用運算符。如a ** 2 等價於torch.pow(a,2), a * 2等價於torch.mul(a,2)

其中clamp(x, min, max)的輸出滿足以下公式:
KaTeX parse error: Expected '}', got 'EOF' at end of input: …ax\\\end{cases}yi=min,xi,max,if xi<minif minximaxif xi>max
clamp常用在某些需要比較大小的地方,如取一個tensor的每個元素與另一個數的較大值。

歸併操作

此類操作會使輸出形狀小於輸入形狀,並可以沿着某一維度進行指定操作。如加法sum,既可以計算整個tensor的和,也可以計算tensor中每一行或每一列的和。常用的歸併操作如表3-5所示。

表3-5: 常用歸併操作

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

以上大多數函數都有一個參數**dim**,用來指定這些操作是在哪個維度上執行的。關於dim(對應於Numpy中的axis)的解釋衆說紛紜,這裏提供一個簡單的記憶方式:

假設輸入的形狀是(m, n, k)

  • 如果指定dim=0,輸出的形狀就是(1, n, k)或者(n, k)
  • 如果指定dim=1,輸出的形狀就是(m, 1, k)或者(m, k)
  • 如果指定dim=2,輸出的形狀就是(m, n, 1)或者(m, n)

size中是否有"1",取決於參數keepdimkeepdim=True會保留維度1。注意,以上只是經驗總結,並非所有函數都符合這種形狀變化方式,如cumsum

比較

比較函數中有一些是逐元素比較,操作類似於逐元素操作,還有一些則類似於歸併操作。常用比較函數如表3-6所示。

表3-6: 常用比較函數

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

表中第一行的比較操作已經實現了運算符重載,因此可以使用a>=ba>ba!=ba==b,其返回結果是一個ByteTensor,可用來選取元素。max/min這兩個操作比較特殊,以max來說,它有以下三種使用情況:

  • t.max(tensor):返回tensor中最大的一個數
  • t.max(tensor,dim):指定維上最大的數,返回tensor和下標
  • t.max(tensor1, tensor2): 比較兩個tensor相比較大的元素

至於比較一個tensor和一個數,可以使用clamp函數。

線性代數

PyTorch的線性函數主要封裝了Blas和Lapack,其用法和接口都與之類似。常用的線性代數函數如表3-7所示。

表3-7: 常用的線性代數函數

函數 功能
trace 對角線元素之和(矩陣的跡)
diag 對角線元素
triu/tril 矩陣的上三角/下三角,可指定偏移量
mm/bmm 矩陣乘法,batch的矩陣乘法
addmm/addbmm/addmv/addr/badbmm… 矩陣運算
t 轉置
dot/cross 內積/外積
inverse 求逆矩陣
svd 奇異值分解

具體使用說明請參見官方文檔2,需要注意的是,矩陣的轉置會導致存儲空間不連續,需調用它的.contiguous方法將其轉爲連續。

GPU/CPU

tensor可以很隨意的在gpu/cpu上傳輸。使用tensor.cuda(device_id)或者tensor.cpu()。另外一個更通用的方法是tensor.to(device)

  • 儘量使用tensor.to(device), 將device設爲一個可配置的參數,這樣可以很輕鬆的使程序同時兼容GPU和CPU
  • 數據在GPU之中傳輸的速度要遠快於內存(CPU)到顯存(GPU), 所以儘量避免頻繁的在內存和顯存中傳輸數據。
a = t.randn(3, 4)
a.device
device(type='cpu')
if t.cuda.is_available():
    a = t.randn(3,4, device=t.device('cuda:1'))
    # 等價於
    # a.t.randn(3,4).cuda(1)
    # 但是前者更快
    a.device
device = t.device('cpu')
a.to(device)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  1. https://stackoverflow.com/questions/872544/what-range-of-numbers-can-be-represented-in-a-16-32-and-64-bit-ieee-754-syste ↩︎

  2. http://pytorch.org/docs/torch.html#blas-and-lapack-operations ↩︎

                                </div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章