AtomVQVAE / trainFixedV2.py
Wojtekb30's picture
New model
652b656 verified
Raw
History Blame Contribute Delete
6.65 kB
# This is code I used to fix the codebook collapse and train atom_vqvae_2_full model.
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
# Import the VQ-VAE model implementation
from AtomVQVAE import ATOMVQVAE
# ---------------------------------------------------------
# 1. Dataset for Loading Images from a Single Directory
# ---------------------------------------------------------
class FlatFolderDataset(Dataset):
def __init__(self, folder_path, transform=None):
# Collect all supported image files from the target directory
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:
# Fallback to the first valid image if loading fails
image = Image.open(self.image_paths[0]).convert('RGB')
if self.transform:
image = self.transform(image)
return image
# ---------------------------------------------------------
# 2. Dead Token Recovery Utility
# ---------------------------------------------------------
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()
# Use a batch of real images to generate replacement feature vectors
images = next(iter(dataloader)).to(device)
with torch.no_grad():
# Update this reference if your encoder uses a different attribute name
try:
continuous_features = model.encoder(images)
# Apply the pre-quantization convolution if present
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
# Flatten features from [B, C, H, W] to [N, C]
# Expected channel size is 128 for this model
flat_features = continuous_features.permute(0, 2, 3, 1).reshape(-1, 128)
# Reinitialize each inactive token with a sampled feature vector
for token_id in dead_tokens:
random_idx = torch.randint(0, flat_features.size(0), (1,)).item()
# Update this reference if the VQ embedding layer uses a different name
model.vq_layer.embedding.weight.data[token_id] = flat_features[random_idx]
model.train()
print("--- Revival Complete! ---")
# ---------------------------------------------------------
# 3. Training Entry Point
# ---------------------------------------------------------
def train():
# --- Training Configuration ---
DATA_FOLDER = "/workspace/10kimages" # Update this path to your image dataset
WEIGHTS_PATH = "atom_vqvae.safetensors"
EPOCHS = 10
BATCH_SIZE = 64
LEARNING_RATE = 5e-5 # Reduced learning rate for fine-tuning
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
VOCAB_SIZE = 2048
print(f"Using device: {DEVICE}")
# --- Model Initialization ---
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!")
# --- Dataset and DataLoader Setup ---
transform = transforms.Compose([
transforms.Resize((128, 128)),
transforms.ToTensor() # Converts image values to the [0, 1] range
])
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 and Loss Functions ---
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
mse_loss_fn = nn.MSELoss()
# --- Main Training Loop ---
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()
# Forward pass
# Adjust unpacking if your model returns different outputs
reconstructed, vq_loss, _ = model(images)
# Reconstruction loss measures image quality
reconstruction_loss = mse_loss_fn(reconstructed, images)
# Combined training objective
loss = reconstruction_loss + vq_loss
# Backpropagation and optimizer step
loss.backward()
optimizer.step()
total_loss += loss.item()
# Track which tokens were used during this epoch
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}")
# --- Epoch Summary and Token Usage Analysis ---
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}")
# Identify tokens that were never activated
dead_tokens = [i for i in range(VOCAB_SIZE) if i not in active_tokens_this_epoch]
# Reinitialize inactive tokens if any are found
if len(dead_tokens) > 0:
revive_dead_tokens(model, dead_tokens, dataloader, DEVICE)
# Save model weights after each epoch
save_model(model, WEIGHTS_PATH)
print(f"Model saved to {WEIGHTS_PATH}\n")
if __name__ == "__main__":
train()