Repository: pytorch/ignite

Issue:
Improve Canberra metric
## 🚀 Feature

Actual implementation of Canberra metric does not use absolute value on terms in denominator. Moreover, `sklearn` can be used in test.

See https://arxiv.org/pdf/1411.7474.pdf 

See https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html



Discussion:

Resolving patch:
diff --git a/ignite/contrib/metrics/regression/canberra_metric.py b/ignite/contrib/metrics/regression/canberra_metric.py
--- a/ignite/contrib/metrics/regression/canberra_metric.py
+++ b/ignite/contrib/metrics/regression/canberra_metric.py
@@ -7,16 +7,19 @@ class CanberraMetric(_BaseRegression):
     r"""
     Calculates the Canberra Metric.
 
-    :math:`\text{CM} = \sum_{j=1}^n\frac{|A_j - P_j|}{A_j + P_j}`
+    :math:`\text{CM} = \sum_{j=1}^n\frac{|A_j - P_j|}{|A_j| + |P_j|}`
 
     where, :math:`A_j` is the ground truth and :math:`P_j` is the predicted value.
 
-    More details can be found in `Botchkarev 2018`__.
+    More details can be found in `Botchkarev 2018`_ or `scikit-learn distance metrics`_
 
     - ``update`` must receive output of the form ``(y_pred, y)`` or ``{'y_pred': y_pred, 'y': y}``.
     - `y` and `y_pred` must be of same shape `(N, )` or `(N, 1)`.
 
-    __ https://arxiv.org/abs/1809.03006
+    .. _Botchkarev 2018: https://arxiv.org/abs/1809.03006
+    .. _scikit-learn distance metrics:
+        https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html
+
     """
 
     def reset(self):
@@ -24,7 +27,7 @@ def reset(self):
 
     def _update(self, output):
         y_pred, y = output
-        errors = torch.abs(y.view_as(y_pred) - y_pred) / (y_pred + y.view_as(y_pred))
+        errors = torch.abs(y.view_as(y_pred) - y_pred) / (torch.abs(y_pred) + torch.abs(y.view_as(y_pred)))
         self._sum_of_errors += torch.sum(errors).item()
 
     def compute(self):


Test patch:
diff --git a/tests/ignite/contrib/metrics/regression/test_canberra_metric.py b/tests/ignite/contrib/metrics/regression/test_canberra_metric.py
--- a/tests/ignite/contrib/metrics/regression/test_canberra_metric.py
+++ b/tests/ignite/contrib/metrics/regression/test_canberra_metric.py
@@ -1,6 +1,7 @@
 import numpy as np
 import pytest
 import torch
+from sklearn.neighbors import DistanceMetric
 
 from ignite.contrib.metrics.regression import CanberraMetric
 
@@ -30,18 +31,30 @@ def test_compute():
 
     m = CanberraMetric()
 
+    canberra = DistanceMetric.get_metric("canberra")
+
     m.update((torch.from_numpy(a), torch.from_numpy(ground_truth)))
-    np_sum = (np.abs(ground_truth - a) / (a + ground_truth)).sum()
+    np_sum = (np.abs(ground_truth - a) / (np.abs(a) + np.abs(ground_truth))).sum()
     assert m.compute() == pytest.approx(np_sum)
+    assert canberra.pairwise([a, ground_truth])[0][1] == pytest.approx(np_sum)
 
     m.update((torch.from_numpy(b), torch.from_numpy(ground_truth)))
-    np_sum += ((np.abs(ground_truth - b)) / (b + ground_truth)).sum()
+    np_sum += ((np.abs(ground_truth - b)) / (np.abs(b) + np.abs(ground_truth))).sum()
     assert m.compute() == pytest.approx(np_sum)
+    v1 = np.hstack([a, b])
+    v2 = np.hstack([ground_truth, ground_truth])
+    assert canberra.pairwise([v1, v2])[0][1] == pytest.approx(np_sum)
 
     m.update((torch.from_numpy(c), torch.from_numpy(ground_truth)))
-    np_sum += ((np.abs(ground_truth - c)) / (c + ground_truth)).sum()
+    np_sum += ((np.abs(ground_truth - c)) / (np.abs(c) + np.abs(ground_truth))).sum()
     assert m.compute() == pytest.approx(np_sum)
+    v1 = np.hstack([v1, c])
+    v2 = np.hstack([v2, ground_truth])
+    assert canberra.pairwise([v1, v2])[0][1] == pytest.approx(np_sum)
 
     m.update((torch.from_numpy(d), torch.from_numpy(ground_truth)))
-    np_sum += (np.abs(ground_truth - d) / (d + ground_truth)).sum()
+    np_sum += (np.abs(ground_truth - d) / (np.abs(d) + np.abs(ground_truth))).sum()
     assert m.compute() == pytest.approx(np_sum)
+    v1 = np.hstack([v1, d])
+    v2 = np.hstack([v2, ground_truth])
+    assert canberra.pairwise([v1, v2])[0][1] == pytest.approx(np_sum)