garmentiq.classification.utils
Training helpers for the classification module.
Holds the in-memory dataset wrapper, the per-epoch train and validation loops,
checkpoint saving, and the parameter validators that apply defaults to the param
dictionary, including normalising device.
1"""Training helpers for the classification module. 2 3Holds the in-memory dataset wrapper, the per-epoch train and validation loops, 4checkpoint saving, and the parameter validators that apply defaults to the `param` 5dictionary, including normalising `device`. 6""" 7import torch 8import torch.nn as nn 9import torch.optim as optim 10from torch.utils.data import DataLoader, Dataset 11from typing import Callable 12from tqdm.auto import tqdm 13import os 14from sklearn.metrics import f1_score, accuracy_score 15import random 16import numpy as np 17from garmentiq.utils.device import resolve_device 18 19 20class CachedDataset(Dataset): 21 """ 22 A PyTorch Dataset that wraps pre-loaded data (images and labels) in memory. 23 24 This dataset is designed to be used when images and labels have already been 25 loaded and preprocessed into PyTorch tensors or NumPy arrays, avoiding 26 repeated disk I/O during training/validation. 27 28 Attributes: 29 indices (list or numpy.ndarray): A list or array of indices that map 30 to specific items in `cached_images` and `cached_labels`. This 31 allows for flexible subsetting (e.g., for train/validation splits). 32 cached_images (torch.Tensor): A tensor containing the pre-loaded images. 33 cached_labels (torch.Tensor): A tensor containing the pre-loaded labels. 34 """ 35 36 def __init__(self, indices, cached_images, cached_labels): 37 """ 38 Initializes the CachedDataset. 39 40 Args: 41 indices (list or numpy.ndarray): Indices to select from the cached data. 42 cached_images (torch.Tensor): Pre-loaded image tensor. 43 cached_labels (torch.Tensor): Pre-loaded label tensor. 44 """ 45 self.indices = indices 46 self.cached_images = cached_images 47 self.cached_labels = cached_labels 48 49 def __len__(self): 50 """ 51 Returns the number of samples in the dataset. 52 53 Returns: 54 int: The number of samples. 55 """ 56 return len(self.indices) 57 58 def __getitem__(self, idx): 59 """ 60 Retrieves a sample from the dataset at the given index. 61 62 Args: 63 idx (int): The index of the sample to retrieve. 64 65 Returns: 66 tuple: A tuple containing the image and its corresponding label. 67 """ 68 actual_idx = self.indices[idx] 69 return self.cached_images[actual_idx], self.cached_labels[actual_idx] 70 71 72def seed_worker(worker_id, SEED=88): 73 """ 74 Seeds the random number generators for a DataLoader worker. 75 76 This function is intended to be passed as `worker_init_fn` to a PyTorch 77 DataLoader to ensure reproducibility across different worker processes. 78 It seeds Python's `random` module, NumPy, and PyTorch for each worker. 79 80 Args: 81 worker_id (int): The ID of the current worker process. 82 SEED (int, optional): The base seed value. The worker's seed will be 83 `SEED + worker_id`. Defaults to 88. 84 """ 85 worker_seed = SEED + worker_id 86 random.seed(worker_seed) 87 np.random.seed(worker_seed) 88 torch.manual_seed(worker_seed) 89 90 91def train_epoch(model, train_loader, optimizer, param): 92 """ 93 Performs a single training epoch for a PyTorch model. 94 95 Sets the model to training mode, iterates through the `train_loader`, 96 performs forward and backward passes, and updates model weights using 97 the provided optimizer. A progress bar is displayed, showing the batch loss. 98 99 Args: 100 model (torch.nn.Module): The PyTorch model to train. 101 train_loader (torch.utils.data.DataLoader): DataLoader for the training data. 102 optimizer (torch.optim.Optimizer): The optimizer used for updating model weights. 103 param (dict): A dictionary containing training parameters, including: 104 - "device" (torch.device): The device to use for training, e.g. `"cpu"`, 105 `"cuda"`, `"cuda:0"`, or `"mps"`. Normalised by `validate_train_param`. 106 107 Returns: 108 float: The average loss for the epoch. 109 """ 110 model.train() 111 running_loss = 0.0 112 train_pbar = tqdm(train_loader, desc=f"Training", leave=False) 113 114 for images, labels in train_pbar: 115 images, labels = images.to(param["device"], non_blocking=True), labels.to( 116 param["device"], non_blocking=True 117 ) 118 optimizer.zero_grad() 119 outputs = model(images) 120 121 # Calculate the loss 122 criterion = nn.CrossEntropyLoss() 123 loss = criterion(outputs, labels) 124 loss.backward() 125 optimizer.step() 126 127 running_loss += loss.item() * images.size(0) 128 train_pbar.set_postfix({"batch_loss": f"{loss.item():.4f}"}) 129 130 epoch_loss = running_loss / len(train_loader.dataset) 131 return epoch_loss 132 133 134def validate_epoch(model, val_loader, param): 135 """ 136 Performs a single validation epoch for a PyTorch model. 137 138 Sets the model to evaluation mode, iterates through the `val_loader` 139 without gradient calculations, and computes the validation loss, F1 score, 140 and accuracy. A progress bar is displayed, showing the batch validation loss. 141 142 Args: 143 model (torch.nn.Module): The PyTorch model to validate. 144 val_loader (torch.utils.data.DataLoader): DataLoader for the validation data. 145 param (dict): A dictionary containing training parameters, including: 146 - "device" (torch.device): The device to use for validation, e.g. `"cpu"`, 147 `"cuda"`, `"cuda:0"`, or `"mps"`. Normalised by `validate_train_param`. 148 149 Returns: 150 tuple: A tuple containing: 151 - val_loss (float): The average validation loss for the epoch. 152 - f1 (float): The weighted average F1 score on the validation set. 153 - acc (float): The accuracy score on the validation set. 154 """ 155 model.eval() 156 val_loss = 0.0 157 all_preds = [] 158 all_labels = [] 159 val_pbar = tqdm(val_loader, desc=f"Validation", leave=False) 160 161 with torch.no_grad(): 162 for images, labels in val_pbar: 163 images, labels = images.to(param["device"]), labels.to(param["device"]) 164 outputs = model(images) 165 criterion = nn.CrossEntropyLoss() 166 loss = criterion(outputs, labels) 167 val_loss += loss.item() * images.size(0) 168 169 # Collect predictions and true labels for F1 score calculation 170 _, preds = torch.max(outputs, 1) 171 all_preds.extend(preds.cpu().numpy()) # Move to CPU and convert to numpy 172 all_labels.extend(labels.cpu().numpy()) # Move to CPU and convert to numpy 173 174 val_pbar.set_postfix({"val_batch_loss": f"{loss.item():.4f}"}) 175 176 # Calculate the average validation loss 177 val_loss /= len(val_loader.dataset) 178 179 # Calculate F1 Score (if needed) 180 f1 = f1_score(all_labels, all_preds, average="weighted") 181 acc = accuracy_score(all_labels, all_preds) 182 183 return val_loss, f1, acc 184 185 186def save_best_model( 187 model, 188 val_loss, 189 best_fold_loss, 190 patience_counter, 191 overall_best_loss, 192 param, 193 fold, 194 best_model_path, 195): 196 """ 197 Saves the best model checkpoints based on validation loss and manages early stopping. 198 199 This function updates the `best_fold_loss` and `patience_counter` for the current 200 cross-validation fold. It also saves the model's state dictionary if it's the best 201 performing model for the current fold or the overall best model across all folds. 202 203 Args: 204 model (torch.nn.Module): The current PyTorch model being trained. 205 val_loss (float): The validation loss from the current epoch. 206 best_fold_loss (float): The best validation loss recorded so far for the current fold. 207 patience_counter (int): The number of epochs since the last improvement for the current fold. 208 overall_best_loss (float): The best validation loss recorded so far across all folds. 209 param (dict): A dictionary containing training parameters, including: 210 - "model_save_dir" (str): Directory where model checkpoints will be saved. 211 fold (int): The current fold number (0-indexed). 212 best_model_path (str): The full path where the overall best model will be saved. 213 214 Returns: 215 tuple: A tuple containing: 216 - best_fold_loss (float): The updated best validation loss for the current fold. 217 - patience_counter (int): The updated patience counter for the current fold. 218 - overall_best_loss (float): The updated overall best validation loss. 219 """ 220 # Save the best model for this fold 221 if val_loss < best_fold_loss: 222 best_fold_loss = val_loss 223 patience_counter = 0 224 torch.save( 225 model.state_dict(), 226 os.path.join(param["model_save_dir"], f"fold_{fold + 1}_best.pt"), 227 ) 228 else: 229 patience_counter += 1 230 231 # Save the overall best model 232 if val_loss < overall_best_loss: 233 overall_best_loss = val_loss 234 torch.save(model.state_dict(), best_model_path) 235 236 return best_fold_loss, patience_counter, overall_best_loss 237 238 239def validate_train_param(param: dict): 240 """ 241 Validates the parameter dictionary for training configuration. 242 243 This function checks for the presence and correct types of required 244 parameters for training, and applies default values for optional parameters 245 if they are not provided. The `device` entry is normalised in place into a 246 `torch.device`, so callers may supply either a string or a `torch.device`. 247 248 Args: 249 param (dict): The dictionary of training parameters to validate. 250 Optional Keys: 251 - `device` (Union[str, torch.device]): The device to train on, e.g. 252 `"cpu"`, `"cuda"`, `"cuda:0"`, or `"mps"`. Hardware acceleration is 253 opt-in; pass it explicitly to use a GPU or Apple Silicon. 254 Default is `"cpu"`. 255 256 Raises: 257 ValueError: If a required parameter is missing, or if the requested `device` is invalid 258 or unavailable on this machine. 259 TypeError: If a parameter has an incorrect type. 260 """ 261 # --- Required fields and types 262 required_keys = {"optimizer_class": type, "optimizer_args": dict} 263 264 # --- Normalise the requested device (accepts str or torch.device, defaults to CPU) 265 param["device"] = resolve_device(param.get("device", "cpu")) 266 267 # --- Optional fields with default values and expected types 268 optional_keys = { 269 "n_fold": (int, 5), 270 "n_epoch": (int, 100), 271 "patience": (int, 5), 272 "batch_size": (int, 64), 273 "model_save_dir": (str, "./models"), 274 "seed": (int, 88), 275 "seed_worker": (Callable, seed_worker), 276 "max_workers": (int, 0), 277 "best_model_name": (str, "best_model.pt"), 278 "pin_memory": (bool, False), 279 "persistent_workers": (bool, False), 280 } 281 282 # --- Validate required keys 283 for key, expected_types in required_keys.items(): 284 if key not in param: 285 raise ValueError(f"Missing required param key: '{key}'") 286 if not isinstance(param[key], expected_types): 287 raise TypeError( 288 f"param['{key}'] must be of type {expected_types}, got {type(param[key])}" 289 ) 290 291 # --- Apply defaults and type-check optional keys 292 for key, (expected_type, default) in optional_keys.items(): 293 if key not in param: 294 param[key] = default 295 elif expected_type is not None and not isinstance(param[key], expected_type): 296 raise TypeError( 297 f"param['{key}'] must be of type {expected_type}, got {type(param[key])}" 298 ) 299 300 301def validate_test_param(param: dict): 302 """ 303 Validates the parameter dictionary for testing configuration. 304 305 This function checks for the presence and correct types of optional 306 parameters for testing, and applies default values if they are not provided. 307 The `device` entry is normalised in place into a `torch.device`, so callers 308 may supply either a string or a `torch.device`. 309 310 Args: 311 param (dict): The dictionary of testing parameters to validate. 312 Optional Keys: 313 - `device` (Union[str, torch.device]): The device to evaluate on, e.g. 314 `"cpu"`, `"cuda"`, `"cuda:0"`, or `"mps"`. Hardware acceleration is 315 opt-in; pass it explicitly to use a GPU or Apple Silicon. 316 Default is `"cpu"`. 317 318 Raises: 319 ValueError: If the requested `device` is invalid or unavailable on this machine. 320 TypeError: If a parameter has an incorrect type. 321 """ 322 # --- Normalise the requested device (accepts str or torch.device, defaults to CPU) 323 param["device"] = resolve_device(param.get("device", "cpu")) 324 325 # --- Optional fields with default values and expected types 326 optional_keys = { 327 "batch_size": (int, 64), 328 } 329 330 # --- Apply defaults and type-check optional keys 331 for key, (expected_type, default) in optional_keys.items(): 332 if key not in param: 333 param[key] = default 334 elif expected_type is not None and not isinstance(param[key], expected_type): 335 raise TypeError( 336 f"param['{key}'] must be of type {expected_type}, got {type(param[key])}" 337 ) 338 339 340def validate_pred_param(param: dict): 341 """ 342 Validates the parameter dictionary for prediction configuration. 343 344 This function checks for the presence and correct types of optional 345 parameters for prediction, and applies default values if they are not provided. 346 The `device` entry is normalised in place into a `torch.device`, so callers 347 may supply either a string or a `torch.device`. 348 349 Args: 350 param (dict): The dictionary of prediction parameters to validate. 351 Optional Keys: 352 - `device` (Union[str, torch.device]): The device to predict on, e.g. 353 `"cpu"`, `"cuda"`, `"cuda:0"`, or `"mps"`. Hardware acceleration is 354 opt-in; pass it explicitly to use a GPU or Apple Silicon. 355 Default is `"cpu"`. 356 357 Raises: 358 ValueError: If the requested `device` is invalid or unavailable on this machine. 359 TypeError: If a parameter has an incorrect type. 360 """ 361 # --- Normalise the requested device (accepts str or torch.device, defaults to CPU) 362 param["device"] = resolve_device(param.get("device", "cpu")) 363 364 # --- Optional fields with default values and expected types 365 optional_keys = { 366 "batch_size": (int, 64), 367 } 368 369 # --- Apply defaults and type-check optional keys 370 for key, (expected_type, default) in optional_keys.items(): 371 if key not in param: 372 param[key] = default 373 elif expected_type is not None and not isinstance(param[key], expected_type): 374 raise TypeError( 375 f"param['{key}'] must be of type {expected_type}, got {type(param[key])}" 376 )
21class CachedDataset(Dataset): 22 """ 23 A PyTorch Dataset that wraps pre-loaded data (images and labels) in memory. 24 25 This dataset is designed to be used when images and labels have already been 26 loaded and preprocessed into PyTorch tensors or NumPy arrays, avoiding 27 repeated disk I/O during training/validation. 28 29 Attributes: 30 indices (list or numpy.ndarray): A list or array of indices that map 31 to specific items in `cached_images` and `cached_labels`. This 32 allows for flexible subsetting (e.g., for train/validation splits). 33 cached_images (torch.Tensor): A tensor containing the pre-loaded images. 34 cached_labels (torch.Tensor): A tensor containing the pre-loaded labels. 35 """ 36 37 def __init__(self, indices, cached_images, cached_labels): 38 """ 39 Initializes the CachedDataset. 40 41 Args: 42 indices (list or numpy.ndarray): Indices to select from the cached data. 43 cached_images (torch.Tensor): Pre-loaded image tensor. 44 cached_labels (torch.Tensor): Pre-loaded label tensor. 45 """ 46 self.indices = indices 47 self.cached_images = cached_images 48 self.cached_labels = cached_labels 49 50 def __len__(self): 51 """ 52 Returns the number of samples in the dataset. 53 54 Returns: 55 int: The number of samples. 56 """ 57 return len(self.indices) 58 59 def __getitem__(self, idx): 60 """ 61 Retrieves a sample from the dataset at the given index. 62 63 Args: 64 idx (int): The index of the sample to retrieve. 65 66 Returns: 67 tuple: A tuple containing the image and its corresponding label. 68 """ 69 actual_idx = self.indices[idx] 70 return self.cached_images[actual_idx], self.cached_labels[actual_idx]
A PyTorch Dataset that wraps pre-loaded data (images and labels) in memory.
This dataset is designed to be used when images and labels have already been loaded and preprocessed into PyTorch tensors or NumPy arrays, avoiding repeated disk I/O during training/validation.
Attributes:
- indices (list or numpy.ndarray): A list or array of indices that map
to specific items in
cached_imagesandcached_labels. This allows for flexible subsetting (e.g., for train/validation splits). - cached_images (torch.Tensor): A tensor containing the pre-loaded images.
- cached_labels (torch.Tensor): A tensor containing the pre-loaded labels.
37 def __init__(self, indices, cached_images, cached_labels): 38 """ 39 Initializes the CachedDataset. 40 41 Args: 42 indices (list or numpy.ndarray): Indices to select from the cached data. 43 cached_images (torch.Tensor): Pre-loaded image tensor. 44 cached_labels (torch.Tensor): Pre-loaded label tensor. 45 """ 46 self.indices = indices 47 self.cached_images = cached_images 48 self.cached_labels = cached_labels
Initializes the CachedDataset.
Arguments:
- indices (list or numpy.ndarray): Indices to select from the cached data.
- cached_images (torch.Tensor): Pre-loaded image tensor.
- cached_labels (torch.Tensor): Pre-loaded label tensor.
73def seed_worker(worker_id, SEED=88): 74 """ 75 Seeds the random number generators for a DataLoader worker. 76 77 This function is intended to be passed as `worker_init_fn` to a PyTorch 78 DataLoader to ensure reproducibility across different worker processes. 79 It seeds Python's `random` module, NumPy, and PyTorch for each worker. 80 81 Args: 82 worker_id (int): The ID of the current worker process. 83 SEED (int, optional): The base seed value. The worker's seed will be 84 `SEED + worker_id`. Defaults to 88. 85 """ 86 worker_seed = SEED + worker_id 87 random.seed(worker_seed) 88 np.random.seed(worker_seed) 89 torch.manual_seed(worker_seed)
Seeds the random number generators for a DataLoader worker.
This function is intended to be passed as worker_init_fn to a PyTorch
DataLoader to ensure reproducibility across different worker processes.
It seeds Python's random module, NumPy, and PyTorch for each worker.
Arguments:
- worker_id (int): The ID of the current worker process.
- SEED (int, optional): The base seed value. The worker's seed will be
SEED + worker_id. Defaults to 88.
92def train_epoch(model, train_loader, optimizer, param): 93 """ 94 Performs a single training epoch for a PyTorch model. 95 96 Sets the model to training mode, iterates through the `train_loader`, 97 performs forward and backward passes, and updates model weights using 98 the provided optimizer. A progress bar is displayed, showing the batch loss. 99 100 Args: 101 model (torch.nn.Module): The PyTorch model to train. 102 train_loader (torch.utils.data.DataLoader): DataLoader for the training data. 103 optimizer (torch.optim.Optimizer): The optimizer used for updating model weights. 104 param (dict): A dictionary containing training parameters, including: 105 - "device" (torch.device): The device to use for training, e.g. `"cpu"`, 106 `"cuda"`, `"cuda:0"`, or `"mps"`. Normalised by `validate_train_param`. 107 108 Returns: 109 float: The average loss for the epoch. 110 """ 111 model.train() 112 running_loss = 0.0 113 train_pbar = tqdm(train_loader, desc=f"Training", leave=False) 114 115 for images, labels in train_pbar: 116 images, labels = images.to(param["device"], non_blocking=True), labels.to( 117 param["device"], non_blocking=True 118 ) 119 optimizer.zero_grad() 120 outputs = model(images) 121 122 # Calculate the loss 123 criterion = nn.CrossEntropyLoss() 124 loss = criterion(outputs, labels) 125 loss.backward() 126 optimizer.step() 127 128 running_loss += loss.item() * images.size(0) 129 train_pbar.set_postfix({"batch_loss": f"{loss.item():.4f}"}) 130 131 epoch_loss = running_loss / len(train_loader.dataset) 132 return epoch_loss
Performs a single training epoch for a PyTorch model.
Sets the model to training mode, iterates through the train_loader,
performs forward and backward passes, and updates model weights using
the provided optimizer. A progress bar is displayed, showing the batch loss.
Arguments:
- model (torch.nn.Module): The PyTorch model to train.
- train_loader (torch.utils.data.DataLoader): DataLoader for the training data.
- optimizer (torch.optim.Optimizer): The optimizer used for updating model weights.
- param (dict): A dictionary containing training parameters, including:
- "device" (torch.device): The device to use for training, e.g.
"cpu","cuda","cuda:0", or"mps". Normalised byvalidate_train_param.
- "device" (torch.device): The device to use for training, e.g.
Returns:
float: The average loss for the epoch.
135def validate_epoch(model, val_loader, param): 136 """ 137 Performs a single validation epoch for a PyTorch model. 138 139 Sets the model to evaluation mode, iterates through the `val_loader` 140 without gradient calculations, and computes the validation loss, F1 score, 141 and accuracy. A progress bar is displayed, showing the batch validation loss. 142 143 Args: 144 model (torch.nn.Module): The PyTorch model to validate. 145 val_loader (torch.utils.data.DataLoader): DataLoader for the validation data. 146 param (dict): A dictionary containing training parameters, including: 147 - "device" (torch.device): The device to use for validation, e.g. `"cpu"`, 148 `"cuda"`, `"cuda:0"`, or `"mps"`. Normalised by `validate_train_param`. 149 150 Returns: 151 tuple: A tuple containing: 152 - val_loss (float): The average validation loss for the epoch. 153 - f1 (float): The weighted average F1 score on the validation set. 154 - acc (float): The accuracy score on the validation set. 155 """ 156 model.eval() 157 val_loss = 0.0 158 all_preds = [] 159 all_labels = [] 160 val_pbar = tqdm(val_loader, desc=f"Validation", leave=False) 161 162 with torch.no_grad(): 163 for images, labels in val_pbar: 164 images, labels = images.to(param["device"]), labels.to(param["device"]) 165 outputs = model(images) 166 criterion = nn.CrossEntropyLoss() 167 loss = criterion(outputs, labels) 168 val_loss += loss.item() * images.size(0) 169 170 # Collect predictions and true labels for F1 score calculation 171 _, preds = torch.max(outputs, 1) 172 all_preds.extend(preds.cpu().numpy()) # Move to CPU and convert to numpy 173 all_labels.extend(labels.cpu().numpy()) # Move to CPU and convert to numpy 174 175 val_pbar.set_postfix({"val_batch_loss": f"{loss.item():.4f}"}) 176 177 # Calculate the average validation loss 178 val_loss /= len(val_loader.dataset) 179 180 # Calculate F1 Score (if needed) 181 f1 = f1_score(all_labels, all_preds, average="weighted") 182 acc = accuracy_score(all_labels, all_preds) 183 184 return val_loss, f1, acc
Performs a single validation epoch for a PyTorch model.
Sets the model to evaluation mode, iterates through the val_loader
without gradient calculations, and computes the validation loss, F1 score,
and accuracy. A progress bar is displayed, showing the batch validation loss.
Arguments:
- model (torch.nn.Module): The PyTorch model to validate.
- val_loader (torch.utils.data.DataLoader): DataLoader for the validation data.
- param (dict): A dictionary containing training parameters, including:
- "device" (torch.device): The device to use for validation, e.g.
"cpu","cuda","cuda:0", or"mps". Normalised byvalidate_train_param.
- "device" (torch.device): The device to use for validation, e.g.
Returns:
tuple: A tuple containing: - val_loss (float): The average validation loss for the epoch. - f1 (float): The weighted average F1 score on the validation set. - acc (float): The accuracy score on the validation set.
187def save_best_model( 188 model, 189 val_loss, 190 best_fold_loss, 191 patience_counter, 192 overall_best_loss, 193 param, 194 fold, 195 best_model_path, 196): 197 """ 198 Saves the best model checkpoints based on validation loss and manages early stopping. 199 200 This function updates the `best_fold_loss` and `patience_counter` for the current 201 cross-validation fold. It also saves the model's state dictionary if it's the best 202 performing model for the current fold or the overall best model across all folds. 203 204 Args: 205 model (torch.nn.Module): The current PyTorch model being trained. 206 val_loss (float): The validation loss from the current epoch. 207 best_fold_loss (float): The best validation loss recorded so far for the current fold. 208 patience_counter (int): The number of epochs since the last improvement for the current fold. 209 overall_best_loss (float): The best validation loss recorded so far across all folds. 210 param (dict): A dictionary containing training parameters, including: 211 - "model_save_dir" (str): Directory where model checkpoints will be saved. 212 fold (int): The current fold number (0-indexed). 213 best_model_path (str): The full path where the overall best model will be saved. 214 215 Returns: 216 tuple: A tuple containing: 217 - best_fold_loss (float): The updated best validation loss for the current fold. 218 - patience_counter (int): The updated patience counter for the current fold. 219 - overall_best_loss (float): The updated overall best validation loss. 220 """ 221 # Save the best model for this fold 222 if val_loss < best_fold_loss: 223 best_fold_loss = val_loss 224 patience_counter = 0 225 torch.save( 226 model.state_dict(), 227 os.path.join(param["model_save_dir"], f"fold_{fold + 1}_best.pt"), 228 ) 229 else: 230 patience_counter += 1 231 232 # Save the overall best model 233 if val_loss < overall_best_loss: 234 overall_best_loss = val_loss 235 torch.save(model.state_dict(), best_model_path) 236 237 return best_fold_loss, patience_counter, overall_best_loss
Saves the best model checkpoints based on validation loss and manages early stopping.
This function updates the best_fold_loss and patience_counter for the current
cross-validation fold. It also saves the model's state dictionary if it's the best
performing model for the current fold or the overall best model across all folds.
Arguments:
- model (torch.nn.Module): The current PyTorch model being trained.
- val_loss (float): The validation loss from the current epoch.
- best_fold_loss (float): The best validation loss recorded so far for the current fold.
- patience_counter (int): The number of epochs since the last improvement for the current fold.
- overall_best_loss (float): The best validation loss recorded so far across all folds.
- param (dict): A dictionary containing training parameters, including:
- "model_save_dir" (str): Directory where model checkpoints will be saved.
- fold (int): The current fold number (0-indexed).
- best_model_path (str): The full path where the overall best model will be saved.
Returns:
tuple: A tuple containing: - best_fold_loss (float): The updated best validation loss for the current fold. - patience_counter (int): The updated patience counter for the current fold. - overall_best_loss (float): The updated overall best validation loss.
240def validate_train_param(param: dict): 241 """ 242 Validates the parameter dictionary for training configuration. 243 244 This function checks for the presence and correct types of required 245 parameters for training, and applies default values for optional parameters 246 if they are not provided. The `device` entry is normalised in place into a 247 `torch.device`, so callers may supply either a string or a `torch.device`. 248 249 Args: 250 param (dict): The dictionary of training parameters to validate. 251 Optional Keys: 252 - `device` (Union[str, torch.device]): The device to train on, e.g. 253 `"cpu"`, `"cuda"`, `"cuda:0"`, or `"mps"`. Hardware acceleration is 254 opt-in; pass it explicitly to use a GPU or Apple Silicon. 255 Default is `"cpu"`. 256 257 Raises: 258 ValueError: If a required parameter is missing, or if the requested `device` is invalid 259 or unavailable on this machine. 260 TypeError: If a parameter has an incorrect type. 261 """ 262 # --- Required fields and types 263 required_keys = {"optimizer_class": type, "optimizer_args": dict} 264 265 # --- Normalise the requested device (accepts str or torch.device, defaults to CPU) 266 param["device"] = resolve_device(param.get("device", "cpu")) 267 268 # --- Optional fields with default values and expected types 269 optional_keys = { 270 "n_fold": (int, 5), 271 "n_epoch": (int, 100), 272 "patience": (int, 5), 273 "batch_size": (int, 64), 274 "model_save_dir": (str, "./models"), 275 "seed": (int, 88), 276 "seed_worker": (Callable, seed_worker), 277 "max_workers": (int, 0), 278 "best_model_name": (str, "best_model.pt"), 279 "pin_memory": (bool, False), 280 "persistent_workers": (bool, False), 281 } 282 283 # --- Validate required keys 284 for key, expected_types in required_keys.items(): 285 if key not in param: 286 raise ValueError(f"Missing required param key: '{key}'") 287 if not isinstance(param[key], expected_types): 288 raise TypeError( 289 f"param['{key}'] must be of type {expected_types}, got {type(param[key])}" 290 ) 291 292 # --- Apply defaults and type-check optional keys 293 for key, (expected_type, default) in optional_keys.items(): 294 if key not in param: 295 param[key] = default 296 elif expected_type is not None and not isinstance(param[key], expected_type): 297 raise TypeError( 298 f"param['{key}'] must be of type {expected_type}, got {type(param[key])}" 299 )
Validates the parameter dictionary for training configuration.
This function checks for the presence and correct types of required
parameters for training, and applies default values for optional parameters
if they are not provided. The device entry is normalised in place into a
torch.device, so callers may supply either a string or a torch.device.
Arguments:
- param (dict): The dictionary of training parameters to validate.
Optional Keys:
-
device(Union[str, torch.device]): The device to train on, e.g."cpu","cuda","cuda:0", or"mps". Hardware acceleration is opt-in; pass it explicitly to use a GPU or Apple Silicon. Default is"cpu".
Raises:
- ValueError: If a required parameter is missing, or if the requested
deviceis invalid or unavailable on this machine. - TypeError: If a parameter has an incorrect type.
302def validate_test_param(param: dict): 303 """ 304 Validates the parameter dictionary for testing configuration. 305 306 This function checks for the presence and correct types of optional 307 parameters for testing, and applies default values if they are not provided. 308 The `device` entry is normalised in place into a `torch.device`, so callers 309 may supply either a string or a `torch.device`. 310 311 Args: 312 param (dict): The dictionary of testing parameters to validate. 313 Optional Keys: 314 - `device` (Union[str, torch.device]): The device to evaluate on, e.g. 315 `"cpu"`, `"cuda"`, `"cuda:0"`, or `"mps"`. Hardware acceleration is 316 opt-in; pass it explicitly to use a GPU or Apple Silicon. 317 Default is `"cpu"`. 318 319 Raises: 320 ValueError: If the requested `device` is invalid or unavailable on this machine. 321 TypeError: If a parameter has an incorrect type. 322 """ 323 # --- Normalise the requested device (accepts str or torch.device, defaults to CPU) 324 param["device"] = resolve_device(param.get("device", "cpu")) 325 326 # --- Optional fields with default values and expected types 327 optional_keys = { 328 "batch_size": (int, 64), 329 } 330 331 # --- Apply defaults and type-check optional keys 332 for key, (expected_type, default) in optional_keys.items(): 333 if key not in param: 334 param[key] = default 335 elif expected_type is not None and not isinstance(param[key], expected_type): 336 raise TypeError( 337 f"param['{key}'] must be of type {expected_type}, got {type(param[key])}" 338 )
Validates the parameter dictionary for testing configuration.
This function checks for the presence and correct types of optional
parameters for testing, and applies default values if they are not provided.
The device entry is normalised in place into a torch.device, so callers
may supply either a string or a torch.device.
Arguments:
- param (dict): The dictionary of testing parameters to validate.
Optional Keys:
-
device(Union[str, torch.device]): The device to evaluate on, e.g."cpu","cuda","cuda:0", or"mps". Hardware acceleration is opt-in; pass it explicitly to use a GPU or Apple Silicon. Default is"cpu".
Raises:
- ValueError: If the requested
deviceis invalid or unavailable on this machine. - TypeError: If a parameter has an incorrect type.
341def validate_pred_param(param: dict): 342 """ 343 Validates the parameter dictionary for prediction configuration. 344 345 This function checks for the presence and correct types of optional 346 parameters for prediction, and applies default values if they are not provided. 347 The `device` entry is normalised in place into a `torch.device`, so callers 348 may supply either a string or a `torch.device`. 349 350 Args: 351 param (dict): The dictionary of prediction parameters to validate. 352 Optional Keys: 353 - `device` (Union[str, torch.device]): The device to predict on, e.g. 354 `"cpu"`, `"cuda"`, `"cuda:0"`, or `"mps"`. Hardware acceleration is 355 opt-in; pass it explicitly to use a GPU or Apple Silicon. 356 Default is `"cpu"`. 357 358 Raises: 359 ValueError: If the requested `device` is invalid or unavailable on this machine. 360 TypeError: If a parameter has an incorrect type. 361 """ 362 # --- Normalise the requested device (accepts str or torch.device, defaults to CPU) 363 param["device"] = resolve_device(param.get("device", "cpu")) 364 365 # --- Optional fields with default values and expected types 366 optional_keys = { 367 "batch_size": (int, 64), 368 } 369 370 # --- Apply defaults and type-check optional keys 371 for key, (expected_type, default) in optional_keys.items(): 372 if key not in param: 373 param[key] = default 374 elif expected_type is not None and not isinstance(param[key], expected_type): 375 raise TypeError( 376 f"param['{key}'] must be of type {expected_type}, got {type(param[key])}" 377 )
Validates the parameter dictionary for prediction configuration.
This function checks for the presence and correct types of optional
parameters for prediction, and applies default values if they are not provided.
The device entry is normalised in place into a torch.device, so callers
may supply either a string or a torch.device.
Arguments:
- param (dict): The dictionary of prediction parameters to validate.
Optional Keys:
-
device(Union[str, torch.device]): The device to predict on, e.g."cpu","cuda","cuda:0", or"mps". Hardware acceleration is opt-in; pass it explicitly to use a GPU or Apple Silicon. Default is"cpu".
Raises:
- ValueError: If the requested
deviceis invalid or unavailable on this machine. - TypeError: If a parameter has an incorrect type.