Repository: chainer/chainer

Issue:
`ignore_label` of `F.softmax_cross_entropy` has to be in the range [-n, n)
* Conditions
  - Chainer version: master
* Code to reproduce
```
>>> x = np.random.randn(2, 4)
>>> chainer.functions.softmax_cross_entropy(x, np.array([2, -1]))
variable(1.48807391)
>>> chainer.functions.softmax_cross_entropy(x, np.array([2, -2]), ignore_label=-2)
variable(1.48807391)
>>> chainer.functions.softmax_cross_entropy(x, np.array([2, 4]), ignore_label=4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/tos/GitHub/chainer/chainer/functions/loss/softmax_cross_entropy.py", line 381, in softmax_cross_entropy
    normalize, cache_score, class_weight, ignore_label, reduce)(x, t)
  File "/Users/tos/GitHub/chainer/chainer/function.py", line 235, in __call__
    ret = node.apply(inputs)
  File "/Users/tos/GitHub/chainer/chainer/function_node.py", line 257, in apply
    outputs = self.forward(in_data)
  File "/Users/tos/GitHub/chainer/chainer/function.py", line 135, in forward
    return self._function.forward(inputs)
  File "/Users/tos/GitHub/chainer/chainer/function.py", line 344, in forward
    return self.forward_cpu(inputs)
  File "/Users/tos/GitHub/chainer/chainer/functions/loss/softmax_cross_entropy.py", line 94, in forward_cpu
    log_p = log_yd[numpy.maximum(t.ravel(), 0), numpy.arange(t.size)]
IndexError: index 4 is out of bounds for axis 0 with size 4
```


Discussion:
i also meet this issue, i wanna know how did you solve it?
`forward_gpu` and `backward_gpu` seem OK.  Use GPUs 😄 

Resolving patch:
diff --git a/chainer/functions/loss/softmax_cross_entropy.py b/chainer/functions/loss/softmax_cross_entropy.py
--- a/chainer/functions/loss/softmax_cross_entropy.py
+++ b/chainer/functions/loss/softmax_cross_entropy.py
@@ -91,14 +91,16 @@ def forward_cpu(self, inputs):
             log_y *= _broadcast_to(self.class_weight.reshape(shape), x.shape)
         log_yd = numpy.rollaxis(log_y, 1)
         log_yd = log_yd.reshape(len(log_yd), -1)
-        log_p = log_yd[numpy.maximum(t.ravel(), 0), numpy.arange(t.size)]
+        t_valid = t != self.ignore_label
+        t = t * t_valid
+        log_p = log_yd[t.ravel(), numpy.arange(t.size)]
 
-        log_p *= (t.ravel() != self.ignore_label)
+        log_p *= t_valid.ravel()
         if self.reduce == 'mean':
             # deal with the case where the SoftmaxCrossEntropy is
             # unpickled from the old version
             if self.normalize:
-                count = (t != self.ignore_label).sum()
+                count = t_valid.sum()
             else:
                 count = len(x)
             self._coeff = 1.0 / max(count, 1)
@@ -170,15 +172,17 @@ def backward_cpu(self, inputs, grad_outputs):
         else:
             y = log_softmax._log_softmax(x)
             numpy.exp(y, out=y)
+        t_valid = t != self.ignore_label
+        t = t * t_valid
         if y.ndim == 2:
             gx = y
-            gx[numpy.arange(len(t)), numpy.maximum(t, 0)] -= 1
+            gx[numpy.arange(len(t)), t] -= 1
             if self.class_weight is not None:
                 shape = [1 if d != 1 else -1 for d in six.moves.range(x.ndim)]
                 c = _broadcast_to(self.class_weight.reshape(shape), x.shape)
-                c = c[numpy.arange(len(t)), numpy.maximum(t, 0)]
+                c = c[numpy.arange(len(t)), t]
                 gx *= _broadcast_to(numpy.expand_dims(c, 1), gx.shape)
-            gx *= (t != self.ignore_label).reshape((len(t), 1))
+            gx *= t_valid.reshape((len(t), 1))
         else:
             # in the case where y.ndim is higher than 2,
             # we think that a current implementation is inefficient
@@ -187,15 +191,15 @@ def backward_cpu(self, inputs, grad_outputs):
             gx = y.reshape(y.shape[0], y.shape[1], -1)
             fst_index = numpy.arange(t.size) // n_unit
             trd_index = numpy.arange(t.size) % n_unit
-            gx[fst_index, numpy.maximum(t.ravel(), 0), trd_index] -= 1
+            gx[fst_index, t.ravel(), trd_index] -= 1
             if self.class_weight is not None:
                 shape = [1 if d != 1 else -1 for d in six.moves.range(x.ndim)]
                 c = _broadcast_to(self.class_weight.reshape(shape), x.shape)
                 c = c.reshape(gx.shape)
-                c = c[fst_index, numpy.maximum(t.ravel(), 0), trd_index]
+                c = c[fst_index, t.ravel(), trd_index]
                 c = c.reshape(y.shape[0], 1, -1)
                 gx *= _broadcast_to(c, gx.shape)
-            gx *= (t != self.ignore_label).reshape((len(t), 1, -1))
+            gx *= t_valid.reshape((len(t), 1, -1))
             gx = gx.reshape(y.shape)
         if self.reduce == 'mean':
             gx *= gloss * self._coeff


Test patch:
diff --git a/tests/chainer_tests/functions_tests/loss_tests/test_softmax_cross_entropy.py b/tests/chainer_tests/functions_tests/loss_tests/test_softmax_cross_entropy.py
--- a/tests/chainer_tests/functions_tests/loss_tests/test_softmax_cross_entropy.py
+++ b/tests/chainer_tests/functions_tests/loss_tests/test_softmax_cross_entropy.py
@@ -396,6 +396,7 @@ def test_invalid_reduce_gpu(self):
 
 
 @testing.parameterize(*testing.product({
+    'ignore_label': [-2, 9],
     'reduce': ['mean', 'no'],
     'enable_double_backprop': [False, True],
     'class_weight': [None, numpy.ones((3,), dtype=numpy.float32)]})
@@ -403,7 +404,6 @@ def test_invalid_reduce_gpu(self):
 class TestNonDefaultIgnoreLabel(unittest.TestCase):
 
     def setUp(self):
-        self.ignore_label = -2
         self.x = numpy.random.uniform(-1, 1, (2, 3)).astype(numpy.float32)
         self.t = numpy.full((2,), self.ignore_label, dtype=numpy.int32)
         if self.reduce == 'mean':