chiruu12
Initial commit of clean OCR application
9543569
Raw
History Blame Contribute Delete
7.05 kB
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader, random_split, Subset
from torchvision import transforms
import os
import numpy as np
import h5py
from tqdm import tqdm
from models import CNNModel_Small, CNNModel_Medium, CNNModel_Large
DATA_FILE = "data/book_dataset.h5"
MODEL_OUTPUT_DIR = "models/saved_weights_finetuned/"
EPOCHS = 3
BATCH_SIZE = 256
LEARNING_RATE = 0.001
VALIDATION_SPLIT = 0.15
EARLY_STOPPING_PATIENCE = 2
DEVICE = torch.device("mps" if torch.backends.mps.is_available() else "cpu")
class EarlyStopping:
def __init__(self, patience=2, path='checkpoint.pth'):
self.patience, self.path = patience, path
self.counter, self.val_loss_min = 0, np.inf
self.best_score, self.early_stop = None, False
def __call__(self, val_loss, model):
score = -val_loss
if self.best_score is None:
self.best_score = score
self.save_checkpoint(val_loss, model)
elif score < self.best_score:
self.counter += 1
if self.counter >= self.patience: self.early_stop = True
else:
self.best_score = score
self.save_checkpoint(val_loss, model)
self.counter = 0
def save_checkpoint(self, val_loss, model):
print(f'Val loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model...')
torch.save(model.state_dict(), self.path)
self.val_loss_min = val_loss
class LabelRemapper:
def __init__(self, remap_dict):
self.remap_dict = remap_dict
def __call__(self, label):
return self.remap_dict.get(label, -1)
class HDF5Dataset(Dataset):
def __init__(self, h5_path, transform=None, target_transform=None):
self.h5_path = h5_path
self.transform = transform
self.target_transform = target_transform
with h5py.File(self.h5_path, 'r') as hf:
self.length = len(hf['labels'])
def __len__(self):
return self.length
def __getitem__(self, idx):
with h5py.File(self.h5_path, 'r') as hf:
image = hf['images'][idx]
label = hf['labels'][idx]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
def prepare_dataloaders():
print("Preparing dataloaders...")
image_transform = transforms.Compose([transforms.ToTensor()])
with h5py.File(DATA_FILE, 'r') as hf:
all_labels = hf['labels'][:]
unique_labels = np.unique(all_labels)
triage_remap = {code: (0 if 48 <= code <= 57 else 1 if 65 <= code <= 90 else 2) for code in unique_labels}
triage_target_transform = LabelRemapper(triage_remap)
triage_dataset = HDF5Dataset(DATA_FILE, transform=image_transform, target_transform=triage_target_transform)
val_size = int(len(triage_dataset) * VALIDATION_SPLIT)
train_size = len(triage_dataset) - val_size
triage_train, triage_val = random_split(triage_dataset, [train_size, val_size])
datasets = {'triage': (triage_train, triage_val)}
expert_filters = {
'digits': (lambda c: 48 <= c <= 57), 'uppercase': (lambda c: 65 <= c <= 90),
'lowercase': (lambda c: 97 <= c <= 122)
}
for name, condition in expert_filters.items():
class_codes = sorted([c for c in unique_labels if condition(c)])
expert_remap = {code: i for i, code in enumerate(class_codes)}
expert_target_transform = LabelRemapper(expert_remap)
indices = [i for i, code in enumerate(all_labels) if condition(code)]
expert_full_dataset = HDF5Dataset(DATA_FILE, transform=image_transform,
target_transform=expert_target_transform)
expert_subset = Subset(expert_full_dataset, indices)
val_expert_size = int(len(expert_subset) * VALIDATION_SPLIT)
train_expert_size = len(expert_subset) - val_expert_size
expert_train, expert_val = random_split(expert_subset, [train_expert_size, val_expert_size])
datasets[name] = (expert_train, expert_val)
print("Dataloaders are ready.")
return datasets
def train_model(model_name, model, train_dataset, val_dataset):
print(f"\n{'=' * 60}\nTraining: {model_name.upper()} MODEL on {len(train_dataset)} samples\n{'=' * 60}")
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
criterion = nn.CrossEntropyLoss()
model_save_path = os.path.join(MODEL_OUTPUT_DIR, f"{model_name}_model_finetuned.pth")
early_stopper = EarlyStopping(patience=EARLY_STOPPING_PATIENCE, path=model_save_path)
for epoch in range(EPOCHS):
model.train()
train_loss = 0.0
for images, labels in tqdm(train_loader, desc=f"Epoch {epoch + 1}/{EPOCHS} [Train]"):
images, labels = images.to(DEVICE), labels.to(DEVICE, non_blocking=True)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item() * images.size(0)
model.eval()
val_loss = 0.0
with torch.no_grad():
for images, labels in tqdm(val_loader, desc=f"Epoch {epoch + 1}/{EPOCHS} [Val]"):
images, labels = images.to(DEVICE), labels.to(DEVICE, non_blocking=True)
outputs = model(images)
loss = criterion(outputs, labels)
val_loss += loss.item() * images.size(0)
avg_train_loss = train_loss / len(train_dataset)
avg_val_loss = val_loss / len(val_dataset)
print(f"Epoch {epoch + 1}/{EPOCHS} | Train Loss: {avg_train_loss:.6f} | Validation Loss: {avg_val_loss:.6f}")
early_stopper(avg_val_loss, model)
if early_stopper.early_stop:
print("Early stopping triggered.")
break
print(f"Training finished. Model saved to {model_save_path}")
def main():
if not os.path.exists(MODEL_OUTPUT_DIR): os.makedirs(MODEL_OUTPUT_DIR)
print(f"Using device: {DEVICE}")
datasets = prepare_dataloaders()
models_to_train = {
'triage': CNNModel_Large(num_classes=3),
'digits': CNNModel_Small(num_classes=10),
'uppercase': CNNModel_Medium(num_classes=26),
'lowercase': CNNModel_Medium(num_classes=26)
}
for name, model in models_to_train.items():
model_path = os.path.join(MODEL_OUTPUT_DIR, f"{name}_model_finetuned.pth")
if os.path.exists(model_path):
print(f"Skipping training for '{name}': Model already exists at {model_path}")
continue
train_d, val_d = datasets[name]
model.to(DEVICE)
train_model(name, model, train_d, val_d)
if __name__ == "__main__":
main()