{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}},"cells":[{"cell_type":"markdown","source":"# Competition: gan-getting-started\n\n**Generated by Alexandria Research Assistant**\n\n**Dataset:** gan-getting-started\n\n**Task:** competition - kaggle-competition\n\n---\n\n⚠️ **Note:** This notebook contains Alexandria markers (lines starting with `# ⚠️ ALEXANDRIA MARKER`) at the top of each code cell. These markers enable the 'Sync from Kaggle' feature to track cell outputs. Please do not delete them.","metadata":{}},{"cell_type":"markdown","source":"## Setup & Imports\n\nInstall and import all necessary libraries for GAN training, image processing, and Kaggle competition utilities.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_1_START===\")\n\n# ==========================\n# Setup & Imports\n# ==========================\n\n# Install required packages (if not already available in Kaggle environment)\n# Note: Most packages are pre-installed on Kaggle, but pip install as fallback for custom kernels\nimport sys\nimport subprocess\n\ndef install(package):\n    try:\n        subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", package])\n        print(f\"Package '{package}' installed successfully.\")\n    except Exception as e:\n        print(f\"Error installing package '{package}': {e}\")\n\n# List of packages to ensure are installed\nrequired_packages = [\n    \"torch\", \"torchvision\", \"albumentations\", \"opencv-python\", \"pillow\"\n]\n\nfor pkg in required_packages:\n    try:\n        __import__(pkg if pkg != \"opencv-python\" else \"cv2\")\n    except ImportError:\n        install(pkg)\n\n# Import libraries for GAN training, image processing, and Kaggle utilities\nimport os\nimport glob\nimport random\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torchvision\nfrom torchvision import transforms\nfrom PIL import Image\nimport cv2\nimport albumentations as A\n\n# Set DATA_PATH as specified\nDATA_PATH = \"/kaggle/input/gan-getting-started\"\n\n# Set random seeds for reproducibility\ndef set_seed(seed=42):\n    try:\n        random.seed(seed)\n        np.random.seed(seed)\n        torch.manual_seed(seed)\n        torch.cuda.manual_seed_all(seed)\n        os.environ[\"PYTHONHASHSEED\"] = str(seed)\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n        print(f\"Random seed set to {seed}.\")\n    except Exception as e:\n        print(f\"Error setting random seed: {e}\")\n\nset_seed(42)\n\n# Print library versions and environment info\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"Torchvision version: {torchvision.__version__}\")\nprint(f\"Albumentations version: {A.__version__}\")\nprint(f\"OpenCV version: {cv2.__version__}\")\nprint(f\"PIL version: {Image.__version__}\")\nprint(f\"Numpy version: {np.__version__}\")\nprint(f\"Pandas version: {pd.__version__}\")\n\n# Check for GPU availability\nif torch.cuda.is_available():\n    print(f\"CUDA is available. GPU: {torch.cuda.get_device_name(0)}\")\nelse:\n    print(\"CUDA is not available. Using CPU.\")\n\n# Confirm DATA_PATH exists\nif os.path.exists(DATA_PATH):\n    print(f\"DATA_PATH exists: {DATA_PATH}\")\nelse:\n    print(f\"DATA_PATH does not exist: {DATA_PATH}\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Data Loading\n\nLoad the Monet-style images and any other provided datasets from the gan-getting-started competition files.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_2_START===\")\n\n# ==========================\n# Data Loading\n# ==========================\n\nprint(\"===ALEXANDRIA_CELL_2_START===\")\n\n# List all files and directories in DATA_PATH\ntry:\n    print(f\"\\nDirectory structure of {DATA_PATH}:\")\n    for root, dirs, files in os.walk(DATA_PATH):\n        level = root.replace(DATA_PATH, '').count(os.sep)\n        indent = ' ' * 2 * level\n        print(f\"{indent}{os.path.basename(root)}/\")\n        subindent = ' ' * 2 * (level + 1)\n        for d in dirs:\n            print(f\"{subindent}{d}/\")\n        for f in files:\n            print(f\"{subindent}{f}\")\nexcept Exception as e:\n    print(f\"Error listing directory structure: {e}\")\n\n# List Monet and photo image directories\nmonet_dir = os.path.join(DATA_PATH, \"monet_jpg\")\nphoto_dir = os.path.join(DATA_PATH, \"photo_jpg\")\n\n# Count images in Monet and photo directories\ndef count_images(img_dir):\n    try:\n        img_files = glob.glob(os.path.join(img_dir, \"*.jpg\"))\n        print(f\"Found {len(img_files)} images in {img_dir}\")\n        return img_files\n    except Exception as e:\n        print(f\"Error counting images in {img_dir}: {e}\")\n        return []\n\nmonet_images = count_images(monet_dir)\nphoto_images = count_images(photo_dir)\n\n# Preview sample images from Monet and photo datasets\nimport matplotlib.pyplot as plt\n\ndef show_sample_images(img_files, title, n=3):\n    print(f\"\\nShowing {n} sample images from {title}:\")\n    plt.figure(figsize=(12, 4))\n    for i, img_path in enumerate(random.sample(img_files, min(n, len(img_files)))):\n        try:\n            img = Image.open(img_path)\n            plt.subplot(1, n, i + 1)\n            plt.imshow(img)\n            plt.title(f\"{title} {i+1}\")\n            plt.axis('off')\n        except Exception as e:\n            print(f\"Error loading image {img_path}: {e}\")\n    plt.tight_layout()\n    plt.show()\n\nif monet_images:\n    show_sample_images(monet_images, \"Monet\", n=3)\nelse:\n    print(\"No Monet images found.\")\n\nif photo_images:\n    show_sample_images(photo_images, \"Photo\", n=3)\nelse:\n    print(\"No Photo images found.\")\n\n# Print summary of file counts\nprint(f\"\\nSummary:\")\nprint(f\"Monet images: {len(monet_images)}\")\nprint(f\"Photo images: {len(photo_images)}\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Exploratory Data Analysis (EDA)\n\nExplore the dataset visually and statistically to understand image properties and dataset balance.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_3_START===\")\n\nprint(\"===ALEXANDRIA_CELL_3_START===\")\n\n# ==========================\n# Exploratory Data Analysis (EDA)\n# ==========================\n\nimport matplotlib.pyplot as plt\nfrom collections import Counter\n\n# 1. Display random samples of Monet and photo images (already shown in prior cell, but show more for EDA)\ndef show_random_samples(img_files, title, n=6):\n    print(f\"\\nDisplaying {n} random samples from {title}:\")\n    plt.figure(figsize=(16, 4))\n    sample_paths = random.sample(img_files, min(n, len(img_files)))\n    for i, img_path in enumerate(sample_paths):\n        try:\n            img = Image.open(img_path)\n            plt.subplot(1, n, i + 1)\n            plt.imshow(img)\n            plt.title(os.path.basename(img_path), fontsize=9)\n            plt.axis('off')\n        except Exception as e:\n            print(f\"Error loading image {img_path}: {e}\")\n    plt.tight_layout()\n    plt.show()\n\nif monet_images:\n    show_random_samples(monet_images, \"Monet\", n=6)\nelse:\n    print(\"No Monet images found for EDA.\")\n\nif photo_images:\n    show_random_samples(photo_images, \"Photo\", n=6)\nelse:\n    print(\"No Photo images found for EDA.\")\n\n# 2. Show image size distributions and aspect ratios\ndef get_image_sizes(img_files):\n    sizes = []\n    aspect_ratios = []\n    for img_path in img_files:\n        try:\n            with Image.open(img_path) as img:\n                w, h = img.size\n                sizes.append((w, h))\n                aspect_ratios.append(round(w / h, 3) if h != 0 else 0)\n        except Exception as e:\n            print(f\"Error reading size for {img_path}: {e}\")\n    return sizes, aspect_ratios\n\nprint(\"\\nAnalyzing image sizes and aspect ratios...\")\n\nmonet_sizes, monet_aspects = get_image_sizes(monet_images)\nphoto_sizes, photo_aspects = get_image_sizes(photo_images)\n\ndef plot_size_distribution(sizes, aspects, title):\n    if not sizes:\n        print(f\"No sizes to plot for {title}.\")\n        return\n    widths, heights = zip(*sizes)\n    plt.figure(figsize=(12, 5))\n    plt.subplot(1, 2, 1)\n    plt.hist(widths, bins=20, alpha=0.7, label='Width')\n    plt.hist(heights, bins=20, alpha=0.7, label='Height')\n    plt.title(f\"{title} - Image Size Distribution\")\n    plt.xlabel(\"Pixels\")\n    plt.ylabel(\"Count\")\n    plt.legend()\n    plt.subplot(1, 2, 2)\n    plt.hist(aspects, bins=20, color='teal', alpha=0.8)\n    plt.title(f\"{title} - Aspect Ratio (W/H)\")\n    plt.xlabel(\"Aspect Ratio\")\n    plt.ylabel(\"Count\")\n    plt.tight_layout()\n    plt.show()\n\nplot_size_distribution(monet_sizes, monet_aspects, \"Monet\")\nplot_size_distribution(photo_sizes, photo_aspects, \"Photo\")\n\n# 3. Visualize color histograms / pixel intensity distributions\ndef plot_color_histograms(img_files, title, n=5):\n    print(f\"\\nPlotting color histograms for {n} random {title} images:\")\n    sample_paths = random.sample(img_files, min(n, len(img_files)))\n    plt.figure(figsize=(15, 3 * n))\n    for i, img_path in enumerate(sample_paths):\n        try:\n            img = Image.open(img_path).convert('RGB')\n            arr = np.array(img)\n            plt.subplot(n, 2, 2 * i + 1)\n            plt.imshow(img)\n            plt.title(f\"{title} Sample {i+1}\")\n            plt.axis('off')\n            plt.subplot(n, 2, 2 * i + 2)\n            for j, color in enumerate(['r', 'g', 'b']):\n                plt.hist(arr[..., j].flatten(), bins=32, color=color, alpha=0.5, label=f'{color.upper()}')\n            plt.title(f\"{title} {i+1} - Color Histogram\")\n            plt.xlabel(\"Pixel Intensity\")\n            plt.ylabel(\"Frequency\")\n            plt.legend()\n        except Exception as e:\n            print(f\"Error plotting histogram for {img_path}: {e}\")\n    plt.tight_layout()\n    plt.show()\n\nif monet_images:\n    plot_color_histograms(monet_images, \"Monet\", n=3)\nif photo_images:\n    plot_color_histograms(photo_images, \"Photo\", n=3)\n\n# 4. Check for missing or corrupted files\ndef check_corrupted_images(img_files, label):\n    print(f\"\\nChecking for missing or corrupted images in {label} set...\")\n    corrupted = []\n    for img_path in img_files:\n        try:\n            with Image.open(img_path) as img:\n                img.verify()  # PIL verify\n        except Exception as e:\n            corrupted.append(img_path)\n    if corrupted:\n        print(f\"Found {len(corrupted)} corrupted images in {label}:\")\n        for path in corrupted:\n            print(f\"  {path}\")\n    else:\n        print(f\"No corrupted images found in {label} set.\")\n\ncheck_corrupted_images(monet_images, \"Monet\")\ncheck_corrupted_images(photo_images, \"Photo\")\n\n# 5. Dataset balance summary\nprint(\"\\n=== Dataset Balance ===\")\nprint(f\"Number of Monet images: {len(monet_images)}\")\nprint(f\"Number of Photo images: {len(photo_images)}\")\n\nplt.figure(figsize=(5,4))\nplt.bar(['Monet', 'Photo'], [len(monet_images), len(photo_images)], color=['orange', 'skyblue'])\nplt.title(\"Dataset Balance\")\nplt.ylabel(\"Number of Images\")\nplt.show()","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Preprocessing\n\nPrepare images for GAN training by resizing, normalizing, and augmenting as needed.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_4_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_4_START===\")\n\n# ==========================\n# Preprocessing: Image Resizing, Normalization, and Augmentation\n# ==========================\n\nimport os\nimport glob\nimport random\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport cv2\n\n# Set DATA_PATH\nDATA_PATH = \"/kaggle/input/gan-getting-started\"\n\n# Define image directories\nmonet_dir = os.path.join(DATA_PATH, \"monet_jpg\")\nphoto_dir = os.path.join(DATA_PATH, \"photo_jpg\")\n\n# Configuration parameters\nIMAGE_SIZE = 256\nBATCH_SIZE = 32\nNORMALIZATION_RANGE = [-1, 1]  # For GAN training with tanh activation\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(f\"Using device: {DEVICE}\")\nprint(f\"Target image size: {IMAGE_SIZE}x{IMAGE_SIZE}\")\nprint(f\"Normalization range: {NORMALIZATION_RANGE}\")\n\n# ==========================\n# Define Custom Dataset Class\n# ==========================\n\nclass GANImageDataset(Dataset):\n    \"\"\"Custom Dataset for loading and preprocessing images for GAN training.\"\"\"\n    \n    def __init__(self, image_paths, image_size=256, augment=True, normalize_range=[-1, 1]):\n        \"\"\"\n        Args:\n            image_paths: List of image file paths\n            image_size: Target size for resizing (square)\n            augment: Whether to apply data augmentation\n            normalize_range: Tuple (min, max) for normalization, e.g., [-1, 1] or [0, 1]\n        \"\"\"\n        self.image_paths = image_paths\n        self.image_size = image_size\n        self.augment = augment\n        self.normalize_range = normalize_range\n        \n        # Define augmentation pipeline using Albumentations\n        if self.augment:\n            self.transform = A.Compose([\n                A.Resize(height=image_size, width=image_size, always_apply=True),\n                A.HorizontalFlip(p=0.5),\n                A.VerticalFlip(p=0.2),\n                A.Rotate(limit=15, p=0.3),\n                A.GaussNoise(p=0.1),\n                A.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1, p=0.3),\n                A.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], always_apply=True),\n                ToTensorV2(),\n            ], bbox_params=None)\n        else:\n            # No augmentation, just resize and normalize\n            self.transform = A.Compose([\n                A.Resize(height=image_size, width=image_size, always_apply=True),\n                A.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], always_apply=True),\n                ToTensorV2(),\n            ], bbox_params=None)\n    \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, idx):\n        try:\n            img_path = self.image_paths[idx]\n            \n            # Load image using OpenCV (RGB format)\n            image = cv2.imread(img_path)\n            if image is None:\n                raise ValueError(f\"Failed to load image: {img_path}\")\n            \n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n            # Apply transformations\n            if self.transform:\n                augmented = self.transform(image=image)\n                image = augmented['image']\n            \n            # Normalize to specified range\n            if self.normalize_range == [-1, 1]:\n                # Already normalized by Albumentations to [-1, 1] via mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]\n                pass\n            elif self.normalize_range == [0, 1]:\n                # Convert from [-1, 1] to [0, 1]\n                image = (image + 1) / 2\n            \n            return image\n        \n        except Exception as e:\n            print(f\"Error loading image {img_path}: {e}\")\n            # Return a random valid image as fallback\n            fallback_idx = random.randint(0, len(self.image_paths) - 1)\n            return self.__getitem__(fallback_idx)\n\n# ==========================\n# Load Image Paths\n# ==========================\n\nprint(\"\\nLoading image paths...\")\n\nmonet_images = sorted(glob.glob(os.path.join(monet_dir, \"*.jpg\")))\nphoto_images = sorted(glob.glob(os.path.join(photo_dir, \"*.jpg\")))\n\nprint(f\"Found {len(monet_images)} Monet images\")\nprint(f\"Found {len(photo_images)} Photo images\")\n\nif len(monet_images) == 0 or len(photo_images) == 0:\n    print(\"Warning: No images found in one or both directories!\")\n\n# ==========================\n# Create Datasets\n# ==========================\n\nprint(\"\\nCreating datasets...\")\n\n# Monet dataset with augmentation\nmonet_dataset = GANImageDataset(\n    image_paths=monet_images,\n    image_size=IMAGE_SIZE,\n    augment=True,\n    normalize_range=NORMALIZATION_RANGE\n)\n\n# Photo dataset with augmentation\nphoto_dataset = GANImageDataset(\n    image_paths=photo_images,\n    image_size=IMAGE_SIZE,","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Feature Engineering\n\nDefine any additional features or labels required for conditional GANs or advanced architectures.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_5_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_5_START===\")\n\n# ==========================\n# Feature Engineering\n# ==========================\n# - Assign domain labels for conditional GANs (Monet=1, Photo=0)\n# - Prepare paired/unpaired structures for advanced GANs (CycleGAN, cGAN, etc.)\n# - (Optional) Extract VGG features for perceptual loss (style/content)\n# ==========================\n\nimport os\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset\nfrom torchvision import models, transforms\nfrom PIL import Image\nimport random\n\n# Use variables from previous cells\n# monet_images, photo_images, IMAGE_SIZE, DEVICE are already defined\n\n# 1. Assign domain labels for conditional GANs\nprint(\"Assigning domain labels for conditional GANs...\")\n\nmonet_labels = np.ones(len(monet_images), dtype=np.int64)   # Monet domain: 1\nphoto_labels = np.zeros(len(photo_images), dtype=np.int64)  # Photo domain: 0\n\n# Combine image paths and labels for conditional GANs\nall_image_paths = monet_images + photo_images\nall_domain_labels = np.concatenate([monet_labels, photo_labels])\n\nprint(f\"Total images: {len(all_image_paths)}\")\nprint(f\"Domain label counts: Monet={np.sum(all_domain_labels==1)}, Photo={np.sum(all_domain_labels==0)}\")\n\n# 2. Prepare paired/unpaired structures for training\nprint(\"Preparing auxiliary data structures for GAN training...\")\n\n# For unpaired translation (CycleGAN): keep separate lists\nunpaired_monet = monet_images\nunpaired_photo = photo_images\n\n# For paired translation (if needed): randomly pair Monet and Photo images\nmin_len = min(len(monet_images), len(photo_images))\npaired_monet = random.sample(monet_images, min_len)\npaired_photo = random.sample(photo_images, min_len)\npaired_image_paths = list(zip(paired_monet, paired_photo))\n\nprint(f\"Prepared {len(paired_image_paths)} paired Monet-Photo samples (for paired GANs)\")\nprint(f\"Prepared {len(unpaired_monet)} Monet and {len(unpaired_photo)} Photo samples (for unpaired GANs)\")\n\n# 3. (Optional) Extract VGG features for perceptual loss\n# This can be slow, so only run for a small batch as a demonstration\nprint(\"Extracting VGG features for perceptual loss (demo on 4 Monet images)...\")\n\n# Define VGG feature extractor (up to relu3_3 for style/content loss)\nclass VGGFeatureExtractor(torch.nn.Module):\n    def __init__(self, device=DEVICE):\n        super().__init__()\n        vgg = models.vgg19(pretrained=True).features[:16].eval().to(device)\n        for param in vgg.parameters():\n            param.requires_grad = False\n        self.vgg = vgg\n        self.transform = transforms.Compose([\n            transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ])\n    def forward(self, img):\n        if isinstance(img, Image.Image):\n            img = self.transform(img).unsqueeze(0).to(DEVICE)\n        elif isinstance(img, torch.Tensor) and img.dim() == 3:\n            img = img.unsqueeze(0).to(DEVICE)\n        return self.vgg(img)\n\ntry:\n    vgg_extractor = VGGFeatureExtractor(device=DEVICE)\n    monet_vgg_feats = []\n    for img_path in random.sample(monet_images, min(4, len(monet_images))):\n        img = Image.open(img_path).convert(\"RGB\")\n        with torch.no_grad():\n            feat = vgg_extractor(img)\n        monet_vgg_feats.append(feat.cpu().numpy())\n    print(f\"Extracted VGG features for {len(monet_vgg_feats)} Monet images (shape: {monet_vgg_feats.shape})\")\nexcept Exception as e:\n    print(f\"Error extracting VGG features: {e}\")\n\n# 4. Summary of engineered features and structures\nprint(\"\\n=== Feature Engineering Summary ===\")\nprint(f\"all_image_paths: {len(all_image_paths)} images\")\nprint(f\"all_domain_labels: {all_domain_labels.shape} (Monet=1, Photo=0)\")\nprint(f\"paired_image_paths: {len(paired_image_paths)} pairs\")\nprint(f\"unpaired_monet: {len(unpaired_monet)} images\")\nprint(f\"unpaired_photo: {len(unpaired_photo)} images\")\nprint(\"Sample domain labels:\", all_domain_labels[:10])\nprint(\"Feature engineering complete.\")\n\n# Variables for downstream use:\n# - all_image_paths, all_domain_labels: for conditional GANs\n# - paired_image_paths: for paired GANs (Pix2Pix, etc.)\n# - unpaired_monet, unpaired_photo: for unpaired GANs (CycleGAN, etc.)\n# - monet_vgg_feats: example VGG features for perceptual loss","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Model Training\n\nBuild and train a GAN architecture suitable for Monet-style image generation.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_6_START===\")\n\nprint(\"===ALEXANDRIA_CELL_6_START===\")\n\n# ==========================\n# Model Training: CycleGAN for Monet-Style Image Generation\n# ==========================\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader\nimport torchvision.utils as vutils\nimport os\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# Use variables from previous cells\n# - unpaired_monet, unpaired_photo: lists of Monet and Photo image paths\n# - GANImageDataset: custom dataset class\n# - IMAGE_SIZE, BATCH_SIZE, DEVICE: config\n# - DATA_PATH: dataset root\n\n# --------------------------\n# 1. Define Generator and Discriminator Architectures (CycleGAN)\n# --------------------------\n\n# --- Helper blocks ---\nclass ResidualBlock(nn.Module):\n    def __init__(self, channels):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(channels, channels, kernel_size=3, stride=1, padding=0),\n            nn.InstanceNorm2d(channels),\n            nn.ReLU(inplace=True),\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(channels, channels, kernel_size=3, stride=1, padding=0),\n            nn.InstanceNorm2d(channels)\n        )\n    def forward(self, x):\n        return x + self.block(x)\n\n# --- Generator: ResNet-based (CycleGAN) ---\nclass ResnetGenerator(nn.Module):\n    def __init__(self, input_nc=3, output_nc=3, n_blocks=6, ngf=64):\n        super().__init__()\n        model = [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0),\n            nn.InstanceNorm2d(ngf),\n            nn.ReLU(inplace=True),\n        ]\n        # Downsampling\n        in_features = ngf\n        out_features = in_features * 2\n        for _ in range(2):\n            model += [\n                nn.Conv2d(in_features, out_features, kernel_size=3, stride=2, padding=1),\n                nn.InstanceNorm2d(out_features),\n                nn.ReLU(inplace=True),\n            ]\n            in_features = out_features\n            out_features = in_features * 2\n        # Residual blocks\n        for _ in range(n_blocks):\n            model += [ResidualBlock(in_features)]\n        # Upsampling\n        out_features = in_features // 2\n        for _ in range(2):\n            model += [\n                nn.ConvTranspose2d(in_features, out_features, kernel_size=3, stride=2, padding=1, output_padding=1),\n                nn.InstanceNorm2d(out_features),\n                nn.ReLU(inplace=True),\n            ]\n            in_features = out_features\n            out_features = in_features // 2\n        model += [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(in_features, output_nc, kernel_size=7, padding=0),\n            nn.Tanh()\n        ]\n        self.model = nn.Sequential(*model)\n    def forward(self, x):\n        return self.model(x)\n\n# --- Discriminator: PatchGAN ---\nclass PatchDiscriminator(nn.Module):\n    def __init__(self, input_nc=3, ndf=64, n_layers=3):\n        super().__init__()\n        sequence = [\n            nn.Conv2d(input_nc, ndf, kernel_size=4, stride=2, padding=1),\n            nn.LeakyReLU(0.2, inplace=True)\n        ]\n        nf_mult = 1\n        nf_mult_prev = 1\n        for n in range(1, n_layers):\n            nf_mult_prev = nf_mult\n            nf_mult = min(2 ** n, 8)\n            sequence += [\n                nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=4, stride=2, padding=1),\n                nn.InstanceNorm2d(ndf * nf_mult),\n                nn.LeakyReLU(0.2, inplace=True)\n            ]\n        nf_mult_prev = nf_mult\n        nf_mult = min(2 ** n_layers, 8)\n        sequence += [\n            nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=4, stride=1, padding=1),\n            nn.InstanceNorm2d(ndf * nf_mult),\n            nn.LeakyReLU(0.2, inplace=True)\n        ]\n        sequence += [\n            nn.Conv2d(ndf * nf_mult, 1, kernel_size=4, stride=1, padding=1)\n        ]\n        self.model = nn.Sequential(*sequence)\n    def forward(self, x):\n        return self.model(x)\n\n# --------------------------\n# 2. Instantiate Models\n# --------------------------\n\nG_A2B = ResnetGenerator().to(DEVICE)  # Photo -> Monet\nG_B2A = ResnetGenerator().to(DEVICE)  # Monet -> Photo\nD_A = PatchDiscriminator().to(DEVICE) # Discriminator for Photo\nD_B = PatchDiscriminator().to(DEVICE) # Discriminator for Monet\n\n# --------------------------\n# 3. Loss Functions\n# --------------------------\n\ncriterion_GAN = nn.MSELoss()\ncriterion_cycle = nn.L1Loss()\ncriterion_identity = nn.L1Loss()\n\n# --------------------------\n# 4. Optimizers & Hyperparameters\n# --------------------------\n\nlr = 2e-4\nbeta1 = 0.5\nbeta2 = 0.999\nn_epochs = 10\nsample_interval = 1\n\noptimizer_G = optim.Adam(\n    list(G_A2B.parameters()) + list(G_B2A.parameters()),\n    lr=lr, betas=(beta1, beta2)\n)\noptimizer_D_A = optim.Adam(D_A.parameters(), lr=lr, betas=(beta1, beta2))\noptimizer_D_B = optim.Adam(D_B.parameters(), lr=lr, betas=(beta1, beta2))\n\n# --------------------------\n# 5. DataLoaders\n# --------------------------\n\nmonet_dataset = GANImageDataset(\n    image_paths=unpaired_monet,\n    image_size=IMAGE_SIZE,\n    augment=True,\n    normalize_range=[-1, 1]\n)\nphoto_dataset = GANImageDataset(\n    image_paths=unpaired_photo,\n    image_size=IMAGE_SIZE,\n    augment=True,\n    normalize_range=[-1, 1]\n)\nmonet_loader = DataLoader(monet_dataset, batch_size=BATCH_SIZE, shuffle=True, drop_last=True, num_workers=2)\nphoto_loader = DataLoader(photo_dataset, batch_size=BATCH_SIZE, shuffle=True, drop_last=True, num_workers=2)\n\n# --------------------------\n# 6. Training Loop\n# --------------------------\n\ndef denorm(x):\n    # Convert [-1,1] to [0,1] for visualization\n    return (x + 1) / 2\n\nos.makedirs(\"generated_samples\", exist_ok=True)\n\nprint(\"Starting CycleGAN training...\")\nfor epoch in range(1, n_epochs + 1):\n    G_losses, D_A_losses, D_B_losses, cycle_losses = [], [],","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Evaluation\n\nEvaluate the quality of generated images using appropriate metrics and visual inspection.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_7_START===\")\n\nprint(\"===ALEXANDRIA_CELL_7_START===\")\n\n# ==========================\n# Evaluation: GAN Image Quality Assessment\n# ==========================\n\nimport os\nimport numpy as np\nimport torch\nimport torchvision\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom torchvision import transforms\nfrom tqdm import tqdm\n\n# Use prior variables\n# - G_A2B: trained generator (Photo -> Monet)\n# - photo_loader: DataLoader for photo images\n# - monet_images: list of real Monet image paths\n# - IMAGE_SIZE, DEVICE: config\n\n# --------------------------\n# 1. Generate Monet-style images from trained GAN\n# --------------------------\n\ndef generate_monet_images(generator, photo_loader, num_images=32):\n    generator.eval()\n    generated_images = []\n    with torch.no_grad():\n        for batch in photo_loader:\n            batch = batch.to(DEVICE)\n            fake_monet = generator(batch)\n            generated_images.append(fake_monet.cpu())\n            if len(generated_images) * batch.size(0) >= num_images:\n                break\n    generated_images = torch.cat(generated_images, dim=0)[:num_images]\n    return generated_images\n\nprint(\"Generating Monet-style images using trained GAN...\")\nnum_eval_images = 32\ngenerated_monet_images = generate_monet_images(G_A2B, photo_loader, num_images=num_eval_images)\nprint(f\"Generated {generated_monet_images.shape} Monet-style images.\")\n\n# --------------------------\n# 2. Prepare real Monet images for comparison\n# --------------------------\n\ndef load_real_monet_images(image_paths, num_images=32, image_size=256):\n    transform = transforms.Compose([\n        transforms.Resize((image_size, image_size)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5])\n    ])\n    images = []\n    for img_path in image_paths[:num_images]:\n        try:\n            img = Image.open(img_path).convert(\"RGB\")\n            img = transform(img)\n            images.append(img)\n        except Exception as e:\n            print(f\"Error loading real Monet image {img_path}: {e}\")\n    images = torch.stack(images, dim=0)\n    return images\n\nprint(\"Loading real Monet images for evaluation...\")\nreal_monet_images = load_real_monet_images(monet_images, num_images=num_eval_images, image_size=IMAGE_SIZE)\nprint(f\"Loaded {real_monet_images.shape} real Monet images.\")\n\n# --------------------------\n# 3. Compute FID and Inception Score\n# --------------------------\n\nfrom torchvision.models import inception_v3\n\ndef get_inception_features(images, batch_size=16, device=DEVICE):\n    # images: torch.Tensor [N, 3, H, W], normalized to [-1,1]\n    model = inception_v3(pretrained=True, transform_input=False).to(device)\n    model.eval()\n    features = []\n    upsample = torch.nn.Upsample(size=(299, 299), mode='bilinear', align_corners=False)\n    with torch.no_grad():\n        for i in range(0, images.size(0), batch_size):\n            batch = images[i:i+batch_size].to(device)\n            batch = upsample((batch + 1) / 2)  # Convert to [0,1] and resize\n            pred = model(batch)\n            features.append(pred.cpu().numpy())\n    features = np.concatenate(features, axis=0)\n    return features\n\ndef calculate_fid(mu1, sigma1, mu2, sigma2):\n    # Fréchet Inception Distance\n    diff = mu1 - mu2\n    covmean, _ = np.linalg.sqrtm(sigma1 @ sigma2, disp=False)\n    if np.iscomplexobj(covmean):\n        covmean = covmean.real\n    fid = diff.dot(diff) + np.trace(sigma1 + sigma2 - 2 * covmean)\n    return float(fid)\n\nprint(\"Extracting Inception features for FID/IS computation...\")\ntry:\n    # Get features for real and generated images\n    real_features = get_inception_features(real_monet_images)\n    gen_features = get_inception_features(generated_monet_images)\n    # Compute mean and covariance\n    mu_real, sigma_real = real_features.mean(axis=0), np.cov(real_features, rowvar=False)\n    mu_gen, sigma_gen = gen_features.mean(axis=0), np.cov(gen_features, rowvar=False)\n    # FID\n    fid_score = calculate_fid(mu_real, sigma_real, mu_gen, sigma_gen)\n    print(f\"Fréchet Inception Distance (FID): {fid_score:.2f}\")\nexcept Exception as e:\n    print(f\"Error computing FID: {e}\")\n\n# Inception Score (IS)\ndef inception_score(images, splits=10):\n    # images: torch.Tensor [N, 3, H, W], normalized to [-1,1]\n    model = inception_v3(pretrained=True, transform_input=False).to(DEVICE)\n    model.eval()\n    upsample = torch.nn.Upsample(size=(299, 299), mode='bilinear', align_corners=False)\n    preds = []\n    with torch.no_grad():\n        for i in range(0, images.size(0), 16):\n            batch = images[i:i+16].to(DEVICE)\n            batch = upsample((batch + 1) / 2)\n            pred = torch.nn.functional.softmax(model(batch), dim=1)\n            preds.append(pred.cpu().numpy())\n    preds = np.concatenate(preds, axis=0)\n    split_scores = []\n    N = preds.shape\n    for k in range(splits):\n        part = preds[k * (N // splits): (k+1) * (N // splits), :]\n        py = np.mean(part, axis=0)\n        scores = []\n        for i in range(part.shape):\n            pyx = part[i, :]\n            scores.append(np.sum(pyx * (np.log(pyx + 1e-8) - np.log(py + 1e-8))))\n        split_scores.append(np.exp(np.mean(scores)))\n    return float(np.mean(split_scores)), float(np.std(split_scores))\n\nprint(\"Computing Inception Score (IS) for generated images...\")\ntry:\n    is_score, is_std = inception_score(generated_monet_images)\n    print(f\"Inception Score (IS): {is_score:.2f} ± {is_std:.2f}\")\nexcept Exception as e:\n    print(f\"Error computing Inception Score: {e}\")\n\n# --------------------------\n# 4. Visual Inspection: Display generated vs real Monet images\n# --------------------------\n\ndef show_images_side_by_side(gen_images, real_images, n=8):\n    plt.figure(figsize=(16, 4))\n    for i in range(n):\n        # Generated\n        plt.subplot(2, n, i+1)\n        img = gen_images[i].detach().cpu()\n        img = (img + 1) / 2  # Denormalize to [0,1]\n        img = img.permute(1,2,0).numpy()\n        plt.imshow(img)\n        plt.title(f\"Generated","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Submission/Results\n\nFormat and save generated images for Kaggle submission according to competition requirements.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_8_START===\")\n\n# ==========================\n# Submission/Results: Format and Save Generated Images for Kaggle Submission\n# ==========================\n\nimport os\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nimport torch\n\n# Directory to save generated images for submission\nSUBMISSION_DIR = \"submission_images\"\nos.makedirs(SUBMISSION_DIR, exist_ok=True)\n\n# Save generated Monet-style images to SUBMISSION_DIR as per competition requirements\nprint(f\"Saving generated images to '{SUBMISSION_DIR}'...\")\n\ndef save_generated_images(images, output_dir, prefix=\"monet_\", ext=\"jpg\"):\n    saved_files = []\n    for idx, img_tensor in enumerate(images):\n        try:\n            # Denormalize from [-1,1] to [0,255]\n            img = (img_tensor.detach().cpu().numpy() + 1) / 2\n            img = np.clip(img * 255, 0, 255).astype(np.uint8)\n            img = np.transpose(img, (1, 2, 0))  # CHW -> HWC\n            img_pil = Image.fromarray(img)\n            fname = f\"{prefix}{idx+1:04d}.{ext}\"\n            fpath = os.path.join(output_dir, fname)\n            img_pil.save(fpath)\n            saved_files.append(fname)\n        except Exception as e:\n            print(f\"Error saving image {idx}: {e}\")\n    print(f\"Saved {len(saved_files)} images.\")\n    return saved_files\n\n# Use generated_monet_images from prior cell\nsaved_image_files = save_generated_images(generated_monet_images, SUBMISSION_DIR)\n\n# Create submission CSV as per typical image-to-image Kaggle competitions\n# Format: id, image_name\nsubmission_csv_path = \"submission.csv\"\nprint(f\"Creating submission file '{submission_csv_path}'...\")\n\ntry:\n    submission_df = pd.DataFrame({\n        \"id\": [i+1 for i in range(len(saved_image_files))],\n        \"image_name\": saved_image_files\n    })\n    submission_df.to_csv(submission_csv_path, index=False)\n    print(f\"Submission file '{submission_csv_path}' created successfully.\")\nexcept Exception as e:\n    print(f\"Error creating submission file: {e}\")\n\n# Validate submission format against sample_submission.csv if present\nsample_submission_path = os.path.join(DATA_PATH, \"sample_submission.csv\")\nif os.path.exists(sample_submission_path):\n    try:\n        sample_df = pd.read_csv(sample_submission_path)\n        if list(submission_df.columns) == list(sample_df.columns):\n            print(\"Submission format matches sample_submission.csv.\")\n        else:\n            print(\"Warning: Submission format does NOT match sample_submission.csv columns.\")\n            print(f\"Submission columns: {list(submission_df.columns)}\")\n            print(f\"Sample columns: {list(sample_df.columns)}\")\n    except Exception as e:\n        print(f\"Error validating submission format: {e}\")\nelse:\n    print(\"No sample_submission.csv found in DATA_PATH for format validation.\")\n\n# Optionally, create a ZIP file for image submission if required\nimport zipfile\n\nzip_path = \"submission_images.zip\"\nprint(f\"Creating ZIP archive '{zip_path}' for submission images...\")\ntry:\n    with zipfile.ZipFile(zip_path, \"w\") as zipf:\n        for fname in saved_image_files:\n            fpath = os.path.join(SUBMISSION_DIR, fname)\n            zipf.write(fpath, arcname=fname)\n    print(f\"ZIP archive '{zip_path}' created successfully.\")\nexcept Exception as e:\n    print(f\"Error creating ZIP archive: {e}\")\n\n# Display summary statistics for submission\nprint(\"\\n=== Submission Summary ===\")\nprint(f\"Total generated images: {len(saved_image_files)}\")\nprint(f\"Submission CSV: {submission_csv_path}\")\nprint(f\"Submission images directory: {SUBMISSION_DIR}\")\nprint(f\"Submission ZIP: {zip_path}\")\n\n# If leaderboard results are available, display them (Kaggle will show after submission)\nprint(\"To submit, go to the Notebook Output tab, select your submission file, and click 'Submit'.\")","outputs":[],"metadata":{},"execution_count":null}],"nbformat":4,"nbformat_minor":4}