当前位置:首页> 正文

Torch7中级教程(一)----- 更加深入理解Tensor

Torch7中级教程(一)----- 更加深入理解Tensor

中级教程写作缘由

好吧,无语了,又开始写Torch7了。前面写的Torch学习专栏,貌似都没什么人看,可能是该框架用的还是比较小众,应该大部分人用Caffe,Tensorflow, mxnet之类了吧。无所谓了,主要是貌似我研究的方向作者的代码基本上还是Torch啊,没法子。那我为什么要写中级教程呢?主要是我写不来高级教程啊。。哈哈,其实发现以前写的Torch7学习专栏还是基本的,入门可能够了,一些技巧和强大的函数,记录一下,总有好处。

中级教程写作原则

由于我以前已经写了, Torch7学习,上面基本上是覆盖了入门该学的东西。这里只写出我认为常见的重要的东西,有一些会和以前写的有重复,不过无所谓了。有一些默认大家都知道了,也就不怎么提。简单一句话,只写我认为该写的!

中级教程写作目的

  1. 更加好的掌握Torch已有的强大函数
    其实用中级教程还是有点小自大的,毕竟用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 outputend

是不是感觉很麻烦。。后来才发现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] = iend> 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 = 0x:apply(function()  i = i + 1  return iend)> 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()> x00000[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 tensorx[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类型的数!

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}] = 1th> 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的搜索

这个就一个函数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()11y = 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()10

可以看见,被扩展的维度变成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]

其他函数略

展开全文阅读

相关内容