Spaces:
Runtime error
Runtime error
| import os | |
| import torch | |
| import torch.nn as nn | |
| import torch.optim as optim | |
| from torchvision import transforms | |
| from torchvision.utils import save_image | |
| from torch.utils.data import DataLoader, Subset | |
| import medmnist | |
| from medmnist import INFO | |
| from tqdm import tqdm | |
| # ========================================== | |
| # 1. The Energy Function (A simple CNN) | |
| # ========================================== | |
| # This network's only job is to output a single scalar "Energy" score. | |
| # Low score = Real Lung. High score = Fake/Noise. | |
| class EnergyModel(nn.Module): | |
| def __init__(self): | |
| super(EnergyModel, self).__init__() | |
| self.net = nn.Sequential( | |
| nn.Conv2d(1, 32, 4, 2, 1), | |
| nn.LeakyReLU(0.2, inplace=True), | |
| nn.Conv2d(32, 64, 4, 2, 1), | |
| nn.LeakyReLU(0.2, inplace=True), | |
| nn.Conv2d(64, 128, 4, 2, 1), | |
| nn.LeakyReLU(0.2, inplace=True), | |
| nn.Conv2d(128, 256, 4, 2, 1), | |
| nn.LeakyReLU(0.2, inplace=True), | |
| nn.Conv2d(256, 512, 4, 2, 1), | |
| nn.LeakyReLU(0.2, inplace=True), | |
| nn.Flatten(), | |
| nn.Linear(512 * 7 * 7, 1) # Output a single number | |
| ) | |
| def forward(self, x): | |
| return self.net(x) | |
| # ========================================== | |
| # 2. Langevin Dynamics (The Thermodynamic Generator) | |
| # ========================================== | |
| def sample_langevin(model, x, steps=60, step_size=10, noise_scale=0.005): | |
| # This is Markov Chain Monte Carlo (MCMC) | |
| # Detach x to start a fresh computational graph | |
| x = x.clone().detach().requires_grad_(True) | |
| for _ in range(steps): | |
| # Calculate the energy of the current image | |
| energy = model(x) | |
| # Sum the energy so autograd can compute gradients for the whole batch at once | |
| grad = torch.autograd.grad(energy.sum(), x, only_inputs=True)[0] | |
| # Langevin Equation: | |
| # Move pixels in the OPPOSITE direction of the gradient (to lower the energy) | |
| # Add a tiny bit of random thermodynamic heat (noise) to prevent getting stuck | |
| x.data -= step_size * grad + noise_scale * torch.randn_like(x) | |
| # Clamp pixels to stay within valid grayscale image bounds [-1, 1] | |
| x.data = torch.clamp(x.data, -1.0, 1.0) | |
| return x.detach() # Strip the autograd receipt so it doesn't cause an OOM! | |
| # ========================================== | |
| # 3. The Training Loop | |
| # ========================================== | |
| def main(): | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| print(f"Igniting Thermodynamic EBM on: {device}") | |
| dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data" | |
| out_dir = os.path.join(dataset_root, "EBM_Outputs") | |
| os.makedirs(out_dir, exist_ok=True) | |
| # We scale to [-1, 1] for stable gradient flows in Langevin dynamics | |
| transform = transforms.Compose([ | |
| transforms.Resize(224), | |
| transforms.ToTensor(), | |
| transforms.Normalize(mean=[0.5], std=[0.5]) | |
| ]) | |
| print("Loading Normal Lungs...") | |
| info = INFO['pneumoniamnist'] | |
| DataClass = getattr(medmnist, info['python_class']) | |
| full_dataset = DataClass(split='train', transform=transform, download=False, size=224, root=dataset_root) | |
| # Isolate healthy lungs | |
| normal_indices = [i for i in range(len(full_dataset)) if full_dataset[i][1][0] == 0] | |
| normal_dataset = Subset(full_dataset, normal_indices) | |
| dataloader = DataLoader(normal_dataset, batch_size=32, shuffle=True, num_workers=0) | |
| model = EnergyModel().to(device) | |
| optimizer = optim.Adam(model.parameters(), lr=1e-4) | |
| num_epochs = 100 | |
| print("Commencing Energy Optimization...") | |
| for epoch in range(num_epochs): | |
| model.train() | |
| loop = tqdm(dataloader, leave=True) | |
| for real_images, _ in loop: | |
| real_images = real_images.to(device) | |
| batch_size = real_images.size(0) | |
| # 1. Start with pure random static | |
| initial_noise = torch.rand_like(real_images) * 2 - 1 | |
| # 2. Cool the static down into fake lungs via Langevin Dynamics | |
| fake_images = sample_langevin(model, initial_noise, steps=60) | |
| optimizer.zero_grad() | |
| # 3. Calculate Energy for both Real and Fake | |
| real_energy = model(real_images) | |
| fake_energy = model(fake_images) | |
| # 4. Contrastive Divergence Loss | |
| # Real Energy low (negative), Fake Energy high (positive) | |
| loss = real_energy.mean() - fake_energy.mean() | |
| # Add L2 Regularization (Prevents the energy values from exploding to infinity) | |
| loss += 0.001 * (real_energy ** 2 + fake_energy ** 2).mean() | |
| loss.backward() | |
| # Gradient clipping to prevent thermodynamic explosions | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) | |
| optimizer.step() | |
| loop.set_description(f"EBM Epoch [{epoch+1}/{num_epochs}]") | |
| loop.set_postfix(Loss=loss.item(), Real_E=real_energy.mean().item(), Fake_E=fake_energy.mean().item()) | |
| # Save visual progression | |
| if (epoch + 1) % 10 == 0: | |
| model.eval() | |
| print(f"\nGenerating checkpoint samples for Epoch {epoch+1}...") | |
| with torch.no_grad(): | |
| # Use gradients to sample, enable grad temporarily | |
| with torch.enable_grad(): | |
| test_noise = (torch.rand(16, 1, 224, 224, device=device) * 2 - 1) | |
| test_samples = sample_langevin(model, test_noise, steps=100) | |
| # Denormalize from [-1, 1] back to [0, 1] for saving | |
| test_samples = (test_samples + 1) / 2.0 | |
| save_image(test_samples, os.path.join(out_dir, f'ebm_sample_{epoch+1}.png'), nrow=4) | |
| torch.save(model.state_dict(), os.path.join(out_dir, 'ebm_baseline.pth')) | |
| print("\nEBM Training Complete.") | |
| if __name__ == '__main__': | |
| main() |