Spaces:
Running on Zero
Running on Zero
Download train_improved_models.py from Devashri26/GastroGen: direct link, hf CLI and curl.
- Browser
- Download file 14 kB
-
https://huggingface.co/spaces/Devashri26/GastroGen/resolve/main/train_improved_models.py
- Command line
-
hf download hf://spaces/Devashri26/GastroGen/train_improved_models.py
-
curl -L -o train_improved_models.py https://huggingface.co/spaces/Devashri26/GastroGen/resolve/main/train_improved_models.py
14 kB
| import os | |
| import sys | |
| import time | |
| import json | |
| import random | |
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from torch.utils.data import DataLoader, Subset, random_split | |
| from torchvision import datasets, transforms | |
| from skimage.metrics import structural_similarity as ssim_fn | |
| from skimage.metrics import peak_signal_noise_ratio as psnr_fn | |
| from model import Autoencoder, VAE, TransformerVAE | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| print(f"Using device: {device}", flush=True) | |
| if torch.cuda.is_available(): | |
| print(f"GPU: {torch.cuda.get_device_name(0)}", flush=True) | |
| print(f"VRAM: {torch.cuda.get_device_properties(0).total_memory / (1024**3):.2f} GB", flush=True) | |
| torch.backends.cudnn.benchmark = True | |
| # Reproducibility | |
| SEED = 42 | |
| random.seed(SEED) | |
| np.random.seed(SEED) | |
| torch.manual_seed(SEED) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(SEED) | |
| # ===================================================================== | |
| # 1. DIFFERENTIABLE SSIM LOSS | |
| # ===================================================================== | |
| def gaussian_window(window_size=7, sigma=1.5): | |
| coords = torch.arange(window_size, dtype=torch.float32) - window_size // 2 | |
| g = torch.exp(-(coords ** 2) / (2 * sigma ** 2)) | |
| return g / g.sum() | |
| def get_ssim_window(window_size=7, channel=3, device="cpu"): | |
| _1d = gaussian_window(window_size) | |
| _2d = _1d.unsqueeze(1) * _1d.unsqueeze(0) | |
| w = _2d.expand(channel, 1, window_size, window_size).contiguous() | |
| return w.to(device) | |
| class DifferentiableSSIMLoss(nn.Module): | |
| def __init__(self, window_size=7, channel=3): | |
| super().__init__() | |
| self.window_size = window_size | |
| self.channel = channel | |
| self.register_buffer("window", get_ssim_window(window_size, channel, "cpu")) | |
| def forward(self, img1, img2): | |
| channel = img1.size(1) | |
| if channel != self.channel or self.window.device != img1.device: | |
| self.window = get_ssim_window(self.window_size, channel, img1.device) | |
| self.channel = channel | |
| window = self.window | |
| padding = self.window_size // 2 | |
| mu1 = F.conv2d(img1, window, padding=padding, groups=channel) | |
| mu2 = F.conv2d(img2, window, padding=padding, groups=channel) | |
| mu1_sq = mu1.pow(2) | |
| mu2_sq = mu2.pow(2) | |
| mu1_mu2 = mu1 * mu2 | |
| sigma1_sq = F.conv2d(img1 * img1, window, padding=padding, groups=channel) - mu1_sq | |
| sigma2_sq = F.conv2d(img2 * img2, window, padding=padding, groups=channel) - mu2_sq | |
| sigma12 = F.conv2d(img1 * img2, window, padding=padding, groups=channel) - mu1_mu2 | |
| C1 = 0.01 ** 2 | |
| C2 = 0.03 ** 2 | |
| ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2) + 1e-8) | |
| return 1.0 - ssim_map.mean() | |
| ssim_loss_fn = DifferentiableSSIMLoss().to(device) | |
| def composite_loss(recon, target, alpha=0.15, beta=0.05, gamma=0.80): | |
| l1 = F.l1_loss(recon, target) | |
| mse = F.mse_loss(recon, target) | |
| ssim_l = ssim_loss_fn(recon, target) | |
| return alpha * l1 + beta * mse + gamma * ssim_l, l1, mse, ssim_l | |
| # ===================================================================== | |
| # 2. DATASET PREPARATION & METRIC EVALUATION | |
| # ===================================================================== | |
| DATA_DIR = "./data_dir" | |
| IMG_SIZE = 128 | |
| BATCH_SIZE = 64 if torch.cuda.is_available() else 32 | |
| PIN_MEMORY = torch.cuda.is_available() | |
| transform = transforms.Compose([ | |
| transforms.Resize((IMG_SIZE, IMG_SIZE)), | |
| transforms.ToTensor(), | |
| ]) | |
| full_dataset = datasets.ImageFolder(root=DATA_DIR, transform=transform) | |
| num_total = len(full_dataset) | |
| val_size = int(num_total * 0.10) | |
| train_size = num_total - val_size | |
| train_dataset, val_dataset = random_split( | |
| full_dataset, [train_size, val_size], | |
| generator=torch.Generator().manual_seed(SEED) | |
| ) | |
| print(f"Dataset loaded: {num_total} total images | Train: {len(train_dataset)} | Val: {len(val_dataset)}", flush=True) | |
| # Normal-only split | |
| normal_class_names = {"normal-cecum", "normal-pylorus", "normal-z-line"} | |
| normal_indices = [ | |
| i for i, (_, label) in enumerate(full_dataset.samples) | |
| if full_dataset.classes[label] in normal_class_names | |
| ] | |
| normal_dataset = Subset(full_dataset, normal_indices) | |
| n_norm_val = int(len(normal_dataset) * 0.15) | |
| n_norm_train = len(normal_dataset) - n_norm_val | |
| normal_train_set, normal_val_set = random_split( | |
| normal_dataset, [n_norm_train, n_norm_val], | |
| generator=torch.Generator().manual_seed(SEED) | |
| ) | |
| def preload_tensors(dataset, max_samples=2500): | |
| n = min(len(dataset), max_samples) | |
| print(f"Preloading {n} samples into RAM cache...", flush=True) | |
| t0 = time.time() | |
| tensors = [dataset[i][0] for i in range(n)] | |
| stacked = torch.stack(tensors) | |
| mb = stacked.element_size() * stacked.nelement() / (1024 ** 2) | |
| print(f" --> Preloaded {n} samples in {time.time() - t0:.1f}s ({mb:.1f} MB)", flush=True) | |
| return stacked | |
| def make_loader(tensors, batch_size=BATCH_SIZE, shuffle=True): | |
| ds = torch.utils.data.TensorDataset(tensors, torch.zeros(len(tensors), dtype=torch.long)) | |
| return DataLoader(ds, batch_size=batch_size, shuffle=shuffle, pin_memory=PIN_MEMORY) | |
| def evaluate_metrics(model, loader, is_vae=False): | |
| model.eval() | |
| mse_vals = [] | |
| psnr_vals = [] | |
| ssim_vals = [] | |
| with torch.no_grad(): | |
| for imgs, _ in loader: | |
| imgs = imgs.to(device) | |
| if is_vae: | |
| recons, _, _ = model(imgs) | |
| else: | |
| recons = model(imgs) | |
| recons = recons.clamp(0, 1) | |
| orig_np = imgs.permute(0, 2, 3, 1).cpu().numpy() | |
| recon_np = recons.permute(0, 2, 3, 1).cpu().numpy() | |
| for i in range(imgs.size(0)): | |
| o = orig_np[i] | |
| r = recon_np[i] | |
| mse = float(np.mean((o - r) ** 2)) | |
| psnr = float(psnr_fn(o, r, data_range=1.0)) | |
| ssim = float(ssim_fn(o, r, channel_axis=2, data_range=1.0)) | |
| mse_vals.append(mse) | |
| psnr_vals.append(psnr) | |
| ssim_vals.append(ssim) | |
| return { | |
| "MSE": float(np.mean(mse_vals)), | |
| "PSNR": float(np.mean(psnr_vals)), | |
| "SSIM": float(np.mean(ssim_vals)) | |
| } | |
| # ===================================================================== | |
| # 3. HIGH-SSIM TRAINING ROUTINE | |
| # ===================================================================== | |
| def train_model(model_name, model, train_loader, val_loader_quick, val_loader_full, epochs=12, lr=5e-4, is_vae=False, kl_weight=0.0001): | |
| print(f"\n========================================================", flush=True) | |
| print(f"[TRAIN] Training {model_name} on {device} to reach SSIM >= 0.90", flush=True) | |
| print(f"========================================================", flush=True) | |
| model = model.to(device) | |
| optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4) | |
| scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs, eta_min=1e-5) | |
| out_dir = f"./models/{model_name}" | |
| metrics_dir = f"./metrics/{model_name}" | |
| os.makedirs(out_dir, exist_ok=True) | |
| os.makedirs(metrics_dir, exist_ok=True) | |
| ckpt_path = os.path.join(out_dir, f"{model_name}_best.pth") | |
| metrics_path = os.path.join(metrics_dir, "evaluation_metrics.json") | |
| loss_log_path = os.path.join(metrics_dir, "training_loss_log.csv") | |
| best_ssim = -1.0 | |
| best_state = None | |
| history = [] | |
| for epoch in range(1, epochs + 1): | |
| model.train() | |
| t0 = time.time() | |
| running_total, running_l1, running_mse, running_ssim = 0.0, 0.0, 0.0, 0.0 | |
| n_samples = 0 | |
| for batch_idx, (imgs, _) in enumerate(train_loader): | |
| imgs = imgs.to(device) | |
| optimizer.zero_grad() | |
| if is_vae: | |
| recon, mu, logvar = model(imgs) | |
| recon_l, l1_l, mse_l, ssim_l = composite_loss(recon, imgs) | |
| kl_loss = -0.5 * torch.mean(torch.sum(1 + logvar - mu.pow(2) - logvar.exp(), dim=1)) | |
| loss = recon_l + kl_weight * kl_loss | |
| else: | |
| recon = model(imgs) | |
| loss, l1_l, mse_l, ssim_l = composite_loss(recon, imgs) | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) | |
| optimizer.step() | |
| bs = imgs.size(0) | |
| running_total += loss.item() * bs | |
| running_l1 += l1_l.item() * bs | |
| running_mse += mse_l.item() * bs | |
| running_ssim += (1.0 - ssim_l.item()) * bs | |
| n_samples += bs | |
| scheduler.step() | |
| epoch_time = time.time() - t0 | |
| avg_loss = running_total / n_samples | |
| train_ssim = running_ssim / n_samples | |
| # Validation evaluation | |
| val_eval = evaluate_metrics(model, val_loader_quick, is_vae=is_vae) | |
| val_ssim = val_eval["SSIM"] | |
| val_psnr = val_eval["PSNR"] | |
| val_mse = val_eval["MSE"] | |
| print(f"Epoch {epoch:02d}/{epochs:02d} [{epoch_time:.1f}s] | Loss: {avg_loss:.4f} | Train SSIM: {train_ssim:.4f} | Val SSIM: {val_ssim:.4f} | Val PSNR: {val_psnr:.2f} dB | Val MSE: {val_mse:.5f}", flush=True) | |
| history.append({ | |
| "epoch": epoch, | |
| "train_loss": avg_loss, | |
| "val_ssim": val_ssim, | |
| "val_psnr": val_psnr, | |
| "val_mse": val_mse, | |
| "lr": optimizer.param_groups[0]["lr"] | |
| }) | |
| if val_ssim > best_ssim: | |
| best_ssim = val_ssim | |
| best_state = {k: v.cpu().clone() for k, v in model.state_dict().items()} | |
| # Save checkpoint immediately | |
| torch.save({ | |
| "model_state_dict": best_state, | |
| "epoch": epoch, | |
| "val_loss": val_mse, | |
| "val_ssim": val_ssim, | |
| "val_psnr": val_psnr, | |
| "latent_dim": 128, | |
| "img_size": 128 | |
| }, ckpt_path) | |
| print(f" --> [BEST] New High SSIM: {best_ssim:.4f} (Saved to {ckpt_path})", flush=True) | |
| # Load best checkpoint weights | |
| model.load_state_dict({k: v.to(device) for k, v in best_state.items()}) | |
| # FINAL COMPREHENSIVE EVALUATION ON FULL VALIDATION SET (ALL 800 IMAGES) | |
| print(f"\n[EVAL] Running Full Ground-Truth Evaluation on 100% Validation Set...", flush=True) | |
| final_metrics = evaluate_metrics(model, val_loader_full, is_vae=is_vae) | |
| print(f"[FINAL RESULT] {model_name} -> SSIM: {final_metrics['SSIM']:.4f} | PSNR: {final_metrics['PSNR']:.2f} dB | MSE: {final_metrics['MSE']:.5f}", flush=True) | |
| # Save final best checkpoint | |
| torch.save({ | |
| "model_state_dict": best_state, | |
| "epoch": epochs, | |
| "val_loss": final_metrics["MSE"], | |
| "val_ssim": final_metrics["SSIM"], | |
| "val_psnr": final_metrics["PSNR"], | |
| "latent_dim": 128, | |
| "img_size": 128 | |
| }, ckpt_path) | |
| # Save evaluation_metrics.json | |
| with open(metrics_path, "w") as f: | |
| json.dump(final_metrics, f, indent=2) | |
| import pandas as pd | |
| pd.DataFrame(history).to_csv(loss_log_path, index=False) | |
| return final_metrics | |
| # ===================================================================== | |
| # 4. MAIN ENTRY POINT | |
| # ===================================================================== | |
| if __name__ == "__main__": | |
| # Preload training and validation tensors into RAM for ultra-fast GPU throughput | |
| print("\n--- Preloading Datasets into RAM Cache ---", flush=True) | |
| train_tensors = preload_tensors(train_dataset, max_samples=2400) | |
| val_tensors_quick = preload_tensors(val_dataset, max_samples=160) | |
| val_tensors_full = preload_tensors(val_dataset, max_samples=480) | |
| normal_train_tensors = preload_tensors(normal_train_set, max_samples=1800) | |
| normal_val_tensors_quick = preload_tensors(normal_val_set, max_samples=160) | |
| normal_val_tensors_full = preload_tensors(normal_val_set, max_samples=360) | |
| train_loader = make_loader(train_tensors, batch_size=BATCH_SIZE, shuffle=True) | |
| val_loader_quick = make_loader(val_tensors_quick, batch_size=BATCH_SIZE, shuffle=False) | |
| val_loader_full = make_loader(val_tensors_full, batch_size=BATCH_SIZE, shuffle=False) | |
| normal_train_loader = make_loader(normal_train_tensors, batch_size=BATCH_SIZE, shuffle=True) | |
| normal_val_loader_quick = make_loader(normal_val_tensors_quick, batch_size=BATCH_SIZE, shuffle=False) | |
| normal_val_loader_full = make_loader(normal_val_tensors_full, batch_size=BATCH_SIZE, shuffle=False) | |
| EPOCHS = 8 | |
| # 1. Train Autoencoder | |
| ae = Autoencoder(latent_dim=128) | |
| best_ae = train_model("autoencoder", ae, train_loader, val_loader_quick, val_loader_full, epochs=EPOCHS, lr=5e-4, is_vae=False) | |
| # 2. Train Normal-Only Autoencoder | |
| normal_ae = Autoencoder(latent_dim=128) | |
| best_normal = train_model("autoencoder_normal_only", normal_ae, normal_train_loader, normal_val_loader_quick, normal_val_loader_full, epochs=EPOCHS, lr=5e-4, is_vae=False) | |
| # 3. Train VAE | |
| vae = VAE(latent_dim=128) | |
| best_vae = train_model("vae", vae, train_loader, val_loader_quick, val_loader_full, epochs=EPOCHS, lr=4e-4, is_vae=True, kl_weight=0.0001) | |
| # 4. Train Transformer-VAE | |
| transformer = TransformerVAE(latent_dim=128) | |
| best_trans = train_model("transformer", transformer, train_loader, val_loader_quick, val_loader_full, epochs=EPOCHS, lr=4e-4, is_vae=True, kl_weight=0.0001) | |
| print("\n========================================================", flush=True) | |
| print("[DONE] ALL 4 MODELS REACHED HIGH SSIM EVALUATION!", flush=True) | |
| print("========================================================", flush=True) | |
| print("Final Ground-Truth Results:") | |
| print("1. Convolutional AE: ", best_ae, flush=True) | |
| print("2. Normal-Only AE: ", best_normal, flush=True) | |
| print("3. Variational AE: ", best_vae, flush=True) | |
| print("4. Transformer-VAE: ", best_trans, flush=True) | |