Repository: chainer/chainer

Issue:
Non-parameterized linear without bias
Hello all. 

I'm sorry if this has been already reported.

According to the document, chainer looks to allow non-parameterized linear function call without bias (`b=None`), but when I try it, python raises an attribute error, which says "NoneType object has no attribute volatile."

This is because `chainer.functions.linear()` has `b=None` and `chainer.function.__call__()` tries to check `b.volatile` at line 132. 

A minor change in `chainer.functions.linear()` like:

```
if b is None:
    return NonparameterizedLinear()(x, W)
else:
    return NonparameterizedLinear()(x, W, b)
```

instead of just doing:

```
return NonparameterizedLinear()(x, W, b)
```

may fix the problem. 



Discussion:
Thank you for the report! It seems a bug. We will fix it.


Resolving patch:
diff --git a/chainer/functions/connection/convolution_2d.py b/chainer/functions/connection/convolution_2d.py
--- a/chainer/functions/connection/convolution_2d.py
+++ b/chainer/functions/connection/convolution_2d.py
@@ -341,13 +341,16 @@ def check_type_forward(self, in_types):
 
     def forward(self, x):
         W = x[1]
-        b = None
         if len(x) == 3:
-            b = x[2]
-        func = Convolution2D(
-            W.shape[1], W.shape[0], W.shape[2:],
-            stride=self.stride, pad=self.pad, use_cudnn=self.use_cudnn,
-            initialW=W, initial_bias=b)
+            func = Convolution2D(
+                W.shape[1], W.shape[0], W.shape[2:],
+                stride=self.stride, pad=self.pad, use_cudnn=self.use_cudnn,
+                initialW=W, initial_bias=x[2])
+        else:
+            func = Convolution2D(
+                W.shape[1], W.shape[0], W.shape[2:],
+                stride=self.stride, pad=self.pad, use_cudnn=self.use_cudnn,
+                initialW=W, nobias=True)
         self.func = func
         if any(isinstance(i, cuda.ndarray) for i in x):
             func.to_gpu()
@@ -368,7 +371,7 @@ def convolution_2d(x, W, b=None, stride=1, pad=0, use_cudnn=True):
     Args:
         x (~chainer.Variable): Input variable.
         W (~chainer.Variable): Weight variable.
-        b (~chainer.Variable): Bias  variable.
+        b (~chainer.Variable): Bias  variable (optional).
         stride (int or (int, int)): Stride of filter applications.
             ``stride=s`` and ``stride=(s, s)`` are equivalent.
         pad (int or (int, int)): Spatial padding width for input arrays.
@@ -381,5 +384,9 @@ def convolution_2d(x, W, b=None, stride=1, pad=0, use_cudnn=True):
     .. seealso:: :class:`Convolution2D`
 
     """
-    return NonparameterizedConvolution2D(
-        stride=stride, pad=pad, use_cudnn=use_cudnn)(x, W, b)
+    if b is None:
+        return NonparameterizedConvolution2D(
+            stride=stride, pad=pad, use_cudnn=use_cudnn)(x, W)
+    else:
+        return NonparameterizedConvolution2D(
+            stride=stride, pad=pad, use_cudnn=use_cudnn)(x, W, b)
diff --git a/chainer/functions/connection/linear.py b/chainer/functions/connection/linear.py
--- a/chainer/functions/connection/linear.py
+++ b/chainer/functions/connection/linear.py
@@ -52,7 +52,6 @@ class Linear(function.Function):
        and the other dimensions are reduced to one dimension.
 
     """
-
     def __init__(self, in_size, out_size, wscale=1, bias=0, nobias=False,
                  initialW=None, initial_bias=None):
         self.W = None
@@ -160,12 +159,13 @@ def check_type_forward(self, in_types):
 
     def forward(self, x):
         W = x[1]
-        b = None
-        if len(x) == 3:
-            b = x[2]
         out_size, in_size = W.shape
-        func = Linear(
-            in_size, out_size, initialW=W, initial_bias=b)
+        if len(x) == 3:
+            func = Linear(
+                in_size, out_size, initialW=W, initial_bias=x[2])
+        else:
+            func = Linear(
+                in_size, out_size, initialW=W, nobias=True)
         self.func = func
         if any(isinstance(i, cuda.ndarray) for i in x):
             func.to_gpu()
@@ -180,13 +180,13 @@ def backward(self, x, gy):
         return (gx[0], func.gW, func.gb)
 
 
-def linear(x, W, b=None, stride=1, pad=0, use_cudnn=True):
+def linear(x, W, b=None):
     """Nonparameterized linear function.
 
     Args:
         x (~chainer.Variable): Input variable.
         W (~chainer.Variable): Weight variable.
-        b (~chainer.Variable): Bias variable.
+        b (~chainer.Variable): Bias variable (optional).
 
     Returns:
         ~chainer.Variable: Output variable.
@@ -194,5 +194,7 @@ def linear(x, W, b=None, stride=1, pad=0, use_cudnn=True):
     .. seealso:: :class:`Linear`
 
     """
-
-    return NonparameterizedLinear()(x, W, b)
+    if b is None:
+        return NonparameterizedLinear()(x, W)
+    else:
+        return NonparameterizedLinear()(x, W, b)


Test patch:
diff --git a/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py b/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py
--- a/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py
+++ b/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py
@@ -143,17 +143,17 @@ def setUp(self, use_cudnn=True):
                                        (2, 2, 2, 2)).astype(numpy.float32)
 
     @attr.cudnn
-    def test_forward_consistency(self):
+    def test_forward_consistency(self, nobias=False):
         x_cpu = chainer.Variable(self.x)
         W_cpu = chainer.Variable(self.W)
-        b_cpu = chainer.Variable(self.b)
+        b_cpu = None if nobias else chainer.Variable(self.b)
         y_cpu = functions.convolution_2d(
             x_cpu, W_cpu, b_cpu, stride=self.stride, pad=self.pad,
             use_cudnn=self.use_cudnn)
 
         x_gpu = chainer.Variable(cuda.to_gpu(self.x))
         W_gpu = chainer.Variable(cuda.to_gpu(self.W))
-        b_gpu = chainer.Variable(cuda.to_gpu(self.b))
+        b_gpu = None if nobias else chainer.Variable(cuda.to_gpu(self.b))
         y_gpu = functions.convolution_2d(
             x_gpu, W_gpu, b_gpu, stride=self.stride, pad=self.pad,
             use_cudnn=self.use_cudnn)
@@ -165,41 +165,68 @@ def test_forward_consistency_im2col(self):
         self.use_cudnn = False
         self.test_forward_consistency()
 
+    @attr.gpu
+    def test_forward_consistency_im2col_nobias(self):
+        self.use_cudnn = False
+        self.test_forward_consistency(nobias=True)
+
     def check_backward(self, x_data, W_data, b_data, y_grad):
         x = chainer.Variable(x_data)
         W = chainer.Variable(W_data)
-        b = chainer.Variable(b_data)
-
-        y = functions.convolution_2d(x, W, b, stride=self.stride, pad=self.pad,
-                                     use_cudnn=self.use_cudnn)
+        b = None if b_data is None else chainer.Variable(b_data)
+        y = functions.convolution_2d(
+            x, W, b, stride=self.stride, pad=self.pad,
+            use_cudnn=self.use_cudnn)
 
         y.grad = y_grad
         y.backward()
 
         func = y.creator
-        f = lambda: func.forward((x.data, W.data, b.data))
-        gx, gW, gb = gradient_check.numerical_grad(
-            f, (x.data, W.data, b.data), (y.grad,), eps=1e-2)
+        if b is None:
+            f = lambda: func.forward((x.data, W.data))
+            gx, gW = gradient_check.numerical_grad(
+                f, (x.data, W.data), (y.grad,), eps=1e-2)
+        else:
+            f = lambda: func.forward((x.data, W.data, b.data))
+            gx, gW, gb = gradient_check.numerical_grad(
+                f, (x.data, W.data, b.data), (y.grad,), eps=1e-2)
 
         gradient_check.assert_allclose(gx, x.grad)
         gradient_check.assert_allclose(gW, W.grad)
-        gradient_check.assert_allclose(gb, b.grad)
+        if b is not None:
+            gradient_check.assert_allclose(gb, b.grad)
 
     @condition.retry(3)
     def test_backward_cpu(self):
         self.check_backward(self.x, self.W, self.b, self.gy)
 
+    @condition.retry(3)
+    def test_backward_cpu_nobias(self):
+        self.check_backward(self.x, self.W, None, self.gy)
+
     @attr.cudnn
     @condition.retry(3)
     def test_backward_gpu(self):
         self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.W),
                             cuda.to_gpu(self.b), cuda.to_gpu(self.gy))
 
+    @attr.cudnn
+    @condition.retry(3)
+    def test_backward_gpu_nobias(self):
+        self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.W),
+                            None, cuda.to_gpu(self.gy))
+
     @attr.gpu
     @condition.retry(3)
     def test_backward_gpu_im2col(self):
         self.use_cudnn = False
         self.test_backward_gpu()
 
+    @attr.gpu
+    @condition.retry(3)
+    def test_backward_gpu_im2col_nobias(self):
+        self.use_cudnn = False
+        self.test_backward_gpu_nobias()
+
 
 testing.run_module(__name__, __file__)
diff --git a/tests/chainer_tests/functions_tests/connection_tests/test_linear.py b/tests/chainer_tests/functions_tests/connection_tests/test_linear.py
--- a/tests/chainer_tests/functions_tests/connection_tests/test_linear.py
+++ b/tests/chainer_tests/functions_tests/connection_tests/test_linear.py
@@ -109,8 +109,11 @@ def setUp(self):
     def check_forward(self, x_data, W_data, b_data, y_expect):
         x = chainer.Variable(x_data)
         W = chainer.Variable(W_data)
-        b = chainer.Variable(b_data)
-        y = functions.linear(x, W, b)
+        if b_data is None:
+            y = functions.linear(x, W)
+        else:
+            b = chainer.Variable(b_data)
+            y = functions.linear(x, W, b)
         gradient_check.assert_allclose(y_expect, y.data)
 
     @condition.retry(3)
@@ -118,6 +121,10 @@ def test_forward_cpu(self):
         self.check_forward(self.x, self.W, self.b,
                            self.x.dot(self.W.T) + self.b)
 
+    @condition.retry(3)
+    def test_forward_cpu_nobias(self):
+        self.check_forward(self.x, self.W, None, self.x.dot(self.W.T))
+
     @attr.gpu
     @condition.retry(3)
     def test_forward_gpu(self):
@@ -125,32 +132,55 @@ def test_forward_gpu(self):
             cuda.to_gpu(self.x), cuda.to_gpu(self.W), cuda.to_gpu(self.b),
             cuda.to_gpu(self.x.dot(self.W.T) + self.b))
 
+    @attr.gpu
+    @condition.retry(3)
+    def test_forward_gpu_nobias(self):
+        self.check_forward(
+            cuda.to_gpu(self.x), cuda.to_gpu(self.W), None,
+            cuda.to_gpu(self.x.dot(self.W.T)))
+
     def check_backward(self, x_data, W_data, b_data, y_grad):
         x = chainer.Variable(x_data)
         W = chainer.Variable(W_data)
-        b = chainer.Variable(b_data)
+        b = None if b_data is None else chainer.Variable(b_data)
         y = functions.linear(x, W, b)
         y.grad = y_grad
         y.backward()
 
         func = y.creator
-        f = lambda: func.forward((x.data, W.data, b.data))
-        gx, gW, gb = gradient_check.numerical_grad(
-            f, (x.data, W.data, b.data), (y.grad,), eps=1e-2)
+        if b_data is None:
+            f = lambda: func.forward((x.data, W.data))
+            gx, gW = gradient_check.numerical_grad(
+                f, (x.data, W.data), (y.grad,), eps=1e-2)
+        else:
+            f = lambda: func.forward((x.data, W.data, b.data))
+            gx, gW, gb = gradient_check.numerical_grad(
+                f, (x.data, W.data, b.data), (y.grad,), eps=1e-2)
 
         gradient_check.assert_allclose(gx, x.grad)
         gradient_check.assert_allclose(gW, W.grad)
-        gradient_check.assert_allclose(gb, b.grad)
+        if b_data is not None:
+            gradient_check.assert_allclose(gb, b.grad)
 
     @condition.retry(3)
     def test_backward_cpu(self):
         self.check_backward(self.x, self.W, self.b, self.gy)
 
+    @condition.retry(3)
+    def test_backward_cpu_nobias(self):
+        self.check_backward(self.x, self.W, None, self.gy)
+
     @attr.gpu
     @condition.retry(3)
     def test_backward_gpu(self):
         self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.W),
                             cuda.to_gpu(self.b), cuda.to_gpu(self.gy))
 
+    @attr.gpu
+    @condition.retry(3)
+    def test_backward_gpu_nobias(self):
+        self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.W),
+                            None, cuda.to_gpu(self.gy))
+
 
 testing.run_module(__name__, __file__)