diff --git a/src/TensorFlowNET.Core/APIs/tf.math.cs b/src/TensorFlowNET.Core/APIs/tf.math.cs index dabdf126..61b7caf4 100644 --- a/src/TensorFlowNET.Core/APIs/tf.math.cs +++ b/src/TensorFlowNET.Core/APIs/tf.math.cs @@ -24,6 +24,8 @@ namespace Tensorflow public Tensor argmax(Tensor input, Axis axis = null, string name = null, int? dimension = null, TF_DataType output_type = TF_DataType.TF_INT64) => gen_math_ops.arg_max(input, axis, name: name, output_type: output_type); + public Tensor count_nonzero(Tensor input, Axis? axis = null, bool? keepdims = null, TF_DataType dtype = TF_DataType.TF_INT64, string name = null) + => math_ops.count_nonzero_v2(input, axis: axis, keepdims: keepdims ?? false, dtype: dtype); public Tensor log(Tensor x, string name = null) => gen_math_ops.log(x, name); diff --git a/src/TensorFlowNET.Core/Keras/Metrics/IMetricsApi.cs b/src/TensorFlowNET.Core/Keras/Metrics/IMetricsApi.cs index 64c2c14f..5d08cc78 100644 --- a/src/TensorFlowNET.Core/Keras/Metrics/IMetricsApi.cs +++ b/src/TensorFlowNET.Core/Keras/Metrics/IMetricsApi.cs @@ -91,7 +91,20 @@ public interface IMetricsApi float? threshold = null, string name = "fbeta_score", TF_DataType dtype = TF_DataType.TF_FLOAT); - + + /// + /// Computes hamming loss. + /// + /// multiclass or multilabel + /// + /// + /// + /// + IMetricFunc HammingLoss(string mode, + float? threshold = null, + string name = "hamming_loss", + TF_DataType dtype = TF_DataType.TF_FLOAT); + /// /// Computes how often targets are in the top K predictions. /// diff --git a/src/TensorFlowNET.Core/Operations/math_ops.cs b/src/TensorFlowNET.Core/Operations/math_ops.cs index 9542f643..36f7db79 100644 --- a/src/TensorFlowNET.Core/Operations/math_ops.cs +++ b/src/TensorFlowNET.Core/Operations/math_ops.cs @@ -821,6 +821,18 @@ namespace Tensorflow .SetAttributes(new { adj_x, adj_y })); }); + public static Tensor count_nonzero_v2(Tensor input, + Axis? axis, + bool keepdims = false, + string name = null, + TF_DataType dtype = TF_DataType.TF_INT64) + => tf_with(ops.name_scope(name, "count_nonzero", input), scope => + { + name = scope; + var zero = array_ops.zeros(Shape.Scalar, dtype: input.dtype); + return reduce_sum(cast(gen_math_ops.not_equal(input, zero), dtype), axis: axis, keepdims: keepdims); + }); + public static Tensor bincount(Tensor arr, Tensor weights = null, Tensor minlength = null, Tensor maxlength = null, diff --git a/src/TensorFlowNET.Core/Tensorflow.Binding.csproj b/src/TensorFlowNET.Core/Tensorflow.Binding.csproj index ecb63a7b..8925228d 100644 --- a/src/TensorFlowNET.Core/Tensorflow.Binding.csproj +++ b/src/TensorFlowNET.Core/Tensorflow.Binding.csproj @@ -109,7 +109,7 @@ https://tensorflownet.readthedocs.io - + diff --git a/src/TensorFlowNET.Keras/Metrics/HammingLoss.cs b/src/TensorFlowNET.Keras/Metrics/HammingLoss.cs new file mode 100644 index 00000000..2b65424e --- /dev/null +++ b/src/TensorFlowNET.Keras/Metrics/HammingLoss.cs @@ -0,0 +1,15 @@ +namespace Tensorflow.Keras.Metrics; + +public class HammingLoss : MeanMetricWrapper +{ + public HammingLoss(string mode, + NDArray threshold = null, + string name = "hamming_loss", + TF_DataType dtype = TF_DataType.TF_FLOAT) + : base((yt, yp) => metrics_utils.hamming_loss_fn(yt, yp, threshold, mode), + name: name, + dtype: dtype) + { + _dtype = dtype; + } +} diff --git a/src/TensorFlowNET.Keras/Metrics/MetricsApi.cs b/src/TensorFlowNET.Keras/Metrics/MetricsApi.cs index bd12f82a..585fefae 100644 --- a/src/TensorFlowNET.Keras/Metrics/MetricsApi.cs +++ b/src/TensorFlowNET.Keras/Metrics/MetricsApi.cs @@ -92,6 +92,9 @@ public IMetricFunc FBetaScore(int num_classes, string? average = null, float beta = 0.1F, float? threshold = null, string name = "fbeta_score", TF_DataType dtype = TF_DataType.TF_FLOAT) => new FBetaScore(num_classes, average: average,beta: beta, threshold: threshold, name: name, dtype: dtype); + public IMetricFunc HammingLoss(string mode, float? threshold = null, string name = "hamming_loss", TF_DataType dtype = TF_DataType.TF_FLOAT) + => new HammingLoss(mode, threshold: threshold, name: name, dtype: dtype); + public IMetricFunc TopKCategoricalAccuracy(int k = 5, string name = "top_k_categorical_accuracy", TF_DataType dtype = TF_DataType.TF_FLOAT) => new TopKCategoricalAccuracy(k: k, name: name, dtype: dtype); diff --git a/src/TensorFlowNET.Keras/Metrics/metrics_utils.cs b/src/TensorFlowNET.Keras/Metrics/metrics_utils.cs index f4bfc3da..69cc789e 100644 --- a/src/TensorFlowNET.Keras/Metrics/metrics_utils.cs +++ b/src/TensorFlowNET.Keras/Metrics/metrics_utils.cs @@ -24,6 +24,36 @@ public class metrics_utils return tf.reduce_sum(y_true * y_pred, axis: axis ?? -1); } + public static Tensor hamming_loss_fn(Tensor y_true, Tensor y_pred, Tensor threshold, string mode) + { + if (threshold == null) + { + threshold = tf.reduce_max(y_pred, axis: -1, keepdims: true); + // make sure [0, 0, 0] doesn't become [1, 1, 1] + // Use abs(x) > eps, instead of x != 0 to check for zero + y_pred = tf.logical_and(y_pred >= threshold, tf.abs(y_pred) > 1e-12); + } + else + { + y_pred = y_pred > threshold; + } + + + y_true = tf.cast(y_true, tf.int32); + y_pred = tf.cast(y_pred, tf.int32); + + if (mode == "multiclass") + { + var nonzero = tf.cast(tf.math.count_nonzero(y_true * y_pred, axis: -1), tf.float32); + return 1.0 - nonzero; + } + else + { + var nonzero = tf.cast(tf.math.count_nonzero(y_true - y_pred, axis: -1), tf.float32); + return nonzero / y_true.shape[-1]; + } + } + /// /// Creates float Tensor, 1.0 for label-prediction match, 0.0 for mismatch. /// diff --git a/test/TensorFlowNET.Keras.UnitTest/Metrics/MetricsTest.cs b/test/TensorFlowNET.Keras.UnitTest/Metrics/MetricsTest.cs index 2b38449b..267cef81 100644 --- a/test/TensorFlowNET.Keras.UnitTest/Metrics/MetricsTest.cs +++ b/test/TensorFlowNET.Keras.UnitTest/Metrics/MetricsTest.cs @@ -142,6 +142,51 @@ public class MetricsTest : EagerModeTestBase Assert.AreEqual(r, new[] { 0.3846154f, 0.90909094f, 0.8333334f }); } + /// + /// https://www.tensorflow.org/addons/api_docs/python/tfa/metrics/HammingLoss + /// + [TestMethod] + public void HammingLoss() + { + // multi-class hamming loss + var y_true = np.array(new[,] + { + { 1, 0, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 }, + { 0, 1, 0, 0 } + }); + var y_pred = np.array(new[,] + { + { 0.8f, 0.1f, 0.1f, 0.0f }, + { 0.2f, 0.0f, 0.8f, 0.0f }, + { 0.05f, 0.05f, 0.1f, 0.8f }, + { 1.0f, 0.0f, 0.0f, 0.0f } + }); + var m = tf.keras.metrics.HammingLoss(mode: "multiclass", threshold: 0.6f); + m.update_state(y_true, y_pred); + var r = m.result().numpy(); + Assert.AreEqual(r, 0.25f); + + // multi-label hamming loss + y_true = np.array(new[,] + { + { 1, 0, 1, 0 }, + { 0, 1, 0, 1 }, + { 0, 0, 0, 1 } + }); + y_pred = np.array(new[,] + { + { 0.82f, 0.5f, 0.9f, 0.0f }, + { 0f, 1f, 0.4f, 0.98f }, + { 0.89f, 0.79f, 0f, 0.3f } + }); + m = tf.keras.metrics.HammingLoss(mode: "multilabel", threshold: 0.8f); + m.update_state(y_true, y_pred); + r = m.result().numpy(); + Assert.AreEqual(r, 0.16666667f); + } + /// /// https://www.tensorflow.org/api_docs/python/tf/keras/metrics/TopKCategoricalAccuracy ///