forked from speechbrain/speechbrain
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathepoch_loop.py
More file actions
133 lines (114 loc) · 4.45 KB
/
Copy pathepoch_loop.py
File metadata and controls
133 lines (114 loc) · 4.45 KB
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
"""Implements a checkpointable epoch counter (loop), optionally integrating early stopping.
Authors
* Aku Rouhe 2020
* Davide Borra 2021
"""
from .checkpoints import register_checkpoint_hooks
from .checkpoints import mark_as_saver
from .checkpoints import mark_as_loader
import logging
logger = logging.getLogger(__name__)
@register_checkpoint_hooks
class EpochCounter:
"""An epoch counter which can save and recall its state.
Use this as the iterator for epochs.
Note that this iterator gives you the numbers from [1 ... limit] not
[0 ... limit-1] as range(limit) would.
Example
-------
>>> from speechbrain.utils.checkpoints import Checkpointer
>>> tmpdir = getfixture('tmpdir')
>>> epoch_counter = EpochCounter(10)
>>> recoverer = Checkpointer(tmpdir, {"epoch": epoch_counter})
>>> recoverer.recover_if_possible()
>>> # Now after recovery,
>>> # the epoch starts from where it left off!
>>> for epoch in epoch_counter:
... # Run training...
... ckpt = recoverer.save_checkpoint()
"""
def __init__(self, limit):
self.current = 0
self.limit = int(limit)
def __iter__(self):
return self
def __next__(self):
if self.current < self.limit:
self.current += 1
logger.info(f"Going into epoch {self.current}")
return self.current
raise StopIteration
@mark_as_saver
def _save(self, path):
with open(path, "w") as fo:
fo.write(str(self.current))
@mark_as_loader
def _recover(self, path, end_of_epoch=True, device=None):
# NOTE: end_of_epoch = True by default so that when
# loaded in parameter transfer, this starts a new epoch.
# However, parameter transfer to EpochCounter should
# probably never be used really.
del device # Not used.
with open(path) as fi:
saved_value = int(fi.read())
if end_of_epoch:
self.current = saved_value
else:
self.current = saved_value - 1
class EpochCounterWithStopper(EpochCounter):
"""An epoch counter which can save and recall its state, integrating an early stopper by tracking a target metric.
Arguments
---------
limit: int
maximum number of epochs
limit_to_stop : int
maximum number of consecutive epochs without improvements in performance
limit_warmup : int
number of epochs to wait until start checking for early stopping
direction : "max" or "min"
direction to optimize the target metric
Example
-------
>>> limit = 10
>>> limit_to_stop = 5
>>> limit_warmup = 2
>>> direction = "min"
>>> epoch_counter = EpochCounterWithStopper(limit, limit_to_stop, limit_warmup, direction)
>>> for epoch in epoch_counter:
... # Run training...
... # Track a validation metric,
... current_valid_metric = 0
... # get the current valid metric (get current_valid_metric)
... if epoch_counter.should_stop(current=epoch,
... current_metric=current_valid_metric,):
... epoch_counter.current = epoch_counter.limit # skipping unpromising epochs
"""
def __init__(self, limit, limit_to_stop, limit_warmup, direction):
super().__init__(limit)
self.limit_to_stop = limit_to_stop
self.limit_warmup = limit_warmup
self.direction = direction
self.best_limit = 0
self.min_delta = 1e-6
if self.limit_to_stop < 0:
raise ValueError("Stopper 'limit_to_stop' must be >= 0")
if self.limit_warmup < 0:
raise ValueError("Stopper 'limit_warmup' must be >= 0")
if self.direction == "min":
self.th, self.sign = float("inf"), 1
elif self.direction == "max":
self.th, self.sign = -float("inf"), -1
else:
raise ValueError("Stopper 'direction' must be 'min' or 'max'")
def should_stop(self, current, current_metric):
"""Returns True is training should stop (based on the performance
metrics)."""
should_stop = False
if current > self.limit_warmup:
if self.sign * current_metric < self.sign * (
(1 - self.min_delta) * self.th
):
self.best_limit = current
self.th = current_metric
should_stop = (current - self.best_limit) >= self.limit_to_stop
return should_stop