| @@ -0,0 +1,16 @@ | |||||
| using Newtonsoft.Json; | |||||
| using Tensorflow.NumPy; | |||||
| namespace Tensorflow.Keras.ArgsDefinition | |||||
| { | |||||
| public class CategoryEncodingArgs : AutoSerializeLayerArgs | |||||
| { | |||||
| [JsonProperty("num_tokens")] | |||||
| public int NumTokens { get; set; } | |||||
| [JsonProperty("output_mode")] | |||||
| public string OutputMode { get; set; } | |||||
| [JsonProperty("sparse")] | |||||
| public bool Sparse { get; set; } | |||||
| public NDArray CountWeights { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using Tensorflow.NumPy; | |||||
| using static Google.Protobuf.Reflection.FieldDescriptorProto.Types; | using static Google.Protobuf.Reflection.FieldDescriptorProto.Types; | ||||
| namespace Tensorflow.Keras.Layers | namespace Tensorflow.Keras.Layers | ||||
| @@ -28,6 +29,17 @@ namespace Tensorflow.Keras.Layers | |||||
| bool renorm = false, | bool renorm = false, | ||||
| float renorm_momentum = 0.99f); | float renorm_momentum = 0.99f); | ||||
| /// <summary> | |||||
| /// A preprocessing layer which encodes integer features. | |||||
| /// </summary> | |||||
| /// <param name="num_tokens">The total number of tokens the layer should support.</param> | |||||
| /// <param name="output_mode">Specification for the output of the layer.</param> | |||||
| /// <returns></returns> | |||||
| public ILayer CategoryEncoding(int num_tokens, | |||||
| string output_mode = "one_hot", | |||||
| bool sparse = false, | |||||
| NDArray count_weights = null); | |||||
| public ILayer Conv1D(int filters, | public ILayer Conv1D(int filters, | ||||
| Shape kernel_size, | Shape kernel_size, | ||||
| int strides = 1, | int strides = 1, | ||||
| @@ -839,10 +839,24 @@ namespace Tensorflow | |||||
| output_size = math_ops.maximum(minlength, output_size); | output_size = math_ops.maximum(minlength, output_size); | ||||
| if (maxlength != null) | if (maxlength != null) | ||||
| output_size = math_ops.minimum(maxlength, output_size); | output_size = math_ops.minimum(maxlength, output_size); | ||||
| var weights = constant_op.constant(new long[0], dtype: dtype); | |||||
| weights = weights ?? constant_op.constant(new int[0], dtype: dtype); | |||||
| return tf.Context.ExecuteOp("Bincount", name, new ExecuteOpArgs(arr, output_size, weights)); | return tf.Context.ExecuteOp("Bincount", name, new ExecuteOpArgs(arr, output_size, weights)); | ||||
| } | } | ||||
| else | |||||
| { | |||||
| var array_is_nonempty = math_ops.reduce_prod(array_ops.shape(arr)) > 0; | |||||
| var output_size = math_ops.cast(array_is_nonempty, arr.dtype) * (math_ops.reduce_max(arr) + 1); | |||||
| if (minlength != null) | |||||
| output_size = math_ops.maximum(minlength, output_size); | |||||
| if (maxlength != null) | |||||
| output_size = math_ops.minimum(maxlength, output_size); | |||||
| weights = weights ?? array_ops.constant(new int[0], dtype: dtype); | |||||
| return tf.Context.ExecuteOp("DenseBincount", name, | |||||
| new ExecuteOpArgs(arr, output_size, weights, binary_output) | |||||
| .SetAttributes(new { binary_output })); | |||||
| } | |||||
| throw new NotImplementedException(""); | throw new NotImplementedException(""); | ||||
| }); | }); | ||||
| @@ -153,6 +153,10 @@ namespace Tensorflow | |||||
| bool allow_broadcast) | bool allow_broadcast) | ||||
| { | { | ||||
| var t = convert_to_eager_tensor(value, tf.Context, dtype: dtype); | var t = convert_to_eager_tensor(value, tf.Context, dtype: dtype); | ||||
| if (dtype != TF_DataType.DtInvalid && dtype != t.dtype) | |||||
| { | |||||
| t = math_ops.cast(t, dtype); | |||||
| } | |||||
| if (shape is null || shape.IsNull) | if (shape is null || shape.IsNull) | ||||
| return t; | return t; | ||||
| @@ -4,6 +4,7 @@ using Tensorflow.Keras.ArgsDefinition.Core; | |||||
| using Tensorflow.Keras.ArgsDefinition.Rnn; | using Tensorflow.Keras.ArgsDefinition.Rnn; | ||||
| using Tensorflow.Keras.Engine; | using Tensorflow.Keras.Engine; | ||||
| using Tensorflow.Keras.Layers.Rnn; | using Tensorflow.Keras.Layers.Rnn; | ||||
| using Tensorflow.NumPy; | |||||
| using static Tensorflow.Binding; | using static Tensorflow.Binding; | ||||
| using static Tensorflow.KerasApi; | using static Tensorflow.KerasApi; | ||||
| @@ -829,5 +830,14 @@ namespace Tensorflow.Keras.Layers | |||||
| "orthogonal" => tf.orthogonal_initializer, | "orthogonal" => tf.orthogonal_initializer, | ||||
| _ => tf.glorot_uniform_initializer | _ => tf.glorot_uniform_initializer | ||||
| }; | }; | ||||
| public ILayer CategoryEncoding(int num_tokens, string output_mode = "one_hot", bool sparse = false, NDArray count_weights = null) | |||||
| => new CategoryEncoding(new CategoryEncodingArgs | |||||
| { | |||||
| NumTokens = num_tokens, | |||||
| OutputMode = output_mode, | |||||
| Sparse = sparse, | |||||
| CountWeights = count_weights | |||||
| }); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,75 @@ | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| using Tensorflow.Keras.Engine; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| /// <summary> | |||||
| /// This layer provides options for condensing data into a categorical encoding when the total number of tokens are known in advance. | |||||
| /// </summary> | |||||
| public class CategoryEncoding : Layer | |||||
| { | |||||
| CategoryEncodingArgs args; | |||||
| public CategoryEncoding(CategoryEncodingArgs args) : base(args) | |||||
| { | |||||
| this.args = args; | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | |||||
| var depth = args.NumTokens; | |||||
| var max_value = tf.reduce_max(inputs); | |||||
| var min_value = tf.reduce_min(inputs); | |||||
| /*var condition = tf.logical_and(tf.greater(tf.cast(constant_op.constant(depth), max_value.dtype), max_value), | |||||
| tf.greater_equal(min_value, tf.cast(constant_op.constant(0), min_value.dtype)));*/ | |||||
| var bincounts = encode_categorical_inputs(inputs, args.OutputMode, depth, args.DType, | |||||
| sparse: args.Sparse, | |||||
| count_weights: args.CountWeights); | |||||
| if(args.OutputMode != "tf_idf") | |||||
| { | |||||
| return bincounts; | |||||
| } | |||||
| return inputs; | |||||
| } | |||||
| public override Shape ComputeOutputShape(Shape input_shape) | |||||
| { | |||||
| return input_shape; | |||||
| } | |||||
| Tensors encode_categorical_inputs(Tensor inputs, string output_mode, int depth, | |||||
| TF_DataType dtype = TF_DataType.TF_FLOAT, | |||||
| bool sparse = false, | |||||
| Tensor count_weights = null) | |||||
| { | |||||
| bool binary_output = false; | |||||
| if (output_mode == "one_hot") | |||||
| { | |||||
| binary_output = true; | |||||
| if (inputs.shape[-1] != 1) | |||||
| { | |||||
| inputs = tf.expand_dims(inputs, -1); | |||||
| } | |||||
| } | |||||
| else if (output_mode == "multi_hot") | |||||
| { | |||||
| binary_output = true; | |||||
| } | |||||
| var depth_tensor = constant_op.constant(depth); | |||||
| var result = tf.math.bincount(inputs, | |||||
| weights: count_weights, | |||||
| minlength: depth_tensor, | |||||
| maxlength: depth_tensor, | |||||
| dtype: dtype, | |||||
| axis: -1, | |||||
| binary_output: binary_output); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -177,5 +177,60 @@ namespace TensorFlowNET.Keras.UnitTest | |||||
| Assert.AreEqual((5, 2), output.shape); | Assert.AreEqual((5, 2), output.shape); | ||||
| Assert.IsTrue(output[0].numpy().Equals(new[] { -0.99998f, 0.99998f })); | Assert.IsTrue(output[0].numpy().Equals(new[] { -0.99998f, 0.99998f })); | ||||
| } | } | ||||
| /// <summary> | |||||
| /// https://www.tensorflow.org/api_docs/python/tf/keras/layers/CategoryEncoding | |||||
| /// </summary> | |||||
| [TestMethod] | |||||
| public void CategoryEncoding() | |||||
| { | |||||
| // one-hot | |||||
| var inputs = np.array(new[] { 3, 2, 0, 1 }); | |||||
| var layer = tf.keras.layers.CategoryEncoding(4); | |||||
| Tensor output = layer.Apply(inputs); | |||||
| Assert.AreEqual((4, 4), output.shape); | |||||
| Assert.IsTrue(output[0].numpy().Equals(new[] { 0, 0, 0, 1f })); | |||||
| Assert.IsTrue(output[1].numpy().Equals(new[] { 0, 0, 1, 0f })); | |||||
| Assert.IsTrue(output[2].numpy().Equals(new[] { 1, 0, 0, 0f })); | |||||
| Assert.IsTrue(output[3].numpy().Equals(new[] { 0, 1, 0, 0f })); | |||||
| // multi-hot | |||||
| inputs = np.array(new[,] | |||||
| { | |||||
| { 0, 1 }, | |||||
| { 0, 0 }, | |||||
| { 1, 2 }, | |||||
| { 3, 1 } | |||||
| }); | |||||
| layer = tf.keras.layers.CategoryEncoding(4, output_mode: "multi_hot"); | |||||
| output = layer.Apply(inputs); | |||||
| Assert.IsTrue(output[0].numpy().Equals(new[] { 1, 1, 0, 0f })); | |||||
| Assert.IsTrue(output[1].numpy().Equals(new[] { 1, 0, 0, 0f })); | |||||
| Assert.IsTrue(output[2].numpy().Equals(new[] { 0, 1, 1, 0f })); | |||||
| Assert.IsTrue(output[3].numpy().Equals(new[] { 0, 1, 0, 1f })); | |||||
| // using weighted inputs in "count" mode | |||||
| inputs = np.array(new[,] | |||||
| { | |||||
| { 0, 1 }, | |||||
| { 0, 0 }, | |||||
| { 1, 2 }, | |||||
| { 3, 1 } | |||||
| }); | |||||
| var weights = np.array(new[,] | |||||
| { | |||||
| { 0.1f, 0.2f }, | |||||
| { 0.1f, 0.1f }, | |||||
| { 0.2f, 0.3f }, | |||||
| { 0.4f, 0.2f } | |||||
| }); | |||||
| layer = tf.keras.layers.CategoryEncoding(4, output_mode: "count", count_weights: weights); | |||||
| output = layer.Apply(inputs); | |||||
| Assert.IsTrue(output[0].numpy().Equals(new[] { 0.1f, 0.2f, 0f, 0f })); | |||||
| Assert.IsTrue(output[1].numpy().Equals(new[] { 0.2f, 0f, 0f, 0f })); | |||||
| Assert.IsTrue(output[2].numpy().Equals(new[] { 0f, 0.2f, 0.3f, 0f })); | |||||
| Assert.IsTrue(output[3].numpy().Equals(new[] { 0f, 0.2f, 0f, 0.4f })); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,11 +4,12 @@ using System.Collections.Generic; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Text; | using System.Text; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Tensorflow; | |||||
| using TensorFlowNET.Keras.UnitTest; | using TensorFlowNET.Keras.UnitTest; | ||||
| using static Tensorflow.Binding; | using static Tensorflow.Binding; | ||||
| using static Tensorflow.KerasApi; | using static Tensorflow.KerasApi; | ||||
| namespace Tensorflow.Keras.UnitTest.Losses; | |||||
| namespace TensorFlowNET.Keras.UnitTest; | |||||
| [TestClass] | [TestClass] | ||||
| public class LossesTest : EagerModeTestBase | public class LossesTest : EagerModeTestBase | ||||