Completed
Pull Request — master (#1162)
by David
102:42 queued 46:10
created

Minimum   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 20
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 10
c 0
b 0
f 0
wmc 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
A get_aggregator() 0 4 1
A _build_aggregator() 0 13 1
1
"""Evaluate Theano variables on auxiliary data and during training."""
2
from functools import partial
3
import logging
4
from abc import ABCMeta, abstractmethod
5
6
from six import add_metaclass
7
from theano import tensor
8
from theano.ifelse import ifelse
9
10
from blocks.utils import shared_like
11
12
logger = logging.getLogger(__name__)
13
14
15
@add_metaclass(ABCMeta)
16
class AggregationScheme(object):
17
    """How to incrementally evaluate a Theano variable over minibatches.
18
19
    An AggregationScheme allocates :class:`Aggregator` that can
20
    incrementally compute the value of a Theano variable on a full dataset
21
    by aggregating partial results computed on multiple batches.
22
23
    The AggregationScheme should be attached via the tag
24
    ``aggregation_scheme`` to a Theano variable which computes the desired
25
    value on a single batch.
26
27
    Parameters
28
    ----------
29
    variable: :class:`~tensor.TensorVariable`
30
        The variable that holds the desired value on a single batch.
31
32
    """
33
    def __init__(self, variable):
34
        self.variable = variable
35
36
    @abstractmethod
37
    def get_aggregator(self):
38
        """Return a new Aggregator for this variable."""
39
        pass
40
41
42
class Aggregator(object):
43
    """An Aggregator incrementally evaluates a Theano variable on a dataset.
44
45
    .. warning::
46
        The Aggregators should never be created directly. Instead use the
47
        :meth:`AggregationScheme.get_aggregator` method.
48
49
    Example usages are:
50
51
    * compute the mean of some value over examples, sequence lengths etc.
52
    * track a parameter of a model
53
    * monitor a penalty
54
55
    The Aggregator maintains a set of Theano sharer values called
56
    accumulators and specifies how they should be initialized, and
57
    updated with incremental calculations. Finally, it
58
    provides a Theano variable that reads the accumulators
59
    and computes the final value.
60
61
    Parameters
62
    ----------
63
    aggregation_scheme : :class:`AggregationScheme`
64
        The aggregation scheme that constructed this Aggregator
65
    initialization_updates : list of Theano updates
66
        Updates that specify how to initialize shared variables of
67
        this Aggregator. *Can only refer to shared variables and
68
        constants.*
69
    accumulation_updates : list of Theano updates
70
        Updates that specify how a new batch of data gets processed
71
        by this Aggregator. *Can refer to model inputs.*
72
    readout_variable : :class:`~tensor.TensorVariable`
73
        Theano variable that holds the final value based on aggregated
74
        partial results. *readout_variable must only consist of shared
75
        variables and constants.*
76
77
    Attributes
78
    ----------
79
    All constructor parameters are accessible as attributes.
80
81
    """
82
    def __init__(self, aggregation_scheme, initialization_updates=None,
83
                 accumulation_updates=None, readout_variable=None):
84
        self.aggregation_scheme = aggregation_scheme
85
        self.readout_variable = readout_variable
86
87
        if initialization_updates is None:
88
            initialization_updates = []
89
        if accumulation_updates is None:
90
            accumulation_updates = []
91
        self.initialization_updates = initialization_updates
92
        self.accumulation_updates = accumulation_updates
93
94
95
class Mean(AggregationScheme):
96
    """Aggregation scheme which computes the mean.
97
98
    Parameters
99
    ----------
100
    numerator : :class:`~tensor.TensorVariable`
101
        Theano variable for the numerator e.g. the likelihood
102
    denominator : :class:`~tensor.TensorVariable`
103
        Theano variable for the denominator e.g. the batch size
104
105
    """
106
    def __init__(self, numerator, denominator):
0 ignored issues
show
Bug introduced by
The __init__ method of the super-class AggregationScheme is not called.

It is generally advisable to initialize the super-class by calling its __init__ method:

class SomeParent:
    def __init__(self):
        self.x = 1

class SomeChild(SomeParent):
    def __init__(self):
        # Initialize the super class
        SomeParent.__init__(self)
Loading history...
107
        self.numerator = numerator
108
        self.denominator = denominator
109
110
    def get_aggregator(self):
111
        initialized = shared_like(0.)
112
        numerator_acc = shared_like(self.numerator)
113
        denominator_acc = shared_like(self.denominator)
114
115
        # Dummy default expression to use as the previously-aggregated
116
        # value, that has the same shape as the new result
117
        numerator_zeros = tensor.as_tensor(self.numerator).zeros_like()
118
        denominator_zeros = tensor.as_tensor(self.denominator).zeros_like()
119
120
        conditional_update_num = self.numerator + ifelse(initialized,
121
                                                         numerator_acc,
122
                                                         numerator_zeros)
123
        conditional_update_den = self.denominator + ifelse(initialized,
124
                                                           denominator_acc,
125
                                                           denominator_zeros)
126
127
        initialization_updates = [(numerator_acc,
128
                                   tensor.zeros_like(numerator_acc)),
129
                                  (denominator_acc,
130
                                   tensor.zeros_like(denominator_acc)),
131
                                  (initialized,
132
                                   tensor.zeros_like(initialized))]
133
        accumulation_updates = [(numerator_acc,
134
                                 conditional_update_num),
135
                                (denominator_acc,
136
                                 conditional_update_den),
137
                                (initialized, tensor.ones_like(initialized))]
138
        aggregator = Aggregator(aggregation_scheme=self,
139
                                initialization_updates=initialization_updates,
140
                                accumulation_updates=accumulation_updates,
141
                                readout_variable=(numerator_acc /
142
                                                  denominator_acc))
143
        return aggregator
144
145
146
def mean(numerator, denominator=1.):
147
    """Mean of quantity (numerator) over a number (denominator) values."""
148
    variable = numerator / denominator
149
    variable.tag.aggregation_scheme = Mean(numerator, denominator)
150
    variable.name = numerator.name
151
    return variable
152
153
154
class _DataIndependent(AggregationScheme):
155
    """Dummy aggregation scheme for values that don't depend on data."""
156
    def get_aggregator(self):
157
        return Aggregator(aggregation_scheme=self,
158
                          initialization_updates=[],
159
                          accumulation_updates=[],
160
                          readout_variable=self.variable)
161
162
163
class TakeLast(AggregationScheme):
164
    """Aggregation scheme which remembers only the last value."""
165
    def get_aggregator(self):
166
        self.storage = shared_like(self.variable)
167
        return Aggregator(aggregation_scheme=self,
168
                          initialization_updates=[
169
                              (self.storage,
170
                               tensor.zeros_like(self.storage))],
171
                          accumulation_updates=[(self.storage, self.variable)],
172
                          readout_variable=self.storage)
173
174
175
def _simple_aggregation(scheme, variable):
176
    variable = variable.copy(variable.name)
177
    variable.tag.aggregation_scheme = scheme(variable)
178
    return variable
179
180
181
take_last = partial(_simple_aggregation, TakeLast)
0 ignored issues
show
Coding Style Naming introduced by
The name take_last does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
182
183
184
class Minimum(AggregationScheme):
185
    """Aggregation scheme which remembers only the minimum value."""
186
    def _build_aggregator(self, accumulate_update):
187
        initialized = shared_like(0.)
188
        accumulate = ifelse(initialized, accumulate_update, self.variable)
189
        return Aggregator(aggregation_scheme=self,
190
                          initialization_updates=[
191
                              (self.storage, tensor.zeros_like(self.storage)),
192
                              (initialized, tensor.zeros_like(initialized))
193
                          ],
194
                          accumulation_updates=[
195
                              (self.storage, accumulate),
196
                              (initialized, tensor.ones_like(initialized))
197
                          ],
198
                          readout_variable=self.storage)
199
200
    def get_aggregator(self):
201
        self.storage = shared_like(self.variable)
202
        return self._build_aggregator(tensor.minimum(self.storage,
203
                                                     self.variable))
204
205
minimum = partial(_simple_aggregation, Minimum)
0 ignored issues
show
Coding Style Naming introduced by
The name minimum does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
206
207
208
class Maximum(Minimum):
209
    """Aggregation scheme which remembers only the maximum value."""
210
    def get_aggregator(self):
211
        self.storage = shared_like(self.variable)
212
        return self._build_aggregator(tensor.maximum(self.storage,
213
                                                     self.variable))
214
215
maximum = partial(_simple_aggregation, Maximum)
0 ignored issues
show
Coding Style Naming introduced by
The name maximum does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
216
217
218
@add_metaclass(ABCMeta)
0 ignored issues
show
Unused Code introduced by
This abstract class does not seem to be used anywhere.
Loading history...
219
class MonitoredQuantity(object):
220
    """The base class for monitored-quantities.
221
222
    To monitor a non-Theano quantity in Blocks you have to implement this
223
    interface for it. The initialize method initializes accumulators and
224
    the parameters needed to compute this quantity, aggregate method
225
    aggregates results for every batch, and finally readout is called
226
    to get the aggregated results.
227
228
    Attributes
229
    ----------
230
    requires : list
231
        List of Theano variables needed to calculate this quantity.
232
    name : str
233
        The name of monitored quantity which appears in the log.
234
235
    See Also
236
    --------
237
    :class:`~blocks.monitoring.evaluators.DatasetEvaluator`
238
    :class:`~blocks.extensions.DataStreamMonitoring`
239
240
    """
241
    def __init__(self, requires=None, name=None):
242
        if requires is None:
243
            requires = []
244
        self.requires = requires
245
        self.name = name
246
247
    @abstractmethod
248
    def initialize(self):
249
        """Initialize accumulators for this monitored quantity."""
250
        pass
251
252
    @abstractmethod
253
    def aggregate(self, *args):
254
        r"""Aggregate results for every batch.
255
256
        \*args : list of :class:`~numpy.ndarray`
257
            The values of the variables required to aggregate the
258
            value of the quantity.
259
260
        """
261
        pass
262
263
    @abstractmethod
264
    def get_aggregated_value(self):
265
        """Obtain the result of aggregation."""
266
        pass
267