base.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. """
  2. Base trainer classes and interfaces for the Trixy ML training framework.
  3. This module provides the fundamental building blocks for all ML trainers,
  4. including abstract base classes, configuration management, and training state tracking.
  5. """
  6. import os
  7. import time
  8. import logging
  9. import hashlib
  10. from abc import ABC, abstractmethod
  11. from typing import Any, Dict, List, Optional, Union, Callable, Tuple
  12. from dataclasses import dataclass, field
  13. from enum import Enum
  14. import torch
  15. import torch.nn as nn
  16. import torch.optim as optim
  17. from torch.utils.data import DataLoader
  18. import numpy as np
  19. class TrainingState(Enum):
  20. """Training state enumeration for tracking trainer status."""
  21. IDLE = "idle"
  22. PREPARING = "preparing"
  23. TRAINING = "training"
  24. VALIDATING = "validating"
  25. TESTING = "testing"
  26. PAUSED = "paused"
  27. COMPLETED = "completed"
  28. FAILED = "failed"
  29. STOPPING = "stopping"
  30. class ModelFormat(Enum):
  31. """Supported model formats."""
  32. PTH = ".pth"
  33. PT = ".pt"
  34. ONNX = ".onnx"
  35. @dataclass
  36. class TrainerConfig:
  37. """Configuration class for ML trainers."""
  38. # General settings
  39. trainer_name: str = "BaseTrainer"
  40. model_name: str = "model"
  41. output_dir: str = "./models"
  42. data_dir: str = "./trainer/data"
  43. config_dir: str = "./config"
  44. # Training parameters
  45. batch_size: int = 32
  46. learning_rate: float = 0.001
  47. num_epochs: int = 100
  48. min_epochs: int = 10
  49. max_epochs: int = 1000
  50. early_stopping_patience: int = 10
  51. # Model parameters
  52. model_format: ModelFormat = ModelFormat.PTH
  53. use_mixed_precision: bool = True
  54. gradient_clip_norm: float = 1.0
  55. weight_decay: float = 1e-4
  56. # Data parameters
  57. validation_split: float = 0.2
  58. test_split: float = 0.1
  59. shuffle_data: bool = True
  60. num_workers: int = 4
  61. pin_memory: bool = True
  62. # Audio processing parameters
  63. sample_rate: int = 16000
  64. bit_depth: int = 16
  65. audio_length: float = 1.5 # seconds
  66. n_mels: int = 40
  67. n_fft: int = 512
  68. hop_length: int = 160 # 10ms at 16kHz
  69. win_length: int = 400 # 25ms at 16kHz
  70. # Augmentation parameters
  71. use_augmentation: bool = True
  72. noise_factor: float = 0.1
  73. speed_factor: float = 0.1
  74. pitch_factor: float = 0.1
  75. volume_factor: float = 0.2
  76. # Security settings
  77. use_password_protection: bool = True
  78. password: Optional[str] = None
  79. encryption_algorithm: str = "AES256"
  80. # Logging and monitoring
  81. log_level: str = "INFO"
  82. save_checkpoints: bool = True
  83. checkpoint_interval: int = 10 # epochs
  84. validate_interval: int = 1 # epochs
  85. log_interval: int = 100 # batches
  86. # Hardware settings
  87. device: str = "auto" # auto, cpu, cuda, mps
  88. use_distributed: bool = False
  89. num_gpus: int = 1
  90. # Advanced settings
  91. resume_from_checkpoint: bool = False
  92. checkpoint_path: Optional[str] = None
  93. freeze_backbone: bool = False
  94. use_scheduler: bool = True
  95. scheduler_type: str = "cosine" # cosine, step, exponential
  96. # Custom parameters (extensible)
  97. custom_params: Dict[str, Any] = field(default_factory=dict)
  98. def __post_init__(self):
  99. """Validate and process configuration after initialization."""
  100. self._validate_config()
  101. self._process_device()
  102. def _validate_config(self):
  103. """Validate configuration parameters."""
  104. if self.batch_size <= 0:
  105. raise ValueError("batch_size must be positive")
  106. if self.learning_rate <= 0:
  107. raise ValueError("learning_rate must be positive")
  108. if self.num_epochs <= 0:
  109. raise ValueError("num_epochs must be positive")
  110. if not 0 < self.validation_split < 1:
  111. raise ValueError("validation_split must be between 0 and 1")
  112. if not 0 <= self.test_split < 1:
  113. raise ValueError("test_split must be between 0 and 1")
  114. if self.validation_split + self.test_split >= 1:
  115. raise ValueError("validation_split + test_split must be less than 1")
  116. def _process_device(self):
  117. """Automatically determine the best available device."""
  118. if self.device == "auto":
  119. if torch.cuda.is_available():
  120. self.device = "cuda"
  121. elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
  122. self.device = "mps"
  123. else:
  124. self.device = "cpu"
  125. def to_dict(self) -> Dict[str, Any]:
  126. """Convert configuration to dictionary."""
  127. config_dict = {}
  128. for key, value in self.__dict__.items():
  129. if isinstance(value, Enum):
  130. config_dict[key] = value.value
  131. else:
  132. config_dict[key] = value
  133. return config_dict
  134. @classmethod
  135. def from_dict(cls, config_dict: Dict[str, Any]) -> 'TrainerConfig':
  136. """Create configuration from dictionary."""
  137. # Handle enum conversion
  138. if 'model_format' in config_dict:
  139. config_dict['model_format'] = ModelFormat(config_dict['model_format'])
  140. return cls(**config_dict)
  141. class TrainingMetrics:
  142. """Class for tracking and managing training metrics."""
  143. def __init__(self):
  144. self.metrics = {
  145. 'train_loss': [],
  146. 'train_accuracy': [],
  147. 'val_loss': [],
  148. 'val_accuracy': [],
  149. 'learning_rates': [],
  150. 'epoch_times': [],
  151. 'total_time': 0.0
  152. }
  153. self.current_epoch = 0
  154. self.best_val_loss = float('inf')
  155. self.best_val_accuracy = 0.0
  156. self.best_epoch = 0
  157. def update(self, epoch: int, train_loss: float, train_acc: float = None,
  158. val_loss: float = None, val_acc: float = None, lr: float = None,
  159. epoch_time: float = None):
  160. """Update metrics for the current epoch."""
  161. self.current_epoch = epoch
  162. self.metrics['train_loss'].append(train_loss)
  163. if train_acc is not None:
  164. self.metrics['train_accuracy'].append(train_acc)
  165. if val_loss is not None:
  166. self.metrics['val_loss'].append(val_loss)
  167. if val_loss < self.best_val_loss:
  168. self.best_val_loss = val_loss
  169. self.best_epoch = epoch
  170. if val_acc is not None:
  171. self.metrics['val_accuracy'].append(val_acc)
  172. if val_acc > self.best_val_accuracy:
  173. self.best_val_accuracy = val_acc
  174. if lr is not None:
  175. self.metrics['learning_rates'].append(lr)
  176. if epoch_time is not None:
  177. self.metrics['epoch_times'].append(epoch_time)
  178. self.metrics['total_time'] += epoch_time
  179. def get_summary(self) -> Dict[str, Any]:
  180. """Get a summary of training metrics."""
  181. return {
  182. 'current_epoch': self.current_epoch,
  183. 'best_epoch': self.best_epoch,
  184. 'best_val_loss': self.best_val_loss,
  185. 'best_val_accuracy': self.best_val_accuracy,
  186. 'total_training_time': self.metrics['total_time'],
  187. 'avg_epoch_time': np.mean(self.metrics['epoch_times']) if self.metrics['epoch_times'] else 0,
  188. 'final_train_loss': self.metrics['train_loss'][-1] if self.metrics['train_loss'] else None,
  189. 'final_val_loss': self.metrics['val_loss'][-1] if self.metrics['val_loss'] else None
  190. }
  191. class BaseTrainer(ABC):
  192. """
  193. Abstract base class for all ML trainers in the Trixy framework.
  194. This class provides the fundamental structure and common functionality
  195. for all training implementations, including state management, logging,
  196. checkpointing, and validation.
  197. """
  198. def __init__(self, config: TrainerConfig):
  199. """
  200. Initialize the base trainer.
  201. Args:
  202. config: TrainerConfig object containing training parameters
  203. """
  204. self.config = config
  205. self.state = TrainingState.IDLE
  206. self.metrics = TrainingMetrics()
  207. self.logger = self._setup_logger()
  208. # Training components (to be initialized by subclasses)
  209. self.model: Optional[nn.Module] = None
  210. self.optimizer: Optional[optim.Optimizer] = None
  211. self.scheduler: Optional[optim.lr_scheduler._LRScheduler] = None
  212. self.criterion: Optional[nn.Module] = None
  213. self.scaler: Optional[torch.cuda.amp.GradScaler] = None
  214. # Data loaders (to be initialized by subclasses)
  215. self.train_loader: Optional[DataLoader] = None
  216. self.val_loader: Optional[DataLoader] = None
  217. self.test_loader: Optional[DataLoader] = None
  218. # Training state
  219. self.current_epoch = 0
  220. self.global_step = 0
  221. self.best_model_state = None
  222. self.early_stopping_counter = 0
  223. # Callbacks
  224. self.callbacks: List[Callable] = []
  225. # Setup mixed precision if requested
  226. if self.config.use_mixed_precision and self.config.device != "cpu":
  227. self.scaler = torch.cuda.amp.GradScaler()
  228. self.logger.info(f"Initialized {self.__class__.__name__} with config: {self.config.trainer_name}")
  229. def _setup_logger(self) -> logging.Logger:
  230. """Setup logger for the trainer."""
  231. logger = logging.getLogger(f"trixy.trainer.{self.config.trainer_name}")
  232. logger.setLevel(getattr(logging, self.config.log_level.upper()))
  233. if not logger.handlers:
  234. # Create console handler
  235. console_handler = logging.StreamHandler()
  236. console_handler.setLevel(logging.INFO)
  237. # Create file handler
  238. os.makedirs(f"{self.config.output_dir}/logs", exist_ok=True)
  239. file_handler = logging.FileHandler(
  240. f"{self.config.output_dir}/logs/{self.config.trainer_name}.log"
  241. )
  242. file_handler.setLevel(logging.DEBUG)
  243. # Create formatter
  244. formatter = logging.Formatter(
  245. '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  246. )
  247. console_handler.setFormatter(formatter)
  248. file_handler.setFormatter(formatter)
  249. logger.addHandler(console_handler)
  250. logger.addHandler(file_handler)
  251. return logger
  252. @abstractmethod
  253. def prepare_data(self) -> Tuple[DataLoader, DataLoader, DataLoader]:
  254. """
  255. Prepare training, validation, and test data loaders.
  256. Returns:
  257. Tuple of (train_loader, val_loader, test_loader)
  258. """
  259. pass
  260. @abstractmethod
  261. def build_model(self) -> nn.Module:
  262. """
  263. Build and return the model architecture.
  264. Returns:
  265. PyTorch model
  266. """
  267. pass
  268. @abstractmethod
  269. def create_criterion(self) -> nn.Module:
  270. """
  271. Create and return the loss criterion.
  272. Returns:
  273. PyTorch loss function
  274. """
  275. pass
  276. def create_optimizer(self) -> optim.Optimizer:
  277. """Create and return the optimizer."""
  278. return optim.AdamW(
  279. self.model.parameters(),
  280. lr=self.config.learning_rate,
  281. weight_decay=self.config.weight_decay
  282. )
  283. def create_scheduler(self) -> Optional[optim.lr_scheduler._LRScheduler]:
  284. """Create and return the learning rate scheduler."""
  285. if not self.config.use_scheduler:
  286. return None
  287. if self.config.scheduler_type == "cosine":
  288. return optim.lr_scheduler.CosineAnnealingLR(
  289. self.optimizer, T_max=self.config.num_epochs
  290. )
  291. elif self.config.scheduler_type == "step":
  292. return optim.lr_scheduler.StepLR(
  293. self.optimizer, step_size=30, gamma=0.1
  294. )
  295. elif self.config.scheduler_type == "exponential":
  296. return optim.lr_scheduler.ExponentialLR(
  297. self.optimizer, gamma=0.95
  298. )
  299. else:
  300. self.logger.warning(f"Unknown scheduler type: {self.config.scheduler_type}")
  301. return None
  302. def setup_training(self):
  303. """Setup all training components."""
  304. self.state = TrainingState.PREPARING
  305. self.logger.info("Setting up training components...")
  306. # Prepare data
  307. self.train_loader, self.val_loader, self.test_loader = self.prepare_data()
  308. # Build model
  309. self.model = self.build_model()
  310. self.model.to(self.config.device)
  311. # Create training components
  312. self.criterion = self.create_criterion()
  313. self.optimizer = self.create_optimizer()
  314. self.scheduler = self.create_scheduler()
  315. # Load checkpoint if requested
  316. if self.config.resume_from_checkpoint and self.config.checkpoint_path:
  317. self.load_checkpoint(self.config.checkpoint_path)
  318. self.logger.info("Training setup completed successfully")
  319. def train_epoch(self) -> Tuple[float, float]:
  320. """
  321. Train for one epoch.
  322. Returns:
  323. Tuple of (average_loss, average_accuracy)
  324. """
  325. self.model.train()
  326. total_loss = 0.0
  327. total_correct = 0
  328. total_samples = 0
  329. for batch_idx, (data, targets) in enumerate(self.train_loader):
  330. data = data.to(self.config.device)
  331. targets = targets.to(self.config.device)
  332. # Zero gradients
  333. self.optimizer.zero_grad()
  334. # Forward pass with mixed precision
  335. if self.scaler is not None:
  336. with torch.cuda.amp.autocast():
  337. outputs = self.model(data)
  338. loss = self.criterion(outputs, targets)
  339. # Backward pass
  340. self.scaler.scale(loss).backward()
  341. # Gradient clipping
  342. if self.config.gradient_clip_norm > 0:
  343. self.scaler.unscale_(self.optimizer)
  344. torch.nn.utils.clip_grad_norm_(
  345. self.model.parameters(), self.config.gradient_clip_norm
  346. )
  347. self.scaler.step(self.optimizer)
  348. self.scaler.update()
  349. else:
  350. outputs = self.model(data)
  351. loss = self.criterion(outputs, targets)
  352. loss.backward()
  353. # Gradient clipping
  354. if self.config.gradient_clip_norm > 0:
  355. torch.nn.utils.clip_grad_norm_(
  356. self.model.parameters(), self.config.gradient_clip_norm
  357. )
  358. self.optimizer.step()
  359. # Update metrics
  360. total_loss += loss.item()
  361. if len(outputs.shape) > 1 and outputs.shape[1] > 1: # Classification
  362. _, predicted = torch.max(outputs.data, 1)
  363. total_correct += (predicted == targets).sum().item()
  364. total_samples += targets.size(0)
  365. self.global_step += 1
  366. # Log progress
  367. if batch_idx % self.config.log_interval == 0:
  368. self.logger.debug(
  369. f"Epoch {self.current_epoch}, Batch {batch_idx}/{len(self.train_loader)}, "
  370. f"Loss: {loss.item():.6f}"
  371. )
  372. avg_loss = total_loss / len(self.train_loader)
  373. avg_accuracy = total_correct / total_samples if total_samples > 0 else 0.0
  374. return avg_loss, avg_accuracy
  375. def validate_epoch(self) -> Tuple[float, float]:
  376. """
  377. Validate for one epoch.
  378. Returns:
  379. Tuple of (average_loss, average_accuracy)
  380. """
  381. self.model.eval()
  382. total_loss = 0.0
  383. total_correct = 0
  384. total_samples = 0
  385. with torch.no_grad():
  386. for data, targets in self.val_loader:
  387. data = data.to(self.config.device)
  388. targets = targets.to(self.config.device)
  389. outputs = self.model(data)
  390. loss = self.criterion(outputs, targets)
  391. total_loss += loss.item()
  392. if len(outputs.shape) > 1 and outputs.shape[1] > 1: # Classification
  393. _, predicted = torch.max(outputs.data, 1)
  394. total_correct += (predicted == targets).sum().item()
  395. total_samples += targets.size(0)
  396. avg_loss = total_loss / len(self.val_loader)
  397. avg_accuracy = total_correct / total_samples if total_samples > 0 else 0.0
  398. return avg_loss, avg_accuracy
  399. def train(self) -> TrainingMetrics:
  400. """
  401. Main training loop.
  402. Returns:
  403. TrainingMetrics object containing training history
  404. """
  405. try:
  406. self.setup_training()
  407. self.state = TrainingState.TRAINING
  408. self.logger.info(f"Starting training for {self.config.num_epochs} epochs")
  409. start_time = time.time()
  410. for epoch in range(self.current_epoch, self.config.num_epochs):
  411. epoch_start_time = time.time()
  412. self.current_epoch = epoch
  413. # Training phase
  414. train_loss, train_acc = self.train_epoch()
  415. # Validation phase
  416. if epoch % self.config.validate_interval == 0:
  417. self.state = TrainingState.VALIDATING
  418. val_loss, val_acc = self.validate_epoch()
  419. self.state = TrainingState.TRAINING
  420. else:
  421. val_loss, val_acc = None, None
  422. # Update learning rate
  423. if self.scheduler is not None:
  424. self.scheduler.step()
  425. # Calculate epoch time
  426. epoch_time = time.time() - epoch_start_time
  427. # Update metrics
  428. current_lr = self.optimizer.param_groups[0]['lr']
  429. self.metrics.update(
  430. epoch=epoch,
  431. train_loss=train_loss,
  432. train_acc=train_acc,
  433. val_loss=val_loss,
  434. val_acc=val_acc,
  435. lr=current_lr,
  436. epoch_time=epoch_time
  437. )
  438. # Log progress
  439. log_msg = f"Epoch {epoch}/{self.config.num_epochs} - "
  440. log_msg += f"Train Loss: {train_loss:.6f}, Train Acc: {train_acc:.4f}"
  441. if val_loss is not None:
  442. log_msg += f", Val Loss: {val_loss:.6f}, Val Acc: {val_acc:.4f}"
  443. log_msg += f", LR: {current_lr:.2e}, Time: {epoch_time:.2f}s"
  444. self.logger.info(log_msg)
  445. # Save checkpoint
  446. if self.config.save_checkpoints and epoch % self.config.checkpoint_interval == 0:
  447. self.save_checkpoint(epoch)
  448. # Early stopping check
  449. if val_loss is not None:
  450. if val_loss < self.metrics.best_val_loss:
  451. self.best_model_state = self.model.state_dict().copy()
  452. self.early_stopping_counter = 0
  453. else:
  454. self.early_stopping_counter += 1
  455. if self.early_stopping_counter >= self.config.early_stopping_patience:
  456. self.logger.info(f"Early stopping triggered after {epoch + 1} epochs")
  457. break
  458. # Check minimum epochs
  459. if epoch < self.config.min_epochs:
  460. continue
  461. # Execute callbacks
  462. for callback in self.callbacks:
  463. callback(self, epoch)
  464. total_time = time.time() - start_time
  465. self.logger.info(f"Training completed in {total_time:.2f} seconds")
  466. # Load best model if available
  467. if self.best_model_state is not None:
  468. self.model.load_state_dict(self.best_model_state)
  469. self.state = TrainingState.COMPLETED
  470. return self.metrics
  471. except Exception as e:
  472. self.state = TrainingState.FAILED
  473. self.logger.error(f"Training failed: {str(e)}")
  474. raise
  475. def test(self) -> Dict[str, float]:
  476. """
  477. Test the trained model.
  478. Returns:
  479. Dictionary containing test metrics
  480. """
  481. self.state = TrainingState.TESTING
  482. self.logger.info("Starting model testing...")
  483. if self.test_loader is None:
  484. raise ValueError("Test loader not available")
  485. self.model.eval()
  486. total_loss = 0.0
  487. total_correct = 0
  488. total_samples = 0
  489. with torch.no_grad():
  490. for data, targets in self.test_loader:
  491. data = data.to(self.config.device)
  492. targets = targets.to(self.config.device)
  493. outputs = self.model(data)
  494. loss = self.criterion(outputs, targets)
  495. total_loss += loss.item()
  496. if len(outputs.shape) > 1 and outputs.shape[1] > 1: # Classification
  497. _, predicted = torch.max(outputs.data, 1)
  498. total_correct += (predicted == targets).sum().item()
  499. total_samples += targets.size(0)
  500. test_loss = total_loss / len(self.test_loader)
  501. test_accuracy = total_correct / total_samples if total_samples > 0 else 0.0
  502. test_results = {
  503. 'test_loss': test_loss,
  504. 'test_accuracy': test_accuracy,
  505. 'total_samples': total_samples
  506. }
  507. self.logger.info(f"Test Results - Loss: {test_loss:.6f}, Accuracy: {test_accuracy:.4f}")
  508. return test_results
  509. def save_checkpoint(self, epoch: int, filepath: Optional[str] = None):
  510. """Save training checkpoint."""
  511. if filepath is None:
  512. os.makedirs(f"{self.config.output_dir}/checkpoints", exist_ok=True)
  513. filepath = f"{self.config.output_dir}/checkpoints/{self.config.model_name}_epoch_{epoch}.pth"
  514. checkpoint = {
  515. 'epoch': epoch,
  516. 'model_state_dict': self.model.state_dict(),
  517. 'optimizer_state_dict': self.optimizer.state_dict(),
  518. 'scheduler_state_dict': self.scheduler.state_dict() if self.scheduler else None,
  519. 'scaler_state_dict': self.scaler.state_dict() if self.scaler else None,
  520. 'metrics': self.metrics,
  521. 'config': self.config.to_dict(),
  522. 'best_model_state': self.best_model_state
  523. }
  524. torch.save(checkpoint, filepath)
  525. self.logger.info(f"Checkpoint saved: {filepath}")
  526. def load_checkpoint(self, filepath: str):
  527. """Load training checkpoint."""
  528. if not os.path.exists(filepath):
  529. raise FileNotFoundError(f"Checkpoint not found: {filepath}")
  530. checkpoint = torch.load(filepath, map_location=self.config.device)
  531. # Load model state
  532. if self.model is not None:
  533. self.model.load_state_dict(checkpoint['model_state_dict'])
  534. # Load optimizer state
  535. if self.optimizer is not None:
  536. self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
  537. # Load scheduler state
  538. if self.scheduler is not None and checkpoint.get('scheduler_state_dict'):
  539. self.scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
  540. # Load scaler state
  541. if self.scaler is not None and checkpoint.get('scaler_state_dict'):
  542. self.scaler.load_state_dict(checkpoint['scaler_state_dict'])
  543. # Load training state
  544. self.current_epoch = checkpoint['epoch'] + 1
  545. self.metrics = checkpoint.get('metrics', TrainingMetrics())
  546. self.best_model_state = checkpoint.get('best_model_state')
  547. self.logger.info(f"Checkpoint loaded: {filepath}")
  548. def add_callback(self, callback: Callable):
  549. """Add a callback function to be executed during training."""
  550. self.callbacks.append(callback)
  551. def get_model_summary(self) -> str:
  552. """Get a summary of the model architecture."""
  553. if self.model is None:
  554. return "Model not initialized"
  555. total_params = sum(p.numel() for p in self.model.parameters())
  556. trainable_params = sum(p.numel() for p in self.model.parameters() if p.requires_grad)
  557. summary = f"Model: {self.model.__class__.__name__}\n"
  558. summary += f"Total parameters: {total_params:,}\n"
  559. summary += f"Trainable parameters: {trainable_params:,}\n"
  560. summary += f"Device: {next(self.model.parameters()).device}\n"
  561. return summary
  562. def stop_training(self):
  563. """Stop training gracefully."""
  564. self.state = TrainingState.STOPPING
  565. self.logger.info("Training stop requested")
  566. def pause_training(self):
  567. """Pause training."""
  568. self.state = TrainingState.PAUSED
  569. self.logger.info("Training paused")
  570. def resume_training(self):
  571. """Resume training from paused state."""
  572. if self.state == TrainingState.PAUSED:
  573. self.state = TrainingState.TRAINING
  574. self.logger.info("Training resumed")
  575. def get_training_status(self) -> Dict[str, Any]:
  576. """Get current training status and progress."""
  577. return {
  578. 'state': self.state.value,
  579. 'current_epoch': self.current_epoch,
  580. 'total_epochs': self.config.num_epochs,
  581. 'progress_percentage': (self.current_epoch / self.config.num_epochs) * 100,
  582. 'global_step': self.global_step,
  583. 'metrics_summary': self.metrics.get_summary(),
  584. 'best_epoch': self.metrics.best_epoch,
  585. 'early_stopping_counter': self.early_stopping_counter
  586. }