Digital-Image-Processing-OCR / src /finetune_models.py
chiruu12
Initial commit of clean OCR application
9543569
Raw
History Blame Contribute Delete
6.36 kB
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader, Subset, WeightedRandomSampler
from torchvision import transforms
import os
import numpy as np
from tqdm import tqdm
import h5py
from models import CNNModel_Small, CNNModel_Medium, CNNModel_Large
DATA_FILE = "data/book_dataset.h5"
KAGGLE_WEIGHTS_DIR = "models/saved_weights/"
OUTPUT_DIR = "models/saved_weights_finetuned/"
BATCH_SIZE = 256
EPOCHS = 20
LEARNING_RATE = 0.0001
DEVICE = torch.device("mps" if torch.backends.mps.is_available() else "cpu")
class HDF5Dataset(Dataset):
def __init__(self, h5_path, transform=None):
self.h5_path = h5_path
self.transform = transform
with h5py.File(self.h5_path, 'r') as hf:
self.labels = hf['labels'][:]
self.images = hf['images'][:]
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
image, label = self.images[idx], self.labels[idx]
if self.transform: image = self.transform(image)
return image, label
class LabelRemapper:
def __init__(self, remap_dict): self.remap_dict = remap_dict
def __call__(self, label): return self.remap_dict.get(label, -1)
def finetune_model(model_name, model, loader):
print(f"\n{'=' * 60}\nFine-tuning: {model_name.upper()} on {len(loader.dataset)} balanced samples\n{'=' * 60}")
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
model.train()
for epoch in range(EPOCHS):
total_loss = 0
for images, labels in tqdm(loader, desc=f"Epoch {epoch + 1}/{EPOCHS}"):
images, labels = images.to(DEVICE), labels.to(DEVICE)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch + 1} Avg Loss: {total_loss / len(loader):.6f}")
save_path = os.path.join(OUTPUT_DIR, f"{model_name}_model_finetuned.pth")
torch.save(model.state_dict(), save_path)
print(f"Saved fine-tuned model to {save_path}")
def main():
if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR)
print(f"Using device: {DEVICE}")
print("\nLoading and analyzing dataset for balancing...")
full_dataset = HDF5Dataset(DATA_FILE, transform=transforms.ToTensor())
all_labels = np.array(full_dataset.labels)
triage_model_path = os.path.join(OUTPUT_DIR, "triage_model_finetuned.pth")
if os.path.exists(triage_model_path):
print("Skipping Triage Model: Fine-tuned version already exists.")
else:
digit_indices = np.where((all_labels >= 48) & (all_labels <= 57))[0]
upper_indices = np.where((all_labels >= 65) & (all_labels <= 90))[0]
lower_indices = np.where((all_labels >= 97) & (all_labels <= 122))[0]
min_class_size = min(len(digit_indices), len(upper_indices), len(lower_indices))
print(f"Balancing Triage data: Using {min_class_size} samples per class.")
balanced_indices = np.concatenate([
np.random.choice(digit_indices, min_class_size, replace=False),
np.random.choice(upper_indices, min_class_size, replace=False),
np.random.choice(lower_indices, min_class_size, replace=False)
])
triage_remap = {code: (0 if 48 <= code <= 57 else 1 if 65 <= code <= 90 else 2) for code in
np.unique(all_labels)}
triage_target_transform = LabelRemapper(triage_remap)
triage_subset = Subset(full_dataset, balanced_indices)
triage_dataset = [(img, triage_target_transform(label)) for img, label in
tqdm(triage_subset, desc="Remapping Triage labels")]
triage_loader = DataLoader(triage_dataset, batch_size=BATCH_SIZE, shuffle=True)
triage_model = CNNModel_Large(num_classes=3).to(DEVICE)
kaggle_triage_path = os.path.join(KAGGLE_WEIGHTS_DIR, "triage_large_model.pth")
if os.path.exists(kaggle_triage_path):
print(f"Loading base weights from {kaggle_triage_path}")
triage_model.load_state_dict(torch.load(kaggle_triage_path, map_location=DEVICE))
finetune_model('triage', triage_model, triage_loader)
expert_configs = {
'digits': (CNNModel_Small, [chr(i) for i in range(48, 58)]),
'uppercase': (CNNModel_Medium, [chr(i) for i in range(65, 91)]),
'lowercase': (CNNModel_Medium, [chr(i) for i in range(97, 123)])
}
for name, (model_class, target_chars) in expert_configs.items():
expert_model_path = os.path.join(OUTPUT_DIR, f"{name}_model_finetuned.pth")
if os.path.exists(expert_model_path):
print(f"Skipping {name.capitalize()} Model: Fine-tuned version already exists.")
continue
target_codes = {ord(c) for c in target_chars}
indices = [i for i, label in enumerate(all_labels) if label in target_codes]
if not indices: continue
expert_remap = {code: i for i, code in enumerate(sorted(list(target_codes)))}
expert_target_transform = LabelRemapper(expert_remap)
expert_subset = Subset(full_dataset, indices)
expert_dataset = [(img, expert_target_transform(label)) for img, label in
tqdm(expert_subset, desc=f"Remapping {name} labels")]
labels_in_subset = [item[1] for item in expert_dataset]
class_counts = np.bincount(labels_in_subset)
class_weights = 1. / np.where(class_counts > 0, class_counts, 1)
sample_weights = [class_weights[label] for label in labels_in_subset]
sampler = WeightedRandomSampler(sample_weights, len(sample_weights), replacement=True)
expert_loader = DataLoader(expert_dataset, batch_size=BATCH_SIZE, sampler=sampler)
expert_model = model_class(num_classes=len(target_chars)).to(DEVICE)
kaggle_expert_path = os.path.join(KAGGLE_WEIGHTS_DIR, f"{name}_model.pth")
if os.path.exists(kaggle_expert_path):
print(f"Loading base weights from {kaggle_expert_path}")
expert_model.load_state_dict(torch.load(kaggle_expert_path, map_location=DEVICE))
finetune_model(name, expert_model, expert_loader)
if __name__ == '__main__':
main()