Repository: SMART-Lab/smartlearner

Issue:
Move tests at the root of the library.
That way test won't appear in the autocomplete of your preferred python IDE.

Discussion:

Patch:
diff --git a/smartlearner/batch_scheduler.py b/smartlearner/batch_scheduler.py
index 664cfb5..99f33ec 100644
--- a/smartlearner/batch_scheduler.py
+++ b/smartlearner/batch_scheduler.py
@@ -26,6 +26,11 @@ class MiniBatchScheduler(BatchScheduler):
         self.batch_size = batch_size
         self.shared_batch_count = theano.shared(np.array(0, dtype='i4'))
 
+        # Keep only `batch_size` examples as test values.
+        self.dataset.symb_inputs.tag.test_value = self.dataset.inputs.get_value()[:batch_size]
+        if self.dataset.has_targets:
+            self.dataset.symb_targets.tag.test_value = self.dataset.targets.get_value()[:batch_size]
+
     @property
     def batch_size(self):
         return self._shared_batch_size.get_value()
diff --git a/smartlearner/interfaces/dataset.py b/smartlearner/interfaces/dataset.py
index d2d0a4c..c67da77 100644
--- a/smartlearner/interfaces/dataset.py
+++ b/smartlearner/interfaces/dataset.py
@@ -1,13 +1,48 @@
-import theano
+import numpy as np
+
+import theano.tensor as T
+
+from smartlearner.utils import sharedX
 
 
 class Dataset(object):
+    """ Dataset interface.
+
+    Attributes
+    ----------
+    symb_inputs : `theano.tensor.TensorType` object
+        Symbolic variables representing the inputs.
+    symb_targets : `theano.tensor.TensorType` object or None
+        Symbolic variables representing the targets.
+
+    Notes
+    -----
+    `symb_inputs` and `symb_targets` have test value already tagged to them. Use
+    THEANO_FLAGS="compute_test_value=warn" to use them.
+    """
     def __init__(self, inputs, targets=None, name="dataset"):
+        """
+        Parameters
+        ----------
+        inputs : ndarray
+            Training examples
+        targets : ndarray (optional)
+            Target for each training example.
+        name : str (optional)
+            The name of the dataset is used to name Theano variables. Default: 'dataset'.
+        """
         self.name = name
         self.inputs = inputs
         self.targets = targets
-        self.symb_inputs = theano.tensor.matrix(name=self.name+'_inputs')
-        self.symb_targets = theano.tensor.matrix(name=self.name+'_targets')
+        self.symb_inputs = T.TensorVariable(type=T.TensorType("floatX", [False]*self.inputs.ndim),
+                                            name=self.name+'_symb_inputs')
+        self.symb_inputs.tag.test_value = self.inputs.get_value()  # For debugging Theano graphs.
+
+        self.symb_targets = None
+        if self.has_targets:
+            self.symb_targets = T.TensorVariable(type=T.TensorType("floatX", [False]*self.targets.ndim),
+                                                 name=self.name+'_symb_targets')
+            self.symb_targets.tag.test_value = self.targets.get_value()  # For debugging Theano graphs.
 
     @property
     def inputs(self):
@@ -15,7 +50,7 @@ class Dataset(object):
 
     @inputs.setter
     def inputs(self, value):
-        self._inputs_shared = theano.shared(value, name=self.name + "_inputs", borrow=True)
+        self._inputs_shared = sharedX(value, name=self.name+"_inputs")
 
     @property
     def targets(self):
@@ -24,20 +59,37 @@ class Dataset(object):
     @targets.setter
     def targets(self, value):
         if value is not None:
-            self._targets_shared = theano.shared(value, name=self.name + "_targets", borrow=True)
+            self._targets_shared = sharedX(np.array(value), name=self.name+"_targets")
         else:
             self._targets_shared = None
 
+    @property
+    def has_targets(self):
+        return self.targets is not None
+
+    @property
+    def input_shape(self):
+        return self.inputs.get_value().shape[1:]
+
+    @property
+    def target_shape(self):
+        if self.has_targets:
+            return self.targets.get_value().shape[1:]
+
+        return None
+
     @property
     def input_size(self):
-        return self.inputs.get_value().shape[-1]
+        # TODO: is this property really useful? If needed one could just call directly `dataset.input_shape[-1]`.
+        return self.input_shape[-1]
 
     @property
     def target_size(self):
-        if self.targets is None:
-            return 0
-        else:
-            return self.targets.get_value().shape[-1]
+        # TODO: is this property really useful? If needed one could just call directly `dataset.target_shape[-1]`.
+        if self.has_targets:
+            return self.target_shape[-1]
+
+        return None
 
     def __len__(self):
         return len(self.inputs.get_value())


Test patch:
diff --git a/tests/interfaces/test_dataset.py b/tests/interfaces/test_dataset.py
new file mode 100644
index 0000000..6378b93
--- /dev/null
+++ b/tests/interfaces/test_dataset.py
@@ -0,0 +1,140 @@
+import numpy as np
+import theano
+import theano.tensor as T
+
+from nose.tools import assert_true
+from numpy.testing import assert_equal, assert_array_equal
+
+from smartlearner.interfaces.dataset import Dataset
+
+floatX = theano.config.floatX
+ALL_DTYPES = np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float']
+
+
+def test_dataset_used_in_theano_function():
+    rng = np.random.RandomState(1234)
+
+    nb_examples = 10
+
+    inputs = (rng.randn(nb_examples, 5) * 100).astype(floatX)
+    targets = (rng.randn(nb_examples, 1) > 0.5).astype(floatX)
+    dataset = Dataset(inputs, targets)
+
+    input_sqr_norm = T.sum(dataset.symb_inputs**2)
+    result = input_sqr_norm - dataset.symb_targets
+    f = theano.function([dataset.symb_inputs, dataset.symb_targets], result)
+
+    assert_array_equal(f(inputs, targets), np.sum(inputs**2)-targets)
+
+
+def test_dataset_without_targets():
+    rng = np.random.RandomState(1234)
+
+    nb_examples = 10
+    nb_features = 3
+    sequences_length = 4
+    nb_channels = 2
+    image_shape = (5, 5)
+
+    # Test creating dataset with different example shapes:
+    # scalar feature, vector features, sequence of vector features, multiple channels images features.
+    for example_shape in [(), (nb_features,), (sequences_length, nb_features), (nb_channels,)+image_shape]:
+        inputs_shape = (nb_examples,) + example_shape
+
+        for dtype in ALL_DTYPES:
+            inputs = (rng.randn(*inputs_shape) * 100).astype(dtype)
+            dataset = Dataset(inputs)
+
+            # Data should be converted into `floatX`.
+            assert_equal(dataset.inputs.dtype, floatX)
+            assert_equal(dataset.symb_inputs.dtype, floatX)
+            assert_equal(dataset.symb_inputs.ndim, inputs.ndim)
+            assert_equal(dataset.input_shape, example_shape)
+            assert_array_equal(dataset.inputs.get_value(), inputs.astype(floatX))
+
+            # Everything related to target should be None
+            assert_true(dataset.targets is None)
+            assert_true(dataset.symb_targets is None)
+            assert_true(dataset.target_shape is None)
+            assert_true(dataset.target_size is None)
+
+    # Create dataset from nested Pyton lists.
+    inputs = [[1, 2, 3]] * nb_examples
+    dataset = Dataset(inputs)
+    # Data should be converted into `floatX`.
+    assert_equal(dataset.inputs.dtype, floatX)
+    assert_equal(dataset.symb_inputs.dtype, floatX)
+    assert_equal(dataset.symb_inputs.ndim, 2)
+    assert_equal(dataset.input_shape, (3,))
+    assert_array_equal(dataset.inputs.get_value(), np.array(inputs, dtype=floatX))
+
+
+def test_dataset_with_targets():
+    rng = np.random.RandomState(1234)
+
+    nb_examples = 10
+    nb_features = 3
+    sequences_length = 4
+    nb_channels = 2
+    image_shape = (5, 5)
+
+    # Test creating dataset with different example shapes and target shapes:
+    # scalar feature, vector features, sequence of vector features, multiple channels images features.
+    for target_shape in [(), (nb_features,), (sequences_length, nb_features), (nb_channels,)+image_shape]:
+        for example_shape in [(), (nb_features,), (sequences_length, nb_features), (nb_channels,)+image_shape]:
+            inputs_shape = (nb_examples,) + example_shape
+            targets_shape = (nb_examples,) + target_shape
+
+            for example_dtype in ALL_DTYPES:
+                for target_dtype in ALL_DTYPES:
+                    inputs = (rng.randn(*inputs_shape) * 100).astype(example_dtype)
+                    targets = (rng.randn(*targets_shape) * 100).astype(target_dtype)
+                    dataset = Dataset(inputs, targets)
+
+                    # Data should be converted into `floatX`.
+                    assert_equal(dataset.inputs.dtype, floatX)
+                    assert_equal(dataset.symb_inputs.dtype, floatX)
+                    assert_equal(dataset.symb_inputs.ndim, inputs.ndim)
+                    assert_equal(dataset.input_shape, example_shape)
+                    assert_array_equal(dataset.inputs.get_value(), inputs.astype(floatX))
+
+                    assert_equal(dataset.targets.dtype, floatX)
+                    assert_equal(dataset.symb_targets.dtype, floatX)
+                    assert_equal(dataset.symb_targets.ndim, targets.ndim)
+                    assert_equal(dataset.target_shape, target_shape)
+                    assert_array_equal(dataset.targets.get_value(), targets.astype(floatX))
+
+    # Create dataset from nested Pyton lists.
+    inputs = [[1, 2, 3]] * nb_examples
+    targets = [[1, 2, 3]] * nb_examples
+    dataset = Dataset(inputs, targets)
+    # Data should be converted into `floatX`.
+    assert_equal(dataset.inputs.dtype, floatX)
+    assert_equal(dataset.symb_inputs.dtype, floatX)
+    assert_equal(dataset.symb_inputs.ndim, 2)
+    assert_equal(dataset.input_shape, (3,))
+    assert_array_equal(dataset.inputs.get_value(), np.array(inputs, dtype=floatX))
+
+    assert_equal(dataset.targets.dtype, floatX)
+    assert_equal(dataset.symb_targets.dtype, floatX)
+    assert_equal(dataset.symb_targets.ndim, 2)
+    assert_equal(dataset.target_shape, (3,))
+    assert_array_equal(dataset.targets.get_value(), np.array(targets, dtype=floatX))
+
+
+def test_dataset_with_test_value():
+    rng = np.random.RandomState(1234)
+
+    nb_examples = 10
+
+    theano.config.compute_test_value = 'warn'
+    try:
+        inputs = (rng.randn(nb_examples, 5) * 100).astype(floatX)
+        targets = (rng.randn(nb_examples, 1) > 0.5).astype(floatX)
+        dataset = Dataset(inputs, targets)
+
+        input_sqr_norm = T.sum(dataset.symb_inputs**2)
+        result = input_sqr_norm - dataset.symb_targets
+        assert_array_equal(result.tag.test_value, np.sum(inputs**2)-targets)
+    finally:
+        theano.config.compute_test_value = 'off'