fdq 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fdq/__about__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Package metadata."""
2
+
3
+ __version__ = "0.0.1"
fdq/__init__.py ADDED
File without changes
fdq/experiment.py ADDED
@@ -0,0 +1,689 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import math
5
+ import torch
6
+ import wandb
7
+ import shutil
8
+ import argparse
9
+ import importlib
10
+ import funkybob
11
+ from tqdm import tqdm
12
+ from typing import List
13
+ from datetime import datetime
14
+ from lossFunctions import createLoss
15
+ from ui_functions import iprint, wprint
16
+ from optimizer import createOptimizer, set_lr_schedule
17
+ from misc import (
18
+ remove_file,
19
+ store_processing_infos,
20
+ FCQmode,
21
+ recursive_dict_update,
22
+ DictToObj,
23
+ replace_tilde_with_abs_path,
24
+ save_train_history,
25
+ )
26
+
27
+
28
+ class fdqExperiment:
29
+ def __init__(self, inargs: argparse.Namespace) -> None:
30
+ self.inargs = inargs
31
+ self.parse_and_clean_args()
32
+ # ------------- GLOBALS ------------------------------
33
+ self.project = self.exp_def.globals.project.replace(" ", "_")
34
+ self.experimentName = self.experiment_file_path.split("/")[-1].split(".json")[0]
35
+ self.funky_name = None
36
+ self.checkpoint_frequency = self.exp_def.store.checkpoint_frequency
37
+ self.mode = FCQmode()
38
+ self.creation_time = datetime.now()
39
+ self.finish_time = None
40
+ self.run_time = None
41
+ self.run_info = {}
42
+ # ------------- Train parameters ------------------------------
43
+ self.gradacc_iter = self.exp_def.train.args.get(
44
+ "accumulate_grad_batches", default=1
45
+ )
46
+ self.useAMP = bool(self.exp_def.train.args.use_AMP)
47
+ self.nb_epochs = self.exp_def.train.args.epochs
48
+ # ------------- Train variables ------------------------------
49
+ self.current_epoch = 0
50
+ self.start_epoch = 0
51
+ self.data = {}
52
+ self.models = {}
53
+ self.inference_model_paths = {}
54
+ self.optimizers = {}
55
+ self.lr_schedulers = {}
56
+ self.losses = {}
57
+ self.last_model_path = {}
58
+ self.best_val_model_path = {}
59
+ self.best_train_model_path = {}
60
+ self.checkpoint_path = None
61
+ self._results_dir = None
62
+ self._test_dir = None
63
+ self._valLoss = float("inf")
64
+ self._trainLoss = float("inf")
65
+ self.bestValLoss = float("inf")
66
+ self.bestTrainLoss = float("inf")
67
+ self.valLoss_per_ep: List[float] = []
68
+ self.trainLoss_per_ep: List[float] = []
69
+ self.new_best_train_loss = False # flag to indicate if a new best epoch was reached according to train loss
70
+ self.new_best_train_loss_ep_id = None
71
+ self.new_best_val_loss = False # flag to indicate if a new best epoch was reached according to val loss
72
+ self.new_best_val_loss_ep_id = None
73
+ self.early_stop_detected = False
74
+ # ------------- MGMT attributes ------------------------------
75
+ self.useTensorboard = self.exp_file.get("store", {}).get("tensorboard", False)
76
+ self.tb_writer = None
77
+ self.useWandb = self.exp_file.get("store", {}).get("use_wandb", False)
78
+ self.wandb_project = self.exp_file.get("store", {}).get("wandb_project", None)
79
+ self.wandb_entity = self.exp_file.get("store", {}).get("wandb_entity", None)
80
+ self.wandb_key = self.exp_file.get("store", {}).get("wandb_key", None)
81
+ self.wandb_initialized = False
82
+ # ------------- SLURM ------------------------------
83
+ slurm_job_id = os.getenv("SLURM_JOB_ID")
84
+ if isinstance(slurm_job_id, str) and slurm_job_id.isdigit():
85
+ self.is_slurm = True
86
+ self.slurm_job_id = slurm_job_id
87
+ else:
88
+ self.is_slurm = False
89
+ self.slurm_job_id = None
90
+ self.previous_slurm_job_id = None
91
+ # ------------- CUDA / CPU -------------------------
92
+ if torch.cuda.is_available() and bool(self.exp_def.train.args.use_GPU):
93
+ torch.cuda.empty_cache()
94
+ self.device = torch.device("cuda")
95
+ self.is_cuda = True
96
+ iprint(
97
+ f"CUDA available: {torch.cuda.is_available()}. NB devices: {torch.cuda.device_count()}"
98
+ )
99
+ else:
100
+ wprint("NO CUDA available - CPU mode")
101
+ self.device = torch.device("cpu")
102
+ self.is_cuda = False
103
+
104
+ def parse_and_clean_args(self):
105
+ self.experiment_file_path = self.inargs.experimentfile
106
+
107
+ with open(self.experiment_file_path, "r", encoding="utf8") as fp:
108
+ try:
109
+ self.exp_file = json.load(fp)
110
+ except Exception as exc:
111
+ raise ValueError(
112
+ f"Error loading experiment file {self.experiment_file_path} (check syntax?)."
113
+ ) from exc
114
+
115
+ self.globals = self.exp_file.get("globals")
116
+ if self.globals is None:
117
+ raise ValueError(
118
+ f"Error: experiment file does not comply - please check template! {self.experiment_file_path}."
119
+ )
120
+
121
+ parent = self.globals.get("parent", {})
122
+ # parent must be in same directory or defined with absolute path
123
+ if parent is not None:
124
+ if parent[0] == "/":
125
+ self.parent_file_path = parent
126
+ else:
127
+ self.parent_file_path = os.path.abspath(
128
+ os.path.join(os.path.split(self.experiment_file_path)[0], parent)
129
+ )
130
+
131
+ if not os.path.exists(self.parent_file_path):
132
+ raise FileNotFoundError(
133
+ f"Error: File {self.parent_file_path} not found."
134
+ )
135
+
136
+ with open(self.parent_file_path, "r", encoding="utf8") as fp:
137
+ try:
138
+ parent_expfile = json.load(fp)
139
+ except Exception as exc:
140
+ raise ValueError(
141
+ f"Error loading experiment file {self.parent_file_path} (check syntax?)."
142
+ ) from exc
143
+
144
+ self.exp_file = recursive_dict_update(
145
+ d_parent=parent_expfile, d_child=self.exp_file
146
+ )
147
+
148
+ else:
149
+ self.parent_file_path = None
150
+ replace_tilde_with_abs_path(self.exp_file)
151
+ self.exp_def = DictToObj(self.exp_file)
152
+
153
+ def setupData(self):
154
+ for data_name, data_source in self.exp_def.data.items():
155
+ processor_path = data_source.processor
156
+
157
+ if not os.path.exists(processor_path):
158
+ raise FileNotFoundError(f"Processor file not found: {processor_path}")
159
+
160
+ parent_dir = os.path.dirname(processor_path)
161
+ if parent_dir not in sys.path:
162
+ sys.path.append(parent_dir)
163
+
164
+ module_name = os.path.splitext(os.path.basename(processor_path))[0]
165
+ processor = importlib.import_module(module_name)
166
+ self.data[data_name] = DictToObj(processor.createDatasets(self))
167
+
168
+ def runEvaluator(self):
169
+ evaluator_path = self.exp_def.test.evaluator
170
+
171
+ if not os.path.exists(evaluator_path):
172
+ raise FileNotFoundError(f"Evaluator file not found: {evaluator_path}")
173
+
174
+ parent_dir = os.path.dirname(evaluator_path)
175
+ if parent_dir not in sys.path:
176
+ sys.path.append(parent_dir)
177
+
178
+ module_name = os.path.splitext(os.path.basename(evaluator_path))[0]
179
+ currentEvaluator = importlib.import_module(module_name)
180
+
181
+ return currentEvaluator.createEvaluator(self)
182
+
183
+ def createModel(self, instantiate=True):
184
+ for model_name, model_source in self.exp_def.models:
185
+ model_path = model_source.name
186
+
187
+ if not os.path.exists(model_path):
188
+ current_file_path = os.path.abspath(__file__)
189
+ networks_dir = os.path.abspath(
190
+ os.path.join(os.path.dirname(current_file_path), "../networks/")
191
+ )
192
+ model_path = os.path.join(networks_dir, model_path)
193
+
194
+ if not os.path.exists(model_path):
195
+ raise FileNotFoundError(f"Model file not found: {model_path}")
196
+
197
+ parent_dir = os.path.dirname(model_path)
198
+ if parent_dir not in sys.path:
199
+ sys.path.append(parent_dir)
200
+
201
+ module_name = os.path.splitext(os.path.basename(model_path))[0]
202
+ model = importlib.import_module(module_name)
203
+ if instantiate:
204
+ self.models[model_name] = model.createNetwork(self).to(self.device)
205
+
206
+ def copy_data_to_scratch(self):
207
+ """
208
+ Copy all datasets to scratch dir, and update the paths
209
+ """
210
+
211
+ def _mkdir(path):
212
+ if not os.path.exists(path):
213
+ os.makedirs(path)
214
+
215
+ def _cp_files(paths, name):
216
+ if paths is not None:
217
+ if not isinstance(paths, list):
218
+ raise ValueError(f"{name} must be defined as a list!")
219
+
220
+ try:
221
+ dst_path = os.path.join(self.clusterDataBasePath, name + "/")
222
+ _mkdir(dst_path)
223
+
224
+ for i, pf in enumerate(tqdm(paths, desc=f"Copying {name} files")):
225
+ new_path = os.path.join(dst_path, os.path.basename(pf))
226
+ os.system(f"rsync -au {pf} {new_path}")
227
+ paths[i] = new_path
228
+
229
+ except Exception as exc:
230
+ raise ValueError(
231
+ f"Unable to copy {pf} to scratch location!"
232
+ ) from exc
233
+
234
+ if self.clusterDataBasePath is None:
235
+ return
236
+
237
+ _mkdir(self.clusterDataBasePath)
238
+
239
+ if self.dataBasePath is not None:
240
+ try:
241
+ dst_path = os.path.join(self.clusterDataBasePath, "base_path/")
242
+ if os.path.exists(dst_path):
243
+ shutil.rmtree(dst_path)
244
+ shutil.copytree(self.dataBasePath, dst_path)
245
+ self.dataBasePath = dst_path
246
+ except Exception as exc:
247
+ raise ValueError(
248
+ f"Unable to copy {self.dataBasePath} to scratch location!"
249
+ ) from exc
250
+
251
+ # if self.run_train: TODO
252
+ _cp_files(self.trainFilesPath, "train_files_path")
253
+ _cp_files(self.valFilesPath, "val_files_path")
254
+ # if self.run_test or self.run_test_auto:
255
+ _cp_files(self.testFilesPath, "test_files_path")
256
+
257
+ iprint("----------------------------------------------------")
258
+ iprint("Copy datasets to temporary scratch location... Done!")
259
+ iprint("----------------------------------------------------")
260
+
261
+ def prepareTrainLoop(self):
262
+ train_path = self.exp_def.train.train_loop
263
+
264
+ if not os.path.exists(train_path):
265
+ raise FileNotFoundError(f"Training file not found: {train_path}")
266
+
267
+ parent_dir = os.path.dirname(train_path)
268
+ if parent_dir not in sys.path:
269
+ sys.path.append(parent_dir)
270
+
271
+ module_name = os.path.splitext(os.path.basename(train_path))[0]
272
+ self.trainer = importlib.import_module(module_name)
273
+
274
+ # try:
275
+ # self.train_function = importlib.import_module(
276
+ # f"trainings.{self.training_strategy}"
277
+ # )
278
+ # except Exception as exc:
279
+ # raise ImportError(
280
+ # f"Error loading training strategy {self.training_strategy}."
281
+ # ) from exc
282
+
283
+ # self.copy_data_to_scratch()
284
+
285
+ def prepareTraining(self):
286
+ self.mode.train()
287
+ self.setupData()
288
+ self.prepareTrainLoop()
289
+ self.createModel()
290
+ createOptimizer(self)
291
+ set_lr_schedule(self)
292
+ createLoss(self)
293
+
294
+ if self.useAMP:
295
+ self.scaler = torch.amp.GradScaler(device=self.device, enabled=True)
296
+
297
+ if self.inargs.resume_path is not None:
298
+ iprint(
299
+ "--------------------------------------------------------------------------"
300
+ )
301
+ iprint(f"Loading checkpoint: {self.inargs.resume_pathh}")
302
+
303
+ self.load_checkpoint(self.inargs.resume_path)
304
+
305
+ self.cp_to_res_dir(file_path=self.experiment_file_path)
306
+
307
+ if self.parent_file_path is not None:
308
+ self.cp_to_res_dir(file_path=self.parent_file_path)
309
+
310
+ store_processing_infos(self)
311
+
312
+ @property
313
+ def results_dir(self):
314
+ if self._results_dir is None:
315
+ dt_string = self.creation_time.strftime("%Y%m%d_%H_%M_%S")
316
+ if self.funky_name is None:
317
+ self.funky_name = next(iter(funkybob.RandomNameGenerator()))
318
+
319
+ folder_name = f"{dt_string}__{self.funky_name}"
320
+
321
+ if self.is_slurm:
322
+ folder_name += f"__{self.slurm_job_id}"
323
+
324
+ if self.is_slurm:
325
+ res_base_path = self.exp_file.get("store", {}).get(
326
+ "cluster_results_path", None
327
+ )
328
+ if res_base_path is None:
329
+ raise ValueError("Error, cluster_results_path was not defined.")
330
+
331
+ else:
332
+ res_base_path = self.exp_file.get("store", {}).get("results_path", None)
333
+ if res_base_path is None:
334
+ raise ValueError("Error, result path was not defined.")
335
+
336
+ if res_base_path[0] == "~":
337
+ res_base_path = os.path.expanduser(res_base_path)
338
+
339
+ self._results_dir = os.path.join(
340
+ res_base_path, self.project, self.experimentName, folder_name
341
+ )
342
+
343
+ if not os.path.exists(self._results_dir):
344
+ os.makedirs(self._results_dir)
345
+
346
+ return self._results_dir
347
+
348
+ @property
349
+ def results_output_dir(self):
350
+ if self._results_output_dir is None:
351
+ self._results_output_dir = os.path.join(
352
+ self.results_dir, "training_outputs"
353
+ )
354
+ if not os.path.exists(self._results_output_dir):
355
+ os.makedirs(self._results_output_dir)
356
+ return self._results_output_dir
357
+
358
+ @property
359
+ def test_dir(self):
360
+ if self._test_dir is None:
361
+ folder_name = self.creation_time.strftime("%Y%m%d_%H_%M_%S")
362
+ if self.is_slurm:
363
+ folder_name += f"__{self.slurm_job_id}"
364
+ self._test_dir = os.path.join(self.results_dir, "test", folder_name)
365
+ if not os.path.exists(self._test_dir):
366
+ os.makedirs(self._test_dir)
367
+ return self._test_dir
368
+
369
+ @property
370
+ def valLoss(self):
371
+ return self._valLoss
372
+
373
+ @valLoss.setter
374
+ def valLoss(self, value):
375
+ self._valLoss = value
376
+ self.valLoss_per_ep.append(value)
377
+ if not math.isnan(value):
378
+ self.bestValLoss = min(self.bestValLoss, self._valLoss)
379
+ self.new_best_val_loss = self.bestValLoss == value
380
+ self.new_best_val_loss_ep_id = self.current_epoch
381
+
382
+ @property
383
+ def trainLoss(self):
384
+ return self._trainLoss
385
+
386
+ @trainLoss.setter
387
+ def trainLoss(self, value):
388
+ self._trainLoss = value
389
+ self.trainLoss_per_ep.append(value)
390
+ if not math.isnan(value):
391
+ self.bestTrainLoss = min(self.bestTrainLoss, self._trainLoss)
392
+ self.new_best_train_loss = self.bestTrainLoss == value
393
+ self.new_best_train_loss_ep_id = self.current_epoch
394
+
395
+ def cp_to_res_dir(self, file_path):
396
+ fn = file_path.split("/")[-1]
397
+ iprint(f"Saving {fn} to {self.results_dir}...")
398
+ shutil.copyfile(file_path, f"{self.results_dir}/{fn}")
399
+
400
+ def copy_files_to_test_dir(self, file_path):
401
+ fn = file_path.split("/")[-1]
402
+ iprint(f"Saving {fn} to {self.test_dir}...")
403
+ shutil.copyfile(file_path, f"{self.test_dir}/{fn}")
404
+
405
+ def load_checkpoint(self, path):
406
+ """
407
+ Load checkpoint to resume training.
408
+ """
409
+ if not os.path.exists(path):
410
+ raise FileNotFoundError(f"Error, checkpoint file {path} not found.")
411
+
412
+ try:
413
+ checkpoint = torch.load(path)
414
+ self.start_epoch = checkpoint["epoch"]
415
+ self.trainLoss = checkpoint["train_loss"]
416
+ self.valLoss = checkpoint["val_loss"]
417
+ self.funky_name = checkpoint["funky_name"]
418
+ self.previous_slurm_job_id = checkpoint.get("slurm_job_id")
419
+ except Exception as exc:
420
+ raise ValueError(f"Error loading checkpoint {path}.") from exc
421
+
422
+ iprint(
423
+ f"Loaded checkpoint {self.start_epoch}. Train loss: {self.trainLoss:.4f}, val loss: {self.valLoss:.4f}"
424
+ )
425
+
426
+ if self.start_epoch >= self.nb_epochs - 1:
427
+ raise ValueError(
428
+ f"Error, checkpoint epoch {self.start_epoch + 1} already reached defined nb epochs ({self.nb_epochs})."
429
+ )
430
+
431
+ self.model.load_state_dict(checkpoint["model_state_dict"])
432
+ self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
433
+
434
+ def save_checkpoint(self):
435
+ if self.checkpoint_frequency is None or self.checkpoint_frequency == 0:
436
+ return
437
+
438
+ if self.current_epoch % self.checkpoint_frequency != 0:
439
+ return
440
+
441
+ remove_file(self.checkpoint_path)
442
+ self.checkpoint_path = os.path.join(
443
+ self.results_dir, f"checkpoint_e{self.current_epoch}.fdqcpt"
444
+ )
445
+
446
+ iprint(f"Saving checkpoint to {self.checkpoint_path}")
447
+
448
+ if self.optimizers == {}:
449
+ optimizer_state = "No optimizers used"
450
+ else:
451
+ optimizer_state = {
452
+ optim_name: optim.state_dict()
453
+ for optim_name, optim in self.optimizers.items()
454
+ }
455
+
456
+ model_state = {
457
+ model_name: model.state_dict() for model_name, model in self.models.items()
458
+ }
459
+
460
+ checkpoint = {
461
+ "epoch": self.current_epoch,
462
+ "model_state_dict": model_state,
463
+ "optimizer": optimizer_state,
464
+ "train_loss": self.trainLoss_per_ep[-1],
465
+ "val_loss": self.valLoss_per_ep[-1],
466
+ "funky_name": self.funky_name,
467
+ "slurm_job_id": self.slurm_job_id,
468
+ }
469
+
470
+ torch.save(checkpoint, self.checkpoint_path)
471
+
472
+ def save_current_model(self):
473
+ """
474
+ Store model including weights.
475
+ This is run at the end of every epoch.
476
+ """
477
+
478
+ for model_name, model in self.models.items():
479
+ if self.exp_def.store.get("save_last_model", False):
480
+ remove_file(self.last_model_path.get(model_name))
481
+ self.last_model_path[model_name] = os.path.join(
482
+ self.results_dir,
483
+ f"last_{model_name}_e{self.current_epoch}.fdqm",
484
+ )
485
+ torch.save(model, self.last_model_path[model_name])
486
+
487
+ # new best val loss (default!)
488
+ best_model_path = os.path.join(
489
+ self.results_dir,
490
+ f"best_val_{model_name}_e{self.current_epoch}.fdqm",
491
+ )
492
+ if (
493
+ self.current_epoch == self.start_epoch
494
+ or self.exp_def.store.get("save_best_val_model", False)
495
+ and self.new_best_val_loss
496
+ ):
497
+ remove_file(self.best_val_model_path.get(model_name))
498
+ self.best_val_model_path[model_name] = best_model_path
499
+ torch.save(model, best_model_path)
500
+
501
+ # save best model according to train loss
502
+ # this might be useful if we use dummy validation losses like in diffusion
503
+ best_train_model_path = os.path.join(
504
+ self.results_dir,
505
+ f"best_train_{model_name}_e{self.current_epoch}.fdqm",
506
+ )
507
+ if (
508
+ self.current_epoch == self.start_epoch
509
+ or self.exp_def.store.get("save_best_train_model", False)
510
+ and self.new_best_train_loss
511
+ ):
512
+ remove_file(self.best_train_model_path.get(model_name))
513
+ self.best_train_model_path[model_name] = best_train_model_path
514
+ torch.save(model, best_train_model_path)
515
+
516
+ def load_models(self):
517
+ self.createModel(instantiate=False)
518
+ for model_name, _ in self.exp_def.models:
519
+ path = self.inference_model_paths[model_name]
520
+ iprint(f"Loading model {model_name} from {path}")
521
+ self.models[model_name] = torch.load(path, weights_only=False).to(
522
+ self.device
523
+ )
524
+ self.models[model_name].eval()
525
+
526
+ def dump_model(self, res_folder=None):
527
+ # https://pytorch.org/tutorials/advanced/cpp_export.html
528
+ iprint("Start model dumping")
529
+
530
+ example = torch.rand(
531
+ 1, self.nb_in_channels, self.net_input_size[0], self.net_input_size[1]
532
+ ).to(self.device)
533
+
534
+ # jit tracer to serialize model using example
535
+ # this only works if there is no flow control applied in the model.
536
+ # otherwise, the model has to be annotated and the torch script compiler applied.
537
+ traced_script_module = torch.jit.trace(self.model, example)
538
+
539
+ # test network
540
+ # test_out = traced_script_module(example)
541
+ # print(test_out)
542
+
543
+ iprint(f"Storing model to {os.path.join(res_folder, 'serialized_model.fdqpt')}")
544
+ traced_script_module.save(os.path.join(res_folder, "serialized_model.fdqpt"))
545
+
546
+ def get_next_export_fn(self, name=None, file_ending="jpg"):
547
+ if self.mode.is_test():
548
+ if name is None:
549
+ path = os.path.join(
550
+ self.test_dir, f"test_image_{self.test_output_id:02}.{file_ending}"
551
+ )
552
+ else:
553
+ path = os.path.join(
554
+ self.test_dir,
555
+ f"test_image_{self.test_output_id:02}__{name}.{file_ending}",
556
+ )
557
+
558
+ self.test_output_id += 1
559
+
560
+ else:
561
+ if name is None:
562
+ path = os.path.join(
563
+ self.results_output_dir,
564
+ f"out_e{self.current_epoch:02}_{self.train_output_id:02}.{file_ending}",
565
+ )
566
+ else:
567
+ path = os.path.join(
568
+ self.results_output_dir,
569
+ f"out_e{self.current_epoch:02}_{self.train_output_id:02}__{name}.{file_ending}",
570
+ )
571
+ self.train_output_id += 1
572
+
573
+ return path
574
+
575
+ def print_dataset_infos(self):
576
+ iprint("-------------------------------------------")
577
+ if self.valset_is_train_subset:
578
+ iprint("Validation set is a subset of the training set.")
579
+ iprint(f"Validation subset ratio: {self.val_from_train_ratio}")
580
+ iprint(f"Nb samples train: {self.trainset_size}")
581
+ iprint(f"Train subset: {self.train_subset}")
582
+ iprint(f"Nb samples val: {self.valset_size}")
583
+ iprint(f"Validation subset: {self.val_subset}")
584
+ iprint(f"Nb samples test: {self.testset_size}")
585
+ iprint(f"Test subset: {self.test_subset}")
586
+ iprint("-------------------------------------------")
587
+
588
+ def clean_up(self):
589
+ iprint("-------------------------------------------")
590
+ iprint("Training done!\nCleaning up..")
591
+ iprint("-------------------------------------------")
592
+ if self.useTensorboard:
593
+ self.tb_writer.close()
594
+
595
+ if self.wandb_initialized:
596
+ wandb.finish()
597
+
598
+ store_processing_infos(self)
599
+
600
+ def check_early_stop(self):
601
+ """
602
+ 1) Stop training if the validation los over last last N epochs did not further decrease.
603
+ We want at least N epochs in each training start, also if its a resume from checkpoint training.
604
+ (--> Therefore, (cur_epoch - self.start_epoch) > self.early_stop_val_loss)
605
+
606
+ 2) Stop training if the loss is NaN for N epochs.
607
+ """
608
+ e_stop_nan = self.exp_def.train.args.early_stop_nan
609
+ e_stop_val = self.exp_def.train.args.early_stop_val_loss
610
+ e_stop_train = self.exp_def.train.args.early_stop_train_loss
611
+
612
+ # early stop NaN ?
613
+ if e_stop_nan is not None:
614
+ if all(math.isnan(x) for x in self.trainLoss_per_ep[-e_stop_nan:]):
615
+ self.early_stop_nan_detected = "NaN detected"
616
+ wprint(
617
+ "\n###############################\n"
618
+ f"!! Early Stop NaN EP {self.current_epoch} !!\n"
619
+ "###############################\n"
620
+ )
621
+ return True
622
+
623
+ # early stop val loss?
624
+ # did we have a new best val loss within the last N epochs?
625
+ # we want at least N losses
626
+ if e_stop_val is not None and len(self.valLoss_per_ep) >= e_stop_val:
627
+ # was there a new best val loss within the last N epochs?
628
+ if min(self.valLoss_per_ep[-e_stop_val:]) != self.bestValLoss:
629
+ self.early_stop_nan_detected = "ValLoss_stagnated"
630
+ wprint(
631
+ "\n###############################\n"
632
+ f"!! Early Stop Val Loss EP {self.current_epoch} !!\n"
633
+ "###############################\n"
634
+ )
635
+ return True
636
+
637
+ # early stop train loss?
638
+ elif e_stop_train is not None and len(self.trainLoss_per_ep) >= e_stop_train:
639
+ if min(self.trainLoss_per_ep[-e_stop_train:]) != self.bestTrainLoss:
640
+ wprint(
641
+ "\n###############################\n"
642
+ f"!! Early Stop Train Loss EP {self.current_epoch} !!\n"
643
+ "###############################\n"
644
+ )
645
+ self.early_stop_nan_detected = "TrainLoss_stagnated"
646
+ return True
647
+
648
+ return False
649
+
650
+ def update_gradients(self, b_idx, loader_name, model_name):
651
+ length_loader = self.data[loader_name].n_train_batches
652
+
653
+ if ((b_idx + 1) % self.gradacc_iter == 0) or (b_idx + 1 == length_loader):
654
+ if self.useAMP:
655
+ self.scaler.step(self.optimizers[model_name])
656
+ self.scaler.update()
657
+ else:
658
+ self.optimizers[model_name].step()
659
+
660
+ self.optimizers[model_name].zero_grad()
661
+
662
+ def finalize_epoch(self):
663
+ # update learning rate
664
+ for model_name in self.models:
665
+ scheduler = self.lr_schedulers[model_name]
666
+ if scheduler is not None:
667
+ current_LR = scheduler.get_last_lr()
668
+ scheduler.step()
669
+ new_LR = scheduler.get_last_lr()
670
+ if current_LR != new_LR:
671
+ iprint(f"Updating LR of {model_name} from {current_LR} to {new_LR}")
672
+
673
+ # end of last epoch
674
+ if self.current_epoch == self.nb_epochs - 1:
675
+ self.finish_time = datetime.now()
676
+ store_processing_infos(self)
677
+
678
+ try:
679
+ self.run_time = datetime.now() - self.creation_time
680
+ td = self.run_time
681
+ run_t_string = f"days: {td.days}, hours: {td.seconds // 3600}, minutes: {td.seconds % 3600 / 60.0:.0f}"
682
+ iprint(f"Current run time: {run_t_string}")
683
+ store_processing_infos(self)
684
+ except Exception:
685
+ pass
686
+
687
+ save_train_history(self)
688
+ self.save_checkpoint()
689
+ self.save_current_model()