File size: 8,400 Bytes
8c212a5 |
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 |
# python3.7
"""Contains the base class for model running controllers."""
__all__ = ['BaseController']
_CONTROLLER_PRIORITY_ALIASES = {
'FIRST': 0,
'HIGH': 25,
'MEDIUM': 50,
'LOW': 75,
'LAST': 100,
}
def _parse_controller_priority(priority):
"""Parses the controller priority.
Smaller number means higher priority. Controllers with higher priority will
be executed first after the running iteration and executed last before the
running iteration. Priority of controllers can be set in the configeration
file. All priorities should be with integer type and lie in range [0, 100].
Followings are some aliases for the default priorities:
(1) FIRST: 0
(2) HIGH: 25
(3) MEDIUM: 50
(4) LOW: 75
(5) LAST: 100
Args:
priority: An integer or a string (alias) indicating the priority.
Returns:
An integer representing the parsed priority.
Raises:
TypeError: If the input `priority` is not with `int` or `str` type.
ValueError: If the input `priority` is out of range [0, 100] or the
`priority` is an invalid alias.
"""
if isinstance(priority, int):
if not 0 <= priority <= 100:
raise ValueError(f'Controller priority should lie in range '
f'[0, 100], but `{priority}` is received!')
return priority
if isinstance(priority, str):
try:
return _CONTROLLER_PRIORITY_ALIASES[priority.upper()]
except KeyError:
raise ValueError(f'Unknown alias `{priority}` for controller '
f'priority!\n'
f'Please choose from: '
f'{list(_CONTROLLER_PRIORITY_ALIASES)}.')
raise TypeError(f'Input `priority` should be with type `int` or `str`, '
f'but `{type(priority)}` is received!')
class BaseController(object):
"""The base class for model running controllers.
Controllers are commonly used to control/monitor the running process, such
as adjusting learning rate, saving log messages, etc. Within each iteration
of model running, all controllers will be checked TWICE (i.e., before and
after the iteration) on whether to execute the control.
This class contains following members for a better control:
(0) priority: Execution priority, which determines the execution order among
all controllers. See function `_parse_controller_priority()` for more
details. (default: `MEDIUM`)
(1) every_n_iters: Executable for every n iterations. `-1` means ignored.
(default: -1)
(2) every_n_epochs: Executable for every n epochs. `-1` means ignored.
(default: -1)
(3) first_iter: Enforce to execute on the first iteration. (default: True)
(4) marster_only: Executable only on the master worker. (default: False)
"""
def __init__(self, config=None):
"""Initializes the controller with basic settings.
Args:
config: The configuration for the controller, which is loaded from
the configuration file. This field should be a dictionary.
(default: None)
"""
config = config or dict()
assert isinstance(config, dict)
self._name = self.__class__.__name__
self._config = config.copy()
priority = config.get('priority', 'MEDIUM')
self._priority = _parse_controller_priority(priority)
self._every_n_iters = config.get('every_n_iters', -1)
self._every_n_epochs = config.get('every_n_epochs', -1)
self._first_iter = config.get('first_iter', True)
self._master_only = config.get('master_only', False)
@property
def name(self):
"""Returns the name of the controller."""
return self._name
@property
def config(self):
"""Returns the configuration for the controller."""
return self._config
@property
def priority(self):
"""Returns the execution priority of the controller."""
return self._priority
@property
def every_n_iters(self):
"""Returns how often (in iterations) the controller is executed."""
return self._every_n_iters
@property
def every_n_epochs(self):
"""Returns how often (in epochs) the controller is executed."""
return self._every_n_epochs
@property
def first_iter(self):
"""Returns whether the controller is forcibly executed initially."""
return self._first_iter
@property
def master_only(self):
"""Returns whether the controller is executed on master worker only."""
return self._master_only
def setup(self, runner):
"""Sets up the controller before running.
Default behavior is to do nothing. Can be overridden in derived classes.
Args:
runner: The runner to control.
"""
def close(self, runner):
"""Closes the controller after running.
Default behavior is to do nothing. Can be overridden in derived classes.
Args:
runner: The runner to control.
"""
def execute_before_iteration(self, runner):
"""Executes the controller before the iteration.
Default behavior is to do nothing. Can be overridden in derived classes.
Args:
runner: The runner to control.
"""
def execute_after_iteration(self, runner):
"""Executes the controller after the iteration.
Default behavior is to do nothing. Can be overridden in derived classes.
Args:
runner: The runner to control.
"""
def is_executable(self, runner):
"""Determines whether the controller is executable at current state.
Basically, the decision is made based on the current running iteration
(epoch) and the execution frequency (i.e., `self.every_n_iters` and
`self.every_n_epochs`).
If `self.master_only` is set as `True`, this function will also check
whether the current work is the master.
Args:
runner: The runner to control.
Returns:
A boolean suggesting whether the controller should be executed.
"""
if self.master_only and runner.rank != 0:
return False
if self.first_iter and runner.iter - runner.start_iter == 1:
return True
if runner.iter == runner.total_iters:
return True
if self.every_n_iters > 0 and runner.iter % self.every_n_iters == 0:
return True
epoch_to_iter = runner.convert_epoch_to_iter(self.every_n_epochs)
if self.every_n_epochs > 0 and runner.iter % epoch_to_iter == 0:
return True
return False
def start(self, runner):
"""Starts the controller.
Default behavior is to do nothing. Can be overridden in derived classes.
Args:
runner: The runner to control.
"""
if self.master_only and runner.rank != 0:
return
self.setup(runner)
def end(self, runner):
"""Ends the controller.
Default behavior is to do nothing. Can be overridden in derived classes.
Args:
runner: The runner to control.
"""
if self.master_only and runner.rank != 0:
return
self.close(runner)
def pre_execute(self, runner):
"""Pre-executes the controller before the running of each iteration.
This function wraps function `self.execute_before_iteration()` and
function `self.is_executable()`. More concretely, the controller will
only be executed at some particular iterations.
Args:
runner: The runner to control.
"""
if self.is_executable(runner):
self.execute_before_iteration(runner)
def post_execute(self, runner):
"""Post-executes the controller after the running of each iteration.
This function wraps function `self.execute_before_iteration()` and
function `self.is_executable()`. More concretely, the controller will
only be executed at some particular iterations.
Args:
runner: The runner to control.
"""
if self.is_executable(runner):
self.execute_after_iteration(runner)
|