Repository: tensorflow/addons

Issue:
Add `tfa.metrics.HarmonicMean`
**Describe the feature and the current behavior/state.**
Computes HarmonicMean

**Relevant information**
- Are you willing to contribute it (yes/no): YES
- Are you willing to maintain it going forward? (yes/no): YES
- Is there a relevant academic paper? (if so, where): NO
- Is there already an implementation in another framework? (if so, where): python stdlib statistics
- Was it part of tf.contrib? (if so, where): NO

**Which API type would this fall under (layer, metric, optimizer, etc.)**
`tfa.metrics`

**Who will benefit with this feature?**
Developers and researchers who want to obtain representative values using harmonic mean

**Any other info.**



Discussion:

Resolving patch:
diff --git a/tensorflow_addons/metrics/__init__.py b/tensorflow_addons/metrics/__init__.py
--- a/tensorflow_addons/metrics/__init__.py
+++ b/tensorflow_addons/metrics/__init__.py
@@ -30,3 +30,4 @@
 )
 from tensorflow_addons.metrics.r_square import RSquare
 from tensorflow_addons.metrics.geometric_mean import GeometricMean
+from tensorflow_addons.metrics.harmonic_mean import HarmonicMean
diff --git a/tensorflow_addons/metrics/harmonic_mean.py b/tensorflow_addons/metrics/harmonic_mean.py
new file mode 100644
--- /dev/null
+++ b/tensorflow_addons/metrics/harmonic_mean.py
@@ -0,0 +1,50 @@
+# Copyright 2020 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Implements HarmonicMean."""
+
+import tensorflow as tf
+
+from typeguard import typechecked
+from tensorflow_addons.utils.types import AcceptableDTypes
+
+
+@tf.keras.utils.register_keras_serializable(package="Addons")
+class HarmonicMean(tf.keras.metrics.Mean):
+    """Compute Harmonic Mean
+    The harmonic mean is a kind of mean. It can be expressed as the reciprocal of
+    the arithmetic mean of the reciprocals of the given set of numbers.
+    Note: `tfa.metrics.HarmonicMean` can be used the same as `tf.keras.metrics.Mean`.
+    Args:
+        name: (Optional) String name of the metric instance.
+        dtype: (Optional) Data type of the metric result.
+    Usage:
+    >>> metric = tfa.metrics.HarmonicMean()
+    >>> metric.update_state([1, 4, 4])
+    >>> metric.result().numpy()
+    2.0
+    """
+
+    @typechecked
+    def __init__(
+        self, name: str = "harmonic_mean", dtype: AcceptableDTypes = None, **kwargs
+    ):
+        super().__init__(name=name, dtype=dtype, **kwargs)
+
+    def update_state(self, values, sample_weight=None) -> None:
+        values = tf.cast(values, dtype=self.dtype)
+        super().update_state(tf.math.reciprocal(values), sample_weight)
+
+    def result(self) -> tf.Tensor:
+        return tf.math.reciprocal_no_nan(super().result())


Test patch:
diff --git a/tensorflow_addons/metrics/tests/harmonic_mean_test.py b/tensorflow_addons/metrics/tests/harmonic_mean_test.py
new file mode 100644
--- /dev/null
+++ b/tensorflow_addons/metrics/tests/harmonic_mean_test.py
@@ -0,0 +1,76 @@
+# Copyright 2020 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests HarmonicMean metrics."""
+
+import numpy as np
+import pytest
+import tensorflow as tf
+
+from tensorflow_addons.metrics import HarmonicMean
+
+
+def get_test_data():
+    return [
+        ([np.inf] * 2, 0),
+        ([0, 0, 0, 0], 0),
+        ([1, 4, 4], 2.0),
+        ([0, 0, 0, 0, 0, 0, 0, 1, 2, 6], 0),
+        ([0.2, 0.5, 0.3, 0.6, 0.1, 0.7], 0.25609756),
+        ([8, 4, 1, 7, 2, 11, 9, 22, 52], 3.9394846),
+        ([8.2, 9.7, 9.1, 2.7, 1.1, 2.0], 2.8376906),
+        ([0.6666666, 0.215213, 0.15167], 0.23548213),
+    ]
+
+
+def assert_result(expected, result):
+    np.testing.assert_allclose(expected, result, atol=1e-6)
+
+
+def check_result(obj, expected_result, expected_count):
+    result = obj.result().numpy()
+    count = obj.count.numpy()
+    assert_result(expected_result, result)
+    np.testing.assert_equal(expected_count, count)
+
+
+@pytest.mark.parametrize("values, expected", get_test_data())
+def test_vector_update_state_hmean(values, expected):
+    obj = HarmonicMean()
+    values = tf.constant(values, tf.float32)
+    obj.update_state(values)
+    check_result(obj, expected, len(values))
+
+
+@pytest.mark.parametrize("values, expected", get_test_data())
+def test_call_hmean(values, expected):
+    obj = HarmonicMean()
+    result = obj(tf.constant(values, tf.float32))
+    count = obj.count.numpy()
+    assert_result(expected, result)
+    np.testing.assert_equal(len(values), count)
+
+
+@pytest.mark.parametrize(
+    "values, sample_weight, expected",
+    [
+        ([1, 2, 3, 4, 5], 1, 2.1897807),
+        ([2.1, 4.6, 7.1], [1, 2, 3], 4.499409),
+        ([9.6, 1.8, 8.2], [0.2, 0.5, 0.3], 2.9833248),
+    ],
+)
+def test_sample_weight_hmean(values, sample_weight, expected):
+    obj = HarmonicMean()
+    obj.update_state(values, sample_weight=sample_weight)
+    assert_result(expected, obj.result().numpy())