@@ -91,6 +91,12 @@ def get_args():
9191 default = 1 ,
9292 help = 'How many dataset chunks to pool, shuffle, and train on in a single fit call (default: 1)'
9393 )
94+ parser .add_argument (
95+ '--resume-from-checkpoint' ,
96+ type = str ,
97+ default = None ,
98+ help = 'Path to .keras checkpoint file to resume training from (default: None)'
99+ )
94100 args , _ = parser .parse_known_args ()
95101 return args
96102
@@ -278,20 +284,58 @@ def categorical_crossentropy_2d_gene_ml(y_true, y_pred):
278284 except Exception :
279285 pass
280286
281- if N_GPUS > 1 :
282- # https://keras.io/guides/distributed_training/
283- strategy = tensorflow .distribute .MirroredStrategy ()
284- tee ('Number of devices: {}' .format (strategy .num_replicas_in_sync ))
285- with strategy .scope ():
287+ if args .resume_from_checkpoint :
288+ tee (f"\033 [1mLoading model from checkpoint: { args .resume_from_checkpoint } \033 [0m" )
289+ # Load with custom objects
290+ custom_objects = {
291+ 'categorical_crossentropy_2d_gene_ml' : categorical_crossentropy_2d_gene_ml ,
292+ 'WarmupSchedule' : WarmupSchedule ,
293+ 'DecayOnPlateauSchedule' : DecayOnPlateauSchedule ,
294+ }
295+ model = keras .models .load_model (args .resume_from_checkpoint , custom_objects = custom_objects )
296+
297+ # Extract the decay_factor from the saved optimizer config
298+ # The learning rate schedule is saved in the optimizer, and we need to extract
299+ # the decay_factor value to continue modifying it if needed
300+ try :
301+ lr_config = model .optimizer .learning_rate .get_config ()
302+ saved_decay_factor = lr_config .get ('decay_factor' , 1.0 )
303+ tee (f"Loaded decay_factor from checkpoint: { saved_decay_factor } " )
304+ except AttributeError :
305+ saved_decay_factor = 1.0
306+ tee ("Could not extract decay_factor from checkpoint, assuming 1.0 (no decay applied yet)" )
307+
308+ # Create a new decay_factor variable initialized to the saved value
309+ decay_factor = tensorflow .Variable (saved_decay_factor , trainable = False , dtype = tensorflow .float32 , name = 'lr_decay_factor' )
310+
311+ # Get current learning rate
312+ current_lr = model .optimizer .learning_rate
313+ if callable (current_lr ):
314+ lr_value = float (tensorflow .keras .backend .get_value (current_lr (model .optimizer .iterations )))
315+ else :
316+ lr_value = float (tensorflow .keras .backend .get_value (current_lr ))
317+ tee (f"Resumed from checkpoint. Current learning rate: { lr_value :.5f} " )
318+
319+ # Extract starting epoch from checkpoint filename (e.g., "ep10" -> start at 11)
320+ match = re .search (r'_ep(\d+)\.keras' , args .resume_from_checkpoint )
321+ start_epoch = int (match .group (1 )) + 1 if match else 1
322+ tee (f"\033 [1mResuming from epoch { start_epoch } \033 [0m" )
323+ else :
324+ if N_GPUS > 1 :
325+ # https://keras.io/guides/distributed_training/
326+ strategy = tensorflow .distribute .MirroredStrategy ()
327+ tee ('Number of devices: {}' .format (strategy .num_replicas_in_sync ))
328+ with strategy .scope ():
329+ model = GeneML (L , W , AR , num_classes )
330+ model .compile (loss = loss ,
331+ optimizer = keras .optimizers .Adam (learning_rate = lr_schedule ,
332+ weight_decay = WEIGHT_DECAY ))
333+ else :
286334 model = GeneML (L , W , AR , num_classes )
287335 model .compile (loss = loss ,
288336 optimizer = keras .optimizers .Adam (learning_rate = lr_schedule ,
289337 weight_decay = WEIGHT_DECAY ))
290- else :
291- model = GeneML (L , W , AR , num_classes )
292- model .compile (loss = loss ,
293- optimizer = keras .optimizers .Adam (learning_rate = lr_schedule ,
294- weight_decay = WEIGHT_DECAY ))
338+ start_epoch = 1
295339 # model.summary()
296340
297341 ###############################################################################
@@ -374,7 +418,7 @@ def print_performance_metrics(indices, max_eval):
374418
375419 return acceptor_score , donor_score
376420
377- for epoch_num in range (1 , args .num_epochs + 1 ):
421+ for epoch_num in range (start_epoch , args .num_epochs + 1 ):
378422 # Shuffle indices for this epoch (no replacement)
379423 # Use epoch-based seed for deterministic but different permutation per epoch
380424 np .random .seed (SEED + epoch_num )
0 commit comments