diff --git a/src/TensorFlowNET.Core/APIs/tf.random.cs b/src/TensorFlowNET.Core/APIs/tf.random.cs index d5656e87..c3d05c13 100644 --- a/src/TensorFlowNET.Core/APIs/tf.random.cs +++ b/src/TensorFlowNET.Core/APIs/tf.random.cs @@ -69,7 +69,17 @@ namespace Tensorflow float maxval = 1, TF_DataType dtype = TF_DataType.TF_FLOAT, int? seed = null, - string name = null) => random_ops.random_uniform(shape, minval, maxval, dtype, seed, name); + string name = null) + { + if (dtype.is_integer()) + { + return random_ops.random_uniform_int(shape, (int)minval, (int)maxval, dtype, seed, name); + } + else + { + return random_ops.random_uniform(shape, minval, maxval, dtype, seed, name); + } + } public Tensor truncated_normal(TensorShape shape, float mean = 0.0f, diff --git a/src/TensorFlowNET.Core/Operations/array_ops.cs b/src/TensorFlowNET.Core/Operations/array_ops.cs index 2eb32775..d683c0be 100644 --- a/src/TensorFlowNET.Core/Operations/array_ops.cs +++ b/src/TensorFlowNET.Core/Operations/array_ops.cs @@ -843,7 +843,22 @@ namespace Tensorflow return gen_array_ops.concat_v2(values, axis, name: name); } - public static Tensor gather(T1 @params, T2 indices, string name = null, int axis = 0) + /// + /// Gather slices from `params` according to `indices`. `indices` must be an integer tensor of any dimension(often 1-D). + /// + /// Element type of the indexed tensor. + /// Element type of the index tensor. + /// The `Tensor` from which to gather values. Must be at least rank `axis + 1`. + /// The index `Tensor`. Must be one of the following types: `int32`, `int64`. The values must be in range `[0, params.shape[axis])`. + /// A name for the operation (optional). + /// + /// A `Tensor`. Must be one of the following types: `int32`, `int64`. + /// The `axis` in `params` to gather `indices` from.Must be greater than or equal to `batch_dims`. + /// Defaults to the first non-batch dimension. Supports negative indexes. + /// + /// An integer. The number of batch dimensions. Must be less than or equal to rank(indices). + /// + public static Tensor gather(T1 @params, T2 indices, string name = null, int axis = 0, int batch_dims = 0) { if (axis != 0) return gen_array_ops.gather_v2(@params, indices, axis, name: name); @@ -913,7 +928,7 @@ namespace Tensorflow } public static Tensor slice(Tensor input, Tensor[] begin, Tensor[] size, string name = null) - => gen_array_ops.slice(input, begin, size, name: name); + => gen_array_ops.slice(input, begin, size, name: name); public static Tensor slice(Tensor input, Tb begin, Ts size, string name = null) => gen_array_ops.slice(input, begin, size, name: name); @@ -928,6 +943,7 @@ namespace Tensorflow } }); + public static Tensor stack(object values, int axis = 0, string name = "stack") { if (axis == 0) diff --git a/src/TensorFlowNET.Core/Operations/gen_array_ops.cs b/src/TensorFlowNET.Core/Operations/gen_array_ops.cs index 5dbbb3f9..55b75c3c 100644 --- a/src/TensorFlowNET.Core/Operations/gen_array_ops.cs +++ b/src/TensorFlowNET.Core/Operations/gen_array_ops.cs @@ -117,28 +117,13 @@ namespace Tensorflow => tf.Context.ExecuteOp("ExpandDims", name, new ExecuteOpArgs(input, axis) .SetAttributes(new { dim = axis })); - public static Tensor gather_v2(T1 @params, T2 indices, int axis, string name = null) + public static Tensor gather_v2(T1 @params, T2 indices, int axis, int batch_dims = 0, string name = null) { - if (tf.Context.executing_eagerly()) - { - try - { - var results = tf.Runner.TFE_FastPathExecute(new FastPathOpExecInfo("GatherV2", name, @params, indices, axis, "batch_dims", 0) - { - ctx = tf.Context, - device_name = tf.Context.DeviceName - }); - return results[0]; - } - catch (Exception exc) - { - return gather_v2_eager_fallback(@params, indices, axis, name, tf.Context); - } - } - - var _op = tf.OpDefLib._apply_op_helper("GatherV2", name: name, new { @params, indices, axis }); - - return _op.outputs[0]; + var result = tf.Context.ExecuteOp("GatherV2", name, new ExecuteOpArgs( + @params, + indices, + axis).SetAttributes(new { batch_dims })); + return result [0]; } private static Tensor gather_v2_eager_fallback(object @params, object indices, int axis, string name, Context ctx) @@ -380,6 +365,12 @@ namespace Tensorflow public static Tensor slice(Tensor input, Tb begin, Ts size, string name = null) { + if (tf.executing_eagerly()) + { + var outputs = tf.Runner.TFE_FastPathExecute(new FastPathOpExecInfo("Slice", name, input, begin, size)); + return outputs[0]; + } + var _op = tf.OpDefLib._apply_op_helper("Slice", name, new { input, begin, size }); return _op.outputs[0]; } diff --git a/src/TensorFlowNET.Core/Operations/gen_random_ops.cs b/src/TensorFlowNET.Core/Operations/gen_random_ops.cs index 7a554379..0edea3aa 100644 --- a/src/TensorFlowNET.Core/Operations/gen_random_ops.cs +++ b/src/TensorFlowNET.Core/Operations/gen_random_ops.cs @@ -43,18 +43,8 @@ namespace Tensorflow /// /// public static Tensor random_uniform_int(Tensor shape, Tensor minval, Tensor maxval, int? seed = 0, int? seed2 = 0, string name = null) - { - if (!seed.HasValue) - seed = 0; - if (!seed2.HasValue) - seed2 = 0; - - var _op = tf.OpDefLib._apply_op_helper("RandomUniformInt", - name: name, - args: new { shape, minval, maxval, seed, seed2 }); - - return _op.outputs[0]; - } + => tf.Context.ExecuteOp("RandomUniformInt", name, new ExecuteOpArgs(shape, minval, maxval) + .SetAttributes(new { seed = seed ?? 0, seed2 = seed2 ?? 0 })); /// /// Outputs random values from a uniform distribution. diff --git a/src/TensorFlowNET.Core/Operations/random_ops.cs b/src/TensorFlowNET.Core/Operations/random_ops.cs index 1523d23f..d973ea7f 100644 --- a/src/TensorFlowNET.Core/Operations/random_ops.cs +++ b/src/TensorFlowNET.Core/Operations/random_ops.cs @@ -81,6 +81,34 @@ namespace Tensorflow }); } + /// + /// Outputs random values from a uniform distribution. + /// + /// + /// + /// + /// The type of the output + /// Used to create a random seed for the distribution. + /// A name for the operation + /// A tensor of the specified shape filled with random uniform values. + public static Tensor random_uniform_int(int[] shape, + int minval = 0, + int maxval = 1, + TF_DataType dtype = TF_DataType.TF_FLOAT, + int? seed = null, + string name = null) + { + return tf_with(ops.name_scope(name, "random_uniform_int", new { shape, minval, maxval }), scope => + { + name = scope; + var (seed1, seed2) = random_seed.get_seed(seed); + var tensorShape = tensor_util.shape_tensor(shape); + var minTensor = ops.convert_to_tensor(minval, dtype: dtype, name: "min"); + var maxTensor = ops.convert_to_tensor(maxval, dtype: dtype, name: "max"); + return gen_random_ops.random_uniform_int(tensorShape, minTensor, maxTensor, seed: seed1, seed2: seed2); + }); + } + public static Tensor random_uniform(Tensor shape, int minval = 0, Tensor maxval = null, diff --git a/test/TensorFlowNET.UnitTest/ManagedAPI/ArrayOpsTest.cs b/test/TensorFlowNET.UnitTest/ManagedAPI/ArrayOpsTest.cs index 2b9d6987..f9e8ba82 100644 --- a/test/TensorFlowNET.UnitTest/ManagedAPI/ArrayOpsTest.cs +++ b/test/TensorFlowNET.UnitTest/ManagedAPI/ArrayOpsTest.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NumSharp; +using NumSharp.Utilities; using Tensorflow; using static Tensorflow.Binding; @@ -7,9 +8,48 @@ namespace TensorFlowNET.UnitTest.ManagedAPI { [TestClass] public class ArrayOpsTest : EagerModeTestBase - { + { /// - /// https://www.tensorflow.org/api_docs/python/tf/keras/layers/Embedding + /// https://www.tensorflow.org/api_docs/python/tf/slice + /// + [TestMethod] + public void Slice() + { + // Tests based on example code in TF documentation + var input_array = tf.constant(np.array(new int[] { 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6 }).reshape(3,2,3)); + var indices = tf.constant(np.array(new int[] { 0, 2 })); + + var r1 = array_ops.slice(input_array, new int[] { 1, 0, 0 }, new int[] { 1, 1, 3 }); + Assert.AreEqual(new TensorShape(1,1,3), r1.shape); + var r1np = r1.numpy(); + Assert.AreEqual(r1np[0, 0, 0], 3); + Assert.AreEqual(r1np[0, 0, 1], 3); + Assert.AreEqual(r1np[0, 0, 2], 3); + + + var r2 = array_ops.slice(input_array, new int[] { 1, 0, 0 }, new int[] { 1, 2, 3 }); + Assert.AreEqual(new TensorShape(1, 2, 3), r2.shape); + var r2np = r2.numpy(); + Assert.AreEqual(r2np[0, 0, 0], 3); + Assert.AreEqual(r2np[0, 0, 1], 3); + Assert.AreEqual(r2np[0, 0, 2], 3); + Assert.AreEqual(r2np[0, 1, 0], 4); + Assert.AreEqual(r2np[0, 1, 1], 4); + Assert.AreEqual(r2np[0, 1, 2], 4); + + var r3 = array_ops.slice(input_array, new int[] { 1, 0, 0 }, new int[] { 2, 1, 3 }); + Assert.AreEqual(new TensorShape(2, 1, 3), r3.shape); + var r3np = r3.numpy(); + Assert.AreEqual(r3np[0, 0, 0], 3); + Assert.AreEqual(r3np[0, 0, 1], 3); + Assert.AreEqual(r3np[0, 0, 2], 3); + Assert.AreEqual(r3np[1, 0, 0], 5); + Assert.AreEqual(r3np[1, 0, 1], 5); + Assert.AreEqual(r3np[1, 0, 2], 5); + } + + /// + /// https://www.tensorflow.org/api_docs/python/tf/gather /// [TestMethod] public void Gather() @@ -19,9 +59,24 @@ namespace TensorFlowNET.UnitTest.ManagedAPI var result = array_ops.gather(input_array, indices); Assert.AreEqual(new TensorShape(2, 4), result.shape); - Assert.AreEqual(result.numpy()[0,0], 0.0f); - Assert.AreEqual(result.numpy()[0,1], 1.0f); - Assert.AreEqual(result.numpy()[1,3], 11.0f); + Assert.AreEqual(result.numpy()[0, 0], 0.0f); + Assert.AreEqual(result.numpy()[0, 1], 1.0f); + Assert.AreEqual(result.numpy()[1, 3], 11.0f); + + // Tests based on example code in Python doc string for tf.gather() + + var p1 = tf.random.normal(new TensorShape(5, 6, 7, 8)); + var i1 = tf.random_uniform(new TensorShape(10, 11), maxval: 7, dtype: tf.int32); + var r1 = tf.gather(p1, i1, axis:2); + Assert.AreEqual(new TensorShape(5, 6, 10, 11, 8), r1.shape); + + var p2 = tf.random.normal(new TensorShape(4,3)); + var i2 = tf.constant(new int[,] { { 0, 2} }); + var r2 = tf.gather(p2, i2, axis: 0); + Assert.AreEqual(new TensorShape(1, 2, 3), r2.shape); + + var r3 = tf.gather(p2, i2, axis: 1); + Assert.AreEqual(new TensorShape(4,1,2), r3.shape); } } }