{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":31013,"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\n# for 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":"2025-04-20T20:56:13.108542Z","iopub.execute_input":"2025-04-20T20:56:13.108775Z","iopub.status.idle":"2025-04-20T20:56:13.414604Z","shell.execute_reply.started":"2025-04-20T20:56:13.108757Z","shell.execute_reply":"2025-04-20T20:56:13.413877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras import layers, Model\nimport os\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:13.415436Z","iopub.execute_input":"2025-04-20T20:56:13.415719Z","iopub.status.idle":"2025-04-20T20:56:16.548908Z","shell.execute_reply.started":"2025-04-20T20:56:13.415700Z","shell.execute_reply":"2025-04-20T20:56:16.548322Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Problem description\nHere we have a normal set of images, where we need to create monet style replica of these images. Goal is to build GAN architecture model to create monet style images. GAN has generator & discriminator. Generator will be generating images & try to fool discriminator with fake images, where as the goal of discriminator is to find the real ones & fake ones. We have 7k+ images in the photo_jpg folder, which we will be using to create monet style images.\n\nInput files\nmonet_jpg - 300 Monet paintings sized 256x256 in JPEG format\nmonet_tfrec - 300 Monet paintings sized 256x256 in TFRecord format\nphoto_jpg - 7028 photos sized 256x256 in JPEG format\nphoto_tfrec - 7028 photos sized 256x256 in TFRecord format\n\nFinally, submissions are evaluated on MiFID (Memorization-informed Fréchet Inception Distance), which is a modification from Fréchet Inception Distance (FID)","metadata":{}},{"cell_type":"markdown","source":"# EDA\nI try loading the files to the monet & photo datasets. I see that each of the images were in 256x256x3 dimension. I tried printing the real & monet images below & see that the monet images doesn't have sharper tone & they look more softer than the real images, looking like the multiple average of 4 pixels filter from the real images would make it closer to the monet images style.","metadata":{}},{"cell_type":"code","source":"GCS_PATH = '/kaggle/input/gan-getting-started'\n\nMONET_FILENAMES = tf.io.gfile.glob(str(GCS_PATH + '/monet_tfrec/*.tfrec'))\nPHOTO_FILENAMES = tf.io.gfile.glob(str(GCS_PATH + '/photo_tfrec/*.tfrec'))\n\ndef image_decoder(image):\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = (tf.cast(image, tf.float32) * 2 / 255) - 1  # Normalize to [-1, 1]\n    return tf.reshape(image, [256, 256, 3]) # given shape is 256 x 256, so this would be reduncant mostly.\n\ndef read_tfrecord(example):\n    tfrecord_format = {\n        \"image_name\": tf.io.FixedLenFeature([], tf.string),\n        \"image\": tf.io.FixedLenFeature([], tf.string)\n    }\n    example = tf.io.parse_single_example(example, tfrecord_format)\n    return image_decoder(example['image'])\n\ndef load_dataset_from_filenames(filenames):\n    dataset = tf.data.TFRecordDataset(filenames)\n    return dataset.map(read_tfrecord, num_parallel_calls=tf.data.AUTOTUNE)\n\nmonet_ds = load_dataset_from_filenames(MONET_FILENAMES).batch(1)\nphoto_ds = load_dataset_from_filenames(PHOTO_FILENAMES).batch(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:16.549778Z","iopub.execute_input":"2025-04-20T20:56:16.550775Z","iopub.status.idle":"2025-04-20T20:56:16.918989Z","shell.execute_reply.started":"2025-04-20T20:56:16.550746Z","shell.execute_reply":"2025-04-20T20:56:16.918099Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MONET_FILENAMES","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:16.919823Z","iopub.execute_input":"2025-04-20T20:56:16.920102Z","iopub.status.idle":"2025-04-20T20:56:16.926223Z","shell.execute_reply.started":"2025-04-20T20:56:16.920081Z","shell.execute_reply":"2025-04-20T20:56:16.925442Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Visualizations\nI have printed couple of real & monet style images to compare.","metadata":{}},{"cell_type":"code","source":"def denormalize_image(image):\n    # Convert from [-1, 1] to [0, 1] for Matplotlib\n    image = (image + 1) / 2 \n    return np.clip(image, 0, 1) \n\n# Function to display sample images from a dataset\ndef display_sample_images(dataset, title, num_images=2):\n    plt.figure(figsize=(10, 5))\n    plt.suptitle(title, fontsize=14)\n    \n    # Extract num_images from the dataset\n    for i, image in enumerate(dataset.take(num_images)):\n        # Convert tensor to numpy and remove batch dimension\n        image = image.numpy()[0]  # Shape: [1, 256, 256, 3] -> [256, 256, 3]\n        image = denormalize_image(image)\n        \n        # Plot image\n        plt.subplot(1, num_images, i + 1)\n        plt.imshow(image)\n        plt.axis('off')\n        plt.title(f'{title} {i+1}')\n    \n    plt.tight_layout()\n    plt.show()\n\n# Display 2 images from each dataset\ndisplay_sample_images(monet_ds, \"Monet Painting\", num_images=2)\ndisplay_sample_images(photo_ds, \"Real Photo\", num_images=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:16.926903Z","iopub.execute_input":"2025-04-20T20:56:16.927105Z","iopub.status.idle":"2025-04-20T20:56:17.791120Z","shell.execute_reply.started":"2025-04-20T20:56:16.927087Z","shell.execute_reply":"2025-04-20T20:56:17.790028Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Plan of analysis\nI'm going to normalize the images to -1 to 1, making it cleaner dataset. I'll be using GAN architecture to monet style images. There are 2 popular GANs for this purpose, CycleGAN & StyleGAN. CycleGAN will be lightweighted, so would be using this for the project. \n\n# Model architecture\n\nGenerator: I'll use build_generator function (U-Net-like with downsampling, upsampling, and skip connections). I'll be adding multiple layers of downsampling & Upsampling with batchnormalization & using LeakyReLU making it more stable than ReLU for this purpose.\n\nDiscriminator: It's a critical component to identify the real images among fake ones which are generated by the generator.\n\nFor generator, in this case we have 5 layers of downsampling to images of size 8x8 & 4 layers of upsampling & an additional upsampling output layer with 3 filters to create the final image size of 256x256x3, which is same as our input images.\n\nFor discriminator, we have 5 downsampling layers along with batch normalizing & leakyReLU activation function & an additional output layer.","metadata":{}},{"cell_type":"code","source":"def build_generator():\n    inputs = layers.Input(shape=[256, 256, 3])\n    \n    # Downsampling layers\n    down_stack = [\n        # 256x256x3 -> 128x128x64\n        [layers.Conv2D(64, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU()],\n        # 128x128x64 -> 64x64x128\n        [layers.Conv2D(128, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU()],\n        # 64x64x128 -> 32x32x256\n        [layers.Conv2D(256, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU()],\n        # 32x32x256 -> 16x16x512\n        [layers.Conv2D(512, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU()],\n        # 16x16x512 -> 8x8x512\n        [layers.Conv2D(512, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU()]\n    ]\n    \n    # Upsampling layers\n    up_stack = [\n        # 8x8x512 -> 16x16x512\n        [layers.Conv2DTranspose(512, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU(),\n         layers.Dropout(0.5)],\n        # 16x16x512 -> 32x32x256\n        [layers.Conv2DTranspose(256, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU(),\n         layers.Dropout(0.5)],\n        # 32x32x256 -> 64x64x128\n        [layers.Conv2DTranspose(128, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU(),\n         layers.Dropout(0.5)],\n        # 64x64x128 -> 128x128x64\n        [layers.Conv2DTranspose(64, 4, strides=2, padding='same'),\n         layers.BatchNormalization(momentum=0.8),\n         layers.LeakyReLU(),\n         layers.Dropout(0.5)]\n    ]\n    \n    # Skip connections storage\n    skips = []\n    x = inputs\n    \n    # Downsampling and store skip connections\n    for block in down_stack:\n        for layer in block:\n            x = layer(x)\n        skips.append(x)  # Store after each block (after ReLU)\n    \n    # Exclude the last downsampling output (16x16x512)\n    skips = skips[:-1]\n    \n    # Reverse skips for upsampling\n    skips = reversed(skips)\n    \n    # Upsampling with skip connections\n    for block, skip in zip(up_stack, skips):\n        for layer in block:\n            x = layer(x)\n        x = layers.Concatenate()([x, skip])  # Add skip connection\n    \n    # The final Output layer: 128x128x128 -> 256x256x3\n    outputs = layers.Conv2DTranspose(3, 4, strides=2, padding='same', activation='tanh')(x)\n    \n    return Model(inputs=inputs, outputs=outputs)\n\nmonet_generator = build_generator()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:17.793127Z","iopub.execute_input":"2025-04-20T20:56:17.793357Z","iopub.status.idle":"2025-04-20T20:56:18.704996Z","shell.execute_reply.started":"2025-04-20T20:56:17.793339Z","shell.execute_reply":"2025-04-20T20:56:18.704174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"monet_generator.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:18.705823Z","iopub.execute_input":"2025-04-20T20:56:18.706086Z","iopub.status.idle":"2025-04-20T20:56:18.746615Z","shell.execute_reply.started":"2025-04-20T20:56:18.706062Z","shell.execute_reply":"2025-04-20T20:56:18.745786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_discriminator():\n    inputs = layers.Input(shape=[256, 256, 3])\n    \n    # Downsampling layers\n    x = layers.Conv2D(64, 4, strides=2, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02))(inputs)  # 256x256x3 -> 128x128x64\n    x = layers.LeakyReLU(alpha=0.2)(x)\n    \n    x = layers.Conv2D(128, 4, strides=2, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02))(x)  # 128x128x64 -> 64x64x128\n    x = layers.BatchNormalization(momentum=0.8)(x)\n    x = layers.LeakyReLU(alpha=0.2)(x)\n    \n    x = layers.Conv2D(256, 4, strides=2, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02))(x)  # 64x64x128 -> 32x32x256\n    x = layers.BatchNormalization(momentum=0.8)(x)\n    x = layers.LeakyReLU(alpha=0.2)(x)\n    \n    x = layers.Conv2D(512, 4, strides=1, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02))(x)  # 32x32x256 -> 32x32x512\n    x = layers.BatchNormalization(momentum=0.8)(x)\n    x = layers.LeakyReLU(alpha=0.2)(x)\n    x = layers.Dropout(0.3)(x)\n    \n    x = layers.Conv2D(512, 4, strides=1, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02))(x)  # 32x32x512 -> 32x32x512\n    x = layers.BatchNormalization(momentum=0.8)(x)\n    x = layers.LeakyReLU(alpha=0.2)(x)\n    x = layers.Dropout(0.3)(x)\n    \n    # Output layer\n    outputs = layers.Conv2D(1, 4, strides=1, padding='same')(x)\n    \n    return Model(inputs=inputs, outputs=outputs)\n\nmonet_discriminator = build_discriminator()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:18.747331Z","iopub.execute_input":"2025-04-20T20:56:18.747555Z","iopub.status.idle":"2025-04-20T20:56:18.833325Z","shell.execute_reply.started":"2025-04-20T20:56:18.747537Z","shell.execute_reply":"2025-04-20T20:56:18.832433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"monet_discriminator.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:18.834112Z","iopub.execute_input":"2025-04-20T20:56:18.834312Z","iopub.status.idle":"2025-04-20T20:56:18.855980Z","shell.execute_reply.started":"2025-04-20T20:56:18.834296Z","shell.execute_reply":"2025-04-20T20:56:18.855251Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"photo_generator = build_generator()\nphoto_discriminator = build_discriminator()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:18.856699Z","iopub.execute_input":"2025-04-20T20:56:18.856913Z","iopub.status.idle":"2025-04-20T20:56:19.061277Z","shell.execute_reply.started":"2025-04-20T20:56:18.856898Z","shell.execute_reply":"2025-04-20T20:56:19.060725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Use Binary Cross Entropy for adversarial loss\n# from_logits=True because the discriminator output is raw logits (no sigmoid)\nbce = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n\ndef discriminator_loss(real, generated):\n    real_loss = bce(tf.ones_like(real), real)       # Target is 1 for real images\n    generated_loss = bce(tf.zeros_like(generated), generated) # Target is 0 for fake images\n    total_disc_loss = real_loss + generated_loss\n    return total_disc_loss * 0.5 # Scale down the loss (common practice)\n\ndef generator_loss(generated):\n    # The generator wants the discriminator to think the fake image is real (target is 1)\n    return bce(tf.ones_like(generated), generated)\n\n# Use Mean Absolute Error (L1 Loss) for cycle and identity losses\nmae = tf.keras.losses.MeanAbsoluteError()\nLAMBDA_CYCLE = 10.0  # Weight for cycle consistency loss\nLAMBDA_ID = 0.5 * LAMBDA_CYCLE # Weight for identity loss (often 0.5 * lambda_cycle)\n\ndef calc_cycle_loss(real_image, cycled_image):\n    loss = mae(real_image, cycled_image)\n    return LAMBDA_CYCLE * loss\n\ndef calc_identity_loss(real_image, same_image):\n    loss = mae(real_image, same_image)\n    return LAMBDA_ID * loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:56:19.066588Z","iopub.execute_input":"2025-04-20T20:56:19.066795Z","iopub.status.idle":"2025-04-20T20:56:19.084434Z","shell.execute_reply.started":"2025-04-20T20:56:19.066778Z","shell.execute_reply":"2025-04-20T20:56:19.083831Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define optimizers (Adam is commonly used for CycleGAN)\ngenerator_m_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5) # For monet_generator\ngenerator_p_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5) # For photo_generator \n\ndiscriminator_m_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5) # For monet_discriminator \ndiscriminator_p_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5) # For photo_discriminator","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T22:09:44.380241Z","iopub.execute_input":"2025-04-20T22:09:44.380531Z","iopub.status.idle":"2025-04-20T22:09:44.392809Z","shell.execute_reply.started":"2025-04-20T22:09:44.380509Z","shell.execute_reply":"2025-04-20T22:09:44.392013Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@tf.function\ndef train_step(real_photo, real_monet):\n    # persistent=True allows accessing gradients from the tape multiple times\n    with tf.GradientTape(persistent=True) as tape:\n        # --- Generator G (Photo -> Monet) and F (Monet -> Photo) ---\n        fake_monet = monet_generator(real_photo, training=True)\n        cycled_photo = photo_generator(fake_monet, training=True)\n\n        fake_photo = photo_generator(real_monet, training=True)\n        cycled_monet = monet_generator(fake_photo, training=True)\n\n        # Identity mapping (optional but recommended)\n        same_monet = monet_generator(real_monet, training=True)\n        same_photo = photo_generator(real_photo, training=True)\n\n        # --- Discriminator Dx (Monet) and Dy (Photo) ---\n        disc_real_monet = monet_discriminator(real_monet, training=True)\n        disc_fake_monet = monet_discriminator(fake_monet, training=True)\n\n        disc_real_photo = photo_discriminator(real_photo, training=True)\n        disc_fake_photo = photo_discriminator(fake_photo, training=True)\n\n        # --- Calculate Losses ---\n        # Generator Adversarial Loss\n        gen_m_loss = generator_loss(disc_fake_monet) # G wants Dx to think fake_monet is real\n        gen_p_loss = generator_loss(disc_fake_photo) # F wants Dy to think fake_photo is real\n\n        # Cycle Consistency Loss\n        total_cycle_loss = calc_cycle_loss(real_photo, cycled_photo) + calc_cycle_loss(real_monet, cycled_monet)\n\n        # Total Generator Loss (adversarial + cycle + identity)\n        total_gen_m_loss = gen_m_loss + total_cycle_loss + calc_identity_loss(real_monet, same_monet)\n        total_gen_p_loss = gen_p_loss + total_cycle_loss + calc_identity_loss(real_photo, same_photo)\n\n        # Discriminator Loss\n        disc_m_loss = discriminator_loss(disc_real_monet, disc_fake_monet) # Dx loss\n        disc_p_loss = discriminator_loss(disc_real_photo, disc_fake_photo) # Dy loss\n\n    # --- Calculate Gradients ---\n    # Generator gradients\n    generator_m_gradients = tape.gradient(total_gen_m_loss, monet_generator.trainable_variables)\n    generator_p_gradients = tape.gradient(total_gen_p_loss, photo_generator.trainable_variables)\n\n    # Discriminator gradients\n    discriminator_m_gradients = tape.gradient(disc_m_loss, monet_discriminator.trainable_variables)\n    discriminator_p_gradients = tape.gradient(disc_p_loss, photo_discriminator.trainable_variables)\n\n    # --- Apply Gradients ---\n    generator_m_optimizer.apply_gradients(zip(generator_m_gradients, monet_generator.trainable_variables))\n    generator_p_optimizer.apply_gradients(zip(generator_p_gradients, photo_generator.trainable_variables))\n\n    discriminator_m_optimizer.apply_gradients(zip(discriminator_m_gradients, monet_discriminator.trainable_variables))\n    discriminator_p_optimizer.apply_gradients(zip(discriminator_p_gradients, photo_discriminator.trainable_variables))\n\n    return {\n        \"gen_m_loss\": total_gen_m_loss,\n        \"gen_p_loss\": total_gen_p_loss,\n        \"disc_m_loss\": disc_m_loss,\n        \"disc_p_loss\": disc_p_loss,\n        \"cycle_loss\": total_cycle_loss,\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T22:09:47.850111Z","iopub.execute_input":"2025-04-20T22:09:47.850598Z","iopub.status.idle":"2025-04-20T22:09:47.858637Z","shell.execute_reply.started":"2025-04-20T22:09:47.850576Z","shell.execute_reply":"2025-04-20T22:09:47.857730Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\n\nEPOCHS = 10\ngen_m_losses = []\ngen_p_losses = []\ndisc_m_losses = []\ndisc_p_losses = []\ncycle_losses = []\n\ndef generate_and_save_images(model, epoch, test_input, save_path):\n    # Make sure the input is a batch\n    if test_input.shape.ndims == 3:\n        test_input = tf.expand_dims(test_input, 0) # Add batch dimension if missing\n\n    prediction = model(test_input, training=False) # Get prediction in [-1, 1] range\n\n    # Denormalize for display/saving\n    display_input = denormalize_image(test_input[0]) # Use [0] to get the single image from batch\n    display_prediction = denormalize_image(prediction[0]) # Use [0] to get the single image from batch\n\n    # --- Save the side-by-side comparison \n    plt.figure(figsize=(8, 4))\n    plt.subplot(1, 2, 1)\n    plt.title('Input Photo')\n    plt.imshow(display_input)\n    plt.axis('off')\n    plt.subplot(1, 2, 2)\n    plt.title(f'Monet Style (Epoch {epoch+1})')\n    plt.imshow(display_prediction)\n    plt.axis('off')\n    comparison_filename = f'{save_path}/comparison_epoch_{epoch+1:04d}.png'\n    plt.savefig(comparison_filename)\n    plt.show() # Usually commented out in Kaggle notebooks\n    plt.close()\n\n# Get a sample photo to monitor progress\nsample_photo = next(iter(photo_ds.take(1))) # Take one batch (size 1)\n\n# Create directory for saving sample images\nimport os\nsave_dir = './training_images'\nif not os.path.exists(save_dir):\n    os.makedirs(save_dir)\n\nprint(\"Starting Training Loop...\")\nfor epoch in range(EPOCHS):\n    start = time.time()\n    n = 0\n    print(f\"Epoch {epoch + 1}/{EPOCHS}\")\n\n    # Use zip to iterate through both datasets simultaneously\n    for image_photo, image_monet in tf.data.Dataset.zip((photo_ds, monet_ds)):\n        losses = train_step(image_photo, image_monet)\n\n    # Generate and save a sample image after each epoch\n    print() # Newline after dots\n    generate_and_save_images(monet_generator, epoch, sample_photo, save_dir)\n\n    gen_m_losses.append(losses['gen_m_loss'].numpy())\n    gen_p_losses.append(losses['gen_p_loss'].numpy())\n    disc_m_losses.append(losses['disc_m_loss'].numpy())\n    disc_p_losses.append(losses['disc_p_loss'].numpy())\n    cycle_losses.append(losses['cycle_loss'].numpy())\n    print (f'Time taken for epoch {epoch + 1} is {time.time()-start:.2f} sec\\n')\n    # Optional: Print loss values from the last step\n    print(f\"  Losses: Gen_G={losses['gen_m_loss']:.4f}, Gen_F={losses['gen_p_loss']:.4f}, Disc_X={losses['disc_m_loss']:.4f}, Disc_Y={losses['disc_p_loss']:.4f}, Cycle_loss={losses['cycle_loss']:.4f}\")\n\nprint(\"Training Finished.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T22:09:52.078366Z","iopub.execute_input":"2025-04-20T22:09:52.079120Z","iopub.status.idle":"2025-04-20T22:19:34.234013Z","shell.execute_reply.started":"2025-04-20T22:09:52.079077Z","shell.execute_reply":"2025-04-20T22:19:34.233220Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"epochs = range(1, 11)\n\nplt.figure(figsize=(10, 6))\n\nplt.plot(epochs, gen_m_losses, label='Generator M Loss (Photo→Monet)')\nplt.plot(epochs, gen_p_losses, label='Generator P Loss (Monet→Photo)')\nplt.plot(epochs, disc_m_losses, label='Discriminator M Loss (Monet)')\nplt.plot(epochs, disc_p_losses, label='Discriminator P Loss (Photo)')\nplt.plot(epochs, cycle_losses, label='Cycle Consistency Loss')\n\nplt.title(\"GAN Training Losses vs each Epochs\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Losses\")\nplt.grid(True)\nplt.legend()\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T22:26:12.373589Z","iopub.execute_input":"2025-04-20T22:26:12.374263Z","iopub.status.idle":"2025-04-20T22:26:12.605160Z","shell.execute_reply.started":"2025-04-20T22:26:12.374237Z","shell.execute_reply":"2025-04-20T22:26:12.604476Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Results & analysis:\nWith CycleGAN model I'm able to translate real-world photographs into the artistic style of Claude Monet paintings. Initially used few layers in downsampling & upsampling & later added more layer, have seen better improvement of the results with the more layers. I didn't see much of improvement in losses with epochs above, only discriminatory losses were consistent. \n\n# Conclusion & takeaways:\nCould experiment with more epochs to see the better performance & add more layers to have improved prediction & can change the activation function to ReLU or some other to see how the model is performing","metadata":{}},{"cell_type":"code","source":"os.makedirs('../images', exist_ok=True)\nfor i, img in enumerate(photo_ds):\n    prediction = monet_generator(img, training=False)[0].numpy()\n    prediction = (prediction * 127.5 + 127.5).astype(np.uint8)\n    plt.imsave(f'../images/image_{i:04d}.jpg', prediction)\nimport shutil\nshutil.make_archive('/kaggle/working/images', 'zip', '../images')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T20:59:43.625196Z","iopub.status.idle":"2025-04-20T20:59:43.625443Z","shell.execute_reply.started":"2025-04-20T20:59:43.625331Z","shell.execute_reply":"2025-04-20T20:59:43.625342Z"}},"outputs":[],"execution_count":null}]}