Repository: chainer/chainer

Issue:
`Function` which get no input cannot select `volatile`
In `Function.__call__`, it checks `volatile` of all input variables, and the same value is used. But, `Function` which get not argument such as `Parameter`, always selects `volatile=False`. it may causes conflict of volatility.

https://github.com/pfnet/chainer/blob/master/chainer/function.py#L132
`Parameter` always returns `False` here.



Discussion:
The simplest solution is defining `Parameter.__call__` with an optional argument `volatile`.

OK, i agree with you.


Resolving patch:
diff --git a/chainer/functions/parameter.py b/chainer/functions/parameter.py
--- a/chainer/functions/parameter.py
+++ b/chainer/functions/parameter.py
@@ -22,6 +22,13 @@ def __init__(self, array):
         self.W = array
         self.gW = numpy.empty_like(array)
 
+    def __call__(self, volatile=False):
+        ret = super(Parameter, self).__call__()
+        if volatile:
+            ret.unchain_backward()
+        ret.volatile = volatile
+        return ret
+
     def check_type_forward(self, in_types):
         type_check.expect(in_types.size() == 0)
 


Test patch:
diff --git a/tests/functions_tests/test_parameter.py b/tests/functions_tests/test_parameter.py
--- a/tests/functions_tests/test_parameter.py
+++ b/tests/functions_tests/test_parameter.py
@@ -26,34 +26,84 @@ def tearDown(self):
     def to_gpu(self):
         self.func.to_gpu()
 
-    def check_forward(self):
-        y = self.func()
+    def check_forward(self, volatile):
+        y = self.func(volatile)
         self.assertEqual(y.data.dtype, numpy.float32)
         self.assertTrue((self.W == cuda.to_cpu(y.data)).all())
 
     def test_forward_cpu(self):
-        self.check_forward()
+        self.check_forward(False)
+
+    def test_forward_cpu_volatile(self):
+        self.check_forward(True)
 
     @attr.gpu
     def test_forward_gpu(self):
         self.to_gpu()
-        self.check_forward()
+        self.check_forward(False)
+
+    @attr.gpu
+    def test_forward_gpu_volatile(self):
+        self.to_gpu()
+        self.check_forward(True)
 
-    def check_backward(self, y_grad):
+    def check_backward(self, y_grad, volatile):
         self.func.gW.fill(0)
-        y = self.func()
+        y = self.func(volatile)
         y.grad = y_grad
         y.backward()
-        self.assertTrue(
-            (cuda.to_cpu(y_grad) == cuda.to_cpu(self.func.gW)).all())
+        if volatile:
+            self.assertTrue(
+                (cuda.to_cpu(self.func.gW) == numpy.zeros_like(y_grad)).all())
+        else:
+            self.assertTrue(
+                (cuda.to_cpu(self.func.gW) == cuda.to_cpu(y_grad)).all())
 
     def test_backward_cpu(self):
-        self.check_backward(self.gW)
+        self.check_backward(self.gW, False)
+
+    def test_backward_cpu_volatile(self):
+        self.check_backward(self.gW, True)
 
     @attr.gpu
     def test_backward_gpu(self):
         self.to_gpu()
-        self.check_backward(cuda.to_gpu(self.gW))
+        self.check_backward(cuda.to_gpu(self.gW), False)
+
+    @attr.gpu
+    def test_backward_gpu_volatile(self):
+        self.to_gpu()
+        self.check_backward(cuda.to_gpu(self.gW), True)
+
+
+class TestVolatile(unittest.TestCase):
+
+    def setUp(self):
+        self.W = numpy.random.uniform(-1, 1, (4, 3)).astype(numpy.float32)
+        self.func = functions.Parameter(self.W)
+
+    def tearDown(self):
+        del self.func
+
+    def check_volatile(self, volatile):
+        y = self.func(volatile=volatile)
+        self.assertEqual(y.volatile, volatile)
+
+    def test_volatile_cpu_volatile(self):
+        self.check_volatile(True)
+
+    def test_volatile_cpu_not_volatile(self):
+        self.check_volatile(False)
+
+    @attr.gpu
+    def test_volatile_gpu_volatile(self):
+        self.func.to_gpu()
+        self.check_volatile(True)
+
+    @attr.gpu
+    def test_volatile_gpu_not_volatile(self):
+        self.func.to_gpu()
+        self.check_volatile(False)
 
 
 testing.run_module(__name__, __file__)