龙空技术网

「C/C++」 torch7中的基本数据结构与cpu算子:TH库和luaT库

老师明明可以靠颜值 197

前言:

今天小伙伴们对“cadd函数怎么用”都比较关心,姐妹们都想要知道一些“cadd函数怎么用”的相关知识。那么小编也在网络上搜集了一些对于“cadd函数怎么用””的相关内容,希望各位老铁们能喜欢,姐妹们快快来了解一下吧!

先看Tensor,

看Tensor的源码之前,先看看如何使用Tensor,这有助于理解Tensor设计的目的。

这个在torch7中最主要的类:

多维矩阵

一个 Tensor 一个 多维矩阵multi-dimensional matrix. 矩阵可以有多少维度?由LongStorage决定

例子程序:

 --- creation of a 4D-tensor 4x5x6x2 z = torch.Tensor(4,5,6,2) --- for more dimensions, (here a 6D tensor) one can do: s = torch.LongStorage(6) s[1] = 4; s[2] = 5; s[3] = 6; s[4] = 2; s[5] = 7; s[6] = 3; x = torch.Tensor(s)

用 nDimension() 或者 dim()可以或者到Tensor的维度个数。

具体第i个维度的长度可使用size(i)得到.。

一个LongStorage 所以维度的具体长度。

> x:nDimension()6> x:size() 4 5 6 2 7 3[torch.LongStorage of size 6]

Tensor就是一个多维矩阵,multi-dimensional matrix。矩阵的维度没有被限制,决定于LongStorage,可以是3维矩阵,4维矩阵,...,6维矩阵等。

内部数据

Tensor对应的具体数据保存在Storage中,可用Storage()访问。

Tensor的内存对应唯一的Storage,地址可以不相邻:Storage的地址通过storageOffset()获取,如果要访问某维度i的第j个元素,就需要用stride(i)跳过去,即这个元素的地址在:storageOffset()+stride(i)*(j-1),我们来看个例子,又一个3D Tensor:

x = torch.Tensor(7,7,7)

获取(3,4,5)位置的元素可以这样:

> x[3][4][5]

或者也可以这样: (但是这样访问很慢!)

> x:storage()[x:storageOffset()              +(3-1)*x:stride(1)+(4-1)*x:stride(2)+(5-1)*x:stride(3)]

或者我们可以这样说:Tensor只不过是Stroage的一种特殊访问方式,这种访问方式就好像让这一堆存储对应成了多维数据。

x = torch.Tensor(4,5)s = x:storage()for i=1,s:size() do -- 填充Storage  s[i] = iend> x -- s这个storage被x解释为一个2D矩阵  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]

同时注意到,在Torch7中,同一行的元素(即多维矩阵的最后一个维度的元素)在内存中是连续的。

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了(不是Fortran).

不同数据类型的Tensors

存在下列Tensor:

ByteTensor -- contains unsigned charsCharTensor -- contains signed charsShortTensor -- contains shortsIntTensor -- contains intsLongTensor -- contains longsFloatTensor -- contains floatsDoubleTensor -- contains doubles

大部分的数值计算,只实现了FloatTensor和DoubleTensor。其他类型的Tensor只在你想节省存储时才有用。

高效的内存管理

Efficient memory management

All tensor operations in this class do not make any memory copy. All these methods transform the existing tensor, or return a new tensor referencing the same storage. This magical behavior is internally obtained by good usage of the stride() and storageOffset(). Example:

所有对tensor的操作,都不能开启新的内存空间。

都是对已经存在的内存做in-place操作,

操作完成后,返回一个新的tensor,但是指向同样的内存。

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]

使用copy()才会开启新内存:

y = torch.Tensor(x:size()):copy(x)

或者使用clone()

y = x:clone()

下面我们看Tensor的methods,如果你想操作不同类型的Tensor,使用CharTensor即可。

Tensor有很多methods。像add,mul,填充fill,更改大小,获取子矩阵等。

这里就查看一个比较特殊的methods吧,

[Tensor] gather(dim, index)

挑选出一些元素组成新的矩阵

当dim=1时,挑选的某个元素序号为::index[i][j][k],j, k

当dim=2时,挑选的某个元素序号为::i , index[i][j][k] , k

当dim=3时,挑选的某个元素序号为::i, j, index[i][j][k]

-- dim = 1result[i][j][k]... = src[index[i][j][k]...][j][k]...-- dim = 2result[i][j][k]... = src[i][index[i][j][k]...][k]...-- etc.

src 是被挑选的 Tensor.

例如:src 的 size 是 n x m x p x q, dim = 3, 我们希望在第3维度上取3个值,我们将得到一个 size 为 n x m x k x q 的矩阵。

gather操作返回的矩阵,将保存一个新的地址。

x = torch.rand(5, 5)> x 0.7259  0.5291  0.4559  0.4367  0.4133 0.0513  0.4404  0.4741  0.0658  0.0653 0.3393  0.1735  0.6439  0.1011  0.7923 0.7606  0.5025  0.5706  0.7193  0.1572 0.1720  0.3546  0.8354  0.8339  0.3025[torch.DoubleTensor of size 5x5]y = x:gather(1, torch.LongTensor{{1, 2, 3, 4, 5}, {2, 3, 4, 5, 1}})> y 0.7259  0.4404  0.6439  0.7193  0.3025 0.0513  0.1735  0.5706  0.8339  0.4133[torch.DoubleTensor of size 2x5]z = x:gather(2, torch.LongTensor{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 1}})> z 0.7259  0.5291 0.4404  0.4741 0.6439  0.1011 0.7193  0.1572 0.3025  0.1720[torch.DoubleTensor of size 5x2]

去看下Tensor的具体实现代码:

torch7/tensor.c

这个文件里只是一些include,先看下general.h

torch7/general.h

除了一些std库的文件,还有一些预定义,最有用的就是8--9 行:luaT.h,TH.h

torch7/lib/TH/TH.h

TH.h里面果然有内容,THTensor.h,THStorage.h等都是重点。

只看头文件,大概可以猜测:torch7中的tensor,是在对TH库的封装。那么这个TH是啥意思?Torch取第一个字母和最后一个字母的意思?

姑且先这么认为吧。

我们找到TH库中,lib/TH/generic/THTensor.h

THTensor是C中的结构体,这就是张量的基本数据结构了。

long *size: 存储THTensor每个维度的长度的数组

long *stride: 存储每个维度在寻址时跳转的量的数组

这两个量的存在,是为了用维度的方式去访问那一堆内存地址的。

THStorage *storage: Tensor元素存储的内存

ptrdiff_t storageOffset: 访问具体某个元素时,地址的开始量

int refcount: 引用计数,有点像智能指针的计数

char flag:还不清楚,

上图是THTensor结构体的访问函数,这个语法有点奇怪,也不知道该怎么查。

这里函数名字是:THTensor_(storage),难道这是个预定义?

从语法上理解,THTensor_只能是个宏定义,果然:

宏定义内部还有宏TH_CONCAT_4,不过这个从名字上看,就是连接4个字符串的意思啦,我们搜索一下:

其中116行的TH_CONCAT_4_EXPAND是源头的宏定义。

回到最开始的问题:

这里函数名字是:THTensor_(storage),就可以展开为:THRealTensor_storage,这就是实际的函数名。

每个函数前,都有个TH_API,

原来这里是指定extern “C”,C++调用C 时,需要指定。参考:

和dllexport或者dllimport的,

具体:

终于把语法搞清楚了。

可以看这个结构是如何构建的了:

THRealTensornew函数内部先THAlloc,然后又THRealTensorrawInit了一下。

上图又是两个建立新tensor的函数。可以看到,这个过程中,就是在不断调用THStora ge的过程。

再接着看看计算部分:

这里有个cadd函数,它是TH库的函数

在torch7/TH/THTensorMath.c

它用在了torch7/generic/TensorOperator.c中,被算子所调用了。

但是这个TensorOperator.c中,是定义了几种简单的算子:加,减,乘,除等。

我搜索了整个torch7,都没法优先️地方调用卷积函数。

难道在torch7库中,只是定义了,没有使用它?

终于,看了torch7中的TensorMath.lua,发现了conv2等算子。

torch7中,使用了luaT把 TH库中的C代码封装了一下,然后又些lua代码来调用TH中定义的结构体和函数。

lua这门脚本语言也挺简单的,看这里:

luaT是把C封装到lua中使用的库,在这里:

看torch7的文件结构:

torch7编译好以后,应该可以写lua脚本来调用。

编译过程:

编译好后,写lua脚本即可,可以参考torch7的项目开发示范代码:

找到这个train()函数了,如下

后面可以train函数里面使用的相关函数为起点,再追查到luaT库和TH库,从而把torch7的整个架构搞清楚。

比如说,以

图中所示的model:forward函数为起点。

在查找model时,发现处理torch包,还有nn包,就回到torch的github上看,原来这里不止troch一个包。

整个torch包含torch7,nn,tutorial, distro,demos,cutorch等,

需要将nn,demos等下载,然后再做解读。

总结:

torch7只是一个提供基本数据结构与cpu算子的包,如果需要完成网络构建,需要使用lua语言nn库,如果使用gpu,需要使用cutorch包。

这份代码非常底层,就比较做到逻辑清晰,相比直接看pytorch的代码,显然看torch代码会理解的更快。

标签: #cadd函数怎么用