Repository: chainer/chainer

Issue:
Fix inconsistency of LSTM and GRU links
It is often the case users compare and evaluate `LSTM` and `GRU` layers as an unit of RNN. So it is natural that frameworks offer the same APIs for them so that users can use them interchangeably. But currently, Chainer does not do so, partially because `GRU` is made independently of `LSTM`.

As this change breaks compatibility, we should offer it as a feature of v2.


Discussion:
This PR is related to #1395. I find that we can abstract single RNN units (a.k.a. cell in e.g. TensorFlow) and provide same APIs for them. But currently I do not have good idea how to extend to be applicable to RNN component that represents multiple units like NStepLSTM.

Resolving patch:
diff --git a/chainer/links/connection/gru.py b/chainer/links/connection/gru.py
--- a/chainer/links/connection/gru.py
+++ b/chainer/links/connection/gru.py
@@ -9,22 +9,20 @@
 
 class GRUBase(link.Chain):
 
-    def __init__(self, n_units, n_inputs=None,
-                 init=None, inner_init=None, bias_init=None):
-        if n_inputs is None:
-            n_inputs = n_units
+    def __init__(self, in_size, out_size, init=None,
+                 inner_init=None, bias_init=None):
         super(GRUBase, self).__init__(
-            W_r=linear.Linear(n_inputs, n_units,
+            W_r=linear.Linear(in_size, out_size,
                               initialW=init, initial_bias=bias_init),
-            U_r=linear.Linear(n_units, n_units,
+            U_r=linear.Linear(out_size, out_size,
                               initialW=inner_init, initial_bias=bias_init),
-            W_z=linear.Linear(n_inputs, n_units,
+            W_z=linear.Linear(in_size, out_size,
                               initialW=init, initial_bias=bias_init),
-            U_z=linear.Linear(n_units, n_units,
+            U_z=linear.Linear(out_size, out_size,
                               initialW=inner_init, initial_bias=bias_init),
-            W=linear.Linear(n_inputs, n_units,
+            W=linear.Linear(in_size, out_size,
                             initialW=init, initial_bias=bias_init),
-            U=linear.Linear(n_units, n_units,
+            U=linear.Linear(out_size, out_size,
                             initialW=inner_init, initial_bias=bias_init),
         )
 
@@ -56,9 +54,12 @@ class GRU(GRUBase):
     Use :class:`~chainer.links.StatefulGRU` as a *stateful* GRU.
 
     Args:
-        n_units(int): Dimension of hidden vector :math:`h`.
-        n_inputs(int): Dimension of input vector :math:`x`. If ``None``,
-            it is set to the same value as ``n_units``.
+        in_size(int): Dimension of input vector :math:`x`.
+            If ``None``, parameter initialization will be deferred
+            until the first forward data pass
+            at which time the size will be determined.
+        out_size(int): Dimension of hidden vector :math:`h`,
+            :math:`\\bar{h}` and :math:`h'`.
 
     See:
         - `On the Properties of Neural Machine Translation: Encoder-Decoder
@@ -138,7 +139,7 @@ class StatefulGRU(GRUBase):
     def __init__(self, in_size, out_size, init=None,
                  inner_init=None, bias_init=0):
         super(StatefulGRU, self).__init__(
-            out_size, in_size, init, inner_init, bias_init)
+            in_size, out_size, init, inner_init, bias_init)
         self.state_size = out_size
         self.reset_state()
 


Test patch:
diff --git a/tests/chainer_tests/links_tests/connection_tests/test_gru.py b/tests/chainer_tests/links_tests/connection_tests/test_gru.py
--- a/tests/chainer_tests/links_tests/connection_tests/test_gru.py
+++ b/tests/chainer_tests/links_tests/connection_tests/test_gru.py
@@ -37,9 +37,9 @@ class TestGRU(unittest.TestCase):
     def setUp(self):
         if self.gru == links.GRU:
             if hasattr(self, 'in_size'):
-                self.link = self.gru(self.out_size, self.in_size)
+                self.link = self.gru(self.in_size, self.out_size)
             else:
-                self.link = self.gru(self.out_size)
+                self.link = self.gru(None, self.out_size)
                 self.in_size = self.out_size
         elif self.gru == links.StatefulGRU:
             self.link = self.gru(self.in_size, self.out_size)