# HG changeset patch # User Olivier Delalleau # Date 1243266337 14400 # Node ID 573e3370d0fa7c415a9f62fe708e4b75b4b8de0e # Parent 88f5b75a4afe38707c10313e9b3e0f4760e3d7ab# Parent fb9fb142098fe2b264f17e44343cf81ca6dbf5bf Merged diff -r fb9fb142098f -r 573e3370d0fa pylearn/algorithms/sandbox/DAA_inputs_groups.py --- a/pylearn/algorithms/sandbox/DAA_inputs_groups.py Sun May 24 17:16:38 2009 -0400 +++ b/pylearn/algorithms/sandbox/DAA_inputs_groups.py Mon May 25 11:45:37 2009 -0400 @@ -46,12 +46,26 @@ def __init__(self, input = None, auxinput = None, 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,**init): + reconstruction_cost_function=cost.cross_entropy, interface = True, + ignore_missing=False, + **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 will be computed only on non missing + components. + If False, 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, + in the current implementation, auxiliary inputs cannot be used when + this option is True. + Another side effect of the current crappy way it is implemented is + that the reconstruction cost is not properly computed. :todo: Default noise level for all daa levels """ print '\t\t**** DAAig.__init__ ****' @@ -72,12 +86,19 @@ self.tie_weights = tie_weights self.reconstruction_cost_function = reconstruction_cost_function self.interface = interface + self.ignore_missing = ignore_missing assert hid_fn in ('sigmoid_act','tanh_act','softsign_act') self.hid_fn = eval(hid_fn) ### 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) + self.input = no_missing[0] # Missing values replaced by zeros. + self.input_missing_mask = no_missing[1] # Missingness pattern. + else: + self.input_missing_mask = None self.noisy_input = None self.auxinput = auxinput self.idx_list = T.ivector('idx_list') if self.auxinput is not None else None @@ -242,6 +263,18 @@ return self.random.binomial(T.shape(self.input), 1, 1 - self.noise_level) * self.input def reconstruction_costs(self, rec): + if self.ignore_missing and self.input is not None: + # Note: the following code is very ugly. It is just a hack to + # ensure that the gradient w.r.t. missing coordinates is (close to) + # zero. It is neither efficient nor elegant. + # The idea is to put a very big negative value in the + # reconstruction for these missing inputs (whose target is 0), so + # that the gradient is 1/(1 - rec) ~= 0. + # This will in particular screw up the cost computations. + zero = rec * 0 + rec = (rec * T.neq(self.input_missing_mask, zero) + + (zero - 1e100) * T.eq(self.input_missing_mask, zero)) + if (self.input is not None) and (self.auxinput is not None): return self.reconstruction_cost_function(T.join(1,self.input,scaninputs(self.idx_list,self.auxinput)), rec) if self.input is not None: diff -r fb9fb142098f -r 573e3370d0fa pylearn/sandbox/scan_inputs_groups.py --- a/pylearn/sandbox/scan_inputs_groups.py Sun May 24 17:16:38 2009 -0400 +++ b/pylearn/sandbox/scan_inputs_groups.py Mon May 25 11:45:37 2009 -0400 @@ -560,3 +560,69 @@ scanmaskenc=ScanMask(True) scanmaskdec=ScanMask(False) + +# TODO The classes FillMissing and MaskSelect below should probably be moved +# to another (more appropriate) file. +class FillMissing(Op): + """ + Given an input, output two elements: + - a copy of the input where missing values (NaN) are replaced by some + constant (zero by default) + - a boolean (actually int8) mask of the same size as input, where each + element is True (i.e. 1) iff the corresponding input is not missing + """ + + def __init__(self, constant_val=0): + super(Op, self).__init__() + self.constant_val = constant_val + + def __eq__(self, other): + return type(self) == type(other) and (self.constant_val == other.constant_val) + + def __hash__(self): + return hash(type(self))^hash(self.constant_val) + + def make_node(self, input): + return Apply(self, [input], [input.type(), T.bmatrix()]) + + def perform(self, node, inputs, output_storage): + input = inputs[0] + out = output_storage[0] + out[0] = input.copy() + out = out[0] + mask = output_storage[1] + mask[0] = numpy.ones(input.shape, dtype = numpy.int8) + mask = mask[0] + for (idx, v) in numpy.ndenumerate(out): + if numpy.isnan(v): + out[idx] = self.constant_val + mask[idx] = 0 + +fill_missing_with_zeros = FillMissing(0) + +class MaskSelect(Op): + """ + Given an input x and a mask m (both vectors), outputs a vector that + contains all elements x[i] such that bool(m[i]) is True. + """ + + def __eq__(self, other): + return type(self) == type(other) + + def __hash__(self): + return hash(type(self)) + + def make_node(self, input, mask): + return Apply(self, [input, mask], [input.type()]) + + def perform(self, node, (input, mask), (output, )): + select = [] + for (i, m) in enumerate(mask): + if bool(m): + select.append(i) + output[0] = numpy.zeros(len(select), dtype = input.dtype) + out = output[0] + for (i, j) in enumerate(select): + out[i] = input[j] + +mask_select = MaskSelect()