import numpy

from chainer import cuda
from chainer import function
from chainer.utils import type_check


class HuberLoss(function.Function):

    def __init__(self, delta):
        self.delta = delta

    def check_type_forward(self, in_types):
        type_check.expect(in_types.size() == 2)
        type_check.expect(
            in_types[0].dtype == numpy.float32,
            in_types[1].dtype == numpy.float32,
            in_types[0].shape == in_types[1].shape
        )

    def forward(self, inputs):
        xp = cuda.get_array_module(*inputs)
        x0, x1 = inputs
        self.diff = x0 - x1
        y = xp.square(self.diff)
        mask = y > (self.delta ** 2)
        y -= mask * xp.square(abs(self.diff) - self.delta)
        y *= 0.5
        return y.sum(axis=1),

    def backward(self, inputs, gy):
        xp = cuda.get_array_module(*inputs)
        mask = xp.abs(self.diff) <= self.delta
        gx = xp.where(mask, self.diff, self.delta * xp.sign(self.diff))
        return gx, -gx


def huber_loss(x, t, delta):
    """Loss function which is less sensitive to outliers in data than MSE.

        .. math::
            a = x - t

        and

        .. math::
            L_{\\delta}(a) = \\left \\{ \\begin{array}{cc}
            \\frac{1}{2} a^2 & {\\rm if~|a| \\leq \\delta} \\\\
            \\delta (|a| - \\frac{1}{2} \\delta) & {\\rm otherwise,}
            \\end{array} \\right.

    Args:
        x (~chainer.Variable): Input variable.
            The shape of ``x`` should be (:math:`N`, :math:`K`).
        t (~chainer.Variable): Target variable for regression.
            The shape of ``t`` should be (:math:`N`, :math:`K`).
        delta (float): Constant variable for huber loss function
            as used in definition.

    Returns:
        ~chainer.Variable: A variable object holding a scalar array of the
            huber loss :math:`L_{\\delta}`.

    See:
        `Huber loss - Wikipedia <https://en.wikipedia.org/wiki/Huber_loss>`_.

    """
    return HuberLoss(delta=delta)(x, t)

chainer/functions/loss/huber_loss.py
import numpy

from chainer import cuda
from chainer import function
from chainer.utils import type_check


class HuberLoss(function.Function):

    def __init__(self, delta):
        self.delta = delta

    def check_type_forward(self, in_types):
        type_check.expect(in_types.size() == 2)
        type_check.expect(
            in_types[0].dtype == numpy.float32,
            in_types[1].dtype == numpy.float32,
            in_types[0].shape == in_types[1].shape
        )

    def forward(self, inputs):
        xp = cuda.get_array_module(*inputs)
        x0, x1 = inputs
        self.diff = x0 - x1
        y = xp.square(self.diff)
        mask = y > (self.delta ** 2)
        y -= mask * xp.square(abs(self.diff) - self.delta)
        y *= 0.5
        return y.sum(axis=1),

    def backward(self, inputs, gy):
        xp = cuda.get_array_module(*inputs)
        mask = xp.abs(self.diff) <= self.delta
        gx = gy[0].reshape(gy[0].shape + (1,) * (self.diff.ndim - 1)) * \
            xp.where(mask, self.diff, self.delta * xp.sign(self.diff))
        return gx, -gx


def huber_loss(x, t, delta):
    """Loss function which is less sensitive to outliers in data than MSE.

        .. math::
            a = x - t

        and

        .. math::
            L_{\\delta}(a) = \\left \\{ \\begin{array}{cc}
            \\frac{1}{2} a^2 & {\\rm if~|a| \\leq \\delta} \\\\
            \\delta (|a| - \\frac{1}{2} \\delta) & {\\rm otherwise,}
            \\end{array} \\right.

    Args:
        x (~chainer.Variable): Input variable.
            The shape of ``x`` should be (:math:`N`, :math:`K`).
        t (~chainer.Variable): Target variable for regression.
            The shape of ``t`` should be (:math:`N`, :math:`K`).
        delta (float): Constant variable for huber loss function
            as used in definition.

    Returns:
        ~chainer.Variable: A variable object holding a scalar array of the
            huber loss :math:`L_{\\delta}`.

    See:
        `Huber loss - Wikipedia <https://en.wikipedia.org/wiki/Huber_loss>`_.

    """
    return HuberLoss(delta=delta)(x, t)

chainer/functions/loss/huber_loss.py
diff --git a/chainer/functions/loss/huber_loss.py b/chainer/functions/loss/huber_loss.py
index de1bf0284e03..a292ee22c2a7 100644
--- a/chainer/functions/loss/huber_loss.py
+++ b/chainer/functions/loss/huber_loss.py
@@ -31,7 +31,8 @@ def forward(self, inputs):
     def backward(self, inputs, gy):
         xp = cuda.get_array_module(*inputs)
         mask = xp.abs(self.diff) <= self.delta
-        gx = xp.where(mask, self.diff, self.delta * xp.sign(self.diff))
+        gx = gy[0].reshape(gy[0].shape + (1,) * (self.diff.ndim - 1)) * \
+            xp.where(mask, self.diff, self.delta * xp.sign(self.diff))
         return gx, -gx
 
 
diff --git a/tests/chainer_tests/functions_tests/loss_tests/test_huber_loss.py b/tests/chainer_tests/functions_tests/loss_tests/test_huber_loss.py
index 9d0f4067453f..a6815bac19e4 100644
--- a/tests/chainer_tests/functions_tests/loss_tests/test_huber_loss.py
+++ b/tests/chainer_tests/functions_tests/loss_tests/test_huber_loss.py
@@ -18,6 +18,7 @@ def setUp(self):
         self.x = (numpy.random.random(self.shape) - 0.5) * 20
         self.x = self.x.astype(numpy.float32)
         self.t = numpy.random.random(self.shape).astype(numpy.float32)
+        self.gy = numpy.random.random(self.shape[0]).astype(numpy.float32)
 
     def check_forward(self, x_data, t_data):
         x = chainer.Variable(x_data)
@@ -43,19 +44,20 @@ def test_forward_cpu(self):
     def test_forward_gpu(self):
         self.check_forward(cuda.to_gpu(self.x), cuda.to_gpu(self.t))
 
-    def check_backward(self, x_data, t_data):
+    def check_backward(self, x_data, t_data, y_grad):
         gradient_check.check_backward(
             functions.HuberLoss(delta=1),
-            (x_data, t_data), None, eps=1e-2, atol=1e-3)
+            (x_data, t_data), y_grad, eps=1e-2, atol=1e-3)
 
     @condition.retry(3)
     def test_backward_cpu(self):
-        self.check_backward(self.x, self.t)
+        self.check_backward(self.x, self.t, self.gy)
 
     @attr.gpu
     @condition.retry(3)
     def test_backward_gpu(self):
-        self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.t))
+        self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.t),
+                            cuda.to_gpu(self.gy))
 
 
 testing.run_module(__name__, __file__)

HuberLoss's backward() should not ignore gy
`HuberLoss`'s `backward()` ignores `gy`, so any computation after `HuberLoss` has no effect for gradients backpropagated by `HuberLoss`. I think such a behavior is not correct.

``` python
>>> x = chainer.Variable(np.zeros((1,1), dtype=np.float32))
>>> t = chainer.Variable(np.ones((1,1), dtype=np.float32))
>>> F.huber_loss(x, t, 1.0).backward()
>>> x.grad
array([[-1.]], dtype=float32)
```

``` python
>>> x = chainer.Variable(np.zeros((1,1), dtype=np.float32))
>>> t = chainer.Variable(np.ones((1,1), dtype=np.float32))
>>> (F.huber_loss(x, t, 1.0) * 0).backward()  # Multiply the loss by zero
>>> x.grad
array([[-1.]], dtype=float32)
```

