From 93bbbc03cd157584296f0de75b06bd1e7b5ed2b1 Mon Sep 17 00:00:00 2001 From: Anshuman Chhabra Date: Sat, 4 Aug 2018 15:55:51 +0530 Subject: [PATCH] Added tensor_to_matrix/1 (with tests/docs) --- c_src/Tensorflex.c | 50 +++++++++++++++++ lib/nifs.ex | 4 ++ lib/tensorflex.ex | 118 +++++++++++++++++++++++++++++++++++++++ test/tensorflex_test.exs | 70 +++++++++++++++++++++++ 4 files changed, 242 insertions(+) diff --git a/c_src/Tensorflex.c b/c_src/Tensorflex.c index c489997..28ca1d6 100644 --- a/c_src/Tensorflex.c +++ b/c_src/Tensorflex.c @@ -1023,6 +1023,55 @@ static ERL_NIF_TERM subtract_matrices(ErlNifEnv* env, int argc, const ERL_NIF_TE return ret; } +static ERL_NIF_TERM tensor_to_matrix(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + unsigned i,j; + ERL_NIF_TERM ret; + TF_Tensor **tensor; + mx_t mx; + mx.p = NULL; + enif_get_resource(env, argv[0], tensor_resource, (void *) &tensor); + TF_DataType type = TF_TensorType(*tensor); + if(TF_NumDims(*tensor) == 2) { + mx.p = alloc_matrix(env, (unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-2))), (unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-1)))); + + if(type == TF_FLOAT){ + float* float_tensor_data = (float*)TF_TensorData(*tensor); + for(j=0; j<(unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-2))); j++) { + for(i=0; i<(unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-1))); i++) + { + POS(mx.p,j,i) = (double)*float_tensor_data++; + + } + } + } + + else if(type == TF_INT32){ + int32_t* int32_tensor_data = (int32_t*)TF_TensorData(*tensor); + for(j=0; j<(unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-2))); j++) { + for(i=0; i<(unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-1))); i++) + { + POS(mx.p,j,i) = (double)*int32_tensor_data++; + } + } + } + + else if(type == TF_DOUBLE){ + double* double_tensor_data = (double*)TF_TensorData(*tensor); + for(j=0; j<(unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-2))); j++) { + for(i=0; i<(unsigned)(TF_Dim(*tensor,(TF_NumDims(*tensor)-1))); i++) + { + POS(mx.p,j,i) = *double_tensor_data++; + } + } + } + } else return enif_make_badarg(env); + + ret = enif_make_resource(env, mx.p); + enif_release_resource(mx.p); + return ret; +} + static ErlNifFunc nif_funcs[] = { {"create_matrix", 3, create_matrix}, @@ -1053,6 +1102,7 @@ static ErlNifFunc nif_funcs[] = { "divide_matrix_by_scalar", 2, divide_matrix_by_scalar }, { "add_matrices", 2, add_matrices }, { "subtract_matrices", 2, subtract_matrices }, + { "tensor_to_matrix", 1, tensor_to_matrix }, }; ERL_NIF_INIT(Elixir.Tensorflex.NIFs, nif_funcs, res_loader, NULL, NULL, NULL) diff --git a/lib/nifs.ex b/lib/nifs.ex index 5a13c67..558f33e 100644 --- a/lib/nifs.ex +++ b/lib/nifs.ex @@ -119,4 +119,8 @@ defmodule Tensorflex.NIFs do raise "NIF subtract_matrices/2 not implemented" end + def tensor_to_matrix(_tensor) do + raise "NIF tensor_to_matrix/1 not implemented" + end + end diff --git a/lib/tensorflex.ex b/lib/tensorflex.ex index 4aa8775..c288f13 100644 --- a/lib/tensorflex.ex +++ b/lib/tensorflex.ex @@ -1407,5 +1407,123 @@ defmodule Tensorflex do new_ref = NIFs.subtract_matrices(ref1, ref2) %Matrix{nrows: nrows1, ncols: ncols2, data: new_ref} end + + @doc """ + Converts the data stored in a 2-D tensor back to a 2-D matrix. + + Takes in a single argument as a `%Tensor` tensor (any `TF_Datatype`). + + Returns a `%Matrix` 2-D matrix. + + __NOTE__: Tensorflex doesn't currently support 3-D matrices, and therefore + tensors that are 3-D (such as created using the `load_image_as_tensor/1` + function) cannot be converted back to a matrix, yet. Support for 3-D matrices + will be added soon. + + ## Examples + `tensor_to_matrix/1` converts any 2-D `%Tensor` tensor back to matrix form. + Consider `sample1.csv` back from the examples of `load_csv_as_matrix/2`: + + ```elixir + iex(1)> vals = Tensorflex.load_csv_as_matrix("sample1.csv", header: :false) + %Tensorflex.Matrix{ + data: #Reference<0.124471106.2360737795.170799>, + ncols: 5, + nrows: 3 + } + + iex(2)> dims = Tensorflex.create_matrix(1,2,[[3,5]]) + %Tensorflex.Matrix{ + data: #Reference<0.124471106.2360737795.170827>, + ncols: 2, + nrows: 1 + } + + iex(3)> {:ok, float64_tensor} = Tensorflex.float64_tensor vals,dims + {:ok, + %Tensorflex.Tensor{ + datatype: :tf_double, + tensor: #Reference<0.124471106.2360737794.171586> + }} + + iex(4)> m_float64 = Tensorflex.tensor_to_matrix float64_tensor + %Tensorflex.Matrix{ + data: #Reference<0.124471106.2360737794.171596>, + ncols: 5, + nrows: 3 + } + + iex(5)> Tensorflex.matrix_to_lists m_float64 + [ + [1.0, 2.0, 3.0, 4.0, 5.0], + [6.0, 7.0, 8.0, 9.0, 10.0], + [11.0, 12.0, 13.0, 14.0, 15.0] + ] + + iex(6)> {:ok, float32_tensor} = Tensorflex.float32_tensor vals,dims + {:ok, + %Tensorflex.Tensor{ + datatype: :tf_float, + tensor: #Reference<0.124471106.2360737794.172555> + }} + + iex(7)> m_float32 = Tensorflex.tensor_to_matrix float32_tensor + %Tensorflex.Matrix{ + data: #Reference<0.124471106.2360737794.172563>, + ncols: 5, + nrows: 3 + } + + iex(8)> Tensorflex.matrix_to_lists m_float32 + [ + [1.0, 2.0, 3.0, 4.0, 5.0], + [6.0, 7.0, 8.0, 9.0, 10.0], + [11.0, 12.0, 13.0, 14.0, 15.0] + ] + + iex(9)> {:ok, int32_tensor} = Tensorflex.int32_tensor vals,dims + {:ok, + %Tensorflex.Tensor{ + datatype: :tf_int32, + tensor: #Reference<0.124471106.2360737794.172578> + }} + + iex(10)> m_int32 = Tensorflex.tensor_to_matrix int32_tensor + %Tensorflex.Matrix{ + data: #Reference<0.124471106.2360737794.172586>, + ncols: 5, + nrows: 3 + } + + iex(11)> Tensorflex.matrix_to_lists m_int32 + [ + [1.0, 2.0, 3.0, 4.0, 5.0], + [6.0, 7.0, 8.0, 9.0, 10.0], + [11.0, 12.0, 13.0, 14.0, 15.0] + ] + + ``` + + The matrix values obtained in the conversions, `m_int32`, `m_float32`, + `m_float64` are identical to the `vals` matrix we had generated from the + `sample1.csv` file: + + ```elixir + + iex(12)> Tensorflex.matrix_to_lists vals + [ + [1.0, 2.0, 3.0, 4.0, 5.0], + [6.0, 7.0, 8.0, 9.0, 10.0], + [11.0, 12.0, 13.0, 14.0, 15.0] + ] + + ``` + """ + + def tensor_to_matrix(%Tensor{datatype: _datatype, tensor: ref}) do + matrix_ref = NIFs.tensor_to_matrix(ref) + {nrows, ncols} = NIFs.size_of_matrix matrix_ref + %Matrix{nrows: nrows, ncols: ncols, data: matrix_ref} + end end diff --git a/test/tensorflex_test.exs b/test/tensorflex_test.exs index e0c6206..a9ed6c2 100644 --- a/test/tensorflex_test.exs +++ b/test/tensorflex_test.exs @@ -27,6 +27,45 @@ defmodule TensorflexTest do assert {5,4} = Tensorflex.size_of_matrix mat assert 7.0 = Tensorflex.matrix_pos(mat,5,3) end + + test "add scalar to matrix check" do + m = Tensorflex.create_matrix(2,3,[[1,2,3],[4,5,6]]) + m = Tensorflex.add_scalar_to_matrix(m, 5) + assert [[6.0, 7.0, 8.0], [9.0, 10.0, 11.0]] = Tensorflex.matrix_to_lists m + end + + test "subtract scalar from matrix check" do + m = Tensorflex.create_matrix(2,3,[[1,2,3],[4,5,6]]) + m = Tensorflex.subtract_scalar_from_matrix m,3 + assert [[-2.0, -1.0, 0.0], [1.0, 2.0, 3.0]] = Tensorflex.matrix_to_lists m + end + + test "multiply matrix with scalar check" do + m = Tensorflex.create_matrix(2,3,[[1,2,3],[4,5,6]]) + m = Tensorflex.multiply_matrix_with_scalar m,5 + assert [[5.0, 10.0, 15.0], [20.0, 25.0, 30.0]] = Tensorflex.matrix_to_lists m + end + + test "divide matrix with scalar check" do + m = Tensorflex.create_matrix(2,3,[[1,2,3],[4,5,6]]) + m = Tensorflex.divide_matrix_by_scalar m,3 + assert [[0.3333333333333333, 0.6666666666666666, 1.0], [1.3333333333333333, 1.6666666666666667, 2.0]] = Tensorflex.matrix_to_lists m + end + + test "add two matrices check" do + m1 = Tensorflex.create_matrix(2,3,[[1,2,3],[4,5,6]]) + m2 = Tensorflex.create_matrix(2,3,[[4,5,6],[1,2,3]]) + m_added = Tensorflex.add_matrices m1,m2 + assert [[5.0, 7.0, 9.0], [5.0, 7.0, 9.0]] = Tensorflex.matrix_to_lists m_added + end + + test "subtract two matrices check" do + m1 = Tensorflex.create_matrix(2,3,[[1,2,3],[4,5,6]]) + m2 = Tensorflex.create_matrix(2,3,[[4,5,6],[1,2,3]]) + m_subtracted = Tensorflex.subtract_matrices m1,m2 + assert [[-3.0, -3.0, -3.0], [3.0, 3.0, 3.0]] = Tensorflex.matrix_to_lists m_subtracted + end + end describe "float32 tensor functionalities" do @@ -199,6 +238,37 @@ defmodule TensorflexTest do {:ok, _tensor} = Tensorflex.load_image_as_tensor("./test/sample1.csv") end end + + test "float64_tensor to matrix conversion check" do + vals = Tensorflex.load_csv_as_matrix("./test/sample1.csv", header: :false) + dims = Tensorflex.create_matrix(1,2,[[3,5]]) + {:ok, float64_tensor} = Tensorflex.float64_tensor vals,dims + m_float64 = Tensorflex.tensor_to_matrix float64_tensor + assert [[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0], [11.0, 12.0, 13.0, 14.0, 15.0]] = Tensorflex.matrix_to_lists m_float64 + end + + test "int32_tensor to matrix conversion check" do + vals = Tensorflex.load_csv_as_matrix("./test/sample1.csv", header: :false) + dims = Tensorflex.create_matrix(1,2,[[3,5]]) + {:ok, int32_tensor} = Tensorflex.int32_tensor vals,dims + m_int32 = Tensorflex.tensor_to_matrix int32_tensor + assert [[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0], [11.0, 12.0, 13.0, 14.0, 15.0]] = Tensorflex.matrix_to_lists m_int32 + end + + test "float32_tensor to matrix conversion check" do + vals = Tensorflex.load_csv_as_matrix("./test/sample1.csv", header: :false) + dims = Tensorflex.create_matrix(1,2,[[3,5]]) + {:ok, float32_tensor} = Tensorflex.float32_tensor vals,dims + m_float32 = Tensorflex.tensor_to_matrix float32_tensor + assert [[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0], [11.0, 12.0, 13.0, 14.0, 15.0]] = Tensorflex.matrix_to_lists m_float32 + end + + test "incorrect usage tensor-to-matrix check" do + {:ok, tensor} = Tensorflex.load_image_as_tensor("./test/cropped_panda.jpg") + assert_raise ArgumentError, fn -> + _m = Tensorflex.tensor_to_matrix tensor + end + end end end