| |
|
|
| import os |
| import glob |
| import torch |
| import torch.nn as nn |
| import torch.optim as optim |
| from torch.utils.data import Dataset, DataLoader |
| from torchvision import transforms |
| from PIL import Image |
| from safetensors.torch import load_model, save_model |
|
|
| |
| from AtomVQVAE import ATOMVQVAE |
|
|
| |
| |
| |
| class FlatFolderDataset(Dataset): |
| def __init__(self, folder_path, transform=None): |
| |
| self.image_paths = [] |
| for ext in ('*.png', '*.jpg', '*.jpeg', '*.PNG', '*.JPG', '*.JPEG'): |
| self.image_paths.extend(glob.glob(os.path.join(folder_path, ext))) |
| |
| if len(self.image_paths) == 0: |
| raise ValueError(f"No images found in {folder_path}!") |
| |
| self.transform = transform |
|
|
| def __len__(self): |
| return len(self.image_paths) |
|
|
| def __getitem__(self, idx): |
| img_path = self.image_paths[idx] |
| try: |
| image = Image.open(img_path).convert('RGB') |
| except Exception as e: |
| |
| image = Image.open(self.image_paths[0]).convert('RGB') |
| |
| if self.transform: |
| image = self.transform(image) |
| return image |
|
|
| |
| |
| |
| def revive_dead_tokens(model, dead_tokens, dataloader, device): |
| """Replace unused codebook entries with encoder feature vectors.""" |
| print(f"--- Reviving {len(dead_tokens)} dead tokens ---") |
| model.eval() |
| |
| |
| images = next(iter(dataloader)).to(device) |
| |
| with torch.no_grad(): |
| |
| try: |
| continuous_features = model.encoder(images) |
| |
| |
| if hasattr(model, 'quant_conv'): |
| continuous_features = model.quant_conv(continuous_features) |
| |
| except AttributeError: |
| print("ERROR: Could not find 'model.encoder'. Please update the variable name in the script!") |
| return |
|
|
| |
| |
| flat_features = continuous_features.permute(0, 2, 3, 1).reshape(-1, 128) |
| |
| |
| for token_id in dead_tokens: |
| random_idx = torch.randint(0, flat_features.size(0), (1,)).item() |
| |
| |
| model.vq_layer.embedding.weight.data[token_id] = flat_features[random_idx] |
|
|
| model.train() |
| print("--- Revival Complete! ---") |
|
|
| |
| |
| |
| def train(): |
| |
| DATA_FOLDER = "/workspace/10kimages" |
| WEIGHTS_PATH = "atom_vqvae.safetensors" |
| EPOCHS = 10 |
| BATCH_SIZE = 64 |
| LEARNING_RATE = 5e-5 |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| VOCAB_SIZE = 2048 |
| |
| print(f"Using device: {DEVICE}") |
|
|
| |
| model = ATOMVQVAE().to(DEVICE) |
| if os.path.exists(WEIGHTS_PATH): |
| print(f"Loading existing weights from {WEIGHTS_PATH}...") |
| load_model(model, WEIGHTS_PATH) |
| else: |
| print("No existing weights found. Starting from scratch!") |
|
|
| |
| transform = transforms.Compose([ |
| transforms.Resize((128, 128)), |
| transforms.ToTensor() |
| ]) |
| |
| dataset = FlatFolderDataset(DATA_FOLDER, transform=transform) |
| dataloader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, drop_last=True) |
| print(f"Loaded {len(dataset)} images.") |
|
|
| |
| optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE) |
| mse_loss_fn = nn.MSELoss() |
|
|
| |
| for epoch in range(EPOCHS): |
| model.train() |
| total_loss = 0 |
| active_tokens_this_epoch = set() |
|
|
| for batch_idx, images in enumerate(dataloader): |
| images = images.to(DEVICE) |
| optimizer.zero_grad() |
|
|
| |
| |
| reconstructed, vq_loss, _ = model(images) |
| |
| |
| reconstruction_loss = mse_loss_fn(reconstructed, images) |
| |
| |
| loss = reconstruction_loss + vq_loss |
| |
| |
| loss.backward() |
| optimizer.step() |
| total_loss += loss.item() |
|
|
| |
| with torch.no_grad(): |
| tokens = model.encode_to_tokens(images) |
| active_tokens_this_epoch.update(tokens.unique().tolist()) |
|
|
| if batch_idx % 10 == 0: |
| print(f"Epoch [{epoch+1}/{EPOCHS}] Batch [{batch_idx}/{len(dataloader)}] Loss: {loss.item():.4f}") |
|
|
| |
| avg_loss = total_loss / len(dataloader) |
| print(f"\n=== Epoch {epoch+1} Summary ===") |
| print(f"Average Loss: {avg_loss:.4f}") |
| print(f"Active Tokens this Epoch: {len(active_tokens_this_epoch)} / {VOCAB_SIZE}") |
| |
| |
| dead_tokens = [i for i in range(VOCAB_SIZE) if i not in active_tokens_this_epoch] |
| |
| |
| if len(dead_tokens) > 0: |
| revive_dead_tokens(model, dead_tokens, dataloader, DEVICE) |
| |
| |
| save_model(model, WEIGHTS_PATH) |
| print(f"Model saved to {WEIGHTS_PATH}\n") |
|
|
| if __name__ == "__main__": |
| train() |