好吧,无语了,又开始写Torch7了。前面写的Torch入门,貌似都没什么人看,可能是该框架用的还是比较小众,应该大部分人用Caffe,Tensorflow, mxnet之类了吧。无所谓了,主要是貌似我研究的方向作者的代码基本上还是Torch啊,没法子。那我为什么要写续集呢?主要是发现以前写的Torch7学入门专栏还是基本的,入门可能够了,一些技巧和强大的函数,记录一下,总有好处。
这里只写出我认为常见的重要的东西,有一些会和以前写的有重复,不过无所谓了。有一些默认大家都知道了,也就不怎么提。简单一句话,只写我认为该写的!
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的同一维度的每个元素之间的步长是一样的,第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()
下面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) --这个正确
这些只能由自己去测试了,当然绝大部分都是完美支持两种的。所以也没啥好担心的。
x = torch.Tensor(2,3)
y = x -- 这个x和y是一样的!没有开辟新的内存
y = x:clone() --这个才等价于大多数语言 y = x 进行的操作。
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)
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},… }]的用法略。
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!!!!
以下只说对单个元素的赋值。
x = torch.Tensor(2,3,4)
--以下下两种方法等价!
x[{1,2,3}] = 2
x[{{1},{2},{3}}] = 2
这个就一个函数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]
这个函数的作用就是在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]
这个函数是需要新申请空间的!
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]
这个函数就是将所有singleton的维度压缩去除。
这里主要是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]
其他函数略