/usr/share/pyshared/mvpa2/mappers/base.py is in python-mvpa2 2.1.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the PyMVPA package for the
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Basic, general purpose and meta mappers."""
__docformat__ = 'restructuredtext'
import numpy as np
import copy
from mvpa2.base.learner import Learner
from mvpa2.base.node import ChainNode
from mvpa2.base.types import is_datasetlike, accepts_dataset_as_samples
from mvpa2.base.dochelpers import _str, _repr_attrs
from mvpa2.base.dochelpers import borrowdoc
if __debug__:
from mvpa2.base import debug
class Mapper(Learner):
"""Basic mapper interface definition.
::
forward
--------->
IN OUT
<--------/
reverse
"""
def __init__(self, **kwargs):
"""
Parameters
----------
**kwargs
All additional arguments are passed to the baseclass.
"""
Learner.__init__(self, **kwargs)
# internal settings that influence what should be done to the dataset
# attributes in the default forward() and reverse() implementations.
# they are passed to the Dataset.copy() method
self._sa_filter = None
self._fa_filter = None
self._a_filter = None
#
# The following methods are abstract and merely define the intended
# interface of a mapper and have to be implemented in derived classes. See
# the docstrings of the respective methods for details about what they
# should do.
#
def _forward_data(self, data):
"""Forward-map some data.
This is a private method that has to be implemented in derived
classes.
Parameters
----------
data : anything (supported the derived class)
"""
raise NotImplementedError
def _reverse_data(self, data):
"""Reverse-map some data.
This is a private method that has to be implemented in derived
classes.
Parameters
----------
data : anything (supported the derived class)
"""
raise NotImplementedError
#
# The following methods are candidates for reimplementation in derived
# classes, in cases where the provided default behavior is not appropriate.
#
def _forward_dataset(self, dataset):
"""Forward-map a dataset.
This is a private method that can be reimplemented in derived
classes. The default implementation forward-maps the dataset samples
and returns a new dataset that is a shallow copy of the input with
the mapped samples.
Parameters
----------
dataset : Dataset-like
"""
if __debug__:
debug('MAP_', "Forward-map %s-shaped samples in dataset with '%s'."
% (dataset.samples.shape, self))
msamples = self._forward_data(dataset.samples)
if __debug__:
debug('MAP_', "Make shallow copy of to-be-forward-mapped dataset "
"and assigned forward-mapped samples ({sf}a_filters: "
"%s, %s, %s)." % (self._sa_filter, self._fa_filter,
self._a_filter))
mds = dataset.copy(deep=False,
sa=self._sa_filter,
fa=self._fa_filter,
a=self._a_filter)
mds.samples = msamples
return mds
def _reverse_dataset(self, dataset):
"""Reverse-map a dataset.
This is a private method that can be reimplemented in derived
classes. The default implementation reverse-maps the dataset samples
and returns a new dataset that is a shallow copy of the input with
the mapped samples.
Parameters
----------
dataset : Dataset-like
"""
if __debug__:
debug('MAP_', "Reverse-map %s-shaped samples in dataset with '%s'."
% (dataset.samples.shape, self))
msamples = self._reverse_data(dataset.samples)
if __debug__:
debug('MAP_', "Make shallow copy of to-be-reverse-mapped dataset "
"and assigned reverse-mapped samples ({sf}a_filters: "
"%s, %s, %s)." % (self._sa_filter, self._fa_filter,
self._a_filter))
mds = dataset.copy(deep=False,
sa=self._sa_filter,
fa=self._fa_filter,
a=self._a_filter)
mds.samples = msamples
return mds
#
# The following methods provide common functionality for all mappers
# and there should be no immediate need to reimplement them
#
def forward(self, data):
"""Map data from input to output space.
Parameters
----------
data : Dataset-like, (at least 2D)-array-like
Typically this is a `Dataset`, but it might also be a plain data
array, or even something completely different(TM) that is supported
by a subclass' implementation. If such an object is Dataset-like it
is handled by a dedicated method that also transforms dataset
attributes if necessary. If an array-like is passed, it has to be
at least two-dimensional, with the first axis separating samples
or observations. For single samples `forward1()` might be more
appropriate.
"""
if is_datasetlike(data):
if __debug__:
debug('MAP', "Forward-map %s-shaped dataset through '%s'."
% (data.shape, self))
return self._forward_dataset(data)
else:
if hasattr(data, 'ndim') and data.ndim < 2:
raise ValueError(
'Mapper.forward() only support mapping of data with '
'at least two dimensions, where the first axis '
'separates samples/observations. Consider using '
'Mapper.forward1() instead.')
if __debug__:
debug('MAP', "Forward-map data through '%s'." % (self))
return self._forward_data(data)
def forward1(self, data):
"""Wrapper method to map single samples.
It is basically identical to `forward()`, but also accepts
one-dimensional arguments. The map whole dataset this method cannot
be used. but `forward()` handles them.
"""
if isinstance(data, np.ndarray):
data = data[np.newaxis]
else:
data = np.array([data])
if __debug__:
debug('MAP', "Forward-map single %s-shaped sample through '%s'."
% (data.shape[1:], self))
return self.forward(data)[0]
def reverse(self, data):
"""Reverse-map data from output back into input space.
Parameters
----------
data : Dataset-like, anything
Typically this is a `Dataset`, but it might also be a plain data
array, or even something completely different(TM) that is supported
by a subclass' implementation. If such an object is Dataset-like it
is handled by a dedicated method that also transforms dataset
attributes if necessary.
"""
if is_datasetlike(data):
if __debug__:
debug('MAP', "Reverse-map %s-shaped dataset through '%s'."
% (data.shape, self))
return self._reverse_dataset(data)
else:
if __debug__:
debug('MAP', "Reverse-map data through '%s'." % (self))
return self._reverse_data(data)
def reverse1(self, data):
"""Wrapper method to map single samples.
It is basically identical to `reverse()`, but accepts one-dimensional
arguments. To map whole dataset this method cannot be used. but
`reverse()` handles them.
"""
if isinstance(data, np.ndarray):
data = data[np.newaxis]
else:
data = np.array([data])
if __debug__:
debug('MAP', "Reverse-map single %s-shaped sample through '%s'."
% (data.shape[1:], self))
mapped = self.reverse(data)[0]
if __debug__:
debug('MAP', "Mapped single %s-shaped sample to %s."
% (data.shape[1:], mapped.shape))
return mapped
def _call(self, ds):
return self.forward(ds)
class ChainMapper(ChainNode):
"""Class that amends ChainNode with a mapper-like interface.
ChainMapper supports sequential training of a mapper chain, as well as
reverse-mapping and mapping of single samples.
"""
def forward(self, ds):
return self(ds)
def forward1(self, data):
"""Forward data or datasets through the chain.
See `Mapper` for more information.
"""
mp = data
for m in self:
if __debug__:
debug('MAP', "Forwarding single input (%s) though '%s'."
% (mp.shape, str(m)))
mp = m.forward1(mp)
return mp
def reverse(self, data):
"""Reverse-maps data or datasets through the chain (backwards).
See `Mapper` for more information.
"""
mp = data
for m in reversed(self):
# we ignore mapper that do not have reverse mapping implemented
# (e.g. detrending). That might cause problems if ignoring the
# mapper make the data incompatible input for the next mapper in
# the chain. If that pops up, we have to think about a proper
# solution.
try:
if __debug__:
debug('MAP',
"Reversing %s-shaped input though '%s'."
% (mp.shape, str(m)))
mp = m.reverse(mp)
except NotImplementedError:
if __debug__:
debug('MAP', "Ignoring %s on reverse mapping." % m)
return mp
def reverse1(self, data):
"""Reverse-maps data or datasets through the chain (backwards).
See `Mapper` for more information.
"""
mp = data
for i, m in enumerate(reversed(self)):
# we ignore mapper that do not have reverse mapping implemented
# (e.g. detrending). That might cause problems if ignoring the
# mapper make the data incompatible input for the next mapper in
# the chain. If that pops up, we have to think about a proper
# solution.
try:
if __debug__:
debug('MAP',
"Reversing single %s-shaped input though chain node '%s'."
% (mp.shape, str(m)))
mp = m.reverse1(mp)
except NotImplementedError:
if __debug__:
debug('MAP', "Ignoring %s on reverse mapping." % m)
except ValueError:
if __debug__:
debug('MAP',
"Failed to reverse-map through chain at '%s'. Maybe "
"previous mapper return multiple samples. Trying to "
"switch to reverse() for the remainder of the chain."
% str(m))
mp = self[:-1*i].reverse(mp)
return mp
return mp
def train(self, dataset):
"""Train the mapper chain sequentially.
The training dataset is used to train the first mapper. Afterwards it is
forward-mapped by this (now trained) mapper and the transformed dataset
and then used to train the next mapper. This procedure is done till all
mappers are trained.
Parameters
----------
dataset: `Dataset`
"""
nmappers = len(self) - 1
tdata = dataset
for i, mapper in enumerate(self):
if __debug__:
debug('MAP',
"Training child mapper (%i/%i) %s with %s-shaped input."
% (i + 1, nmappers + 1, str(mapper), tdata.shape))
mapper.train(tdata)
# forward through all but the last mapper
if i < nmappers:
tdata = mapper.forward(tdata)
def untrain(self):
"""Untrain all embedded mappers."""
for m in self:
m.untrain()
def __str__(self):
return super(ChainMapper, self).__str__().replace('Mapper', '')
class CombinedMapper(Mapper):
"""Mapper to pass a dataset on to a set of mappers and combine there output.
Output combination or aggregation is currently done by hstacking or
vstacking the resulting datasets.
"""
def __init__(self, mappers, combine_axis, **kwargs):
"""
Parameters
----------
mappers : list
combine_axis : ['h', 'v']
Examples
--------
>>> import numpy as np
>>> from mvpa2.mappers.base import CombinedMapper
>>> from mvpa2.featsel.base import StaticFeatureSelection
>>> from mvpa2.datasets import Dataset
>>> mp = CombinedMapper([StaticFeatureSelection([1,2]),
... StaticFeatureSelection([2,3])],
... combine_axis='h')
>>> mp.is_trained = True
>>> ds = Dataset(np.arange(12).reshape(3,4))
>>> out = mp(ds)
>>> out.samples
array([[ 1, 2, 2, 3],
[ 5, 6, 6, 7],
[ 9, 10, 10, 11]])
"""
Mapper.__init__(self, **kwargs)
self._mappers = mappers
self._combine_axis = combine_axis
@borrowdoc(Mapper)
def __repr__(self, prefixes=[]):
return super(CombinedMapper, self).__repr__(
prefixes=prefixes
+ _repr_attrs(self, ['mappers', 'combine_axis']))
def __str__(self):
return _str(self)
def _train(self, ds):
for mapper in self._mappers:
mapper.train(ds)
def _untrain(self):
for mapper in self._mappers:
mapper.untrain()
@borrowdoc(Mapper)
def _forward_dataset(self, ds):
from mvpa2.datasets import hstack, vstack
mapped_ds = [mapper.forward(ds) for mapper in self._mappers]
stacker = {'h': hstack, 'v': vstack}
out = stacker[self._combine_axis](mapped_ds)
return out
mappers = property(fget=lambda self:self._mappers)
|