Dynamically create nonsequential HybridBlocks inside a HybridBlock?


#1

I want to create a HybridBlock with some non-sequential blocks inside it, like this:

class Net1(gluon.HybridBlock):
    def __init__(self, **kwargs):
        super(Net1, self).__init__(**kwargs)
        with self.name_scope():
            self.conv1 = nn.Conv2D(3, kernel_size=5)
            self.conv2 = nn.Conv2D(3, kernel_size=5)
    def hybrid_forward(self, F, x):
        x1 = self.conv1(x)
        x2 = self.conv2(x)
        return x1 + x2

The above example works. But I want to be able to pass in num_convs into init and declare the convolutional units inside a for loop. This fails (below). How can I create a dynamic number of non-sequential blocks in the init of this block?

>>> import mxnet as mx
>>> from mxnet import gluon
>>> from mxnet.gluon import nn
>>> 
>>> # fails
... class Net2(gluon.HybridBlock):
...     def __init__(self, **kwargs):
...         super(Net2, self).__init__(**kwargs)
...         with self.name_scope():
...             self.convs = [nn.Conv2D(3, kernel_size=5), nn.Conv2D(3, kernel_size=5)]
...     def hybrid_forward(self, F, x):
...         x1 = self.convs[0](x)
...         x2 = self.convs[1](x)
...         return x1 + x2
... 
>>> net2 = Net2()
>>> net2.initialize()
/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/block.py:228: UserWarning: "Net2.convs" is a container with Blocks. Note that Blocks inside the list, tuple or dict will not be registered automatically. Make sure to register them using register_child() or switching to nn.Sequential/nn.HybridSequential instead. 
  .format(name=self.__class__.__name__ + "." + k))
>>> net2.collect_params()
net20_ (

)
>>> x = mx.nd.random_normal(shape=(16, 3, 28, 28))
>>> y = net2(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/block.py", line 360, in __call__
    return self.forward(*args)
  File "/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/block.py", line 575, in forward
    return self.hybrid_forward(ndarray, x, *args, **params)
  File "<stdin>", line 8, in hybrid_forward
  File "/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/block.py", line 360, in __call__
    return self.forward(*args)
  File "/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/block.py", line 568, in forward
    params = {i: j.data(ctx) for i, j in self._reg_params.items()}
  File "/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/block.py", line 568, in <dictcomp>
    params = {i: j.data(ctx) for i, j in self._reg_params.items()}
  File "/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/parameter.py", line 389, in data
    return self._check_and_get(self._data, ctx)
  File "/home/local/ANT/gautierp/anaconda3/lib/python3.6/site-packages/mxnet/gluon/parameter.py", line 189, in _check_and_get
    "nested child Blocks"%(self.name))
RuntimeError: Parameter net20_conv0_weight has not been initialized. Note that you should initialize parameters and create Trainer with Block.collect_params() instead of Block.params because the later does not include Parameters of nested child Blocks

#2

It seems I found a workaround, so I’ll post it here.

It’s possible to add the children blocks after initializing the parent. I followed the example of HybridConcurrent from contrib.

>>> from mxnet.gluon import nn
>>> import mxnet as mx
>>> 
>>> class AddConvs(nn.HybridSequential):
...     def __init__(self, prefix=None, params=None):
...         super(AddConvs, self).__init__(prefix=prefix, params=params)
...     def hybrid_forward(self, F, x):
...         out = self._children[0](x)
...         for block in self._children[1:]:
...             out += block(x)
...         return out
... 
>>> convs = AddConvs()
>>> for i in range(2):
...     convs.add(nn.Conv2D(3, kernel_size=5))
... 
>>> convs.initialize()
>>> x = mx.nd.random_normal(shape=(16, 3, 28, 28))
>>> y = convs(x)
>>> 
>>> convs.collect_params()
addconvs0_ (
  Parameter conv0_weight (shape=(3, 3, 5, 5), dtype=<class 'numpy.float32'>)
  Parameter conv0_bias (shape=(3,), dtype=<class 'numpy.float32'>)
  Parameter conv1_weight (shape=(3, 3, 5, 5), dtype=<class 'numpy.float32'>)
  Parameter conv1_bias (shape=(3,), dtype=<class 'numpy.float32'>)
)

#3

You can use HybridSequential as a container, see this and references therein.