Skip to content

Commit

Permalink
rename IoU -> Jaccard Index (#662)
Browse files Browse the repository at this point in the history
* rename IoU -> Jaccard Index
* deprecate
* imports

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
Borda and pre-commit-ci[bot] authored Dec 6, 2021
1 parent 4e26593 commit 965a6cf
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 204 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Deprecated

- Renamed IoU -> Jaccard Index ([#662](https://github.com/PyTorchLightning/metrics/pull/662))


### Removed

Expand Down
6 changes: 3 additions & 3 deletions docs/source/references/functional.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ hinge [func]
.. autofunction:: torchmetrics.functional.hinge
:noindex:

iou [func]
~~~~~~~~~~
jaccard_index [func]
~~~~~~~~~~~~~~~~~~~~

.. autofunction:: torchmetrics.functional.iou
.. autofunction:: torchmetrics.functional.jaccard_index
:noindex:

kl_divergence [func]
Expand Down
6 changes: 3 additions & 3 deletions docs/source/references/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,10 @@ Hinge
.. autoclass:: torchmetrics.Hinge
:noindex:

IoU
~~~
JaccardIndex
~~~~~~~~~~~~

.. autoclass:: torchmetrics.IoU
.. autoclass:: torchmetrics.JaccardIndex
:noindex:

KLDivergence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,60 +27,60 @@
from tests.classification.inputs import _input_multilabel as _input_mlb
from tests.classification.inputs import _input_multilabel_prob as _input_mlb_prob
from tests.helpers.testers import NUM_CLASSES, THRESHOLD, MetricTester
from torchmetrics.classification.iou import IoU
from torchmetrics.functional import iou
from torchmetrics.classification.jaccard import JaccardIndex
from torchmetrics.functional import jaccard_index


def _sk_iou_binary_prob(preds, target, average=None):
def _sk_jaccard_binary_prob(preds, target, average=None):
sk_preds = (preds.view(-1).numpy() >= THRESHOLD).astype(np.uint8)
sk_target = target.view(-1).numpy()

return sk_jaccard_score(y_true=sk_target, y_pred=sk_preds, average=average)


def _sk_iou_binary(preds, target, average=None):
def _sk_jaccard_binary(preds, target, average=None):
sk_preds = preds.view(-1).numpy()
sk_target = target.view(-1).numpy()

return sk_jaccard_score(y_true=sk_target, y_pred=sk_preds, average=average)


def _sk_iou_multilabel_prob(preds, target, average=None):
def _sk_jaccard_multilabel_prob(preds, target, average=None):
sk_preds = (preds.view(-1).numpy() >= THRESHOLD).astype(np.uint8)
sk_target = target.view(-1).numpy()

return sk_jaccard_score(y_true=sk_target, y_pred=sk_preds, average=average)


def _sk_iou_multilabel(preds, target, average=None):
def _sk_jaccard_multilabel(preds, target, average=None):
sk_preds = preds.view(-1).numpy()
sk_target = target.view(-1).numpy()

return sk_jaccard_score(y_true=sk_target, y_pred=sk_preds, average=average)


def _sk_iou_multiclass_prob(preds, target, average=None):
def _sk_jaccard_multiclass_prob(preds, target, average=None):
sk_preds = torch.argmax(preds, dim=len(preds.shape) - 1).view(-1).numpy()
sk_target = target.view(-1).numpy()

return sk_jaccard_score(y_true=sk_target, y_pred=sk_preds, average=average)


def _sk_iou_multiclass(preds, target, average=None):
def _sk_jaccard_multiclass(preds, target, average=None):
sk_preds = preds.view(-1).numpy()
sk_target = target.view(-1).numpy()

return sk_jaccard_score(y_true=sk_target, y_pred=sk_preds, average=average)


def _sk_iou_multidim_multiclass_prob(preds, target, average=None):
def _sk_jaccard_multidim_multiclass_prob(preds, target, average=None):
sk_preds = torch.argmax(preds, dim=len(preds.shape) - 2).view(-1).numpy()
sk_target = target.view(-1).numpy()

return sk_jaccard_score(y_true=sk_target, y_pred=sk_preds, average=average)


def _sk_iou_multidim_multiclass(preds, target, average=None):
def _sk_jaccard_multidim_multiclass(preds, target, average=None):
sk_preds = preds.view(-1).numpy()
sk_target = target.view(-1).numpy()

Expand All @@ -91,47 +91,47 @@ def _sk_iou_multidim_multiclass(preds, target, average=None):
@pytest.mark.parametrize(
"preds, target, sk_metric, num_classes",
[
(_input_binary_prob.preds, _input_binary_prob.target, _sk_iou_binary_prob, 2),
(_input_binary.preds, _input_binary.target, _sk_iou_binary, 2),
(_input_mlb_prob.preds, _input_mlb_prob.target, _sk_iou_multilabel_prob, 2),
(_input_mlb.preds, _input_mlb.target, _sk_iou_multilabel, 2),
(_input_mcls_prob.preds, _input_mcls_prob.target, _sk_iou_multiclass_prob, NUM_CLASSES),
(_input_mcls.preds, _input_mcls.target, _sk_iou_multiclass, NUM_CLASSES),
(_input_mdmc_prob.preds, _input_mdmc_prob.target, _sk_iou_multidim_multiclass_prob, NUM_CLASSES),
(_input_mdmc.preds, _input_mdmc.target, _sk_iou_multidim_multiclass, NUM_CLASSES),
(_input_binary_prob.preds, _input_binary_prob.target, _sk_jaccard_binary_prob, 2),
(_input_binary.preds, _input_binary.target, _sk_jaccard_binary, 2),
(_input_mlb_prob.preds, _input_mlb_prob.target, _sk_jaccard_multilabel_prob, 2),
(_input_mlb.preds, _input_mlb.target, _sk_jaccard_multilabel, 2),
(_input_mcls_prob.preds, _input_mcls_prob.target, _sk_jaccard_multiclass_prob, NUM_CLASSES),
(_input_mcls.preds, _input_mcls.target, _sk_jaccard_multiclass, NUM_CLASSES),
(_input_mdmc_prob.preds, _input_mdmc_prob.target, _sk_jaccard_multidim_multiclass_prob, NUM_CLASSES),
(_input_mdmc.preds, _input_mdmc.target, _sk_jaccard_multidim_multiclass, NUM_CLASSES),
],
)
class TestIoU(MetricTester):
class TestJaccardIndex(MetricTester):
@pytest.mark.parametrize("ddp", [True, False])
@pytest.mark.parametrize("dist_sync_on_step", [True, False])
def test_iou(self, reduction, preds, target, sk_metric, num_classes, ddp, dist_sync_on_step):
def test_jaccard(self, reduction, preds, target, sk_metric, num_classes, ddp, dist_sync_on_step):
average = "macro" if reduction == "elementwise_mean" else None # convert tags
self.run_class_metric_test(
ddp=ddp,
preds=preds,
target=target,
metric_class=IoU,
metric_class=JaccardIndex,
sk_metric=partial(sk_metric, average=average),
dist_sync_on_step=dist_sync_on_step,
metric_args={"num_classes": num_classes, "threshold": THRESHOLD, "reduction": reduction},
)

def test_iou_functional(self, reduction, preds, target, sk_metric, num_classes):
def test_jaccard_functional(self, reduction, preds, target, sk_metric, num_classes):
average = "macro" if reduction == "elementwise_mean" else None # convert tags
self.run_functional_metric_test(
preds,
target,
metric_functional=iou,
metric_functional=jaccard_index,
sk_metric=partial(sk_metric, average=average),
metric_args={"num_classes": num_classes, "threshold": THRESHOLD, "reduction": reduction},
)

def test_iou_differentiability(self, reduction, preds, target, sk_metric, num_classes):
def test_jaccard_differentiability(self, reduction, preds, target, sk_metric, num_classes):
self.run_differentiability_test(
preds=preds,
target=target,
metric_module=IoU,
metric_functional=iou,
metric_module=JaccardIndex,
metric_functional=jaccard_index,
metric_args={"num_classes": num_classes, "threshold": THRESHOLD, "reduction": reduction},
)

Expand All @@ -147,18 +147,18 @@ def test_iou_differentiability(self, reduction, preds, target, sk_metric, num_cl
(True, "none", 0, Tensor([2 / 3, 1 / 2])),
],
)
def test_iou(half_ones, reduction, ignore_index, expected):
def test_jaccard(half_ones, reduction, ignore_index, expected):
preds = (torch.arange(120) % 3).view(-1, 1)
target = (torch.arange(120) % 3).view(-1, 1)
if half_ones:
preds[:60] = 1
iou_val = iou(
jaccard_val = jaccard_index(
preds=preds,
target=target,
ignore_index=ignore_index,
reduction=reduction,
)
assert torch.allclose(iou_val, expected, atol=1e-9)
assert torch.allclose(jaccard_val, expected, atol=1e-9)


# test `absent_score`
Expand Down Expand Up @@ -194,16 +194,16 @@ def test_iou(half_ones, reduction, ignore_index, expected):
([0, 2], [0, 2], 0, 1.0, 3, [1.0, 1.0]),
],
)
def test_iou_absent_score(pred, target, ignore_index, absent_score, num_classes, expected):
iou_val = iou(
def test_jaccard_absent_score(pred, target, ignore_index, absent_score, num_classes, expected):
jaccard_val = jaccard_index(
preds=tensor(pred),
target=tensor(target),
ignore_index=ignore_index,
absent_score=absent_score,
num_classes=num_classes,
reduction="none",
)
assert torch.allclose(iou_val, tensor(expected).to(iou_val))
assert torch.allclose(jaccard_val, tensor(expected).to(jaccard_val))


# example data taken from
Expand All @@ -224,12 +224,12 @@ def test_iou_absent_score(pred, target, ignore_index, absent_score, num_classes,
([0, 1, 1, 2, 2], [0, 1, 2, 2, 2], 0, 3, "sum", [7 / 6]),
],
)
def test_iou_ignore_index(pred, target, ignore_index, num_classes, reduction, expected):
iou_val = iou(
def test_jaccard_ignore_index(pred, target, ignore_index, num_classes, reduction, expected):
jaccard_val = jaccard_index(
preds=tensor(pred),
target=tensor(target),
ignore_index=ignore_index,
num_classes=num_classes,
reduction=reduction,
)
assert torch.allclose(iou_val, tensor(expected).to(iou_val))
assert torch.allclose(jaccard_val, tensor(expected).to(jaccard_val))
5 changes: 3 additions & 2 deletions torchmetrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from torchmetrics import functional # noqa: E402
from torchmetrics.aggregation import CatMetric, MaxMetric, MeanMetric, MinMetric, SumMetric # noqa: E402
from torchmetrics.audio import PIT, SDR, SI_SDR, SI_SNR, SNR # noqa: E402
from torchmetrics.classification import ( # noqa: E402
from torchmetrics.classification import ( # noqa: E402, F401
AUC,
AUROC,
F1,
Expand All @@ -31,6 +31,7 @@
HammingDistance,
Hinge,
IoU,
JaccardIndex,
KLDivergence,
MatthewsCorrcoef,
Precision,
Expand Down Expand Up @@ -101,7 +102,7 @@
"FBeta",
"HammingDistance",
"Hinge",
"IoU",
"JaccardIndex",
"KLDivergence",
"MatthewsCorrcoef",
"MaxMetric",
Expand Down
1 change: 1 addition & 0 deletions torchmetrics/classification/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from torchmetrics.classification.hamming_distance import HammingDistance # noqa: F401
from torchmetrics.classification.hinge import Hinge # noqa: F401
from torchmetrics.classification.iou import IoU # noqa: F401
from torchmetrics.classification.jaccard import JaccardIndex # noqa: F401
from torchmetrics.classification.kl_divergence import KLDivergence # noqa: F401
from torchmetrics.classification.matthews_corrcoef import MatthewsCorrcoef # noqa: F401
from torchmetrics.classification.precision_recall import Precision, Recall # noqa: F401
Expand Down
65 changes: 9 additions & 56 deletions torchmetrics/classification/iou.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Any, Optional
from warnings import warn

import torch
from torch import Tensor

from torchmetrics.classification.confusion_matrix import ConfusionMatrix
from torchmetrics.functional.classification.iou import _iou_from_confmat
from torchmetrics.classification.jaccard import JaccardIndex


class IoU(ConfusionMatrix):
class IoU(JaccardIndex):
r"""
Computes Intersection over union, or `Jaccard index`_:
.. math:: J(A,B) = \frac{|A\cap B|}{|A\cup B|}
Where: :math:`A` and :math:`B` are both tensors of the same size, containing integer class values.
They may be subject to conversion from input data (see description below). Note that it is different from box IoU.
Works with binary, multiclass and multi-label data.
Accepts probabilities from a model output or integer class values in prediction.
Works with multi-dimensional preds and target.
Forward accepts
- ``preds`` (float or long tensor): ``(N, ...)`` or ``(N, C, ...)`` where C is the number of classes
- ``target`` (long tensor): ``(N, ...)``
If preds and target are the same shape and preds is a float tensor, we use the ``self.threshold`` argument
to convert into integer labels. This is the case for binary and multi-label probabilities.
If preds has an extra dimension as in the case of multi-class scores we perform an argmax on ``dim=1``.
Args:
num_classes: Number of classes in the dataset.
ignore_index: optional int specifying a target class to ignore. If given, this class index does not contribute
to the returned score, regardless of reduction method. Has no effect if given an int that is not in the
range [0, num_classes-1]. By default, no index is ignored, and all classes are used.
absent_score: score to use for an individual class, if no instances of the class index were present in
`pred` AND no instances of the class index were present in `target`. For example, if we have 3 classes,
[0, 0] for `pred`, and [0, 2] for `target`, then class 1 would be assigned the `absent_score`.
threshold:
Threshold value for binary or multi-label probabilities.
reduction: a method to reduce metric score over labels.
- ``'elementwise_mean'``: takes the mean (default)
- ``'sum'``: takes the sum
- ``'none'``: no reduction will be applied
compute_on_step:
Forward only calls ``update()`` and return None if this is set to False.
dist_sync_on_step:
Synchronize metric state across processes at each ``forward()``
before returning the value at the step.
process_group:
Specify the process group on which synchronization is called. default: None (which selects the entire world)
.. deprecated:: v0.7
Use :class:`torchmetrics.JaccardIndex`. Will be removed in v0.8.
Example:
>>> from torchmetrics import IoU
Expand All @@ -77,8 +36,6 @@ class IoU(ConfusionMatrix):
tensor(0.9660)
"""
is_differentiable = False
higher_is_better = True

def __init__(
self,
Expand All @@ -91,18 +48,14 @@ def __init__(
dist_sync_on_step: bool = False,
process_group: Optional[Any] = None,
) -> None:
warn("`IoU` was renamed to `JaccardIndex` in v0.7 and it will be removed in v0.8", DeprecationWarning)
super().__init__(
num_classes=num_classes,
normalize=None,
ignore_index=ignore_index,
absent_score=absent_score,
threshold=threshold,
reduction=reduction,
compute_on_step=compute_on_step,
dist_sync_on_step=dist_sync_on_step,
process_group=process_group,
)
self.reduction = reduction
self.ignore_index = ignore_index
self.absent_score = absent_score

def compute(self) -> Tensor:
"""Computes intersection over union (IoU)"""
return _iou_from_confmat(self.confmat, self.num_classes, self.ignore_index, self.absent_score, self.reduction)
Loading

0 comments on commit 965a6cf

Please sign in to comment.