We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
F.bilinear requires huge GPU memory
The following code results `cupy.cuda.memory.OutOfMemoryError: out of memory to allocate 18014398509481984 bytes (total 18014399785863168 bytes)`
```
import chainer
import cupy
b = chainer.links.Bilinear(256, 256, 256).to_gpu()
e1 = cupy.random.randn(64, 256).astype('f')
e2 = cupy.random.randn(64, 256).astype('f')
y = b(e1, e2)
print(y)
```

How to fix: merge cupy/cupy#1218 (or do not use `cupy.einsum`).
I confirmed the code run in ~5sec with
- chainer: master(6bab773dec70f291108ab2575622805252f9a208)
- cupy: (Merge: cupy/cupy@6162f9a cupy/cupy@7f89bd0)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `chainer/functions/connection/bilinear.py`
Content:
```
1 import numpy
2 
3 import chainer
4 from chainer.backends import cuda
5 from chainer import function_node
6 from chainer.utils import type_check
7 
8 
9 def _as_mat(x):
10     if x.ndim == 2:
11         return x
12     return x.reshape(len(x), -1)
13 
14 
15 def _ij_ik_il_to_jkl(a, b, c):
16     ab = chainer.functions.matmul(a[:, :, None], b[:, None, :])  # ijk
17     return chainer.functions.matmul(_as_mat(ab).T, c).reshape(
18         a.shape[1], b.shape[1], c.shape[1])
19 
20 
21 def _ij_ik_jkl_to_il(a, b, c):
22     ab = chainer.functions.matmul(a[:, :, None], b[:, None, :])  # ijk
23     c = c.reshape(-1, c.shape[-1])  # [jk]l
24     return chainer.functions.matmul(_as_mat(ab), c)
25 
26 
27 def _ij_il_jkl_to_ik(a, b, c):
28     return _ij_ik_jkl_to_il(a, b, chainer.functions.swapaxes(c, 1, 2))
29 
30 
31 def _ik_il_jkl_to_ij(a, b, c):
32     return _ij_ik_jkl_to_il(a, b, chainer.functions.rollaxis(c, 0, c.ndim))
33 
34 
35 class BilinearFunction(function_node.FunctionNode):
36     def check_type_forward(self, in_types):
37         n_in = type_check.eval(in_types.size())
38         if n_in != 3 and n_in != 6:
39             raise type_check.InvalidType(
40                 '{0} or {1}'.format(
41                     in_types.size() == 3, in_types.size() == 6),
42                 '{0} == {1}'.format(in_types.size(), n_in))
43 
44         e1_type, e2_type, W_type = in_types[:3]
45         type_check_prod = type_check.make_variable(numpy.prod, 'prod')
46         type_check.expect(
47             e1_type.dtype == numpy.float32,
48             e1_type.ndim >= 2,
49             e2_type.dtype == numpy.float32,
50             e2_type.ndim >= 2,
51             e1_type.shape[0] == e2_type.shape[0],
52             W_type.dtype == numpy.float32,
53             W_type.ndim == 3,
54             type_check_prod(e1_type.shape[1:]) == W_type.shape[0],
55             type_check_prod(e2_type.shape[1:]) == W_type.shape[1],
56         )
57 
58         if n_in == 6:
59             out_size = W_type.shape[2]
60             V1_type, V2_type, b_type = in_types[3:]
61             type_check.expect(
62                 V1_type.dtype == numpy.float32,
63                 V1_type.ndim == 2,
64                 V1_type.shape[0] == W_type.shape[0],
65                 V1_type.shape[1] == out_size,
66                 V2_type.dtype == numpy.float32,
67                 V2_type.ndim == 2,
68                 V2_type.shape[0] == W_type.shape[1],
69                 V2_type.shape[1] == out_size,
70                 b_type.dtype == numpy.float32,
71                 b_type.ndim == 1,
72                 b_type.shape[0] == out_size,
73             )
74 
75     def forward(self, inputs):
76         self.retain_inputs(tuple(range(len(inputs))))
77 
78         e1 = _as_mat(inputs[0])
79         e2 = _as_mat(inputs[1])
80         W = inputs[2]
81 
82         xp = cuda.get_array_module(*inputs)
83         y = xp.einsum('ij,ik,jkl->il', e1, e2, W)
84 
85         if len(inputs) == 6:
86             V1, V2, b = inputs[3:]
87             y += e1.dot(V1)
88             y += e2.dot(V2)
89             y += b
90         return y,
91 
92     def backward(self, indexes, grad_outputs):
93         inputs = self.get_retained_inputs()
94         e1, e2, W = inputs[:3]
95         gy, = grad_outputs
96 
97         if len(inputs) == 6:
98             V1, V2 = inputs[3], inputs[4]
99             return BilinearFunctionGrad().apply((e1, e2, W, V1, V2, gy))
100         return BilinearFunctionGrad().apply((e1, e2, W, gy))
101 
102 
103 class BilinearFunctionGrad(function_node.FunctionNode):
104 
105     def forward(self, inputs):
106         self.retain_inputs(tuple(range(len(inputs))))
107 
108         e1 = _as_mat(inputs[0])
109         e2 = _as_mat(inputs[1])
110         W, gy = inputs[2], inputs[-1]
111 
112         xp = cuda.get_array_module(*inputs)
113         ge1 = xp.einsum('ik,jkl,il->ij', e2, W, gy)
114         ge2 = xp.einsum('ij,jkl,il->ik', e1, W, gy)
115         gW = xp.einsum('ij,ik,il->jkl', e1, e2, gy)
116 
117         ret = ge1.reshape(inputs[0].shape), ge2.reshape(inputs[1].shape), gW
118 
119         if len(inputs) == 6:
120             V1, V2 = inputs[3], inputs[4]
121             gV1 = e1.T.dot(gy)
122             gV2 = e2.T.dot(gy)
123             gb = gy.sum(0)
124             ge1 += gy.dot(V1.T)
125             ge2 += gy.dot(V2.T)
126             ret += gV1, gV2, gb
127 
128         return ret
129 
130     def backward(self, indexes, grad_outputs):
131         inputs = self.get_retained_inputs()
132 
133         e1 = _as_mat(inputs[0])
134         e2 = _as_mat(inputs[1])
135         W, gy = inputs[2], inputs[-1]
136 
137         gge1 = _as_mat(grad_outputs[0])
138         gge2 = _as_mat(grad_outputs[1])
139         ggW = grad_outputs[2]
140 
141         dge1_de2 = _ij_il_jkl_to_ik(gge1, gy, W)
142         dge1_dW = _ij_ik_il_to_jkl(gge1, e2, gy)
143         dge1_dgy = _ij_ik_jkl_to_il(gge1, e2, W)
144 
145         dge2_de1 = _ik_il_jkl_to_ij(gge2, gy, W)
146         dge2_dW = _ij_ik_il_to_jkl(e1, gge2, gy)
147         dge2_dgy = _ij_ik_jkl_to_il(e1, gge2, W)
148 
149         dgW_de1 = _ik_il_jkl_to_ij(e2, gy, ggW)
150         dgW_de2 = _ij_il_jkl_to_ik(e1, gy, ggW)
151         dgW_dgy = _ij_ik_jkl_to_il(e1, e2, ggW)
152 
153         ge1 = dgW_de1 + dge2_de1
154         ge2 = dgW_de2 + dge1_de2
155         gW = dge1_dW + dge2_dW
156         ggy = dgW_dgy + dge1_dgy + dge2_dgy
157 
158         if len(inputs) == 6:
159             V1, V2 = inputs[3], inputs[4]
160             ggV1, ggV2, ggb = grad_outputs[3:]
161 
162             gV1 = chainer.functions.matmul(gge1, gy, transa=True)
163             gV2 = chainer.functions.matmul(gge2, gy, transa=True)
164 
165             ge1 += chainer.functions.matmul(gy, ggV1, transb=True)
166             ge2 += chainer.functions.matmul(gy, ggV2, transb=True)
167             ggy += chainer.functions.matmul(gge1, V1)
168             ggy += chainer.functions.matmul(gge2, V2)
169             ggy += chainer.functions.matmul(e1, ggV1)
170             ggy += chainer.functions.matmul(e2, ggV2)
171             ggy += chainer.functions.broadcast_to(ggb, ggy.shape)
172 
173         ge1 = ge1.reshape(inputs[0].shape)
174         ge2 = ge2.reshape(inputs[1].shape)
175 
176         if len(inputs) == 6:
177             return ge1, ge2, gW, gV1, gV2, ggy
178         return ge1, ge2, gW, ggy
179 
180 
181 def bilinear(e1, e2, W, V1=None, V2=None, b=None):
182     """Applies a bilinear function based on given parameters.
183 
184     This is a building block of Neural Tensor Network (see the reference paper
185     below). It takes two input variables and one or four parameters, and
186     outputs one variable.
187 
188     To be precise, denote six input arrays mathematically by
189     :math:`e^1\\in \\mathbb{R}^{I\\cdot J}`,
190     :math:`e^2\\in \\mathbb{R}^{I\\cdot K}`,
191     :math:`W\\in \\mathbb{R}^{J \\cdot K \\cdot L}`,
192     :math:`V^1\\in \\mathbb{R}^{J \\cdot L}`,
193     :math:`V^2\\in \\mathbb{R}^{K \\cdot L}`, and
194     :math:`b\\in \\mathbb{R}^{L}`,
195     where :math:`I` is mini-batch size.
196     In this document, we call :math:`V^1`, :math:`V^2`, and :math:`b` linear
197     parameters.
198 
199     The output of forward propagation is calculated as
200 
201     .. math::
202 
203       y_{il} = \\sum_{jk} e^1_{ij} e^2_{ik} W_{jkl} + \\
204         \\sum_{j} e^1_{ij} V^1_{jl} + \\sum_{k} e^2_{ik} V^2_{kl} + b_{l}.
205 
206     Note that V1, V2, b are optional. If these are not given, then this
207     function omits the last three terms in the above equation.
208 
209     .. note::
210 
211        This function accepts an input variable ``e1`` or ``e2`` of a non-matrix
212        array. In this case, the leading dimension is treated as the batch
213        dimension, and the other dimensions are reduced to one dimension.
214 
215     .. note::
216 
217        In the original paper, :math:`J` and :math:`K`
218        must be equal and the author denotes :math:`[V^1 V^2]`
219        (concatenation of matrices) by :math:`V`.
220 
221     Args:
222         e1 (~chainer.Variable): Left input variable.
223         e2 (~chainer.Variable): Right input variable.
224         W (~chainer.Variable): Quadratic weight variable.
225         V1 (~chainer.Variable): Left coefficient variable.
226         V2 (~chainer.Variable): Right coefficient variable.
227         b (~chainer.Variable): Bias variable.
228 
229     Returns:
230         ~chainer.Variable: Output variable.
231 
232     See:
233         `Reasoning With Neural Tensor Networks for Knowledge Base Completion
234         <https://papers.nips.cc/paper/5028-reasoning-with-neural-tensor-
235         networks-for-knowledge-base-completion>`_ [Socher+, NIPS2013].
236 
237     """
238     flags = [V1 is None, V2 is None, b is None]
239     if any(flags):
240         if not all(flags):
241             raise ValueError('All coefficients and bias for bilinear() must '
242                              'be None, if at least one of them is None.')
243         return BilinearFunction().apply((e1, e2, W))[0]
244     return BilinearFunction().apply((e1, e2, W, V1, V2, b))[0]
245
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/chainer/functions/connection/bilinear.py b/chainer/functions/connection/bilinear.py
--- a/chainer/functions/connection/bilinear.py
+++ b/chainer/functions/connection/bilinear.py
@@ -80,7 +80,19 @@
         W = inputs[2]
 
         xp = cuda.get_array_module(*inputs)
-        y = xp.einsum('ij,ik,jkl->il', e1, e2, W)
+        if xp is numpy:
+            y = numpy.einsum('ij,ik,jkl->il', e1, e2, W)
+        else:
+            i_len, j_len = e1.shape
+            k_len = e2.shape[1]
+            # 'ij,ik->ijk'
+            e1e2 = e1[:, :, None] * e2[:, None, :]
+            # ijk->i[jk]
+            e1e2 = e1e2.reshape(i_len, j_len * k_len)
+            # jkl->[jk]l
+            W_mat = W.reshape(-1, W.shape[2])
+            # 'i[jk],[jk]l->il'
+            y = e1e2.dot(W_mat)
 
         if len(inputs) == 6:
             V1, V2, b = inputs[3:]
@@ -110,9 +122,23 @@
         W, gy = inputs[2], inputs[-1]
 
         xp = cuda.get_array_module(*inputs)
-        ge1 = xp.einsum('ik,jkl,il->ij', e2, W, gy)
-        ge2 = xp.einsum('ij,jkl,il->ik', e1, W, gy)
-        gW = xp.einsum('ij,ik,il->jkl', e1, e2, gy)
+        if xp is numpy:
+            gW = numpy.einsum('ij,ik,il->jkl', e1, e2, gy)
+            ge1 = numpy.einsum('ik,jkl,il->ij', e2, W, gy)
+            ge2 = numpy.einsum('ij,jkl,il->ik', e1, W, gy)
+        else:
+            kern = cuda.reduce('T in0, T in1, T in2', 'T out',
+                               'in0 * in1 * in2', 'a + b', 'out = a', 0,
+                               'bilinear_product')
+
+            e1_b = e1[:, :, None, None]  # ij
+            e2_b = e2[:, None, :, None]  # ik
+            gy_b = gy[:, None, None, :]  # il
+            W_b = W[None, :, :, :]  # jkl
+
+            gW = kern(e1_b, e2_b, gy_b, axis=0)  # 'ij,ik,il->jkl'
+            ge1 = kern(e2_b, W_b, gy_b, axis=(2, 3))  # 'ik,jkl,il->ij'
+            ge2 = kern(e1_b, W_b, gy_b, axis=(1, 3))  # 'ij,jkl,il->ik'
 
         ret = ge1.reshape(inputs[0].shape), ge2.reshape(inputs[1].shape), gW

{"golden_diff": "diff --git a/chainer/functions/connection/bilinear.py b/chainer/functions/connection/bilinear.py\n--- a/chainer/functions/connection/bilinear.py\n+++ b/chainer/functions/connection/bilinear.py\n@@ -80,7 +80,19 @@\n         W = inputs[2]\n \n         xp = cuda.get_array_module(*inputs)\n-        y = xp.einsum('ij,ik,jkl->il', e1, e2, W)\n+        if xp is numpy:\n+            y = numpy.einsum('ij,ik,jkl->il', e1, e2, W)\n+        else:\n+            i_len, j_len = e1.shape\n+            k_len = e2.shape[1]\n+            # 'ij,ik->ijk'\n+            e1e2 = e1[:, :, None] * e2[:, None, :]\n+            # ijk->i[jk]\n+            e1e2 = e1e2.reshape(i_len, j_len * k_len)\n+            # jkl->[jk]l\n+            W_mat = W.reshape(-1, W.shape[2])\n+            # 'i[jk],[jk]l->il'\n+            y = e1e2.dot(W_mat)\n \n         if len(inputs) == 6:\n             V1, V2, b = inputs[3:]\n@@ -110,9 +122,23 @@\n         W, gy = inputs[2], inputs[-1]\n \n         xp = cuda.get_array_module(*inputs)\n-        ge1 = xp.einsum('ik,jkl,il->ij', e2, W, gy)\n-        ge2 = xp.einsum('ij,jkl,il->ik', e1, W, gy)\n-        gW = xp.einsum('ij,ik,il->jkl', e1, e2, gy)\n+        if xp is numpy:\n+            gW = numpy.einsum('ij,ik,il->jkl', e1, e2, gy)\n+            ge1 = numpy.einsum('ik,jkl,il->ij', e2, W, gy)\n+            ge2 = numpy.einsum('ij,jkl,il->ik', e1, W, gy)\n+        else:\n+            kern = cuda.reduce('T in0, T in1, T in2', 'T out',\n+                               'in0 * in1 * in2', 'a + b', 'out = a', 0,\n+                               'bilinear_product')\n+\n+            e1_b = e1[:, :, None, None]  # ij\n+            e2_b = e2[:, None, :, None]  # ik\n+            gy_b = gy[:, None, None, :]  # il\n+            W_b = W[None, :, :, :]  # jkl\n+\n+            gW = kern(e1_b, e2_b, gy_b, axis=0)  # 'ij,ik,il->jkl'\n+            ge1 = kern(e2_b, W_b, gy_b, axis=(2, 3))  # 'ik,jkl,il->ij'\n+            ge2 = kern(e1_b, W_b, gy_b, axis=(1, 3))  # 'ij,jkl,il->ik'\n \n         ret = ge1.reshape(inputs[0].shape), ge2.reshape(inputs[1].shape), gW\n", "issue": "F.bilinear requires huge GPU memory\nThe following code results `cupy.cuda.memory.OutOfMemoryError: out of memory to allocate 18014398509481984 bytes (total 18014399785863168 bytes)`\r\n```\r\nimport chainer\r\nimport cupy\r\nb = chainer.links.Bilinear(256, 256, 256).to_gpu()\r\ne1 = cupy.random.randn(64, 256).astype('f')\r\ne2 = cupy.random.randn(64, 256).astype('f')\r\ny = b(e1, e2)\r\nprint(y)\r\n```\r\n\r\nHow to fix: merge cupy/cupy#1218 (or do not use `cupy.einsum`).\r\nI confirmed the code run in ~5sec with\r\n- chainer: master(6bab773dec70f291108ab2575622805252f9a208)\r\n- cupy: (Merge: cupy/cupy@6162f9a cupy/cupy@7f89bd0)\n", "before_files": [{"content": "import numpy\n\nimport chainer\nfrom chainer.backends import cuda\nfrom chainer import function_node\nfrom chainer.utils import type_check\n\n\ndef _as_mat(x):\n    if x.ndim == 2:\n        return x\n    return x.reshape(len(x), -1)\n\n\ndef _ij_ik_il_to_jkl(a, b, c):\n    ab = chainer.functions.matmul(a[:, :, None], b[:, None, :])  # ijk\n    return chainer.functions.matmul(_as_mat(ab).T, c).reshape(\n        a.shape[1], b.shape[1], c.shape[1])\n\n\ndef _ij_ik_jkl_to_il(a, b, c):\n    ab = chainer.functions.matmul(a[:, :, None], b[:, None, :])  # ijk\n    c = c.reshape(-1, c.shape[-1])  # [jk]l\n    return chainer.functions.matmul(_as_mat(ab), c)\n\n\ndef _ij_il_jkl_to_ik(a, b, c):\n    return _ij_ik_jkl_to_il(a, b, chainer.functions.swapaxes(c, 1, 2))\n\n\ndef _ik_il_jkl_to_ij(a, b, c):\n    return _ij_ik_jkl_to_il(a, b, chainer.functions.rollaxis(c, 0, c.ndim))\n\n\nclass BilinearFunction(function_node.FunctionNode):\n    def check_type_forward(self, in_types):\n        n_in = type_check.eval(in_types.size())\n        if n_in != 3 and n_in != 6:\n            raise type_check.InvalidType(\n                '{0} or {1}'.format(\n                    in_types.size() == 3, in_types.size() == 6),\n                '{0} == {1}'.format(in_types.size(), n_in))\n\n        e1_type, e2_type, W_type = in_types[:3]\n        type_check_prod = type_check.make_variable(numpy.prod, 'prod')\n        type_check.expect(\n            e1_type.dtype == numpy.float32,\n            e1_type.ndim >= 2,\n            e2_type.dtype == numpy.float32,\n            e2_type.ndim >= 2,\n            e1_type.shape[0] == e2_type.shape[0],\n            W_type.dtype == numpy.float32,\n            W_type.ndim == 3,\n            type_check_prod(e1_type.shape[1:]) == W_type.shape[0],\n            type_check_prod(e2_type.shape[1:]) == W_type.shape[1],\n        )\n\n        if n_in == 6:\n            out_size = W_type.shape[2]\n            V1_type, V2_type, b_type = in_types[3:]\n            type_check.expect(\n                V1_type.dtype == numpy.float32,\n                V1_type.ndim == 2,\n                V1_type.shape[0] == W_type.shape[0],\n                V1_type.shape[1] == out_size,\n                V2_type.dtype == numpy.float32,\n                V2_type.ndim == 2,\n                V2_type.shape[0] == W_type.shape[1],\n                V2_type.shape[1] == out_size,\n                b_type.dtype == numpy.float32,\n                b_type.ndim == 1,\n                b_type.shape[0] == out_size,\n            )\n\n    def forward(self, inputs):\n        self.retain_inputs(tuple(range(len(inputs))))\n\n        e1 = _as_mat(inputs[0])\n        e2 = _as_mat(inputs[1])\n        W = inputs[2]\n\n        xp = cuda.get_array_module(*inputs)\n        y = xp.einsum('ij,ik,jkl->il', e1, e2, W)\n\n        if len(inputs) == 6:\n            V1, V2, b = inputs[3:]\n            y += e1.dot(V1)\n            y += e2.dot(V2)\n            y += b\n        return y,\n\n    def backward(self, indexes, grad_outputs):\n        inputs = self.get_retained_inputs()\n        e1, e2, W = inputs[:3]\n        gy, = grad_outputs\n\n        if len(inputs) == 6:\n            V1, V2 = inputs[3], inputs[4]\n            return BilinearFunctionGrad().apply((e1, e2, W, V1, V2, gy))\n        return BilinearFunctionGrad().apply((e1, e2, W, gy))\n\n\nclass BilinearFunctionGrad(function_node.FunctionNode):\n\n    def forward(self, inputs):\n        self.retain_inputs(tuple(range(len(inputs))))\n\n        e1 = _as_mat(inputs[0])\n        e2 = _as_mat(inputs[1])\n        W, gy = inputs[2], inputs[-1]\n\n        xp = cuda.get_array_module(*inputs)\n        ge1 = xp.einsum('ik,jkl,il->ij', e2, W, gy)\n        ge2 = xp.einsum('ij,jkl,il->ik', e1, W, gy)\n        gW = xp.einsum('ij,ik,il->jkl', e1, e2, gy)\n\n        ret = ge1.reshape(inputs[0].shape), ge2.reshape(inputs[1].shape), gW\n\n        if len(inputs) == 6:\n            V1, V2 = inputs[3], inputs[4]\n            gV1 = e1.T.dot(gy)\n            gV2 = e2.T.dot(gy)\n            gb = gy.sum(0)\n            ge1 += gy.dot(V1.T)\n            ge2 += gy.dot(V2.T)\n            ret += gV1, gV2, gb\n\n        return ret\n\n    def backward(self, indexes, grad_outputs):\n        inputs = self.get_retained_inputs()\n\n        e1 = _as_mat(inputs[0])\n        e2 = _as_mat(inputs[1])\n        W, gy = inputs[2], inputs[-1]\n\n        gge1 = _as_mat(grad_outputs[0])\n        gge2 = _as_mat(grad_outputs[1])\n        ggW = grad_outputs[2]\n\n        dge1_de2 = _ij_il_jkl_to_ik(gge1, gy, W)\n        dge1_dW = _ij_ik_il_to_jkl(gge1, e2, gy)\n        dge1_dgy = _ij_ik_jkl_to_il(gge1, e2, W)\n\n        dge2_de1 = _ik_il_jkl_to_ij(gge2, gy, W)\n        dge2_dW = _ij_ik_il_to_jkl(e1, gge2, gy)\n        dge2_dgy = _ij_ik_jkl_to_il(e1, gge2, W)\n\n        dgW_de1 = _ik_il_jkl_to_ij(e2, gy, ggW)\n        dgW_de2 = _ij_il_jkl_to_ik(e1, gy, ggW)\n        dgW_dgy = _ij_ik_jkl_to_il(e1, e2, ggW)\n\n        ge1 = dgW_de1 + dge2_de1\n        ge2 = dgW_de2 + dge1_de2\n        gW = dge1_dW + dge2_dW\n        ggy = dgW_dgy + dge1_dgy + dge2_dgy\n\n        if len(inputs) == 6:\n            V1, V2 = inputs[3], inputs[4]\n            ggV1, ggV2, ggb = grad_outputs[3:]\n\n            gV1 = chainer.functions.matmul(gge1, gy, transa=True)\n            gV2 = chainer.functions.matmul(gge2, gy, transa=True)\n\n            ge1 += chainer.functions.matmul(gy, ggV1, transb=True)\n            ge2 += chainer.functions.matmul(gy, ggV2, transb=True)\n            ggy += chainer.functions.matmul(gge1, V1)\n            ggy += chainer.functions.matmul(gge2, V2)\n            ggy += chainer.functions.matmul(e1, ggV1)\n            ggy += chainer.functions.matmul(e2, ggV2)\n            ggy += chainer.functions.broadcast_to(ggb, ggy.shape)\n\n        ge1 = ge1.reshape(inputs[0].shape)\n        ge2 = ge2.reshape(inputs[1].shape)\n\n        if len(inputs) == 6:\n            return ge1, ge2, gW, gV1, gV2, ggy\n        return ge1, ge2, gW, ggy\n\n\ndef bilinear(e1, e2, W, V1=None, V2=None, b=None):\n    \"\"\"Applies a bilinear function based on given parameters.\n\n    This is a building block of Neural Tensor Network (see the reference paper\n    below). It takes two input variables and one or four parameters, and\n    outputs one variable.\n\n    To be precise, denote six input arrays mathematically by\n    :math:`e^1\\\\in \\\\mathbb{R}^{I\\\\cdot J}`,\n    :math:`e^2\\\\in \\\\mathbb{R}^{I\\\\cdot K}`,\n    :math:`W\\\\in \\\\mathbb{R}^{J \\\\cdot K \\\\cdot L}`,\n    :math:`V^1\\\\in \\\\mathbb{R}^{J \\\\cdot L}`,\n    :math:`V^2\\\\in \\\\mathbb{R}^{K \\\\cdot L}`, and\n    :math:`b\\\\in \\\\mathbb{R}^{L}`,\n    where :math:`I` is mini-batch size.\n    In this document, we call :math:`V^1`, :math:`V^2`, and :math:`b` linear\n    parameters.\n\n    The output of forward propagation is calculated as\n\n    .. math::\n\n      y_{il} = \\\\sum_{jk} e^1_{ij} e^2_{ik} W_{jkl} + \\\\\n        \\\\sum_{j} e^1_{ij} V^1_{jl} + \\\\sum_{k} e^2_{ik} V^2_{kl} + b_{l}.\n\n    Note that V1, V2, b are optional. If these are not given, then this\n    function omits the last three terms in the above equation.\n\n    .. note::\n\n       This function accepts an input variable ``e1`` or ``e2`` of a non-matrix\n       array. In this case, the leading dimension is treated as the batch\n       dimension, and the other dimensions are reduced to one dimension.\n\n    .. note::\n\n       In the original paper, :math:`J` and :math:`K`\n       must be equal and the author denotes :math:`[V^1 V^2]`\n       (concatenation of matrices) by :math:`V`.\n\n    Args:\n        e1 (~chainer.Variable): Left input variable.\n        e2 (~chainer.Variable): Right input variable.\n        W (~chainer.Variable): Quadratic weight variable.\n        V1 (~chainer.Variable): Left coefficient variable.\n        V2 (~chainer.Variable): Right coefficient variable.\n        b (~chainer.Variable): Bias variable.\n\n    Returns:\n        ~chainer.Variable: Output variable.\n\n    See:\n        `Reasoning With Neural Tensor Networks for Knowledge Base Completion\n        <https://papers.nips.cc/paper/5028-reasoning-with-neural-tensor-\n        networks-for-knowledge-base-completion>`_ [Socher+, NIPS2013].\n\n    \"\"\"\n    flags = [V1 is None, V2 is None, b is None]\n    if any(flags):\n        if not all(flags):\n            raise ValueError('All coefficients and bias for bilinear() must '\n                             'be None, if at least one of them is None.')\n        return BilinearFunction().apply((e1, e2, W))[0]\n    return BilinearFunction().apply((e1, e2, W, V1, V2, b))[0]\n", "path": "chainer/functions/connection/bilinear.py"}], "after_files": [{"content": "import numpy\n\nimport chainer\nfrom chainer.backends import cuda\nfrom chainer import function_node\nfrom chainer.utils import type_check\n\n\ndef _as_mat(x):\n    if x.ndim == 2:\n        return x\n    return x.reshape(len(x), -1)\n\n\ndef _ij_ik_il_to_jkl(a, b, c):\n    ab = chainer.functions.matmul(a[:, :, None], b[:, None, :])  # ijk\n    return chainer.functions.matmul(_as_mat(ab).T, c).reshape(\n        a.shape[1], b.shape[1], c.shape[1])\n\n\ndef _ij_ik_jkl_to_il(a, b, c):\n    ab = chainer.functions.matmul(a[:, :, None], b[:, None, :])  # ijk\n    c = c.reshape(-1, c.shape[-1])  # [jk]l\n    return chainer.functions.matmul(_as_mat(ab), c)\n\n\ndef _ij_il_jkl_to_ik(a, b, c):\n    return _ij_ik_jkl_to_il(a, b, chainer.functions.swapaxes(c, 1, 2))\n\n\ndef _ik_il_jkl_to_ij(a, b, c):\n    return _ij_ik_jkl_to_il(a, b, chainer.functions.rollaxis(c, 0, c.ndim))\n\n\nclass BilinearFunction(function_node.FunctionNode):\n    def check_type_forward(self, in_types):\n        n_in = type_check.eval(in_types.size())\n        if n_in != 3 and n_in != 6:\n            raise type_check.InvalidType(\n                '{0} or {1}'.format(\n                    in_types.size() == 3, in_types.size() == 6),\n                '{0} == {1}'.format(in_types.size(), n_in))\n\n        e1_type, e2_type, W_type = in_types[:3]\n        type_check_prod = type_check.make_variable(numpy.prod, 'prod')\n        type_check.expect(\n            e1_type.dtype == numpy.float32,\n            e1_type.ndim >= 2,\n            e2_type.dtype == numpy.float32,\n            e2_type.ndim >= 2,\n            e1_type.shape[0] == e2_type.shape[0],\n            W_type.dtype == numpy.float32,\n            W_type.ndim == 3,\n            type_check_prod(e1_type.shape[1:]) == W_type.shape[0],\n            type_check_prod(e2_type.shape[1:]) == W_type.shape[1],\n        )\n\n        if n_in == 6:\n            out_size = W_type.shape[2]\n            V1_type, V2_type, b_type = in_types[3:]\n            type_check.expect(\n                V1_type.dtype == numpy.float32,\n                V1_type.ndim == 2,\n                V1_type.shape[0] == W_type.shape[0],\n                V1_type.shape[1] == out_size,\n                V2_type.dtype == numpy.float32,\n                V2_type.ndim == 2,\n                V2_type.shape[0] == W_type.shape[1],\n                V2_type.shape[1] == out_size,\n                b_type.dtype == numpy.float32,\n                b_type.ndim == 1,\n                b_type.shape[0] == out_size,\n            )\n\n    def forward(self, inputs):\n        self.retain_inputs(tuple(range(len(inputs))))\n\n        e1 = _as_mat(inputs[0])\n        e2 = _as_mat(inputs[1])\n        W = inputs[2]\n\n        xp = cuda.get_array_module(*inputs)\n        if xp is numpy:\n            y = numpy.einsum('ij,ik,jkl->il', e1, e2, W)\n        else:\n            i_len, j_len = e1.shape\n            k_len = e2.shape[1]\n            # 'ij,ik->ijk'\n            e1e2 = e1[:, :, None] * e2[:, None, :]\n            # ijk->i[jk]\n            e1e2 = e1e2.reshape(i_len, j_len * k_len)\n            # jkl->[jk]l\n            W_mat = W.reshape(-1, W.shape[2])\n            # 'i[jk],[jk]l->il'\n            y = e1e2.dot(W_mat)\n\n        if len(inputs) == 6:\n            V1, V2, b = inputs[3:]\n            y += e1.dot(V1)\n            y += e2.dot(V2)\n            y += b\n        return y,\n\n    def backward(self, indexes, grad_outputs):\n        inputs = self.get_retained_inputs()\n        e1, e2, W = inputs[:3]\n        gy, = grad_outputs\n\n        if len(inputs) == 6:\n            V1, V2 = inputs[3], inputs[4]\n            return BilinearFunctionGrad().apply((e1, e2, W, V1, V2, gy))\n        return BilinearFunctionGrad().apply((e1, e2, W, gy))\n\n\nclass BilinearFunctionGrad(function_node.FunctionNode):\n\n    def forward(self, inputs):\n        self.retain_inputs(tuple(range(len(inputs))))\n\n        e1 = _as_mat(inputs[0])\n        e2 = _as_mat(inputs[1])\n        W, gy = inputs[2], inputs[-1]\n\n        xp = cuda.get_array_module(*inputs)\n        if xp is numpy:\n            gW = numpy.einsum('ij,ik,il->jkl', e1, e2, gy)\n            ge1 = numpy.einsum('ik,jkl,il->ij', e2, W, gy)\n            ge2 = numpy.einsum('ij,jkl,il->ik', e1, W, gy)\n        else:\n            kern = cuda.reduce('T in0, T in1, T in2', 'T out',\n                               'in0 * in1 * in2', 'a + b', 'out = a', 0,\n                               'bilinear_product')\n\n            e1_b = e1[:, :, None, None]  # ij\n            e2_b = e2[:, None, :, None]  # ik\n            gy_b = gy[:, None, None, :]  # il\n            W_b = W[None, :, :, :]  # jkl\n\n            gW = kern(e1_b, e2_b, gy_b, axis=0)  # 'ij,ik,il->jkl'\n            ge1 = kern(e2_b, W_b, gy_b, axis=(2, 3))  # 'ik,jkl,il->ij'\n            ge2 = kern(e1_b, W_b, gy_b, axis=(1, 3))  # 'ij,jkl,il->ik'\n\n        ret = ge1.reshape(inputs[0].shape), ge2.reshape(inputs[1].shape), gW\n\n        if len(inputs) == 6:\n            V1, V2 = inputs[3], inputs[4]\n            gV1 = e1.T.dot(gy)\n            gV2 = e2.T.dot(gy)\n            gb = gy.sum(0)\n            ge1 += gy.dot(V1.T)\n            ge2 += gy.dot(V2.T)\n            ret += gV1, gV2, gb\n\n        return ret\n\n    def backward(self, indexes, grad_outputs):\n        inputs = self.get_retained_inputs()\n\n        e1 = _as_mat(inputs[0])\n        e2 = _as_mat(inputs[1])\n        W, gy = inputs[2], inputs[-1]\n\n        gge1 = _as_mat(grad_outputs[0])\n        gge2 = _as_mat(grad_outputs[1])\n        ggW = grad_outputs[2]\n\n        dge1_de2 = _ij_il_jkl_to_ik(gge1, gy, W)\n        dge1_dW = _ij_ik_il_to_jkl(gge1, e2, gy)\n        dge1_dgy = _ij_ik_jkl_to_il(gge1, e2, W)\n\n        dge2_de1 = _ik_il_jkl_to_ij(gge2, gy, W)\n        dge2_dW = _ij_ik_il_to_jkl(e1, gge2, gy)\n        dge2_dgy = _ij_ik_jkl_to_il(e1, gge2, W)\n\n        dgW_de1 = _ik_il_jkl_to_ij(e2, gy, ggW)\n        dgW_de2 = _ij_il_jkl_to_ik(e1, gy, ggW)\n        dgW_dgy = _ij_ik_jkl_to_il(e1, e2, ggW)\n\n        ge1 = dgW_de1 + dge2_de1\n        ge2 = dgW_de2 + dge1_de2\n        gW = dge1_dW + dge2_dW\n        ggy = dgW_dgy + dge1_dgy + dge2_dgy\n\n        if len(inputs) == 6:\n            V1, V2 = inputs[3], inputs[4]\n            ggV1, ggV2, ggb = grad_outputs[3:]\n\n            gV1 = chainer.functions.matmul(gge1, gy, transa=True)\n            gV2 = chainer.functions.matmul(gge2, gy, transa=True)\n\n            ge1 += chainer.functions.matmul(gy, ggV1, transb=True)\n            ge2 += chainer.functions.matmul(gy, ggV2, transb=True)\n            ggy += chainer.functions.matmul(gge1, V1)\n            ggy += chainer.functions.matmul(gge2, V2)\n            ggy += chainer.functions.matmul(e1, ggV1)\n            ggy += chainer.functions.matmul(e2, ggV2)\n            ggy += chainer.functions.broadcast_to(ggb, ggy.shape)\n\n        ge1 = ge1.reshape(inputs[0].shape)\n        ge2 = ge2.reshape(inputs[1].shape)\n\n        if len(inputs) == 6:\n            return ge1, ge2, gW, gV1, gV2, ggy\n        return ge1, ge2, gW, ggy\n\n\ndef bilinear(e1, e2, W, V1=None, V2=None, b=None):\n    \"\"\"Applies a bilinear function based on given parameters.\n\n    This is a building block of Neural Tensor Network (see the reference paper\n    below). It takes two input variables and one or four parameters, and\n    outputs one variable.\n\n    To be precise, denote six input arrays mathematically by\n    :math:`e^1\\\\in \\\\mathbb{R}^{I\\\\cdot J}`,\n    :math:`e^2\\\\in \\\\mathbb{R}^{I\\\\cdot K}`,\n    :math:`W\\\\in \\\\mathbb{R}^{J \\\\cdot K \\\\cdot L}`,\n    :math:`V^1\\\\in \\\\mathbb{R}^{J \\\\cdot L}`,\n    :math:`V^2\\\\in \\\\mathbb{R}^{K \\\\cdot L}`, and\n    :math:`b\\\\in \\\\mathbb{R}^{L}`,\n    where :math:`I` is mini-batch size.\n    In this document, we call :math:`V^1`, :math:`V^2`, and :math:`b` linear\n    parameters.\n\n    The output of forward propagation is calculated as\n\n    .. math::\n\n      y_{il} = \\\\sum_{jk} e^1_{ij} e^2_{ik} W_{jkl} + \\\\\n        \\\\sum_{j} e^1_{ij} V^1_{jl} + \\\\sum_{k} e^2_{ik} V^2_{kl} + b_{l}.\n\n    Note that V1, V2, b are optional. If these are not given, then this\n    function omits the last three terms in the above equation.\n\n    .. note::\n\n       This function accepts an input variable ``e1`` or ``e2`` of a non-matrix\n       array. In this case, the leading dimension is treated as the batch\n       dimension, and the other dimensions are reduced to one dimension.\n\n    .. note::\n\n       In the original paper, :math:`J` and :math:`K`\n       must be equal and the author denotes :math:`[V^1 V^2]`\n       (concatenation of matrices) by :math:`V`.\n\n    Args:\n        e1 (~chainer.Variable): Left input variable.\n        e2 (~chainer.Variable): Right input variable.\n        W (~chainer.Variable): Quadratic weight variable.\n        V1 (~chainer.Variable): Left coefficient variable.\n        V2 (~chainer.Variable): Right coefficient variable.\n        b (~chainer.Variable): Bias variable.\n\n    Returns:\n        ~chainer.Variable: Output variable.\n\n    See:\n        `Reasoning With Neural Tensor Networks for Knowledge Base Completion\n        <https://papers.nips.cc/paper/5028-reasoning-with-neural-tensor-\n        networks-for-knowledge-base-completion>`_ [Socher+, NIPS2013].\n\n    \"\"\"\n    flags = [V1 is None, V2 is None, b is None]\n    if any(flags):\n        if not all(flags):\n            raise ValueError('All coefficients and bias for bilinear() must '\n                             'be None, if at least one of them is None.')\n        return BilinearFunction().apply((e1, e2, W))[0]\n    return BilinearFunction().apply((e1, e2, W, V1, V2, b))[0]\n", "path": "chainer/functions/connection/bilinear.py"}]}