""" Training and validation functions for the smoker detection model. """ import torch import torch.nn as nn from tqdm import tqdm def train_one_epoch(model, train_loader, criterion, optimizer, device): """ Train the model for one epoch. Args: model: PyTorch model train_loader: DataLoader for training data criterion: Loss function optimizer: Optimizer device: Device to train on (cuda/cpu) Returns: tuple: (epoch_loss, epoch_accuracy) """ model.train() running_loss = 0.0 correct = 0 total = 0 for images, labels in tqdm(train_loader, desc="Training", leave=False): images, labels = images.to(device), labels.to(device) # Zero gradients optimizer.zero_grad() # Forward pass outputs = model(images) loss = criterion(outputs, labels) # Backward pass and optimization loss.backward() optimizer.step() # Statistics running_loss += loss.item() _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() epoch_loss = running_loss / len(train_loader) epoch_acc = 100. * correct / total return epoch_loss, epoch_acc def validate(model, val_loader, criterion, device): """ Validate the model. Args: model: PyTorch model val_loader: DataLoader for validation data criterion: Loss function device: Device to validate on (cuda/cpu) Returns: tuple: (epoch_loss, epoch_accuracy) """ model.eval() running_loss = 0.0 correct = 0 total = 0 with torch.no_grad(): for images, labels in tqdm(val_loader, desc="Validation", leave=False): images, labels = images.to(device), labels.to(device) # Forward pass outputs = model(images) loss = criterion(outputs, labels) # Statistics running_loss += loss.item() _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() epoch_loss = running_loss / len(val_loader) epoch_acc = 100. * correct / total return epoch_loss, epoch_acc def train_model(model, train_loader, val_loader, criterion, optimizer, device, num_epochs=15, save_path='best_model.pth'): """ Complete training loop with validation and model checkpointing. Args: model: PyTorch model train_loader: DataLoader for training data val_loader: DataLoader for validation data criterion: Loss function optimizer: Optimizer device: Device to train on (cuda/cpu) num_epochs: Number of training epochs (default: 15) save_path: Path to save best model (default: 'best_model.pth') Returns: dict: Training history with losses and accuracies """ best_val_acc = 0.0 history = { 'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': [] } print("🚀 Starting training...") print(f" Epochs: {num_epochs}") print(f" Device: {device}") print(f" Training batches: {len(train_loader)}") print(f" Validation batches: {len(val_loader)}\n") for epoch in range(num_epochs): print(f"\nEpoch {epoch+1}/{num_epochs}") print("-" * 60) # Train train_loss, train_acc = train_one_epoch( model, train_loader, criterion, optimizer, device ) # Validate val_loss, val_acc = validate( model, val_loader, criterion, device ) # Save history history['train_loss'].append(train_loss) history['train_acc'].append(train_acc) history['val_loss'].append(val_loss) history['val_acc'].append(val_acc) # Print results print(f"\nResults:") print(f" Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.2f}%") print(f" Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.2f}%") # Save best model if val_acc > best_val_acc: best_val_acc = val_acc torch.save(model.state_dict(), save_path) print(f" ✅ New best model saved! (Val Acc: {val_acc:.2f}%)") print("\n" + "="*60) print(f"🎉 Training completed!") print(f" Best validation accuracy: {best_val_acc:.2f}%") print(f" Model saved to: {save_path}") return history def get_optimizer_and_criterion(model, lr=1e-4, weight_decay=1e-4): """ Create optimizer and loss criterion with standard hyperparameters. Args: model: PyTorch model lr: Learning rate (default: 1e-4, conservative for fine-tuning) weight_decay: L2 regularization (default: 1e-4) Returns: tuple: (optimizer, criterion) """ # Loss function criterion = nn.CrossEntropyLoss() # Optimizer - only optimize trainable parameters optimizer = torch.optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr=lr, weight_decay=weight_decay ) print("✅ Training configuration ready") print(f" Loss: CrossEntropyLoss") print(f" Optimizer: AdamW") print(f" Learning rate: {lr}") print(f" Weight decay: {weight_decay}") trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) print(f" Optimizing {trainable_params:,} parameters") return optimizer, criterion