Download src/train.py from notrito/smoker-detection: direct link, hf CLI and curl.
- Browser
- Download file 5.93 kB
-
https://huggingface.co/notrito/smoker-detection/resolve/main/src/train.py
- Command line
-
hf download hf://notrito/smoker-detection/src/train.py
-
curl -L -o train.py https://huggingface.co/notrito/smoker-detection/resolve/main/src/train.py
5.93 kB
| """ | |
| 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 |