{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# TinyCycleGAN: Monet Style Transfer with Small Images","metadata":{}},{"cell_type":"markdown","source":"<img src=\"https://user-images.githubusercontent.com/83595196/226116676-13b0d8eb-99b8-4b3a-aab7-e305dba669c6.png\">","metadata":{}},{"cell_type":"markdown","source":"### 🎯 Project Objective\n\nThe goal of this project is to translate real-world photographs into\nClaude Monet–style paintings using an unpaired image-to-image\ntranslation approach.\n\nThis notebook solves the Kaggle competition\n**\"I'm Something of a Painter Myself\"** by training a lightweight\nCycleGAN model and exporting generated Monet-style images\nin the required submission format.\n","metadata":{}},{"cell_type":"markdown","source":"### 🗂️ Exploratory Data Analysis\n\nIn this section, we:\n- Inspect the dataset structure\n- Count available Monet paintings and photographs\n- Visualize random samples from both domains\n","metadata":{}},{"cell_type":"code","source":"import os, glob, random\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:10:54.926110Z","iopub.execute_input":"2026-02-09T07:10:54.926551Z","iopub.status.idle":"2026-02-09T07:10:54.932174Z","shell.execute_reply.started":"2026-02-09T07:10:54.926519Z","shell.execute_reply":"2026-02-09T07:10:54.931617Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(os.listdir(\"/kaggle/input\"))\n\nBASE = \"/kaggle/input/gan-getting-started\"\nMONET_DIR = os.path.join(BASE, \"monet_jpg\")\nPHOTO_DIR = os.path.join(BASE, \"photo_jpg\")\n\nmonet_files = sorted(glob.glob(MONET_DIR + \"/*.jpg\"))\nphoto_files = sorted(glob.glob(PHOTO_DIR + \"/*.jpg\"))\n\nprint(\"Monet images:\", len(monet_files))\nprint(\"Photo images:\", len(photo_files))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:10:54.933387Z","iopub.execute_input":"2026-02-09T07:10:54.933588Z","iopub.status.idle":"2026-02-09T07:10:55.045292Z","shell.execute_reply.started":"2026-02-09T07:10:54.933569Z","shell.execute_reply":"2026-02-09T07:10:55.044684Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_random(files, title, n=6):\n    pick = random.sample(files, n)\n    plt.figure(figsize=(10, 4))\n    for i, f in enumerate(pick):\n        img = Image.open(f)\n        plt.subplot(2, 3, i + 1)\n        plt.imshow(img)\n        plt.axis(\"off\")\n    plt.suptitle(title)\n    plt.tight_layout()\n    plt.show()\n\nshow_random(monet_files, \"Random Monet Paintings\")\nshow_random(photo_files, \"Random Photos\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:10:55.046133Z","iopub.execute_input":"2026-02-09T07:10:55.046407Z","iopub.status.idle":"2026-02-09T07:10:55.574250Z","shell.execute_reply.started":"2026-02-09T07:10:55.046387Z","shell.execute_reply":"2026-02-09T07:10:55.573541Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🧪 Data Processing & Preparation\n\nImages are:\n- Resized to a small resolution for fast experimentation\n- Normalized to the range [-1, 1]\n- Loaded using the tf.data pipeline\n","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\n\nIMG_SIZE = 128\nBATCH_SIZE = 1\n\ndef load_image(path):\n    img = tf.io.read_file(path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = (img / 127.5) - 1.0\n    return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:10:55.575116Z","iopub.execute_input":"2026-02-09T07:10:55.575401Z","iopub.status.idle":"2026-02-09T07:11:09.934136Z","shell.execute_reply.started":"2026-02-09T07:10:55.575369Z","shell.execute_reply":"2026-02-09T07:11:09.933542Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"monet_ds = tf.data.Dataset.from_tensor_slices(monet_files)\\\n    .shuffle(512)\\\n    .map(load_image)\\\n    .batch(BATCH_SIZE)\n\nphoto_ds = tf.data.Dataset.from_tensor_slices(photo_files)\\\n    .shuffle(512)\\\n    .map(load_image)\\\n    .batch(BATCH_SIZE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:11:09.935174Z","iopub.execute_input":"2026-02-09T07:11:09.935775Z","iopub.status.idle":"2026-02-09T07:11:11.205899Z","shell.execute_reply.started":"2026-02-09T07:11:09.935742Z","shell.execute_reply":"2026-02-09T07:11:11.205277Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🧠Model Architecture & Training\n\nWe use a lightweight CycleGAN architecture consisting of:\n- Two generators (Photo → Monet, Monet → Photo)\n- Two discriminators (Monet, Photo)\n\nTraining uses:\n- Adversarial loss\n- Cycle consistency loss\n- Separate optimizers to avoid variable conflicts\n","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import layers\n\ndef build_generator():\n    inp = layers.Input((IMG_SIZE, IMG_SIZE, 3))\n    x = layers.Conv2D(64, 7, padding=\"same\")(inp)\n    x = layers.ReLU()(x)\n    x = layers.Conv2D(128, 3, strides=2, padding=\"same\")(x)\n    x = layers.ReLU()(x)\n    x = layers.Conv2DTranspose(64, 3, strides=2, padding=\"same\")(x)\n    out = layers.Conv2D(3, 7, padding=\"same\", activation=\"tanh\")(x)\n    return tf.keras.Model(inp, out)\n\ndef build_discriminator():\n    inp = layers.Input((IMG_SIZE, IMG_SIZE, 3))\n    x = layers.Conv2D(64, 4, strides=2, padding=\"same\")(inp)\n    x = layers.LeakyReLU(0.2)(x)\n    out = layers.Conv2D(1, 4, padding=\"same\")(x)\n    return tf.keras.Model(inp, out)\n\nG_photo2monet = build_generator()\nG_monet2photo = build_generator()\nD_monet = build_discriminator()\nD_photo = build_discriminator()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:11:11.206831Z","iopub.execute_input":"2026-02-09T07:11:11.207363Z","iopub.status.idle":"2026-02-09T07:11:12.493048Z","shell.execute_reply.started":"2026-02-09T07:11:11.207338Z","shell.execute_reply":"2026-02-09T07:11:12.492505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"bce = tf.keras.losses.BinaryCrossentropy(from_logits=True)\nLAMBDA = 10.0\n\ng_opt  = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ndm_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ndp_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:11:12.495073Z","iopub.execute_input":"2026-02-09T07:11:12.495369Z","iopub.status.idle":"2026-02-09T07:11:12.507528Z","shell.execute_reply.started":"2026-02-09T07:11:12.495348Z","shell.execute_reply":"2026-02-09T07:11:12.506968Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 3\nSTEPS = 100\n\nfor epoch in range(EPOCHS):\n    monet_it = iter(monet_ds.repeat())\n    photo_it = iter(photo_ds.repeat())\n\n    for step in range(STEPS):\n        monet = next(monet_it)\n        photo = next(photo_it)\n\n        with tf.GradientTape(persistent=True) as tape:\n            fake_monet = G_photo2monet(photo, training=True)\n            fake_photo = G_monet2photo(monet, training=True)\n\n            cycle_photo = G_monet2photo(fake_monet, training=True)\n            cycle_monet = G_photo2monet(fake_photo, training=True)\n\n            Dm_real = D_monet(monet, training=True)\n            Dm_fake = D_monet(fake_monet, training=True)\n            Dp_real = D_photo(photo, training=True)\n            Dp_fake = D_photo(fake_photo, training=True)\n\n            g_loss = (\n                bce(tf.ones_like(Dm_fake), Dm_fake) +\n                bce(tf.ones_like(Dp_fake), Dp_fake) +\n                LAMBDA * (\n                    tf.reduce_mean(tf.abs(photo - cycle_photo)) +\n                    tf.reduce_mean(tf.abs(monet - cycle_monet))\n                )\n            )\n\n            Dm_loss = (bce(tf.ones_like(Dm_real), Dm_real) +\n                       bce(tf.zeros_like(Dm_fake), Dm_fake)) * 0.5\n            Dp_loss = (bce(tf.ones_like(Dp_real), Dp_real) +\n                       bce(tf.zeros_like(Dp_fake), Dp_fake)) * 0.5\n\n        g_vars = G_photo2monet.trainable_variables + G_monet2photo.trainable_variables\n        g_opt.apply_gradients(zip(tape.gradient(g_loss, g_vars), g_vars))\n        dm_opt.apply_gradients(zip(tape.gradient(Dm_loss, D_monet.trainable_variables), D_monet.trainable_variables))\n        dp_opt.apply_gradients(zip(tape.gradient(Dp_loss, D_photo.trainable_variables), D_photo.trainable_variables))\n\n    print(f\"Epoch {epoch+1}/{EPOCHS} completed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:11:12.508294Z","iopub.execute_input":"2026-02-09T07:11:12.508575Z","iopub.status.idle":"2026-02-09T07:12:23.336879Z","shell.execute_reply.started":"2026-02-09T07:11:12.508545Z","shell.execute_reply":"2026-02-09T07:12:23.336272Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Submission Generation\n\nAll photographs are translated into Monet style and saved as JPG files.\nThe final submission is a ZIP file named `images.zip`.\n","metadata":{}},{"cell_type":"code","source":"import shutil\n\ndef denorm(x):\n    return tf.clip_by_value((x + 1.0) / 2.0, 0.0, 1.0)\n\nOUT_DIR = \"output_images\"\nos.makedirs(OUT_DIR, exist_ok=True)\n\nfor fp in photo_files:\n    img = load_image(fp)[None, ...]\n    fake = G_photo2monet(img, training=False)[0]\n    arr = (denorm(fake).numpy() * 255).astype(np.uint8)\n    Image.fromarray(arr).save(os.path.join(OUT_DIR, os.path.basename(fp)))\n\nshutil.make_archive(\"images\", \"zip\", OUT_DIR)\nprint(\"Submission ready: images.zip\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T07:12:23.337752Z","iopub.execute_input":"2026-02-09T07:12:23.337983Z","iopub.status.idle":"2026-02-09T07:14:04.859768Z","shell.execute_reply.started":"2026-02-09T07:12:23.337962Z","shell.execute_reply":"2026-02-09T07:14:04.859106Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Final Notes\n\n- This notebook follows Kaggle competition requirements\n- Uses small images for fast iteration\n- Designed for clarity and reproducibility\n- Can be extended with larger image sizes and longer training\n","metadata":{}}]}