Repository: chainer/chainer

Issue:
`Link.copyparams` does not work with `Sequential` with non-links
* Conditions
```
Platform: Darwin-18.2.0-x86_64-i386-64bit
Chainer: 6.0.0b1
NumPy: 1.15.4
CuPy: Not Available
iDeep: Not Available
```

* Code to reproduce
```python
l1 = chainer.Sequential(chainer.links.Linear(3, 4), chainer.functions.relu, chainer.links.Linear(4, 5))
l2 = chainer.Sequential(chainer.links.Linear(3, 4), chainer.functions.relu, chainer.links.Linear(4, 5))
l1.copyparams(l2)
```

* Error messages, stack traces, or logs
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/tos/GitHub/chainer/chainer/link.py", line 1227, in copyparams
    child.copyparams(link[idx], copy_persistent)
  File "/Users/tos/GitHub/chainer/chainer/link.py", line 611, in copyparams
    dst[name].copydata(src[name])
KeyError: 'b'
```


Discussion:

Resolving patch:
diff --git a/chainer/sequential.py b/chainer/sequential.py
--- a/chainer/sequential.py
+++ b/chainer/sequential.py
@@ -3,10 +3,10 @@
 import inspect
 
 from chainer import function
-from chainer import link
+from chainer import link as _link
 
 
-class Sequential(link.ChainList):
+class Sequential(_link.ChainList):
 
     """Sequential model which has a single-stream forward pass.
 
@@ -145,9 +145,9 @@ def __setitem__(self, i, layer):
 
     def __delitem__(self, i):
         layer = self._layers.pop(i)
-        if isinstance(layer, link.Link):
-            for i, _link in enumerate(self._children):
-                if _link.name == layer.name:
+        if isinstance(layer, _link.Link):
+            for i, link in enumerate(self._children):
+                if link.name == layer.name:
                     del self._children[i]
                     break
             for j, layer in enumerate(self._children[i:]):
@@ -235,15 +235,15 @@ def __str__(self):
             if isinstance(layer, Sequential):
                 name = layer.__class__.__name__
                 name += '\twhich has {} layers'.format(len(layer))
-            elif isinstance(layer, link.Chain):
+            elif isinstance(layer, _link.Chain):
                 name = layer.__class__.__name__
                 name += '\tThe structure behind a Chain is determined at '
                 name += 'runtime.'
-            elif isinstance(layer, link.ChainList):
+            elif isinstance(layer, _link.ChainList):
                 name = layer.__class__.__name__
                 name += '\tThe structure behind a ChainList is determined at '
                 name += 'runtime.'
-            elif isinstance(layer, link.Link):
+            elif isinstance(layer, _link.Link):
                 name = layer.__class__.__name__
                 param_info = '\t'
                 for param in sorted(layer.params(), key=lambda p: p.name):
@@ -279,7 +279,7 @@ def insert(self, i, layer):
                 'given {} is not callable.'.format(layer))
 
         self._layers.insert(i, layer)
-        if isinstance(layer, link.Link):
+        if isinstance(layer, _link.Link):
             if i == 0:
                 self._children.insert(0, layer)
             else:
@@ -288,7 +288,7 @@ def insert(self, i, layer):
                 last_link_pos = 0
                 for j in range(i - 1, -1, -1):
                     # The last link before the given position
-                    if isinstance(self._layers[j], link.Link):
+                    if isinstance(self._layers[j], _link.Link):
                         last_link_pos = j
                 self._children.insert(last_link_pos + 1, layer)
             for i, layer in enumerate(self._children):
@@ -319,7 +319,7 @@ def remove_by_layer_type(self, type_name):
 
         names = []
         for layer in self:
-            if isinstance(layer, link.Link):
+            if isinstance(layer, _link.Link):
                 name = layer.__class__.__name__
             else:
                 name = layer.__name__
@@ -364,7 +364,7 @@ def count_by_layer_type(self, type_name):
 
         num = 0
         for layer in self._layers:
-            if isinstance(layer, link.Link):
+            if isinstance(layer, _link.Link):
                 if layer.__class__.__name__ == type_name:
                     num += 1
             else:
@@ -375,12 +375,20 @@ def count_by_layer_type(self, type_name):
     def copy(self, mode='share'):
         ret = Sequential()
         for layer in self:
-            if isinstance(layer, link.Link):
+            if isinstance(layer, _link.Link):
                 ret.append(layer.copy(mode))
             else:
                 ret.append(copy.copy(layer))
         return ret
 
+    def copyparams(self, link, copy_persistent=True):
+        if not isinstance(link, Sequential):
+            raise ValueError('Objects other than Sequential object cannot be '
+                             'copied to a Sequential object.')
+        for idx, child in enumerate(self):
+            if isinstance(child, _link.Link):
+                child.copyparams(link[idx], copy_persistent)
+
     def flatten(self):
         """Flatten nested :class:`~chainer.Sequential` links.
 


Test patch:
diff --git a/tests/chainer_tests/test_sequential.py b/tests/chainer_tests/test_sequential.py
--- a/tests/chainer_tests/test_sequential.py
+++ b/tests/chainer_tests/test_sequential.py
@@ -77,6 +77,37 @@ def test_copy(self):
         self.assertIs(s2[1].W.data, self.l3.W.data)
         self.assertIs(s2[1].W.grad, None)
 
+    def test_copy_with_nonparametric_function(self):
+        self.s1.insert(1, functions.relu)
+        # l1 -> relu -> l2
+
+        # The default copy mode is 'share'
+        s1 = self.s1.copy()
+        self.assertIsNot(s1[0], self.s1[0])  # l1
+        self.assertIs(s1[1], self.s1[1])  # relu
+        self.assertIsNot(s1[2], self.s1[2])  # l2
+
+        # parameters of l1
+        self.assertIsNot(s1[0].W, self.s1[0].W)
+        self.assertIsNot(s1[0].b, self.s1[0].b)
+        # W of the first link has not been initialized
+        self.assertIs(s1[0].W.array, None)
+        self.assertIs(s1[0].W.grad, None)
+        # The bias is initialized
+        self.assertIs(s1[0].b.array, self.s1[0].b.array)
+        self.assertIs(s1[0].b.grad, None)
+
+        # The copied Function should be identical
+        self.assertIs(s1[1], self.s1[1])
+
+        # parameters of l2
+        self.assertIsNot(s1[2].W, self.s1[2].W)
+        self.assertIsNot(s1[2].b, self.s1[2].b)
+        self.assertIs(s1[2].W.array, self.s1[2].W.array)
+        self.assertIs(s1[2].W.grad, None)
+        self.assertIs(s1[2].b.array, self.s1[2].b.array)
+        self.assertIs(s1[2].b.grad, None)
+
     @attr.gpu
     def test_copy_and_send_to_gpu(self):
         s2 = self.s2.copy()