Repository: pytorch/pytorch

Issue:
Deprecate NLLLoss2d
[`nn.NLLLoss2d`](https://github.com/pytorch/pytorch/blob/0b7f1e5efdec62f297202a636312223116be1b31/torch/nn/modules/loss.py#L150) is redundant because nn.NLLLoss takes care of the 2d case.

I'll work on this soon



Discussion:

Resolving patch:
diff --git a/torch/nn/functional.py b/torch/nn/functional.py
--- a/torch/nn/functional.py
+++ b/torch/nn/functional.py
@@ -1091,13 +1091,12 @@ def nll_loss(input, target, weight=None, size_average=True, ignore_index=-100, r
     See :class:`~torch.nn.NLLLoss` for details.
 
     Args:
-        input: :math:`(N, C)` where `C = number of classes` or `(N, C, H, W)`
-            in case of 2D Loss, or `(N, C, *) in the case of K-dimensional Loss,
-            where :math:`K > 2` and `*` is `K` extra dimensions.
-        target: :math:`(N)` where each value is `0 <= targets[i] <= C-1`.
-            In the case of 2D Loss, then :math:`(N, H, W)`. For K-dimensional
-            Loss where :math:`K > 2`, then :math:`(N, *)`, where `*` is `K`
-            extra dimensions.
+        input: :math:`(N, C)` where `C = number of classes` or :math:`(N, C, H, W)`
+            in case of 2D Loss, or :math:`(N, C, d_1, d_2, ..., d_K)` where :math:`K > 2`
+            in the case of K-dimensional loss.
+        target: :math:`(N)` where each value is `0 <= targets[i] <= C-1`,
+            or :math:`(N, C, d_1, d_2, ..., d_K)` where :math:`K >= 2` for
+            K-dimensional loss.
         weight (Tensor, optional): a manual rescaling weight given to each
             class. If given, has to be a Tensor of size `C`
         size_average (bool, optional): By default, the losses are averaged
diff --git a/torch/nn/modules/loss.py b/torch/nn/modules/loss.py
--- a/torch/nn/modules/loss.py
+++ b/torch/nn/modules/loss.py
@@ -1,3 +1,5 @@
+import warnings
+
 from torch.autograd import Variable
 import torch
 from .module import Module
@@ -93,8 +95,9 @@ class NLLLoss(_WeightedLoss):
     unbalanced training set.
 
     The input given through a forward call is expected to contain
-    log-probabilities of each class: input has to be a 2D Tensor of size
-    `(minibatch, C)`
+    log-probabilities of each class. input has to be a Tensor of size either
+    :math:`(minibatch, C)` or :math:`(minibatch, C, d_1, d_2, ..., d_K)`
+    with :math:`K >= 2` for the `K`-dimensional case (described later).
 
     Obtaining log-probabilities in a neural network is easily achieved by
     adding a  `LogSoftmax`  layer in the last layer of your network.
@@ -122,6 +125,11 @@ class NLLLoss(_WeightedLoss):
             \text{size_average} = \text{False}.
         \end{cases}
 
+    Can also be used for higher dimension inputs, such as 2D images, by providing
+    an input of size :math:`(minibatch, C, d_1, d_2, ..., d_K)` with :math:`K >= 2`,
+    where :math:`K` is the number of dimensions, and a target of appropriate shape
+    (see below). In the case of images, it computes NLL loss per-pixel.
+
     Args:
         weight (Tensor, optional): a manual rescaling weight given to each
            class. If given, it has to be a Tensor of size `C`. Otherwise, it is
@@ -141,15 +149,16 @@ class NLLLoss(_WeightedLoss):
             ignores :attr:`size_average`. Default: ``True``
 
     Shape:
-        - Input: :math:`(N, C)` where `C = number of classes`.
-            In the case of K-dimensional loss where :math:`K >= 2`, then
-            :math:`(N, C, *)` where `*` is `K` extra dimensions.
-        - Target: :math:`(N)` where each value is `0 <= targets[i] <= C-1`.
-            In the case of K-dimensional loss, where :math:`K >= 2`, then
-            :math:`(N, C, *)` where `*` is `K` extra dimensions.
-        - Output: scalar. If reduce is ``False``, then :math:`(N)` instead.
-            In the case of K-dimensional loss and reduce is ``False``, then
-            :math:`(N, C, *)`, the same size as the target.
+        - Input: :math:`(N, C)` where `C = number of classes`, or
+            :math:`(N, C, d_1, d_2, ..., d_K)` with :math:`K >= 2`
+            in the case of `K`-dimensional loss.
+        - Target: :math:`(N)` where each value is `0 <= targets[i] <= C-1`, or
+            :math:`(N, d_1, d_2, ..., d_K)` with :math:`K >= 2` in the case of
+            K-dimensional loss.
+        - Output: :math:`(1)`. If reduce is ``False``, then the same size
+            as the target: :math:`(N)`, or
+            :math:`(N, d_1, d_2, ..., d_K)` with :math:`K >= 2` in the case
+            of K-dimensional loss.
 
     Examples::
 
@@ -161,6 +170,18 @@ class NLLLoss(_WeightedLoss):
         >>> target = autograd.Variable(torch.LongTensor([1, 0, 4]))
         >>> output = loss(m(input), target)
         >>> output.backward()
+        >>>
+        >>>
+        >>> # 2D loss example (used, for example, with image inputs)
+        >>> N, C = 5, 4
+        >>> loss = nn.NLLLoss()
+        >>> # input is of size N x C x height x width
+        >>> data = Variable(torch.randn(N, 16, 10, 10))
+        >>> m = nn.Conv2d(16, C, (3, 3))
+        >>> # each element in target has to have 0 <= value < C
+        >>> target = Variable(torch.LongTensor(N, 8, 8).random_(0, C))
+        >>> output = loss(m(data), target)
+        >>> output.backward()
     """
 
     def __init__(self, weight=None, size_average=True, ignore_index=-100, reduce=True):
@@ -175,46 +196,11 @@ def forward(self, input, target):
 
 
 class NLLLoss2d(NLLLoss):
-    r"""This is negative log likehood loss, but for image inputs. It computes
-    NLL loss per-pixel.
-
-    Args:
-        weight (Tensor, optional): a manual rescaling weight given to each
-            class. If given, has to be a 1D Tensor having as many elements,
-            as there are classes.
-        size_average: By default, the losses are averaged over observations
-            for each minibatch. However, if the field :attr:`size_average` is
-            set to ``False``, the losses are instead summed for each minibatch.
-            Ignored when :attr:`reduce` is ``False``. Default: ``True``
-        ignore_index (int, optional): Specifies a target value that is ignored
-            and does not contribute to the input gradient. When
-            :attr:`size_average` is ``True``, the loss is averaged over
-            non-ignored targets.
-        reduce (bool, optional): By default, the losses are averaged or summed
-            for each minibatch depending on :attr:`size_average`. When
-            :attr:`reduce` is ``False``, the loss function returns a loss per
-            batch element instead and ignores :attr:`size_average`.
-            Default: ``True``
-
-
-    Shape:
-        - Input: :math:`(N, C, H, W)` where `C = number of classes`
-        - Target: :math:`(N, H, W)` where each value is `0 <= targets[i] <= C-1`
-        - Output: scalar. If :attr:`reduce` is ``False``, then :math:`(N, H, W)` instead.
-
-    Examples::
-
-        >>> N, C = 5, 4
-        >>> loss = nn.NLLLoss2d()
-        >>> # input is of size N x C x height x width
-        >>> data = Variable(torch.randn(N, 16, 10, 10))
-        >>> m = nn.Conv2d(16, C, (3, 3))
-        >>> # each element in target has to have 0 <= value < C
-        >>> target = Variable(torch.LongTensor(N, 8, 8).random_(0, C))
-        >>> output = loss(m(data), target)
-        >>> output.backward()
-    """
-    pass
+    def __init__(self, weight=None, size_average=True, ignore_index=-100, reduce=True):
+        warnings.warn("NLLLoss2d has been deprecated. "
+                      "Please use NLLLoss instead as a drop-in replacement and see "
+                      "http://pytorch.org/docs/master/nn.html#torch.nn.NLLLoss for more details.")
+        super(NLLLoss2d, self).__init__(weight, size_average, ignore_index, reduce)
 
 
 class PoissonNLLLoss(_Loss):


Test patch:
diff --git a/test/common_nn.py b/test/common_nn.py
--- a/test/common_nn.py
+++ b/test/common_nn.py
@@ -426,32 +426,6 @@ def smoothl1loss_reference(input, target, size_average=True, reduce=True):
         target_fn=lambda: torch.Tensor(15).uniform_().mul(10).floor().long(),
         desc='weights',
     ),
-    dict(
-        module_name='NLLLoss2d',
-        input_size=(2, 3, 5, 5),
-        target_fn=lambda: torch.rand(2, 5, 5).mul(3).floor().long(),
-        reference_fn=lambda i, t, m:
-            nlllossNd_reference(i, t, size_average=get_size_average(m)),
-        check_no_size_average=True,
-    ),
-    dict(
-        module_name='NLLLoss2d',
-        constructor_args_fn=lambda: (torch.rand(3),),
-        input_size=(2, 3, 5, 5),
-        target=torch.rand(2, 5, 5).mul(3).floor().long(),
-        reference_fn=lambda i, t, m:
-            nlllossNd_reference(i, t, weight=get_weight(m)),
-        desc='weights',
-    ),
-    dict(
-        module_name='NLLLoss2d',
-        constructor_args=(None, True, 1),
-        input_size=(2, 3, 5, 5),
-        target_fn=lambda: torch.rand(2, 5, 5).mul(3).floor().long(),
-        reference_fn=lambda i, t, m:
-            nlllossNd_reference(i, t, ignore_index=1),
-        desc='ignore_index',
-    ),
     dict(
         module_name='HingeEmbeddingLoss',
         input_size=(10,),
diff --git a/test/test_legacy_nn.py b/test/test_legacy_nn.py
--- a/test/test_legacy_nn.py
+++ b/test/test_legacy_nn.py
@@ -617,6 +617,11 @@ def add_test(test):
         test_params = deepcopy(test_params)
         name = test_params.pop('module_name')
         name = name_remap.get(name, name.replace('Loss', 'Criterion'))
+
+        # nn.NLLLoss2d is deprecated, but there is a NLLLoss test for 2d
+        if name == 'ClassNLLCriterion' and 'desc' in test_params.keys() and '2d' in test_params['desc']:
+            name = 'SpatialClassNLLCriterion'
+
         test_params['constructor'] = getattr(nn, name)
         test = CriterionTest(**test_params)
         add_test(test)
diff --git a/test/test_nn.py b/test/test_nn.py
--- a/test/test_nn.py
+++ b/test/test_nn.py
@@ -28,7 +28,7 @@
 from torch.nn.parallel._functions import Broadcast
 from common_nn import NNTestCase, ModuleTest, CriterionTest, TestBase, \
     module_tests, criterion_tests, TEST_CUDA, TEST_MULTIGPU, TEST_CUDNN, \
-    TEST_CUDNN_VERSION, loss_reference_fns, get_size_average
+    TEST_CUDNN_VERSION, loss_reference_fns, get_size_average, get_weight
 from common import freeze_rng_state, run_tests, TestCase, skipIfNoLapack, \
     TEST_SCIPY, download_file, IS_WINDOWS
 
@@ -4047,6 +4047,33 @@ def forward(self, *args):
         target_fn=lambda: torch.randn(15, 10).gt(0).double(),
         desc='weights'
     ),
+    dict(
+        module_name='NLLLoss',
+        input_size=(2, 3, 5, 5),
+        target_fn=lambda: torch.rand(2, 5, 5).mul(3).floor().long(),
+        reference_fn=lambda i, t, m:
+            loss_reference_fns['NLLLossNd'](i, t, size_average=get_size_average(m)),
+        check_no_size_average=True,
+        desc='2d'
+    ),
+    dict(
+        module_name='NLLLoss',
+        constructor_args_fn=lambda: (torch.rand(3),),
+        input_size=(2, 3, 5, 5),
+        target=torch.rand(2, 5, 5).mul(3).floor().long(),
+        reference_fn=lambda i, t, m:
+            loss_reference_fns['NLLLossNd'](i, t, weight=get_weight(m)),
+        desc='2d_weights',
+    ),
+    dict(
+        module_name='NLLLoss',
+        constructor_args=(None, True, 1),
+        input_size=(2, 3, 5, 5),
+        target_fn=lambda: torch.rand(2, 5, 5).mul(3).floor().long(),
+        reference_fn=lambda i, t, m:
+            loss_reference_fns['NLLLossNd'](i, t, ignore_index=1),
+        desc='2d_ignore_index',
+    ),
     dict(
         module_name='NLLLoss',
         input_size=(2, 3, 5, 5, 2, 2),