1
2
3
4
5
6
7
8
9 """Data mapper"""
10
11 __docformat__ = 'restructuredtext'
12
13 import numpy as N
14
15 from mvpa.mappers.metric import Metric
16
17 from mvpa.misc.vproperty import VProperty
18 from mvpa.base.dochelpers import enhancedDocString
19
20 if __debug__:
21 from mvpa.base import warning
22 from mvpa.base import debug
23
24
26 """Interface to provide mapping between two spaces: IN and OUT.
27 Methods are prefixed correspondingly. forward/reverse operate
28 on the entire dataset. get(In|Out)Id[s] operate per element::
29
30 forward
31 --------->
32 IN OUT
33 <--------/
34 reverse
35 """
37 """
38 :Parameters:
39 metric : Metric
40 Optional metric
41 """
42 self.__metric = None
43 """Pylint happiness"""
44 self.setMetric(metric)
45 """Actually assign the metric"""
46
47
48
49
50
51
52
53
55 """Map data from the IN dataspace into OUT space.
56 """
57 raise NotImplementedError
58
59
61 """Reverse map data from OUT space into the IN space.
62 """
63 raise NotImplementedError
64
65
67 """Returns the size of the entity in input space"""
68 raise NotImplementedError
69
70
72 """Returns the size of the entity in output space"""
73 raise NotImplementedError
74
75
77 """Limit the OUT space to a certain set of features.
78
79 :Parameters:
80 outIds: sequence
81 Subset of ids of the current feature in OUT space to keep.
82 """
83 raise NotImplementedError
84
85
87 """Translate a feature id into a coordinate/index in input space.
88
89 Such a translation might not be meaningful or even possible for a
90 particular mapping algorithm and therefore cannot be relied upon.
91 """
92 raise NotImplementedError
93
94
95
96
97
98
100 """Validate feature id in OUT space.
101
102 Override if OUT space is not simly a 1D vector
103 """
104 return(outId >= 0 and outId < self.getOutSize())
105
106
108 """Validate id in IN space.
109
110 Override if IN space is not simly a 1D vector
111 """
112 return(inId >= 0 and inId < self.getInSize())
113
114
115 - def train(self, dataset):
116 """Perform training of the mapper.
117
118 This method is called to put the mapper in a state that allows it to
119 perform to intended mapping.
120
121 :Parameter:
122 dataset: Dataset or subclass
123
124 .. note::
125 The default behavior of this method is to do nothing.
126 """
127 pass
128
129
131 """Get feature neighbors in input space, given an id in output space.
132
133 This method has to be reimplemented whenever a derived class does not
134 provide an implementation for :meth:`~mvpa.mappers.base.Mapper.getInId`.
135 """
136 if self.metric is None:
137 raise RuntimeError, "No metric was assigned to %s, thus no " \
138 "neighboring information is present" % self
139
140 if self.isValidOutId(outId):
141 inId = self.getInId(outId)
142 for inId in self.getNeighborIn(inId, *args, **kwargs):
143 yield self.getOutId(inId)
144
145
146
147
148
149
151 """Return the list of coordinates for the neighbors.
152
153 :Parameters:
154 inId
155 id (index) of an element in input dataspace.
156 *args, **kwargs
157 Any additional arguments are passed to the embedded metric of the
158 mapper.
159
160 XXX See TODO below: what to return -- list of arrays or list
161 of tuples?
162 """
163 if self.metric is None:
164 raise RuntimeError, "No metric was assigned to %s, thus no " \
165 "neighboring information is present" % self
166
167 isValidInId = self.isValidInId
168 if isValidInId(inId):
169 for neighbor in self.metric.getNeighbor(inId, *args, **kwargs):
170 if isValidInId(neighbor):
171 yield neighbor
172
173
175 """Return the list of coordinates for the neighbors.
176
177 By default it simply constructs the list based on
178 the generator returned by getNeighbor()
179 """
180 return [ x for x in self.getNeighbor(outId, *args, **kwargs) ]
181
182
184 if self.__metric is not None:
185 s = "metric=%s" % repr(self.__metric)
186 else:
187 s = ''
188 return "%s(%s)" % (self.__class__.__name__, s)
189
190
192 """Calls the mappers forward() method.
193 """
194 return self.forward(data)
195
196
198 """To make pylint happy"""
199 return self.__metric
200
201
203 """To make pylint happy"""
204 if metric is not None and not isinstance(metric, Metric):
205 raise ValueError, "metric for Mapper must be an " \
206 "instance of a Metric class . Got %s" \
207 % `type(metric)`
208 self.__metric = metric
209
210
211 metric = property(fget=getMetric, fset=setMetric)
212 nfeatures = VProperty(fget=getOutSize)
213
214
215
217 """Mapper using a projection matrix to transform the data.
218
219 This class cannot be used directly. Sub-classes have to implement
220 the `_train()` method, which has to compute the projection matrix
221 given a dataset (see `_train()` docstring for more information).
222
223 Once the projection matrix is available, this class provides
224 functionality to perform forward and backwards mapping of data, the
225 latter using the hermitian (conjugate) transpose of the projection
226 matrix. Additionally, `ProjectionMapper` supports optional (but done
227 by default) demeaning of the data and selection of arbitrary
228 component (i.e. columns of the projection matrix) of the projection.
229
230 Forward and back-projection matrices (a.k.a. *projection* and
231 *reconstruction*) are available via the `proj` and `recon`
232 properties. the latter only after it has been computed (after first
233 call to `reverse`).
234 """
235
236 - def __init__(self, selector=None, demean=True):
237 """Initialize the ProjectionMapper
238
239 :Parameters:
240 selector: None | list
241 Which components (i.e. columns of the projection matrix)
242 should be used for mapping. If `selector` is `None` all
243 components are used. If a list is provided, all list
244 elements are treated as component ids and the respective
245 components are selected (all others are discarded).
246 demean: bool
247 Either data should be demeaned while computing
248 projections and applied back while doing reverse()
249
250 """
251 Mapper.__init__(self)
252
253 self._selector = selector
254 self._proj = None
255 """Forward projection matrix."""
256 self._recon = None
257 """Reverse projection (reconstruction) matrix."""
258 self._demean = demean
259 """Flag whether to demean the to be projected data, prior to projection.
260 """
261 self._mean = None
262 """Data mean"""
263 self._mean_out = None
264 """Forward projected data mean."""
265
266 __doc__ = enhancedDocString('ProjectionMapper', locals(), Mapper)
267
268
269 - def train(self, dataset):
270 """Determine the projection matrix."""
271
272 self._mean = dataset.samples.mean(axis=0)
273
274 self._train(dataset)
275
276
277 if self._selector is not None:
278 self.selectOut(self._selector)
279
280
282 """Helper which optionally demeans
283 """
284 if self._demean:
285
286 data = data - self._mean
287
288 if __debug__ and "MAP_" in debug.active:
289 debug("MAP_",
290 "%s: Mean of data in input space %s was subtracted" %
291 (self.__class__.__name__, self._mean))
292 return data
293
294
296 """Worker method. Needs to be implemented by subclass.
297
298 This method has to train the mapper and store the resulting
299 transformation matrix in `self._proj`.
300 """
301 raise NotImplementedError
302
303
304 - def forward(self, data, demean=None):
305 """Perform forward projection.
306
307 :Parameters:
308 data: ndarray
309 Data array to map
310 demean: boolean | None
311 Override demean setting for this method call.
312
313 :Returns:
314 NumPy array
315 """
316
317 if demean is None:
318 demean = self._demean
319
320 if self._proj is None:
321 raise RuntimeError, "Mapper needs to be train before used."
322 if demean and self._mean is not None:
323 return ((N.asmatrix(data) - self._mean) * self._proj).A
324 else:
325 return (N.asmatrix(data) * self._proj).A
326
327
329 """Reproject (reconstruct) data into the original feature space.
330
331 :Returns:
332 NumPy array
333 """
334 if self._proj is None:
335 raise RuntimeError, "Mapper needs to be trained before used."
336
337
338 if self._demean and self._mean_out is None:
339
340 self._mean_out = self.forward(self._mean, demean=False)
341 if __debug__:
342 debug("MAP_",
343 "Mean of data in input space %s became %s in " \
344 "outspace" % (self._mean, self._mean_out))
345
346
347
348 if self._recon is None:
349 self._recon = self._proj.H
350
351 if self._demean:
352 return ((N.asmatrix(data) + self._mean_out) * self._recon).A
353 else:
354 return ((N.asmatrix(data)) * self._recon).A
355
356
358 """Returns the number of original features."""
359 return self._proj.shape[0]
360
361
363 """Returns the number of components to project on."""
364 return self._proj.shape[1]
365
366
368 """Choose a subset of components (and remove all others)."""
369 self._proj = self._proj[:, outIds]
370
371 self._recon = None
372 self._mean_out = None
373
374
375 proj = property(fget=lambda self: self._proj, doc="Projection matrix")
376 recon = property(fget=lambda self: self._recon, doc="Backprojection matrix")
377
378
379
381 """Meta mapper that combines several embedded mappers.
382
383 This mapper can be used the map from several input dataspaces into a common
384 output dataspace. When :meth:`~mvpa.mappers.base.CombinedMapper.forward`
385 is called with a sequence of data, each element in that sequence is passed
386 to the corresponding mapper, which in turned forward-maps the data. The
387 output of all mappers is finally stacked (horizontally or column or
388 feature-wise) into a single large 2D matrix (nsamples x nfeatures).
389
390 .. note::
391 This mapper can only embbed mappers that transform data into a 2D
392 (nsamples x nfeatures) representation. For mappers not supporting this
393 transformation, consider wrapping them in a
394 :class:`~mvpa.mappers.base.ChainMapper` with an appropriate
395 post-processing mapper.
396
397 CombinedMapper fully supports forward and backward mapping, training,
398 runtime selection of a feature subset (in output dataspace) and retrieval
399 of neighborhood information.
400 """
402 """
403 :Parameters:
404 mappers: list of Mapper instances
405 The order of the mappers in the list is important, as it will define
406 the order in which data snippets have to be passed to
407 :meth:`~mvpa.mappers.base.CombinedMapper.forward`.
408 **kwargs
409 All additional arguments are passed to the base-class constructor.
410 """
411 Mapper.__init__(self, **kwargs)
412
413 if not len(mappers):
414 raise ValueError, \
415 'CombinedMapper needs at least one embedded mapper.'
416
417 self._mappers = mappers
418
419
421 """Map data from the IN spaces into to common OUT space.
422
423 :Parameter:
424 data: sequence
425 Each element in the `data` sequence is passed to the corresponding
426 embedded mapper and is mapped individually by it. The number of
427 elements in `data` has to match the number of embedded mappers. Each
428 element is `data` has to provide the same number of samples
429 (first dimension).
430
431 :Returns:
432 array: nsamples x nfeatures
433 Horizontally stacked array of all embedded mapper outputs.
434 """
435 if not len(data) == len(self._mappers):
436 raise ValueError, \
437 "CombinedMapper needs a sequence with data for each " \
438 "Mapper"
439
440
441
442 try:
443 return N.hstack(
444 [self._mappers[i].forward(d) for i, d in enumerate(data)])
445 except ValueError:
446 raise ValueError, \
447 "Embedded mappers do not generate same number of samples. " \
448 "Check input data."
449
450
452 """Reverse map data from OUT space into the IN spaces.
453
454 :Parameter:
455 data: array
456 Single data array to be reverse mapped into a sequence of data
457 snippets in their individual IN spaces.
458
459 :Returns:
460 list
461 """
462
463
464
465 data = N.asanyarray(data).T
466
467 if not len(data) == self.getOutSize():
468 raise ValueError, \
469 "Data shape does match mapper reverse mapping properties."
470
471 result = []
472 fsum = 0
473 for m in self._mappers:
474
475 fsum_new = fsum + m.getOutSize()
476
477 result.append(m.reverse(data[fsum:fsum_new].T))
478
479 fsum = fsum_new
480
481 return result
482
483
484 - def train(self, dataset):
485 """Trains all embedded mappers.
486
487 The provided training dataset is splitted appropriately and the
488 corresponding pieces are passed to the
489 :meth:`~mvpa.mappers.base.Mapper.train` method of each embedded mapper.
490
491 :Parameter:
492 dataset: :class:`~mvpa.datasets.base.Dataset` or subclass
493 A dataset with the number of features matching the `outSize` of the
494 `CombinedMapper`.
495 """
496 if dataset.nfeatures != self.getOutSize():
497 raise ValueError, "Training dataset does not match the mapper " \
498 "properties."
499
500 fsum = 0
501 for m in self._mappers:
502
503 fsum_new = fsum + m.getOutSize()
504 m.train(dataset.selectFeatures(range(fsum, fsum_new)))
505 fsum = fsum_new
506
507
509 """Returns the size of the entity in input space"""
510 return N.sum(m.getInSize() for m in self._mappers)
511
512
514 """Returns the size of the entity in output space"""
515 return N.sum(m.getOutSize() for m in self._mappers)
516
517
519 """Remove some elements and leave only ids in 'out'/feature space.
520
521 .. note::
522 The subset selection is done inplace
523
524 :Parameter:
525 outIds: sequence
526 All output feature ids to be selected/kept.
527 """
528
529
530 ids = N.asanyarray(outIds)
531 fsum = 0
532 for m in self._mappers:
533
534 selector = N.logical_and(ids < fsum + m.getOutSize(), ids >= fsum)
535
536 selected = ids[selector] - fsum
537 fsum += m.getOutSize()
538
539 m.selectOut(selected)
540
541
543 """Get the ids of the neighbors of a single feature in output dataspace.
544
545 :Parameters:
546 outId: int
547 Single id of a feature in output space, whos neighbors should be
548 determined.
549 *args, **kwargs
550 Additional arguments are passed to the metric of the embedded
551 mapper, that is responsible for the corresponding feature.
552
553 Returns a list of outIds
554 """
555 fsum = 0
556 for m in self._mappers:
557 fsum_new = fsum + m.getOutSize()
558 if outId >= fsum and outId < fsum_new:
559 return m.getNeighbor(outId - fsum, *args, **kwargs)
560 fsum = fsum_new
561
562 raise ValueError, "Invalid outId passed to CombinedMapper.getNeighbor()"
563
564
566 s = Mapper.__repr__(self).rstrip(' )')
567
568 if not s[-1] == '(':
569 s += ' '
570 s += 'mappers=[%s])' % ', '.join([m.__repr__() for m in self._mappers])
571 return s
572
573
574
576 """Meta mapper that embedded a chain of other mappers.
577
578 Each mapper in the chain is called successively to perform forward or
579 reverse mapping.
580
581 .. note::
582
583 In its current implementation the `ChainMapper` treats all but the last
584 mapper as simple pre-processing (in forward()) or post-processing (in
585 reverse()) steps. All other capabilities, e.g. training and neighbor
586 metrics are provided by or affect *only the last mapper in the chain*.
587
588 With respect to neighbor metrics this means that they are determined
589 based on the input space of the *last mapper* in the chain and *not* on
590 the input dataspace of the `ChainMapper` as a whole
591 """
593 """
594 :Parameters:
595 mappers: list of Mapper instances
596 **kwargs
597 All additional arguments are passed to the base-class constructor.
598 """
599 Mapper.__init__(self, **kwargs)
600
601 if not len(mappers):
602 raise ValueError, 'ChainMapper needs at least one embedded mapper.'
603
604 self._mappers = mappers
605
606
608 """Calls all mappers in the chain successively.
609
610 :Parameter:
611 data
612 data to be chain-mapped.
613 """
614 mp = data
615 for m in self._mappers:
616 mp = m.forward(mp)
617
618 return mp
619
620
622 """Calls all mappers in the chain successively, in reversed order.
623
624 :Parameter:
625 data: array
626 data array to be reverse mapped into the orginal dataspace.
627 """
628 mp = data
629 for m in reversed(self._mappers):
630 mp = m.reverse(mp)
631
632 return mp
633
634
635 - def train(self, dataset):
636 """Trains the *last* mapper in the chain.
637
638 :Parameter:
639 dataset: :class:`~mvpa.datasets.base.Dataset` or subclass
640 A dataset with the number of features matching the `outSize` of the
641 last mapper in the chain (which is identical to the one of the
642 `ChainMapper` itself).
643 """
644 if dataset.nfeatures != self.getOutSize():
645 raise ValueError, "Training dataset does not match the mapper " \
646 "properties."
647
648 self._mappers[-1].train(dataset)
649
650
652 """Returns the size of the entity in input space"""
653 return self._mappers[0].getInSize()
654
655
657 """Returns the size of the entity in output space"""
658 return self._mappers[-1].getOutSize()
659
660
662 """Remove some elements from the *last* mapper in the chain.
663
664 :Parameter:
665 outIds: sequence
666 All output feature ids to be selected/kept.
667 """
668 self._mappers[-1].selectOut(outIds)
669
670
672 """Get the ids of the neighbors of a single feature in output dataspace.
673
674 .. note::
675
676 The neighbors are determined based on the input space of the *last
677 mapper* in the chain and *not* on the input dataspace of the
678 `ChainMapper` as a whole!
679
680 :Parameters:
681 outId: int
682 Single id of a feature in output space, whos neighbors should be
683 determined.
684 *args, **kwargs
685 Additional arguments are passed to the metric of the embedded
686 mapper, that is responsible for the corresponding feature.
687
688 Returns a list of outIds
689 """
690 return self._mappers[-1].getNeighbor(outId, *args, **kwargs)
691
692
694 s = Mapper.__repr__(self).rstrip(' )')
695
696 if not s[-1] == '(':
697 s += ' '
698 s += 'mappers=[%s])' % ', '.join([m.__repr__() for m in self._mappers])
699 return s
700