Skip to content

Commit 05857b6

Browse files
committed
bugfix: Correctly set learning rate from LR Finder when resuming
1 parent 0370d2b commit 05857b6

5 files changed

Lines changed: 37 additions & 13 deletions

File tree

.pylintrc

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ persistent=yes
3636
# Specify a configuration file.
3737
#rcfile=
3838

39-
# When enabled, pylint would attempt to guess common misconfiguration and emit
40-
# user-friendly hints instead of false-positive error messages.
41-
suggestion-mode=yes
42-
4339
# Allow loading of arbitrary C extensions. Extensions are imported into the
4440
# active Python interpreter and may run arbitrary code.
4541
unsafe-load-any-extension=no

lib/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def add_section(self, title: str, info: str) -> None:
302302
logger.debug("Add section: (title: '%s', info: '%s')", title, info)
303303
self.defaults[title] = ConfigSection(helptext=info, items=OrderedDict())
304304

305-
def add_item(self,
305+
def add_item(self, # pylint:disable=too-many-arguments,too-many-positional-arguments
306306
section: str | None = None,
307307
title: str | None = None,
308308
datatype: type = str,
@@ -374,7 +374,7 @@ def add_item(self,
374374
group=group)
375375

376376
@classmethod
377-
def _expand_helptext(cls,
377+
def _expand_helptext(cls, # pylint:disable=too-many-positional-arguments
378378
helptext: str,
379379
choices: str | list[str],
380380
default: ConfigValueType,
@@ -453,7 +453,7 @@ def insert_config_section(self,
453453
config.set(section, helptext)
454454
logger.debug("Inserted section: '%s'", section)
455455

456-
def _insert_config_item(self,
456+
def _insert_config_item(self, # pylint:disable=too-many-positional-arguments
457457
section: str,
458458
item: str,
459459
default: ConfigValueType,
@@ -526,7 +526,7 @@ def _load_config(self) -> None:
526526

527527
def save_config(self) -> None:
528528
""" Save a config file """
529-
logger.info("Updating config at: '%s'", self.configfile)
529+
logger.debug("Updating config at: '%s'", self.configfile)
530530
with open(self.configfile, "w", encoding="utf-8", errors="replace") as f_cfgfile:
531531
self.config.write(f_cfgfile)
532532
logger.debug("Updated config at: '%s'", self.configfile)

lib/training/lr_finder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class LearningRateFinder:
4848
beta: float
4949
Amount to smooth loss by, for graphing purposes
5050
"""
51-
def __init__(self,
51+
def __init__(self, # pylint:disable=too-many-positional-arguments
5252
model: ModelBase,
5353
config: dict[str, ConfigValueType],
5454
feeder: Feeder,
@@ -149,7 +149,7 @@ def _reset_model(self, original_lr: float, new_lr: float) -> None:
149149
new_lr: float
150150
The discovered optimal learning rate
151151
"""
152-
self._model.state.update_session_config("learning_rate", new_lr)
152+
self._model.state.add_lr_finder(new_lr)
153153
self._model.state.save()
154154

155155
logger.debug("Loading initial weights")

plugins/train/model/_base/model.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,11 +367,13 @@ def _output_summary(self) -> None:
367367
else:
368368
# print to logger
369369
print_fn = lambda x: logger.verbose("%s", x) #type:ignore[attr-defined] # noqa[E731] # pylint:disable=C3001
370+
parent = None
370371
for idx, model in enumerate(get_all_sub_models(self.model)):
371372
if idx == 0:
372373
parent = model
373374
continue
374375
model.summary(line_length=100, print_fn=print_fn)
376+
assert parent is not None
375377
parent.summary(line_length=100, print_fn=print_fn)
376378

377379
def _compile_model(self) -> None:
@@ -453,6 +455,7 @@ def __init__(self,
453455
self._name = model_name
454456
self._iterations = 0
455457
self._mixed_precision_layers: list[str] = []
458+
self._lr_finder = -1.0
456459
self._rebuild_model = False
457460
self._sessions: dict[int, dict] = {}
458461
self._lowest_avg_loss: dict[str, float] = {}
@@ -503,6 +506,11 @@ def mixed_precision_layers(self) -> list[str]:
503506
"""list: Layers that can be switched between mixed-float16 and float32. """
504507
return self._mixed_precision_layers
505508

509+
@property
510+
def lr_finder(self) -> float:
511+
""" The value discovered from the learning rate finder. -1 if no value stored """
512+
return self._lr_finder
513+
506514
@property
507515
def model_needs_rebuild(self) -> bool:
508516
"""bool: ``True`` if mixed precision policy has changed so model needs to be rebuilt
@@ -595,6 +603,17 @@ def add_mixed_precision_layers(self, layers: list[str]) -> None:
595603
logger.debug("Storing mixed precision layers: %s", layers)
596604
self._mixed_precision_layers = layers
597605

606+
def add_lr_finder(self, learning_rate: float) -> None:
607+
""" Add the optimal discovered learning rate from the learning rate finder
608+
609+
Parameters
610+
----------
611+
learning_rate : float
612+
The discovered learning rate
613+
"""
614+
logger.debug("Storing learning rate from LR Finder: %s", learning_rate)
615+
self._lr_finder = learning_rate
616+
598617
def _load(self, config_changeable_items: dict) -> None:
599618
""" Load a state file and set the serialized values to the class instance.
600619
@@ -616,6 +635,7 @@ def _load(self, config_changeable_items: dict) -> None:
616635
self._lowest_avg_loss = state.get("lowest_avg_loss", {})
617636
self._iterations = state.get("iterations", 0)
618637
self._mixed_precision_layers = state.get("mixed_precision_layers", [])
638+
self._lr_finder = state.get("lr_finder", -1.0)
619639
self._config = state.get("config", {})
620640
logger.debug("Loaded state: %s", state)
621641
self._replace_config(config_changeable_items)
@@ -624,10 +644,12 @@ def save(self) -> None:
624644
""" Save the state values to the serialized state file. """
625645
logger.debug("Saving State")
626646
state = {"name": self._name,
627-
"sessions": self._sessions,
647+
"sessions": {k: v for k, v in self._sessions.items()
648+
if v.get("iterations", 0) > 0},
628649
"lowest_avg_loss": self._lowest_avg_loss,
629650
"iterations": self._iterations,
630651
"mixed_precision_layers": self._mixed_precision_layers,
652+
"lr_finder": self._lr_finder,
631653
"config": _CONFIG}
632654
self._serializer.save(self._filename, state)
633655
logger.debug("Saved State")

plugins/train/trainer/_base.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import cv2
1616
import numpy as np
1717

18+
import keras.backend as K
1819
import tensorflow as tf
1920
from tensorflow.python.framework import ( # pylint:disable=no-name-in-module
2021
errors_impl as tf_errors)
@@ -162,9 +163,14 @@ def _handle_lr_finder(self) -> bool:
162163
success = lrf.find()
163164
return self._config["lr_finder_mode"] == "graph_and_exit" or not success
164165

165-
learning_rate = self._model.state.sessions[1]["config"]["learning_rate"]
166+
learning_rate = self._model.state.lr_finder
167+
if learning_rate < 0.:
168+
logger.debug("No learning rate finder rate stored. Not setting")
169+
return False
170+
166171
logger.info("Setting learning rate from Learning Rate Finder to %s",
167172
f"{learning_rate:.1e}")
173+
K.set_value(self._model.model.optimizer.lr, learning_rate)
168174
return False
169175

170176
def _set_tensorboard(self) -> tf.keras.callbacks.TensorBoard:
@@ -807,7 +813,7 @@ class _Timelapse(): # pylint:disable=too-few-public-methods
807813
image_paths: dict
808814
The full paths to the training images for each side of the model
809815
"""
810-
def __init__(self,
816+
def __init__(self, # pylint:disable=too-many-positional-arguments
811817
model: ModelBase,
812818
coverage_ratio: float,
813819
image_count: int,

0 commit comments

Comments
 (0)