-
Notifications
You must be signed in to change notification settings - Fork 11
Tutorial
The basic data structure of mshadow is Tensor. The following is a simplified equivalent version of the declaration in mashadow/tensor.h
typedef real_t float;
typedef unsigned index_t;
template<int dimension>
struct Shape{
index_t shape_[ dimension ];
index_t stride_;
};
template<typename Device, int dimension>
struct Tensor{
real_t *dptr;
Shape<dimension> shape;
};
// this is how shape object declaration
Shape<2> shape2;
// this is how tensor object declaration
Tensor<cpu,2> ts2;
Tensor<gpu,3> ts3;
Tensor<cpu,2>
means a two dimensional tensor in CPU, while Tensor<gpu,3>
means three dimensional tensor in CPU. Shape<k>
gives the shape information of k-dimensional tensor. The declaration use template, and can be specialized into tensor of specific device and dimension. This is what two dimensional tensor will look like:
struct Shape<2>{
index_t shape_[ 2 ];
index_t stride_;
};
struct Tensor<cpu,2>{
real_t *dptr;
Shape<2> shape;
};
Tensor<cpu,2>
contains dptr
, which points to the space that backup the tensor.
Shape<2>
is a structure that stores shape information:
-
shape_[0]
gives the lowest dimension,shape_[1]
gives the second dimension, etc. This is different from numpy. -
stride_
gives the number of cell space allocated in the lowest dimension. This is introduced when we introduce some padding cells in lowest dimension to make sure memory is aligned.stride_
is automatically set during memory allocation of tensor in mshadow.
To understand the data structure, consider the following code:
real_t data[9] = {0,1,2,3,4,5,6,7,8};
Tensor<cpu,2> ts; ts.dptr = data;
ts.shape[0] = 2, ts.shape[1] = 3; ts.shape.stride_ = 3;
// now: ts[0][0] == 0, ts[0][1] == 1 , ts[1][0] == 3, ts[1][1] == 4
for( index_t i = 0; i < ts.shape[1]; i ++ ){
for( index_t j = 0; j < ts.shape[0], j ++ ){
printf("ts[%u][%u]=%f\n", i,j, ts[i][j] );
}
}
The result ts should be a 3 * 2 matrix, where data[2], data[5], data[8] are padding cells that are ignored. If you want a continuous memory, set stride_=shape_[0]
.
An important design choice about mshadow is that the data structure is a whitebox. We have introduced the Tensor data structure. Basically it works so long as we can set the space pointer dptr
, and corresponding shape
, the tensor will work. For Tensor<cpu,k>
, the space can be created by new real_t[]
, or pointer to some existing space such as float array in last example. For Tensor<gpu,k>
, the space
The following code is from example/basic.cpp, that illustrate basic usage of mshadow.
// header file to use mshadow
#include "mshadow/tensor.h"
// this namespace contains all data structures, functions
using namespace mshadow;
// this namespace contains all operator overloads
using namespace mshadow::expr;
int main( void ){
// assume we have a float space
float data[ 20 ];
// create a 2 x 5 x 2 tensor, from existing space
Tensor<cpu,3> ts( data, Shape3(2,5,2) );
// take first subscript of the tensor
Tensor<cpu,2> mat = ts[0];
// Tensor object is only a handle, assignment means they have same data content
Tensor<cpu,2> mat2 = mat;
// shape of matrix, note shape order is different from numpy
// shape[i] indicate the shape of i-th dimension
printf("%u X %u matrix\n", mat.shape[1], mat.shape[0] );
// initialize all element to zero
mat = 0.0f;
// assign some values
mat[0][1] = 1.0f; mat[1][0] = 2.0f;
// elementwise operations
mat += ( mat + 10.0f ) / 10.0f + 2.0f;
// print out matrix, note: mat2 and mat1 are handles(pointers)
for( index_t i = 0; i < mat.shape[1]; i ++ ){
for( index_t j = 0; j < mat.shape[0]; j ++ ){
printf("%.2f ", mat2[i][j]);
}
printf("\n");
}
return 0;
}