Repository: chainer/chainer

Issue:
Optimizer tries to update uninitialized parameters
MWE: https://pastebin.com/DXp4msHx
Running the code will result in the following error:
```
  File "/home/tommi/.pyenv/versions/2.7.12/lib/python2.7/site-packages/chainer/optimizer.py", line 628, in __call__
    g += self.rate * p
TypeError: unsupported operand type(s) for *: 'float' and 'NoneType'
```
The issue is that the parameter `p` above is `None` due to `model.l3` having no initialized parameters yet on the first forward pass.
This happens since when we are using shape placeholders, `model.l3.params()` returns an empty set, which the optimizer tries to update. An easy fix for this is to write the code as `self.l3 = L.Linear(n_units, n_units)` instead, but I think it is confusing for Chainer users that this error occurs when using placeholders. Would it perhaps be possible to modify the optimizer so that it will not update links with uninitialized parameters? 


Discussion:
I came across this issue too.
Another way to fix this issue is to modify all hook functions.
This is good because a hook function can be called to do something with a uninitialized param.

Resolving patch:
diff --git a/chainer/optimizer.py b/chainer/optimizer.py
--- a/chainer/optimizer.py
+++ b/chainer/optimizer.py
@@ -185,7 +185,8 @@ def update(self, param):
             return
 
         self.t += 1
-        self._prepare(param)
+        if param.data is not None:
+            self._prepare(param)
         for hook in six.itervalues(self._hooks):
             hook(self, param)
         self.update_core(param)
@@ -625,6 +626,8 @@ def __init__(self, rate):
 
     def __call__(self, rule, param):
         p, g = param.data, param.grad
+        if p is None or g is None:
+            return
         with cuda.get_device_from_array(p) as dev:
             if int(dev) == -1:
                 g += self.rate * p
@@ -655,6 +658,8 @@ def __init__(self, rate):
 
     def __call__(self, rule, param):
         p, g = param.data, param.grad
+        if p is None or g is None:
+            return
         xp = cuda.get_array_module(p)
         with cuda.get_device_from_array(p) as dev:
             sign = xp.sign(p)
@@ -732,6 +737,8 @@ def __init__(self, eta, noise_func=exponential_decay_noise):
 
     def __call__(self, rule, param):
         g = param.grad
+        if g is None:
+            return
         xp = cuda.get_array_module(g)
         with cuda.get_device_from_array(g) as dev:
             noise = self.noise_func(xp, g.shape, g.dtype, self, rule)
@@ -768,6 +775,8 @@ def __init__(self, lower_bound, upper_bound):
 
     def __call__(self, rule, param):
         grad = param.grad
+        if grad is None:
+            return
         xp = cuda.get_array_module(grad)
         with cuda.get_device_from_array(grad):
             xp.clip(grad, self.lower_bound, self.upper_bound, out=grad)


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
@@ -6,6 +6,8 @@
 
 import chainer
 from chainer import cuda
+from chainer import functions
+from chainer import links
 from chainer import optimizer
 from chainer import optimizers
 from chainer import testing
@@ -286,6 +288,26 @@ def __init__(self, w, g):
             self.param.grad = g
 
 
+class UninitializedChain(chainer.Chain):
+
+    def __init__(self):
+        super(UninitializedChain, self).__init__()
+        w = 10
+        with self.init_scope():
+            self.f0 = links.Linear(w)
+            self.f1 = links.Linear(w)
+            self.f2 = links.Linear(w)
+        self.count = 0
+
+    def __call__(self, h):
+        h = self.f0(h)
+        if self.count % 2 == 1:
+            h = self.f1(h)
+        h = self.f2(h)
+        self.count += 1
+        return functions.sum(h)
+
+
 class TestOptimizerWeightDecay(unittest.TestCase):
 
     def setUp(self):
@@ -315,6 +337,18 @@ def test_weight_decay_gpu(self):
         self.target.to_gpu()
         self.check_weight_decay()
 
+    def test_call_hooks_uninitialized_param(self):
+        target = UninitializedChain()
+        opt = optimizers.MomentumSGD()
+        opt.setup(target)
+        opt.add_hook(optimizer.WeightDecay(rate=0.0005))
+        target(np.ones((4, 10), dtype=np.float32))
+        opt.call_hooks()
+
+        # This test is for asserting that calling the hook on a chain
+        # with uninitialized parameters does not crash, so if we reach
+        # here, the test has passed.
+
 
 class TestOptimizerLasso(unittest.TestCase):
 
@@ -345,6 +379,18 @@ def test_lasso_gpu(self):
         self.target.to_gpu()
         self.check_lasso()
 
+    def test_call_hooks_uninitialized_param(self):
+        target = UninitializedChain()
+        opt = optimizers.MomentumSGD()
+        opt.setup(target)
+        opt.add_hook(optimizer.Lasso(rate=0.0005))
+        target(np.ones((4, 10), dtype=np.float32))
+        opt.call_hooks()
+
+        # This test is for asserting that calling the hook on a chain
+        # with uninitialized parameters does not crash, so if we reach
+        # here, the test has passed.
+
 
 class TestOptimizerGradientNoise(unittest.TestCase):
 
@@ -386,6 +432,18 @@ def test_gradient_noise_gpu(self):
         self.target.to_gpu()
         self.check_gradient_noise()
 
+    def test_call_hooks_uninitialized_param(self):
+        target = UninitializedChain()
+        opt = optimizers.MomentumSGD()
+        opt.setup(target)
+        opt.add_hook(optimizer.GradientNoise(eta=1))
+        target(np.ones((4, 10), dtype=np.float32))
+        opt.call_hooks()
+
+        # This test is for asserting that calling the hook on a chain
+        # with uninitialized parameters does not crash, so if we reach
+        # here, the test has passed.
+
 
 class TestGradientHardClipping(unittest.TestCase):
 
@@ -417,6 +475,18 @@ def test_hardclipping_gpu(self):
         self.target.to_gpu()
         self.check_hardclipping()
 
+    def test_call_hooks_uninitialized_param(self):
+        target = UninitializedChain()
+        opt = optimizers.MomentumSGD()
+        opt.setup(target)
+        opt.add_hook(optimizer.GradientClipping(threshold=1))
+        target(np.ones((4, 10), dtype=np.float32))
+        opt.call_hooks()
+
+        # This test is for asserting that calling the hook on a chain
+        # with uninitialized parameters does not crash, so if we reach
+        # here, the test has passed.
+
 
 class TestGradientMethod(unittest.TestCase):