changeset 745:fc85ce33b518

FillMissing can now impute missing values by an array instead of a single constant
author Olivier Delalleau <delallea@iro>
date Tue, 02 Jun 2009 10:14:00 -0400
parents 4d22396678e6
children 6117969dd37f
files pylearn/algorithms/sandbox/DAA_inputs_groups.py pylearn/sandbox/scan_inputs_groups.py
diffstat 2 files changed, 44 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/pylearn/algorithms/sandbox/DAA_inputs_groups.py	Mon Jun 01 17:44:56 2009 -0400
+++ b/pylearn/algorithms/sandbox/DAA_inputs_groups.py	Tue Jun 02 10:14:00 2009 -0400
@@ -7,7 +7,7 @@
 from theano.tensor.nnet import sigmoid
 
 from pylearn.sandbox.scan_inputs_groups import scaninputs, scandotdec, scandotenc, scannoise, scanbiasdec, \
-        scanmaskenc,scanmaskdec, fill_missing_with_zeros, mask_gradient
+        scanmaskenc,scanmaskdec, FillMissing, mask_gradient
 
 from pylearn.algorithms import cost
 from pylearn.algorithms.logistic_regression import LogRegN
@@ -47,18 +47,21 @@
                 in_size=None, auxin_size= None, n_hid=1,
                 regularize = False, tie_weights = False, hid_fn = 'sigmoid_act',
                 reconstruction_cost_function=cost.cross_entropy, interface = True,
-                ignore_missing=False,
+                ignore_missing=None,
                 **init):
         """
         :param regularize: WRITEME
         :param tie_weights: WRITEME
         :param hid_fn: WRITEME
         :param reconstruction_cost: Should return one cost per example (row)
-        :param ignore_missing: if True, the input will be scanned in order to
-            detect missing values, and these values will be replaced by zeros.
-            Also, the reconstruction cost's gradient will be computed only on
-            non missing components.
-            If False, the presence of missing values may cause crashes or other
+        :param ignore_missing: if not None, the input will be scanned in order
+            to detect missing values, and these values will be replaced. Also,
+            the reconstruction cost's gradient will be computed only on non
+            missing components. The value of this parameter indicates how to
+            replace missing values:
+                - some numpy.ndarray: value of this array at the same index
+                - a constant: this same value everywhere
+            If None, the presence of missing values may cause crashes or other
             weird and unexpected behavior.
             Please note that this option only affects the permanent input, not
             auxilary ones (that should never contain missing values). In fact,
@@ -91,8 +94,8 @@
         
         ### DECLARE MODEL VARIABLES and default
         self.input = input
-        if self.ignore_missing and self.input is not None:
-            no_missing = fill_missing_with_zeros(self.input)
+        if self.ignore_missing is not None and self.input is not None:
+            no_missing = FillMissing(self.ignore_missing)(self.input)
             self.input = no_missing[0]  # Missing values replaced by zeros.
             self.input_missing_mask = no_missing[1] # Missingness pattern.
         else:
@@ -152,7 +155,7 @@
         container.hidden = self.hid_fn(container.hidden_activation)
         self.define_propdown(container, idx_list , auxinput)
         container.rec = self.hid_fn(container.rec_activation)
-        if self.ignore_missing and self.input is not None:
+        if self.ignore_missing is not None and self.input is not None:
             # Apply mask to gradient to ensure we do not backpropagate on the
             # cost computed on missing inputs (that were replaced with zeros).
             container.rec = mask_gradient(container.rec,
@@ -340,7 +343,7 @@
                 regularize = False, tie_weights = False, hid_fn = 'sigmoid_act',
                 reconstruction_cost_function=cost.cross_entropy,
                 n_out = 2, target = None, debugmethod = False, totalupdatebool=False,
-                ignore_missing=False,
+                ignore_missing=None,
                 **init):
         
         super(StackedDAAig, self).__init__()
--- a/pylearn/sandbox/scan_inputs_groups.py	Mon Jun 01 17:44:56 2009 -0400
+++ b/pylearn/sandbox/scan_inputs_groups.py	Tue Jun 02 10:14:00 2009 -0400
@@ -567,23 +567,37 @@
     """
     Given an input, output two elements:
         - a copy of the input where missing values (NaN) are replaced by some
-        constant (zero by default)
+        other value (zero by default)
         - a mask of the same size and type as input, where each element is zero
         iff the corresponding input is missing
-    Currently, the gradient is computed as if the input value was really zero.
-    It may be safer to replace the gradient w.r.t. missing values with either
-    zeros or missing values (?).
+    The 'fill_with' parameter may either be:
+        - a scalar: all missing values are replaced with this value
+        - a Numpy array: a missing value is replaced by the value in this array
+        at the same position (ignoring the first k dimensions if 'fill_with'
+        has k less dimensions than the input)
+    Currently, the gradient is computed as if the input value was really what
+    it was replaced with. It may be safer to replace the gradient w.r.t.
+    missing values with either zeros or missing values (?).
     """
 
-    def __init__(self, constant_val=0):
+    def __init__(self, fill_with=0):
         super(Op, self).__init__()
-        self.constant_val = constant_val
+        self.fill_with = fill_with
+        self.fill_with_is_array = isinstance(self.fill_with, numpy.ndarray)
 
     def __eq__(self, other):
-        return type(self) == type(other) and (self.constant_val == other.constant_val)
+        return (type(self) == type(other) and
+                self.fill_with_is_array == other.fill_with_is_array and
+                ((self.fill_with_is_array and 
+                    (self.fill_with == other.fill_with).all()) or
+                    self.fill_with == other.fill_with))
 
-	def __hash__(self):
-		return hash(type(self))^hash(self.constant_val)
+    def __hash__(self):
+        if self.fill_with_is_array:
+            fill_hash = self.fill_with.__hash__()
+        else:
+            fill_hash = hash(self.fill_with)
+        return hash(type(self))^hash(self.fill_with_is_array)^fill_hash
 	
     def make_node(self, input):
         return Apply(self, [input], [input.type(), input.type()])
@@ -595,9 +609,15 @@
         mask = output_storage[1]
         mask[0] = numpy.ones(input.shape)
         mask = mask[0]
+        if self.fill_with_is_array:
+            ignore_k = len(out.shape) - len(self.fill_with.shape)
+            assert ignore_k >= 0
         for (idx, v) in numpy.ndenumerate(out):
             if numpy.isnan(v):
-                out[idx] = self.constant_val
+                if self.fill_with_is_array:
+                    out[idx] = self.fill_with[idx[ignore_k:]]
+                else:
+                    out[idx] = self.fill_with
                 mask[idx] = 0
 
     def grad(self, inputs, (out_grad, mask_grad, )):