1. 程式人生 > >Torch7入門續集(一)----- 更加深入理解Tensor

Torch7入門續集(一)----- 更加深入理解Tensor

續集寫作緣由

好吧,無語了,又開始寫Torch7了。前面寫的Torch入門,貌似都沒什麼人看,可能是該框架用的還是比較小眾,應該大部分人用Caffe,Tensorflow, mxnet之類了吧。無所謂了,主要是貌似我研究的方向作者的程式碼基本上還是Torch啊,沒法子。那我為什麼要寫續集呢?主要是發現以前寫的Torch7學入門專欄還是基本的,入門可能夠了,一些技巧和強大的函式,記錄一下,總有好處。

續集寫作原則

這裡只寫出我認為常見的重要的東西,有一些會和以前寫的有重複,不過無所謂了。有一些預設大家都知道了,也就不怎麼提。簡單一句話,只寫我認為該寫的!

續集寫作目的

  1. 更加好的掌握Torch已有的強大函式

    續集教程指的是除了基本的使用方式外,如何能使用內建的函式完成更多的任務,這才是這篇系列寫作的原因。舉個栗子,比如我要在一個Tensor加入padding,並且是“對稱型”的padding,然後再進行其他操作。然後自己寫了個函式,這裡預設輸入的是三維的Tensor
function ex_tensor(input, pad, method)
-- mirror and zero
    assert((pad-1)%2 == 0, 'pad should be odd number!')
    local padding = (pad-1)/2
    local method = method or
'mirror' local k = input:size() local output = torch.Tensor(k[1], k[2]+padding*2, k[3]+padding*2):typeAs(input):zero() output[{{},{padding+1, -padding-1},{padding+1, -padding-1}}] = input:clone() if method == 'mirror' then for i = 1, padding do output[{{},{i},{padding+1
, -padding-1}}] = output[{{},{padding*2+1-i},{padding+1, -padding-1}}] -- up output[{{},{-i},{padding+1, -padding-1}}] = output[{{},{-padding*2-1+i},{padding+1, -padding-1}}] --down output[{{},{padding+1, -padding-1},{i}}] = output[{{},{padding+1, -padding-1},{padding*2+1-i}}] --left output[{{},{padding+1, -padding-1},{-i}}] = output[{{},{padding+1, -padding-1},{-padding*2-1+i}}] --right end for i = 1, padding do output[{{},{1, padding},{i}}] = output[{{},{1, padding},{padding*2+1-i}}] --left_up output[{{},{-padding,-1},{i}}] = output[{{},{-padding,-1},{padding*2+1-i}}] --left_down output[{{},{1, padding},{-i}}] = output[{{},{1, padding},{-padding*2-1+i}}] --right_up output[{{},{-padding,-1},{-i}}] = output[{{},{-padding, -1},{-padding*2-1+i}}] --right_down end else -- done end return output end

是不是感覺很麻煩。。後來才發現Torch內部 nn.SpatialReplicationPadding 就可以完成這個任務。知道真相的我眼淚掉下來。。
2. 學習他人寫torch的技巧
隨時記錄一些好的技巧,可能以後會用到。
3. 讀N遍文件
這個很重要!這個也是續集的重點,著重讀重要的文件!

再探Tensor

Tensor的內部儲存

Tensor的同一維度的每個元素之間的步長是一樣的,第i個維度的步長為stride(i)。Tensor的首地址可以用storageOffset()來獲得。因此:

x = torch.Tensor(4,5)
s = x:storage()  
for i=1,s:size() do -- fill up the Storage
  s[i] = i
end
> x -- s is interpreted by x as a 2D matrix
  1   2   3   4   5
  6   7   8   9  10
 11  12  13  14  15
 16  17  18  19  20
[torch.DoubleTensor of dimension 4x5]

由於stride(i)不為1時,該Tensor的記憶體空間就是不連續的。但是最後一個維度是連續的

x = torch.Tensor(4,5)
i = 0

x:apply(function()
  i = i + 1
  return i
end)

> x
  1   2   3   4   5
  6   7   8   9  10
 11  12  13  14  15
 16  17  18  19  20
[torch.DoubleTensor of dimension 4x5]

> x:stride()
 5
 1  -- element in the last dimension are contiguous!
[torch.LongStorage of size 2]

值得注意的是,這種方式是類C的方法,與matlab(類似Fortran)的矩陣是不一樣的。
熟讀下面句子,並背誦。。。
One could say that a Tensor is a particular way of viewing a Storage: a Storage only represents a chunk of memory, while the Tensor interprets this chunk of memory as having dimensions
Tensor其實就是對儲存空間的另一種view!Tensor型別就是在儲存空間中看成是有維度的!!

另外,所有的Tensor操作都是直接在原儲存空間中進行操作!這一切都是通過內部的stride()與storageOffset()來實現的。要想得到一個新的Tensor。用clone()

x = torch.Tensor(5):zero()
> x
0
0
0
0
0
[torch.DoubleTensor of dimension 5]
> x:narrow(1, 2, 3):fill(1) -- narrow() returns a Tensor
                            -- referencing the same Storage as x
> x
 0
 1
 1
 1
 0
[torch.Tensor of dimension 5]
y = x:clone()

Tensor構造的三種方式

下面2種比較常用。

-- 最多到4維度
x = torch.Tensor(2,5):fill(3.14) 
-- 大於4維度的
x = torch.Tensor(torch.LongStorage({4,4,3,2}))

注意:第二個函式呼叫方式是torch.Tensor(sizes, [strides]) 。sizes和[strides]都是LongStorage型別的。並且strides是每一維度中,一個元素到下一個元素之間的步長。並且可以隨意設定!

x = torch.Tensor(torch.LongStorage({4}), torch.LongStorage({0})):zero() -- zeroes the tensor
x[1] = 1 -- all elements point to the same address!
> x
 1
 1
 1
 1

可以看到上面令stride為0了,也就是說x雖然有4個單元,但都實際上指向實體地址的同一個單元!讓x[1]=1,整個x都變成1了。厲害了。

另外一個就是通過table進行構造。

> torch.Tensor({{1,2,3,4}, {5,6,7,8}})
 1  2  3  4
 5  6  7  8
[torch.DoubleTensor of dimension 2x4]

附:torch為了方便大家的使用,內部內部的絕大部分函式都可以用2種方法來進行呼叫—– src:function()或是torch.function(src, …)。也就是說,第二種風格的呼叫是將自身作為第一個引數進行呼叫。然而由於內部還是不太完善,有些函式只能用第一種方法或是第二種方法。比如

--下面兩種是一樣的
x = torch.Tensor(2,3):fill(2)
y = torch.fill(torch.Tensor(2,3),2)

--下面只能由第二種方法
local x = torch.transpose(vecInput, 2,3)  --這個會出錯
local y = vecInput:transpose(2,3)  --這個正確

這些只能由自己去測試了,當然絕大部分都是完美支援兩種的。所以也沒啥好擔心的。

Tensor的常用的函式

  1. clone
    這個沒啥好說的,上面已經演示了。簡單來說
x = torch.Tensor(2,3)
y = x  -- 這個x和y是一樣的!沒有開闢新的記憶體
y = x:clone() --這個才等價於大多數語言 y = x 進行的操作。
  1. contiguous
    有時候你賦值時會出現xxx is not contiguous的錯誤資訊。那麼用這個函式就可以將記憶體變連續。
    這個函式對於那些已經是記憶體連續的函式,則返回該函式的同一記憶體地址,如果該函式記憶體不連續,那麼就會新申請空間,進行賦值。這樣做,符合torch就是儘量讓運算加快的原則。
    那麼如果是申請Tensor我就用torch.Tensor(2,3,4)常規的操作,那得到的Tensor肯定是連續的吧,那是什麼原因導致tensor不連續的呢?沒錯,就是對Tensor的擷取!
a = torch.Tensor(3,4)
th> a
 6.9436e-310  6.9436e-310   0.0000e+00   0.0000e+00
 2.0337e-110   4.0708e-27  6.9981e-308  7.6284e+228
 1.0626e+248  6.1255e-154   3.9232e-85   1.9473e-57
[torch.DoubleTensor of size 3x4]

th> a:stride()
 4
 1
[torch.LongStorage of size 2]
-- 顯然a是連續的。

th> b = a[{{1,3},{2}}]
                                                                      [0.0000s]
th> b
 6.9436e-310
  4.0708e-27
 6.1255e-154
[torch.DoubleTensor of size 3x1]

th> b:stride()
 4
 1
[torch.LongStorage of size 2]

th> b:isContiguous()
false

b顯然不連續,因為b的6.9e-310後面第一個單元是a的第二個單元,並不是b的第二個單元!這時候

th> k = b:contiguous()
                                                                      [0.0000s]
th> k
  0.0000e+00
 7.6284e+228
  1.9473e-57
[torch.DoubleTensor of size 3x1]

                                                                      [0.0001s]
th> k:stride()
 1
 1
[torch.LongStorage of size 2]

可看到,k的stride變成1 1了,此時連續!
3. type與其他一些函式

x = torch.Tensor(3):fill(3.14)
> x
 3.1400
 3.1400
 3.1400
[torch.DoubleTensor of dimension 3]

y = x:type('torch.IntTensor')
> y
 3
 3
 3
[torch.IntTensor of dimension 3]

直接在Tensor後面可以進行int(), byte(), float()等操作。
一些看名字就知道怎麼用的函式,記記有好處:
isSameSizeAs, isSize(Tensor),

typeAs(Tensor)可以將一個Tensor變成指定Tensor的大小

stride(i),求第i維度的步長

size(dim)與(#x)[dim]是等價的。(你可能會問為什麼不是 #x[1],因為#是size()的簡寫。返回的是一個LongStorage資料!並不是一個number!LongStorage就是Tensor的內部儲存形式!因此相當於一個Tensor!所以我們要用(#x)[dim]來獲得number型別的數!

這個resize除了正常的等空間的變換,還可以變大的。。

th> a = torch.Tensor(3,4):fill(2)
                                                                      [0.0001s]
th> a
 2  2  2  2
 2  2  2  2
 2  2  2  2
[torch.DoubleTensor of size 3x4]

                                                                      [0.0002s]
th> a:resize(3,4,2)
(1,.,.) =
   2.0000e+00   2.0000e+00
   2.0000e+00   2.0000e+00
   2.0000e+00   2.0000e+00
   2.0000e+00   2.0000e+00

(2,.,.) =
   2.0000e+00   2.0000e+00
   2.0000e+00   2.0000e+00
  9.2205e+140  9.8730e+169
  1.2016e-306  5.7623e-114

(3,.,.) =
   2.0869e-76   8.5004e-96
  9.0050e+130   2.4793e-70
  1.1195e-307   2.9068e-14
  3.1443e-120  1.7743e+159
[torch.DoubleTensor of size 3x4x2]

                                                                      [0.0003s]
-- 一個比較重要的問題是,如果直接在最前面加一維度,則其自身的資料會在最前面,而後面的是不確定的數。這可以作為一個靈活的操作。                                                                    
th> b = torch.Tensor(2,4):fill(1)
                                                                      [0.0001s]
th> b:resize(2,2,4)
(1,.,.) =
   1.0000e+00   1.0000e+00   1.0000e+00   1.0000e+00
   1.0000e+00   1.0000e+00   1.0000e+00   1.0000e+00

(2,.,.) =
  1.3069e+179  1.1757e+214  5.9669e-154  1.7713e+159
  8.2678e+140   4.0622e-66  7.2010e+252  3.8143e+228
[torch.DoubleTensor of size 2x2x4]

resizeAs(Tensor)

擷取Tensor資料

x[index],等價與select(1,index). 如果x是二維資料,那麼x[2]代表第2行!就是相當於 x:select(1,2)。 選取第一維的第二個。

narrow, select, sub略!

這三個函式可以用x[{ {dim1s, dim1e}, {dim2s, dim2e},… }]來替代!

x = torch.Tensor(2,5,6):zero()

-- 第一種,如果是隻有一個大括號,裡面是單純數字的話,
-- 就說明是"全部"
-- 選擇"第一塊,第三行,沒指定列,所以是全部"
x[{1,3}] = 1
th> x
(1,.,.) =
  0  0  0  0  0  0
  0  0  0  0  0  0
  1  1  1  1  1  1
  0  0  0  0  0  0
  0  0  0  0  0  0

(2,.,.) =
  0  0  0  0  0  0
  0  0  0  0  0  0
  0  0  0  0  0  0
  0  0  0  0  0  0
  0  0  0  0  0  0
[torch.DoubleTensor of size 2x5x6]

-- 我麼也可以通過這個快速取某個座標的值
-- 比如 x[{1,3,2}],而不用寫成x[{ {1},{3},{2}]

x[{ {dim1s, dim1e}, {dim2s, dim2e},… }]的用法略。

取Tensor中的某個座標的值

x = torch.Tensor(2,3,4)

v = x[{1,2,2}]  --取座標值時,用擷取Tensor資料的方法,當index的元素個數等於x的nDimension時,才是取某個值!否則是等價於`select`的!!!!!!!!!!!!!!!!!!!!!!!!
v_false = x[{{1},{2},{3}}] --這個是錯的!這個返回的是Tensor型別,不是num!

x[v] = 2  -- 這個會讓x的一半的值都變成2!!!!

Tensor的賦值

以下只說對單個元素的賦值。

x = torch.Tensor(2,3,4)
--以下下兩種方法等價!
x[{1,2,3}] = 2
x[{{1},{2},{3}}] = 2  

Tensor的搜尋

這個就一個函式nonzero(). 返回非0元素的座標的。

> x = torch.rand(4, 4):mul(3):floor():int()
> x
 2  0  2  0
 0  0  1  2
 0  2  2  1
 2  1  2  2
[torch.IntTensor of dimension 4x4]
> torch.nonzero(x)
 1  1
 1  3
 2  3
 2  4
 3  2
 3  3
 3  4
 4  1
 4  2
 4  3
 4  4
[torch.LongTensor of dimension 11x2]

Expanding/Replicating/Squeezing Tensors

expand

這個函式的作用就是在singleton維度進行擴充套件!嚇人的是,這裡使用了一個技巧:expand Tensor時並不需要開闢新的空間,而是直接讓被擴充套件的那個維度的stride為0!

x = torch.rand(10,1)
> x
 0.3837
 0.5966
 0.0763
 0.1896
 0.4958
 0.6841
 0.4038
 0.4068
 0.1502
 0.2239
[torch.DoubleTensor of dimension 10x1]
>x:stride()
1
1

y = torch.expand(x,10,2)
> y
 0.3837  0.3837
 0.5966  0.5966
 0.0763  0.0763
 0.1896  0.1896
 0.4958  0.4958
 0.6841  0.6841
 0.4038  0.4038
 0.4068  0.4068
 0.1502  0.1502
 0.2239  0.2239
[torch.DoubleTensor of dimension 10x2]
>y:stride()
1
0

可以看見,被擴充套件的維度變成0了!!
又比如:

th> x = torch.rand(5,1,4)
                                                                      [0.0001s]
th> x:stride()
 4    -- 1*4
 4    -- 4
 1
[torch.LongStorage of size 3]

th> x = torch.rand(5,2,4)
                                                                      [0.0001s]
th> x:stride()
 8    -- 2*4
 4    -- 4
 1
[torch.LongStorage of size 3]

這是因為直接呼叫建構函式,不指定stride的話,都是連續的。


th> x = torch.rand(10,1,5)
                                                                      [0.0001s]
th> x:stride()
 5
 5
 1
[torch.LongStorage of size 3]

th> y = torch.expand(x,10,2,5)
                                                                      [0.0001s]
th> y:stride()
 5
 0
 1
[torch.LongStorage of size 3]

repeatTensor

這個函式是需要新申請空間的!

x = torch.rand(5)
> x
 0.7160
 0.6514
 0.0704
 0.7856
 0.7452
[torch.DoubleTensor of dimension 5]

> torch.repeatTensor(x,3,2)
 0.7160  0.6514  0.0704  0.7856  0.7452  0.7160  0.6514  0.0704  0.7856  0.7452
 0.7160  0.6514  0.0704  0.7856  0.7452  0.7160  0.6514  0.0704  0.7856  0.7452
 0.7160  0.6514  0.0704  0.7856  0.7452  0.7160  0.6514  0.0704  0.7856  0.7452
[torch.DoubleTensor of dimension 3x10]

Squeeze

這個函式就是將所有singleton的維度壓縮去除。

View—重新看待儲存Storage

這裡主要是5個函式:view,viewAs, transpose(), t(), permute()。
這些都是對於Tensor以另一種角度取看待!因此是直接對原儲存空間進行更改的!
These methods are very fast, because they do not involve any memory copy.

x = torch.zeros(4)
> x:view(2,2)
 0 0
 0 0
[torch.DoubleTensor of dimension 2x2]

> x:view(2,-1)
 0 0
 0 0
[torch.DoubleTensor of dimension 2x2]

-- 利用view是進行增加一個“1”維的很好的方法
x = x:view(2,1,2)
th> x
(1,.,.) =
  0  0

(2,.,.) =
  0  0
[torch.DoubleTensor of size 2x1x2]

th> x:stride()
 2
 2
 1
[torch.LongStorage of size 3]

其他函式略