Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions src/training/base_trainer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
DeepVision AI

Base Trainer

Defines the common functionality shared by all trainers.
"""
from src.training.state import TrainingState
from abc import ABC, abstractmethod
import time

import torch

from src.training.history import History
from src.training.metrics import Metrics
from src.training.callbacks import CallbackManager


class BaseTrainer(ABC):
"""
Base class for all DeepVision AI trainers.
"""

def __init__(
self,
model,
optimizer,
criterion,
train_loader,
val_loader,
device,
logger=None,
scheduler=None,
checkpoint_manager=None,
early_stopping=None,
):

self.model = model
self.optimizer = optimizer
self.criterion = criterion

self.train_loader = train_loader
self.val_loader = val_loader

self.device = device

Comment on lines +45 to +46
self.logger = logger
self.scheduler = scheduler

self.checkpoint_manager = checkpoint_manager
self.early_stopping = early_stopping

# Training state
self.state = TrainingState()

# Utilities
self.history = History()
self.metrics = Metrics()
self.callbacks = CallbackManager()

# AMP (safe on CPU)
self.use_amp = (
torch.cuda.is_available()
and device.type == "cuda"
)
Comment on lines +62 to +65

self.scaler = torch.cuda.amp.GradScaler(
enabled=self.use_amp
)

# --------------------------------------------------
# Logging
# --------------------------------------------------

def log(self, message):

if self.logger is not None:
self.logger.info(message)
else:
print(message)

# --------------------------------------------------
# Current Learning Rate
# --------------------------------------------------

def get_learning_rate(self):

return self.optimizer.param_groups[0]["lr"]

# --------------------------------------------------
# Epoch Timer
# --------------------------------------------------

def start_timer(self):

self._start_time = time.time()

def stop_timer(self):

return time.time() - self._start_time

# --------------------------------------------------
# Abstract Methods
# --------------------------------------------------

@abstractmethod
def train_one_epoch(self):
"""
Train for one epoch.
"""
pass

@abstractmethod
def validate(self):
"""
Validate one epoch.
"""
pass

@abstractmethod
def train(self, epochs):
"""
Complete training loop.
"""
pass
65 changes: 65 additions & 0 deletions src/training/callbacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
DeepVision AI

Training Callback System
"""


class Callback:
"""
Base callback class.
"""

def on_train_begin(self, trainer):
pass

def on_epoch_begin(self, trainer):
pass

def on_epoch_end(self, trainer):
pass

def on_validation_end(self, trainer):
pass

def on_train_end(self, trainer):
pass


class CallbackManager:
"""
Manages all callbacks.
"""

def __init__(self):

self.callbacks = []

def add(self, callback):

self.callbacks.append(callback)

def on_train_begin(self, trainer):

for cb in self.callbacks:
cb.on_train_begin(trainer)

def on_epoch_begin(self, trainer):

for cb in self.callbacks:
cb.on_epoch_begin(trainer)

def on_epoch_end(self, trainer):

for cb in self.callbacks:
cb.on_epoch_end(trainer)

def on_validation_end(self, trainer):

for cb in self.callbacks:
cb.on_validation_end(trainer)

def on_train_end(self, trainer):

for cb in self.callbacks:
cb.on_train_end(trainer)
115 changes: 115 additions & 0 deletions src/training/history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
DeepVision AI

Training History Manager
"""

from pathlib import Path
import json
import csv


class History:
"""
Stores and manages training history.
"""

def __init__(self):
self.records = []

# --------------------------------------------------
# Add epoch
# --------------------------------------------------

def add(self, **kwargs):
"""
Add one epoch record.

Example:
history.add(
epoch=1,
train_loss=0.4,
val_loss=0.3,
train_accuracy=0.95,
val_accuracy=0.94,
)
Comment on lines +28 to +35
"""
self.records.append(kwargs)

# --------------------------------------------------
# Length
# --------------------------------------------------

def __len__(self):
return len(self.records)

# --------------------------------------------------
# Best Epoch
# --------------------------------------------------

def best(self, metric="val_loss", mode="min"):

if len(self.records) == 0:
return None

if mode == "min":
return min(
self.records,
key=lambda x: x[metric]
)

return max(
self.records,
key=lambda x: x[metric]
)

# --------------------------------------------------
# Save JSON
# --------------------------------------------------

def save_json(self, path):

path = Path(path)

with open(path, "w", encoding="utf-8") as f:
Comment on lines +72 to +74

json.dump(
self.records,
f,
indent=4,
)

# --------------------------------------------------
# Save CSV
# --------------------------------------------------

def save_csv(self, path):

path = Path(path)

if len(self.records) == 0:
return

with open(
path,
Comment on lines +90 to +94
"w",
newline="",
encoding="utf-8",
) as f:

writer = csv.DictWriter(
f,
fieldnames=self.records[0].keys(),
)

writer.writeheader()

writer.writerows(self.records)

# --------------------------------------------------
# Return records
# --------------------------------------------------

def get(self):

return self.records
Loading