| from pathlib import Path
|
| import json
|
| import pickle
|
|
|
| import torch
|
| import torch.nn as nn
|
| from torch.optim import Adam
|
| from torch.optim.lr_scheduler import ReduceLROnPlateau
|
|
|
| from src.logger import get_logger
|
| from src.model import build_model
|
|
|
| logger = get_logger(__name__)
|
|
|
|
|
| def train(
|
| train_loader,
|
| val_loader,
|
| target_epochs=50
|
| ):
|
|
|
| logger.info("Training pipeline started")
|
|
|
|
|
|
|
|
|
| Path("models").mkdir(exist_ok=True)
|
| Path("outputs").mkdir(exist_ok=True)
|
| Path("outputs/reports").mkdir(parents=True, exist_ok=True)
|
|
|
| model_path = Path("models/resnet_cifar10.pth")
|
| best_model_path = Path("models/best_resnet_cifar10.pth")
|
| history_file = Path("outputs/history.pkl")
|
|
|
|
|
|
|
|
|
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| logger.info(f"Device: {device}")
|
|
|
|
|
|
|
|
|
| model = build_model().to(device)
|
|
|
| criterion = nn.CrossEntropyLoss(
|
| label_smoothing=0.1
|
| )
|
|
|
| optimizer = Adam(
|
| model.parameters(),
|
| lr=1e-4,
|
| weight_decay=1e-4
|
| )
|
|
|
| scheduler = ReduceLROnPlateau(
|
| optimizer,
|
| mode="min",
|
| factor=0.5,
|
| patience=3
|
| )
|
|
|
|
|
|
|
| start_epoch = 0
|
|
|
| if model_path.exists():
|
|
|
| logger.info(
|
| "Loading checkpoint"
|
| )
|
|
|
| checkpoint = torch.load(
|
| model_path,
|
| map_location=device
|
| )
|
|
|
| model.load_state_dict(
|
| checkpoint["model"]
|
| )
|
|
|
| optimizer.load_state_dict(
|
| checkpoint["optimizer"]
|
| )
|
|
|
| scheduler.load_state_dict(
|
| checkpoint["scheduler"]
|
| )
|
|
|
| start_epoch = checkpoint[
|
| "epoch"
|
| ]
|
|
|
| logger.info(
|
| f"Resuming from epoch {start_epoch}"
|
| )
|
|
|
|
|
|
|
|
|
| criterion = nn.CrossEntropyLoss(label_smoothing=0.1)
|
|
|
| optimizer = Adam(
|
| model.parameters(),
|
| lr=1e-4,
|
| weight_decay=1e-4
|
| )
|
|
|
| scheduler = ReduceLROnPlateau(
|
| optimizer,
|
| mode="min",
|
| factor=0.5,
|
| patience=3
|
| )
|
|
|
|
|
|
|
|
|
| if history_file.exists():
|
| with open(history_file, "rb") as f:
|
| history = pickle.load(f)
|
| else:
|
| history = {
|
| "loss": [],
|
| "accuracy": [],
|
| "val_loss": [],
|
| "val_accuracy": []
|
| }
|
|
|
|
|
|
|
|
|
| best_val_loss = float("inf")
|
| patience = 5
|
| early_counter = 0
|
|
|
|
|
|
|
|
|
| for epoch in range(start_epoch, target_epochs):
|
|
|
| logger.info(f"Epoch {epoch+1}/{target_epochs}")
|
|
|
|
|
| model.train()
|
|
|
| train_loss = 0.0
|
| correct = 0
|
| total = 0
|
|
|
| for images, labels in train_loader:
|
| images, labels = images.to(device), labels.to(device)
|
|
|
| optimizer.zero_grad()
|
|
|
| outputs = model(images)
|
| loss = criterion(outputs, labels)
|
|
|
| loss.backward()
|
| optimizer.step()
|
|
|
| train_loss += loss.item()
|
|
|
| preds = outputs.argmax(dim=1)
|
| correct += (preds == labels).sum().item()
|
| total += labels.size(0)
|
|
|
| train_loss /= len(train_loader)
|
| train_acc = correct / total
|
|
|
|
|
| model.eval()
|
|
|
| val_loss = 0.0
|
| val_correct = 0
|
| val_total = 0
|
|
|
| with torch.no_grad():
|
| for images, labels in val_loader:
|
| images, labels = images.to(device), labels.to(device)
|
|
|
| outputs = model(images)
|
| loss = criterion(outputs, labels)
|
|
|
| val_loss += loss.item()
|
|
|
| preds = outputs.argmax(dim=1)
|
| val_correct += (preds == labels).sum().item()
|
| val_total += labels.size(0)
|
|
|
| val_loss /= len(val_loader)
|
| val_acc = val_correct / val_total
|
|
|
| scheduler.step(val_loss)
|
|
|
| logger.info(
|
| f"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f}"
|
| )
|
| logger.info(
|
| f"Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f}"
|
| )
|
|
|
|
|
| history["loss"].append(train_loss)
|
| history["accuracy"].append(train_acc)
|
| history["val_loss"].append(val_loss)
|
| history["val_accuracy"].append(val_acc)
|
|
|
|
|
| torch.save(
|
| {
|
| "epoch": epoch + 1,
|
|
|
| "model":
|
| model.state_dict(),
|
|
|
| "optimizer":
|
| optimizer.state_dict(),
|
|
|
| "scheduler":
|
| scheduler.state_dict(),
|
|
|
| "best_val_loss":
|
| best_val_loss
|
| },
|
| model_path
|
| )
|
|
|
|
|
| if val_loss < best_val_loss:
|
|
|
| best_val_loss = val_loss
|
|
|
| early_counter = 0
|
|
|
| torch.save(
|
| model.state_dict(),
|
| best_model_path
|
| )
|
|
|
| logger.info(
|
| "New best model saved"
|
| )
|
|
|
| else:
|
|
|
| early_counter += 1
|
|
|
|
|
|
|
|
|
| with open(history_file, "wb") as f:
|
| pickle.dump(history, f)
|
|
|
|
|
| if early_counter >= patience:
|
| logger.info("Early stopping triggered")
|
| break
|
|
|
| logger.info("Training completed")
|
| return history |