Repository: chainer/chainer

Issue:
static_graph with batch_norm use_beta false error -  'NoneType' object is not subscriptable
When beta is set to false in batchnorm...

```
L.BatchNormalization(64, use_beta=False)
```

static_graph raises a type error

```
TypeError: 'NoneType' object is not subscriptable
```

```
Platform: Linux-4.15.0-47-generic-x86_64-with-debian-buster-sid
Chainer: 7.0.0rc1
ChainerX: Available
NumPy: 1.17.3
CuPy:
  CuPy Version          : 7.0.0rc1
  CUDA Root             : /xx/cuda/10.1
  CUDA Build Version    : 10010
  CUDA Driver Version   : 10010
  CUDA Runtime Version  : 10010
  cuDNN Build Version   : 7500
  cuDNN Version         : 7604
  NCCL Build Version    : 2402
  NCCL Runtime Version  : 2402
iDeep: 2.0.0.post3
None
```


Discussion:
Reproduced with

```python
import chainer
import chainer.links as L
import numpy


class TestBN(chainer.Chain):
    def __init__(self):
        super(TestBN, self).__init__()
        with self.init_scope():
            self.link = L.BatchNormalization(64, use_beta=False)

    @chainer.static_graph
    def __call__(self, x):
        self.link(x)


x = numpy.random.uniform(
    -1, 1, (10, 64)).astype(numpy.float32)
x = chainer.Variable(x)

chain = TestBN()
chain(x)
```

Resolving patch:
diff --git a/chainer/links/normalization/batch_normalization.py b/chainer/links/normalization/batch_normalization.py
--- a/chainer/links/normalization/batch_normalization.py
+++ b/chainer/links/normalization/batch_normalization.py
@@ -6,6 +6,7 @@
 from chainer import functions
 from chainer import initializers
 from chainer import link
+from chainer.graph_optimizations.static_graph_utilities import static_code
 from chainer.utils import argument
 from chainer import variable
 
@@ -252,6 +253,20 @@ def _init_array(self, initializer, default_value, size):
             initializer, size, self.xp, dtype=self._highprec_dtype,
             device=self.device)
 
+    @static_code
+    def _get_gamma(self):
+        with chainer.using_device(self.device):
+            gamma = self.xp.ones(
+                self.avg_mean.shape, dtype=self._highprec_dtype)
+        return gamma,
+
+    @static_code
+    def _get_beta(self):
+        with chainer.using_device(self.device):
+            beta = self.xp.zeros(
+                self.avg_mean.shape, dtype=self._highprec_dtype)
+        return beta,
+
     @property
     def printable_specs(self):
         specs = [
@@ -295,17 +310,16 @@ def forward(self, x, **kwargs):
                 if i not in self.axis])
             self._initialize_params(param_shape)
 
+        # When using static_graph optimizations beta or gamma might not be
+        # initialized and is not retained by the function, so the
+        # static forward pass will get a None instead
         gamma = self.gamma
         if gamma is None:
-            with chainer.using_device(self.device):
-                gamma = self.xp.ones(
-                    self.avg_mean.shape, dtype=self._highprec_dtype)
+            gamma, = self._get_gamma()
 
         beta = self.beta
         if beta is None:
-            with chainer.using_device(self.device):
-                beta = self.xp.zeros(
-                    self.avg_mean.shape, dtype=self._highprec_dtype)
+            beta, = self._get_beta()
 
         if configuration.config.train:
             if finetune:


Test patch:
diff --git a/tests/chainer_tests/graph_optimization_tests/test_static_graph_models.py b/tests/chainer_tests/graph_optimization_tests/test_static_graph_models.py
--- a/tests/chainer_tests/graph_optimization_tests/test_static_graph_models.py
+++ b/tests/chainer_tests/graph_optimization_tests/test_static_graph_models.py
@@ -358,6 +358,143 @@ def test_backward_custom_cpu(self):
         chainer.testing.assert_allclose(x_var_dyn.grad, x_var_static.grad)
 
 
+class StaticBN(chainer.Chain):
+
+    def __init__(self, in_size, dtype, use_gamma, use_beta):
+        super(StaticBN, self).__init__()
+        with self.init_scope():
+            self.l1 = links.BatchNormalization(
+                in_size,
+                dtype=dtype,
+                use_gamma=use_gamma,
+                use_beta=use_beta)
+
+    @static_graph(verbosity_level=2)
+    def __call__(self, x):
+        return F.relu(self.l1(x))
+
+
+class DynamicBN(chainer.Chain):
+
+    def __init__(self, in_size, dtype, use_gamma, use_beta):
+        super(DynamicBN, self).__init__()
+        with self.init_scope():
+            self.l1 = links.BatchNormalization(
+                in_size,
+                dtype=dtype,
+                use_gamma=use_gamma,
+                use_beta=use_beta)
+
+    def __call__(self, x):
+        return F.relu(self.l1(x))
+
+
+class BN(chainer.Chain):
+
+    def __init__(self, in_size, dtype, use_gamma, use_beta):
+        super(BN, self).__init__()
+        with self.init_scope():
+            self.l1 = links.BatchNormalization(
+                in_size,
+                dtype=dtype,
+                use_gamma=use_gamma,
+                use_beta=use_beta)
+
+        self.mode = 'static'
+
+    def __call__(self, x):
+        if self.mode == 'static':
+            return self.static_call(x)
+        else:
+            return self.dynamic_call(x)
+
+    def dynamic_call(self, x):
+        # Dynamic graph only.
+        return F.relu(self.l1(x))
+
+    @static_graph(verbosity_level=2)
+    def static_call(self, x):
+        # Static graph.
+        return F.relu(self.l1(x))
+
+
+@testing.parameterize(*testing.product({
+    'x_dtype': [numpy.float16, numpy.float32, numpy.float64],
+    'use_static_graph': [True, False],
+    'use_gamma': [True, False],
+    'use_beta': [True, False],
+}))
+class TestBNChain(unittest.TestCase):
+
+    def setUp(self):
+        self.batch_size = 4
+        self.in_units = 5
+        self.out_units = 5
+        x_size = (self.batch_size, self.in_units)
+        self.x = numpy.random.uniform(size=x_size).astype(self.x_dtype)
+        gy_size = (self.batch_size, self.out_units)
+        self.gy = numpy.random.uniform(size=gy_size).astype(self.x_dtype)
+        self.chain = BN(
+            self.in_units,
+            self.x_dtype,
+            self.use_gamma,
+            self.use_beta)
+        self.chain.l1.cleargrads()
+        self.check_forward_options = {}
+        self.check_backward_options = {'atol': 1e-2, 'rtol': 5e-2}
+        self.dynamic_chain = DynamicBN(
+            self.in_units,
+            self.x_dtype,
+            self.use_gamma,
+            self.use_beta)
+        self.static_chain = StaticBN(
+            self.in_units,
+            self.x_dtype,
+            self.use_gamma,
+            self.use_beta)
+
+    def check_forward(self, x):
+        chain = self.chain
+        y_dyn = chain.dynamic_call(x)
+        use_static_graph = self.use_static_graph
+
+        with chainer.using_config('use_static_graph', use_static_graph), \
+                chainer.using_config('enable_backprop', False):
+            y_static = chain.static_call(x)
+            y_static = chain.static_call(x)
+            y_static = chain.static_call(x)
+
+        assert use_static_graph == hasattr(chain, 'schedule_manager')
+        assert use_static_graph == hasattr(chain, 'static_schedule')
+
+        chainer.testing.assert_allclose(y_dyn.data, y_static.data)
+
+    def test_forward_cpu(self):
+        self.check_forward(self.x)
+
+    @attr.gpu
+    def test_forward_gpu(self):
+        with testing.assert_warns(DeprecationWarning):
+            self.chain.to_gpu()
+        self.check_forward(cuda.to_gpu(self.x))
+
+    def check_backward(self, x_data, y_grad, chain):
+        to_check = tuple()
+        if self.use_gamma:
+            to_check += (chain.l1.gamma,)
+        if self.use_beta:
+            to_check += (chain.l1.beta,)
+        gradient_check.check_backward(
+            chain, x_data, y_grad, to_check,
+            dtype='f', **self.check_backward_options)
+
+    @condition.retry(3)
+    def test_backward_cpu(self):
+        chain = self.static_chain
+        with configuration.using_config('train', False):
+            self.check_backward(self.x, self.gy, chain)
+
+
 testing.run_module(__name__, __file__)
 
 if __name__ == '__main__':