{"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":21755,"databundleVersionId":1475600},{"sourceType":"datasetVersion","sourceId":16501995,"datasetId":10570305,"databundleVersionId":17504410}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-28T10:00:11.220751Z","iopub.execute_input":"2026-05-28T10:00:11.221021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from glob import glob\nimport os\n\n# Try standard path first, fall back to competitions path\nbase = '/kaggle/input/gan-getting-started'\nif not os.path.exists(base):\n    base = '/kaggle/input/competitions/gan-getting-started'\n\nmonet_paths = glob(f'{base}/monet_jpg/*.jpg')\nphoto_paths = glob(f'{base}/photo_jpg/*.jpg')\n\nprint(\"Monet images:\", len(monet_paths))\nprint(\"Photo images:\", len(photo_paths))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Lets see what kind of images are there in the given data","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\nimg1 = mpimg.imread(monet_paths[0])\nimg2 = mpimg.imread(photo_paths[0])\n\nplt.figure(figsize=(10,5))\n\nplt.subplot(1,2,1)\nplt.imshow(img1)\nplt.title(\"Monet\")\n\nplt.subplot(1,2,2)\nplt.imshow(img2)\nplt.title(\"Photo\")\n\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\n\nIMG_SIZE = 256\n\ndef load_image(path):\n    img = tf.io.read_file(path)                  # read jpg file\n    img = tf.image.decode_jpeg(img, channels=3)  # convert to pixels\n    img = tf.image.resize(img, [IMG_SIZE, IMG_SIZE])\n    img = (tf.cast(img, tf.float32) / 127.5) - 1 # normalize to [-1,1]\n    return img\n\nmonet_tensor = load_image(monet_paths[0])\nphoto_tensor = load_image(photo_paths[0])\n\nprint(\"Monet tensor shape:\", monet_tensor.shape)\nprint(\"Min/Max:\", tf.reduce_min(monet_tensor).numpy(),\n      tf.reduce_max(monet_tensor).numpy())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 1\n\nmonet_ds = (\n    tf.data.Dataset.from_tensor_slices(monet_paths)\n    .shuffle(len(monet_paths))\n    .map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\n    .batch(BATCH_SIZE)\n)\n\nphoto_ds = (\n    tf.data.Dataset.from_tensor_slices(photo_paths)\n    .shuffle(len(photo_paths))\n    .map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\n    .batch(BATCH_SIZE)\n)\n\nsample_batch = next(iter(monet_ds))\n\nprint(sample_batch.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"raw","source":"The residual block","metadata":{}},{"cell_type":"code","source":"def residual_block(x):\n    y = tf.keras.layers.Conv2D(256, 3, padding='same')(x)\n    y = tf.keras.layers.ReLU()(y)\n    y = tf.keras.layers.Conv2D(256, 3, padding='same')(y)\n\n    return tf.keras.layers.Add()([x, y])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Now we meet the generator for monet","metadata":{}},{"cell_type":"code","source":"def build_generator():\n    inputs = tf.keras.Input(shape=(256, 256, 3))\n\n    # Encoder\n    x = tf.keras.layers.Conv2D(64, 7, padding='same')(inputs)\n    x = tf.keras.layers.ReLU()(x)\n\n    x = tf.keras.layers.Conv2D(128, 3, strides=2, padding='same')(x)\n    x = tf.keras.layers.ReLU()(x)\n\n    x = tf.keras.layers.Conv2D(256, 3, strides=2, padding='same')(x)\n    x = tf.keras.layers.ReLU()(x)\n\n    # Residual blocks (style transformation core)\n    for _ in range(6):\n        x = residual_block(x)\n\n    # Decoder (upsampling back to image size)\n    x = tf.keras.layers.Conv2DTranspose(128, 3, strides=2, padding='same')(x)\n    x = tf.keras.layers.ReLU()(x)\n\n    x = tf.keras.layers.Conv2DTranspose(64, 3, strides=2, padding='same')(x)\n    x = tf.keras.layers.ReLU()(x)\n\n    # Output layer\n    outputs = tf.keras.layers.Conv2D(3, 7, padding='same', activation='tanh')(x)\n\n    return tf.keras.Model(inputs, outputs)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"raw","source":"Discriminator for monet","metadata":{}},{"cell_type":"code","source":"def build_discriminator():\n    inputs = tf.keras.Input(shape=(256, 256, 3))\n\n    x = tf.keras.layers.Conv2D(64, 4, strides=2, padding='same')(inputs)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n\n    x = tf.keras.layers.Conv2D(128, 4, strides=2, padding='same')(x)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n\n    x = tf.keras.layers.Conv2D(256, 4, strides=2, padding='same')(x)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n\n    x = tf.keras.layers.Conv2D(512, 4, strides=1, padding='same')(x)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n\n    # Patch output (NOT single value)\n    outputs = tf.keras.layers.Conv2D(1, 4, padding='same')(x)\n\n    return tf.keras.Model(inputs, outputs)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loss_obj = tf.keras.losses.BinaryCrossentropy(\n    from_logits=True)\n\ndef discriminator_loss(real, fake):\n    real_loss = loss_obj(\n        tf.ones_like(real), real)\n\n    fake_loss = loss_obj(\n        tf.zeros_like(fake), fake)\n\n    return real_loss + fake_loss\n\ndef generator_loss(fake):\n    return loss_obj(\n        tf.ones_like(fake), fake)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LAMBDA = 10\n\ndef cycle_loss(real, cycled):\n    return LAMBDA * tf.reduce_mean(\n        tf.abs(real - cycled))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def identity_loss(real, same):\n    return LAMBDA * 0.5 * tf.reduce_mean(\n        tf.abs(real - same))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"| Optimizer                 | Updates    |\n| ------------------------- | ---------- |\n| generator_g_optimizer     | G weights  |\n| generator_f_optimizer     | F weights  |\n| discriminator_x_optimizer | Dx weights |\n| discriminator_y_optimizer | Dy weights |","metadata":{}},{"cell_type":"code","source":"generator_g_optimizer = tf.keras.optimizers.Adam(\n    2e-4, beta_1=0.5)\n\ngenerator_f_optimizer = tf.keras.optimizers.Adam(\n    2e-4, beta_1=0.5)\n\ndiscriminator_x_optimizer = tf.keras.optimizers.Adam(\n    2e-4, beta_1=0.5)\n\ndiscriminator_y_optimizer = tf.keras.optimizers.Adam(\n    2e-4, beta_1=0.5)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generator_g = build_generator()   # Photo -> Monet\ngenerator_f = build_generator()   # Monet -> Photo\n\ndiscriminator_x = build_discriminator()  # Photo critic\ndiscriminator_y = build_discriminator()  # Monet critic","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@tf.function\ndef train_step(real_x, real_y):\n\n    with tf.GradientTape(persistent=True) as tape:\n\n        # Generate images\n        fake_y = generator_g(real_x, training=True)\n        fake_x = generator_f(real_y, training=True)\n\n        # Cycle reconstruction\n        cycled_x = generator_f(fake_y, training=True)\n        cycled_y = generator_g(fake_x, training=True)\n\n        # Identity\n        same_x = generator_f(real_x, training=True)\n        same_y = generator_g(real_y, training=True)\n\n        # Discriminator predictions\n        disc_real_x = discriminator_x(real_x, training=True)\n        disc_real_y = discriminator_y(real_y, training=True)\n\n        disc_fake_x = discriminator_x(fake_x, training=True)\n        disc_fake_y = discriminator_y(fake_y, training=True)\n\n        # Generator adversarial losses\n        gen_g_loss = generator_loss(disc_fake_y)\n        gen_f_loss = generator_loss(disc_fake_x)\n\n        # Cycle loss\n        total_cycle_loss = (\n            cycle_loss(real_x, cycled_x) +\n            cycle_loss(real_y, cycled_y)\n        )\n\n        # Total generator loss\n        total_gen_g_loss = (\n            gen_g_loss\n            + total_cycle_loss\n            + identity_loss(real_y, same_y)\n        )\n\n        total_gen_f_loss = (\n            gen_f_loss\n            + total_cycle_loss\n            + identity_loss(real_x, same_x)\n        )\n\n        # Discriminator loss\n        disc_x_loss = discriminator_loss(\n            disc_real_x, disc_fake_x)\n\n        disc_y_loss = discriminator_loss(\n            disc_real_y, disc_fake_y)\n\n    # Compute gradients\n    generator_g_gradients = tape.gradient(\n        total_gen_g_loss,\n        generator_g.trainable_variables)\n\n    generator_f_gradients = tape.gradient(\n        total_gen_f_loss,\n        generator_f.trainable_variables)\n\n    discriminator_x_gradients = tape.gradient(\n        disc_x_loss,\n        discriminator_x.trainable_variables)\n\n    discriminator_y_gradients = tape.gradient(\n        disc_y_loss,\n        discriminator_y.trainable_variables)\n\n    # Apply gradients\n    generator_g_optimizer.apply_gradients(\n        zip(generator_g_gradients,\n            generator_g.trainable_variables))\n\n    generator_f_optimizer.apply_gradients(\n        zip(generator_f_gradients,\n            generator_f.trainable_variables))\n\n    discriminator_x_optimizer.apply_gradients(\n        zip(discriminator_x_gradients,\n            discriminator_x.trainable_variables))\n\n    discriminator_y_optimizer.apply_gradients(\n        zip(discriminator_y_gradients,\n            discriminator_y.trainable_variables))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 15\n\nfor epoch in range(EPOCHS):\n    print(f\"\\nEpoch {epoch+1}/{EPOCHS}\")\n\n    step = 0\n    for image_x, image_y in tf.data.Dataset.zip((photo_ds, monet_ds)):\n        train_step(image_x, image_y)\n\n        step += 1\n        if step % 100 == 0:\n            print(f\"Step {step}\")\n\n    print(\"Done\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nfor img_batch in photo_ds.take(3):\n\n    prediction = generator_g(\n        img_batch,\n        training=False\n    )\n\n    original = img_batch[0].numpy()\n    generated = prediction[0].numpy()\n\n    # denormalize\n    original = (original + 1) / 2\n    generated = (generated + 1) / 2\n\n    plt.figure(figsize=(10,5))\n\n    plt.subplot(1,2,1)\n    plt.imshow(original)\n    plt.title(\"Original Photo\")\n    plt.axis(\"off\")\n\n    plt.subplot(1,2,2)\n    plt.imshow(generated)\n    plt.title(\"Generated Monet\")\n    plt.axis(\"off\")\n\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\nimport io\nimport numpy as np\nfrom PIL import Image\nfrom tqdm import tqdm\n\nphoto_ds_infer = (\n    tf.data.Dataset.from_tensor_slices(photo_paths)\n    .map(lambda path: (path, load_image(path)), num_parallel_calls=tf.data.AUTOTUNE)\n    .batch(1)\n)\n\nwith zipfile.ZipFile(\"images.zip\", \"w\") as img_zip:\n    for path_batch, img_batch in tqdm(photo_ds_infer.take(10000)):\n        prediction = generator_g(img_batch, training=False)\n        img = prediction[0].numpy()\n        img = ((img + 1) * 127.5).astype(np.uint8)\n        img_pil = Image.fromarray(img)\n        buffer = io.BytesIO()\n        img_pil.save(buffer, format=\"JPEG\")\n        \n        # 1. Grab the base name from the tensor just like before\n        raw_name = os.path.basename(path_batch[0].numpy().decode())\n\n# 2. Strip any existing extensions (just in case) and force .jpg\n        fname = f\"{raw_name.split('.')[0]}.jpg\"\n        img_zip.writestr(fname, buffer.getvalue())\n\n\nprint(\"images.zip created!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir())\n!find . -maxdepth 2 -type f | head -50","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\n\nwith zipfile.ZipFile(\"images.zip\", \"r\") as z:\n    print(\"Image count:\", len(z.namelist()))\n    print(\"First few:\", z.namelist()[:5])","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}