Repository: chainer/chainer

Issue:
Unused params cause NoneType error
When some params in a model are not used to calculate loss value, its gradient is None. It causes an error when an optimizer update its value.

```
import numpy

import chainer
import chainer.functions as F
import chainer.links as L
import chainer.optimizers as O


class MyModel(chainer.Chain):

    def __init__(self):
        super(MyModel, self).__init__(
            l1=L.Linear(3, 4),
            l2=L.Linear(3, 4),
        )

    def __call__(self, x):
        return F.sum(self.l1(x))


model = MyModel()
opt = O.Adam()
opt.setup(model)
model.cleargrads()
loss = model(numpy.random.uniform(-1, 1, (5, 3)).astype('f'))
loss.backward()
opt.update()
```



Discussion:
I have faced a problem like this in training Discriminator of  GAN.
In order to clarfify where grad get `NoneType`, I edited the `adam.py` to print the type of `grad` and the result below.
However, I might used all variables to calculate loss value, and optimizer propagated gradients to some extent.

```
## dis loss:  0.569088876247406
# type(grad):  <class 'numpy.ndarray'>
# type(grad):  <class 'numpy.ndarray'>
# type(grad):  <class 'numpy.ndarray'>
# type(grad):  <class 'numpy.ndarray'>
# type(grad):  <class 'numpy.ndarray'>
# type(grad):  <class 'numpy.ndarray'>
# type(grad):  <class 'NoneType'>

...

    m += (1 - self.beta1) * (grad - m)
TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'
```


Resolving patch:
diff --git a/chainer/optimizer.py b/chainer/optimizer.py
--- a/chainer/optimizer.py
+++ b/chainer/optimizer.py
@@ -396,6 +396,15 @@ def update(self, lossfun=None, *args, **kwds):
                 self.target.zerograds()
             loss.backward()
             del loss
+
+        # TODO(unno): Some optimizers can skip this process if they does not
+        # affect to a parameter when its gradient is zero.
+        for name, param in self.target.namedparams():
+            if param.grad is None:
+                with cuda.get_device(param.data):
+                    xp = cuda.get_array_module(param.data)
+                    param.grad = xp.zeros_like(param.data)
+
         self.call_hooks()
         self.prepare()
 


Test patch:
diff --git a/tests/chainer_tests/test_optimizer.py b/tests/chainer_tests/test_optimizer.py
--- a/tests/chainer_tests/test_optimizer.py
+++ b/tests/chainer_tests/test_optimizer.py
@@ -380,4 +380,42 @@ def test_clip_grads_gpu(self):
         self.check_clip_grads()
 
 
+class DummyOptimizer(chainer.GradientMethod):
+
+    def __init__(self, test):
+        self.test = test
+
+    def update_one(self, param, state):
+        # Confirm all grads are not None
+        self.test.assertIsNotNone(param.grad)
+
+
+class DummyHook(object):
+
+    name = 'Dummy'
+
+    def __init__(self, test):
+        self.test = test
+
+    def __call__(self, opt):
+        for param in opt.target.params():
+            # Confirm all grads are not None
+            self.test.assertIsNotNone(param.grad)
+
+
+class TestGradientMethodClearGrads(unittest.TestCase):
+
+    def setUp(self):
+        self.optimizer = DummyOptimizer(self)
+        self.target = SimpleLink(
+            np.arange(3).astype(np.float32),
+            np.arange(3).astype(np.float32))
+        self.optimizer.setup(self.target)
+        self.optimizer.add_hook(DummyHook(self))
+
+    def test_update(self):
+        self.target.cleargrads()
+        self.optimizer.update()
+
+
 testing.run_module(__name__, __file__)