1 | """The event-based main loop of Blocks.""" |
||
2 | from abc import ABCMeta |
||
3 | from collections import defaultdict |
||
4 | from numbers import Integral |
||
5 | from uuid import uuid4 |
||
6 | |||
7 | import six |
||
8 | |||
9 | |||
10 | @six.add_metaclass(ABCMeta) |
||
11 | class TrainingLogBase(object): |
||
12 | """Base class for training log. |
||
13 | |||
14 | A training log stores the training timeline, statistics and other |
||
15 | auxiliary information. Training logs can use different backends e.g. |
||
16 | in-memory Python objects or an SQLite database. |
||
17 | |||
18 | Information is stored similar to a nested dictionary, so use |
||
19 | ``log[time][key]`` to read data. An entry without stored data will |
||
20 | return an empty dictionary-like object that can be written to, |
||
21 | ``log[time][key] = value``. |
||
22 | |||
23 | Depending on the backend, ``log[time] = {'key': 'value'}`` could fail. |
||
24 | Use ``log[time].update({'key': 'value'})`` for compatibility across |
||
25 | backends. |
||
26 | |||
27 | In addition to the set of records displaying training dynamics, a |
||
28 | training log has a :attr:`status` attribute, which is a dictionary with |
||
29 | data that is not bound to a particular time. |
||
30 | |||
31 | .. warning:: |
||
32 | |||
33 | Changes to mutable objects might not be reflected in the log, |
||
34 | depending on the backend. So don't use |
||
35 | ``log.status['key'].append(...)``, use ``log.status['key'] = ...`` |
||
36 | instead. |
||
37 | |||
38 | Parameters |
||
39 | ---------- |
||
40 | uuid : :class:`uuid.UUID`, optional |
||
41 | The UUID of this log. For persistent log backends, passing the UUID |
||
42 | will result in an old log being loaded. Otherwise a new, random |
||
43 | UUID will be created. |
||
44 | |||
45 | Attributes |
||
46 | ---------- |
||
47 | status : dict |
||
48 | A dictionary with data representing the current state of training. |
||
49 | By default it contains ``iterations_done``, ``epochs_done`` and |
||
50 | ``_epoch_ends`` (a list of time stamps when epochs ended). |
||
51 | |||
52 | """ |
||
53 | def __init__(self, uuid=None): |
||
54 | if uuid is None: |
||
55 | self.uuid = uuid4() |
||
56 | else: |
||
57 | self.uuid = uuid |
||
58 | if uuid is None: |
||
59 | self.status.update({ |
||
0 ignored issues
–
show
|
|||
60 | 'iterations_done': 0, |
||
61 | 'epochs_done': 0, |
||
62 | '_epoch_ends': [], |
||
63 | 'resumed_from': None |
||
64 | }) |
||
65 | |||
66 | @property |
||
67 | def h_uuid(self): |
||
68 | """Return a hexadecimal version of the UUID bytes. |
||
69 | |||
70 | This is necessary to store ids in an SQLite database. |
||
71 | |||
72 | """ |
||
73 | return self.uuid.hex |
||
74 | |||
75 | def resume(self): |
||
76 | """Resume a log by setting a new random UUID. |
||
77 | |||
78 | Keeps a record of the old log that this is a continuation of. It |
||
79 | copies the status of the old log into the new log. |
||
80 | |||
81 | """ |
||
82 | old_uuid = self.h_uuid |
||
83 | old_status = dict(self.status) |
||
0 ignored issues
–
show
|
|||
84 | self.uuid = uuid4() |
||
85 | self.status.update(old_status) |
||
0 ignored issues
–
show
|
|||
86 | self.status['resumed_from'] = old_uuid |
||
0 ignored issues
–
show
|
|||
87 | |||
88 | def _check_time(self, time): |
||
89 | if not isinstance(time, Integral) or time < 0: |
||
90 | raise ValueError("time must be a non-negative integer") |
||
91 | |||
92 | @property |
||
93 | def current_row(self): |
||
94 | return self[self.status['iterations_done']] |
||
0 ignored issues
–
show
|
|||
95 | |||
96 | @property |
||
97 | def previous_row(self): |
||
98 | return self[self.status['iterations_done'] - 1] |
||
0 ignored issues
–
show
|
|||
99 | |||
100 | @property |
||
101 | def last_epoch_row(self): |
||
102 | return self[self.status['_epoch_ends'][-1]] |
||
0 ignored issues
–
show
|
|||
103 | |||
104 | |||
105 | class TrainingLog(defaultdict, TrainingLogBase): |
||
106 | """Training log using a `defaultdict` as backend. |
||
107 | |||
108 | Notes |
||
109 | ----- |
||
110 | For analysis of the logs, it can be useful to convert the log to a |
||
111 | Pandas_ data frame: |
||
112 | |||
113 | .. code:: python |
||
114 | |||
115 | df = DataFrame.from_dict(log, orient='index') |
||
116 | |||
117 | .. _Pandas: http://pandas.pydata.org |
||
118 | |||
119 | """ |
||
120 | def __init__(self): |
||
121 | defaultdict.__init__(self, dict) |
||
122 | self.status = {} |
||
123 | TrainingLogBase.__init__(self) |
||
124 | |||
125 | def __reduce__(self): |
||
126 | constructor, args, _, _, items = super(TrainingLog, self).__reduce__() |
||
127 | return constructor, (), self.__dict__, _, items |
||
128 | |||
129 | def __getitem__(self, time): |
||
130 | self._check_time(time) |
||
131 | return super(TrainingLog, self).__getitem__(time) |
||
132 | |||
133 | def __setitem__(self, time, value): |
||
134 | self._check_time(time) |
||
135 | return super(TrainingLog, self).__setitem__(time, value) |
||
136 |
This check looks for calls to members that are non-existent. These calls will fail.
The member could have been renamed or removed.