{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":30887,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# GAN Mini-Project: Monet Painting Dataset\n\n## Introduction\n\nGenerative Adversarial Networks (GANs) are a powerful deep learning approach used for generating new data based on a given dataset. In this project, we aim to apply GANs to transform photos into Monet-style paintings using the **Monet Painting Dataset** provided by Kaggle. This project provides a hands-on opportunity to explore GAN architectures and understand their capabilities in artistic image generation.\n\n## Problem Statement\n\nThe main goal of this project is to design, train, and evaluate a GAN model that can generate Monet-style paintings from input photographs. The performance of the model will be assessed using Kaggle's **Memorization-informed Fréchet Inception Distance (MiFID)** metric.\n\n## Dataset Overview\n\n- **Dataset Name:** Monet Painting Dataset\n- **Source:** [Kaggle Monet Painting Dataset](https://www.kaggle.com/competitions/gan-getting-started)\n- **Dataset Structure:**\n  - **Monet Paintings:** A collection of Monet-style artwork images.\n  - **Photos:** A set of real-world photos to be converted into Monet's painting style.\n- **Data Format:** JPEG images with uniform resolution.\n\nThis dataset provides sufficient diversity and quantity to train a robust GAN model capable of learning and replicating Monet's unique artistic style.\n\n## Objectives\n\n1. Build and train a GAN model to generate Monet-style paintings.\n2. Evaluate the model's performance using the MiFID score on Kaggle.\n3. Discuss the challenges, outcomes, and potential improvements for the model.\n\n## Deliverables\n\n1. **Jupyter Notebook:**\n   - Problem definition, dataset description, exploratory data analysis, model design, training, and discussion.\n2. **GitHub Repository:**\n   - Public repository containing all the code and relevant files.\n3. **Kaggle Leaderboard Screenshot:**\n   - Showing the MiFID score of the trained model on Kaggle.\n\n---\n\nNext steps will involve loading the dataset, performing exploratory data analysis (EDA), and designing the GAN architecture.\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"# Exploratory Data Analysis (EDA) ","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport os\nimport matplotlib\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport tensorflow as tf\nimport sklearn\nfrom tqdm import tqdm\nimport IPython","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport matplotlib.pyplot as plt\nimport glob\n\nmonet_paths = glob.glob(\"../input/gan-getting-started/monet_jpg/*.jpg\")\n\nprint(f'Total number of Monet images: {len(monet_paths)}')\n\nfig, axes = plt.subplots(4, 5, figsize=(12, 6))\n\nfor ax, img_path in zip(axes.flatten(), monet_paths[:20]):\n    img = Image.open(img_path)\n    ax.imshow(img)\n    ax.axis(\"off\")\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Extracting Basic Features of Images**\n\nPurpose: To examine basic information such as dimensions, color features, pixel distribution of images.","metadata":{}},{"cell_type":"code","source":"from PIL import Image\nimport pandas as pd\n\nimage_sizes = []\n\nfor img_path in monet_paths:\n    img = Image.open(img_path)\n    image_sizes.append(img.size)\n\ndf_sizes = pd.DataFrame(image_sizes, columns=['Width', 'Height'])\n\nprint(df_sizes.head())\n\ndf_sizes.describe()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\n\nplt.figure(figsize=(5, 5))\nsns.histplot(df_sizes['Width'], bins=20, color=\"blue\", label=\"Width\", kde=True)\nsns.histplot(df_sizes['Height'], bins=20, color=\"red\", label=\"Height\", kde=True)\nplt.legend()\nplt.title(\"Image Size Distribution\")\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Examining Color Distribution**\n\nPurpose: What are the color palettes and tones of Monet paintings? If a fake Monet is to be produced with GAN, the original Monet color distribution must be well understood.","metadata":{}},{"cell_type":"code","source":"import cv2\nimport numpy as np\n\nimg = cv2.imread(monet_paths[0])  \nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\nplt.figure(figsize=(10, 5))\ncolor = ('r', 'g', 'b')\nfor i, col in enumerate(color):\n    hist = cv2.calcHist([img], [i], None, [256], [0, 256])\n    plt.plot(hist, color=col)\n\nplt.title(\"Color Distribution (RGB Histogram)\")\nplt.xlabel(\"Color Value\")\nplt.ylabel(\"Pixel Count\")\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Understanding the Edges and Structure of Images**\n\nPurpose: To analyze the edge structure and level of detail of Monet's paintings.","metadata":{}},{"cell_type":"code","source":"import cv2\n\nimg = cv2.imread(monet_paths[0], cv2.IMREAD_GRAYSCALE)\n\nsobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)\nsobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)\n\nplt.figure(figsize=(10, 5))\nplt.subplot(1, 2, 1)\nplt.imshow(sobelx, cmap='gray')\nplt.title(\"Sobel X Edge Detection\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(sobely, cmap='gray')\nplt.title(\"Sobel Y Edge Detection\")\n\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Uniqueness of Images (Are There Very Similar or Identical Images?)**\n\nPurpose: If there are duplicate images in the dataset, let's detect this.","metadata":{}},{"cell_type":"code","source":"import cv2\nimport numpy as np\n\ndef image_difference(img1_path, img2_path):\n    img1 = cv2.imread(img1_path, cv2.IMREAD_GRAYSCALE)\n    img2 = cv2.imread(img2_path, cv2.IMREAD_GRAYSCALE)\n    img1 = cv2.resize(img1, (256, 256))\n    img2 = cv2.resize(img2, (256, 256))\n    return np.mean(np.abs(img1 - img2))\n\nfor i in range(5):\n    for j in range(i+1, 5):\n        diff = image_difference(monet_paths[i], monet_paths[j])\n        print(f\"Difference between image {i} and {j}: {diff}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Color Palette Distribution (Understanding Monet's Art Style)**\n\nPurpose: To find the dominant colors in Monet's paintings. We can do this by extracting the color palette with k-means clustering.","metadata":{}},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.cluster import KMeans\n\nimg = cv2.imread(monet_paths[0])\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\nimg = img.reshape((-1, 3))\n\nkmeans = KMeans(n_clusters=5)\nkmeans.fit(img)\ncolors = kmeans.cluster_centers_\n\nplt.figure(figsize=(8, 2))\nplt.imshow([colors.astype(int)])\nplt.axis(\"off\")\nplt.title(\"Dominant Colors (K-Means)\")\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Are There Noise or Distortion in Images?**\n\nPurpose: We can use the Laplace Variance method to analyze the clarity or distortion level of images.","metadata":{}},{"cell_type":"code","source":"def image_sharpness(img_path):\n    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n    laplacian = cv2.Laplacian(img, cv2.CV_64F).var()\n    return laplacian\n\nsharpness_scores = [image_sharpness(img) for img in monet_paths[:300]]\nprint(\"Sharpness Scores for images:\", sharpness_scores)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Similarity Analysis of Monet Images with Deep Learning**\n\nPurpose: To examine the degree of similarity between Monet paintings in terms of style. We can compare Image Features with VGG16.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import VGG16\nfrom tensorflow.keras.preprocessing.image import img_to_array, load_img\nfrom tensorflow.keras.applications.vgg16 import preprocess_input\nfrom sklearn.metrics.pairwise import cosine_similarity\n\nmodel = VGG16(weights=\"imagenet\", include_top=False, pooling=\"avg\")\n\ndef extract_features(img_path):\n    img = load_img(img_path, target_size=(224, 224))\n    img = img_to_array(img)\n    img = np.expand_dims(img, axis=0)\n    img = preprocess_input(img)\n    features = model.predict(img)\n    return features.flatten()\n\nfeatures = [extract_features(img) for img in monet_paths[:5]]\n\nsimilarities = cosine_similarity(features)\nprint(\"Similarity Matrix:\")\nprint(similarities)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Pixel Intensity Distribution Analysis in Monet Paintings**\n\nPurpose: To examine the distribution of pixel intensities in Monet paintings and understand the overall brightness levels.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(8, 6))\nsns.histplot(img.flatten(), bins=50, kde=True)\nplt.title('Distribution of Pixel Intensities')\nplt.xlabel('Pixel Intensity')\nplt.ylabel('Frequency')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Firstly, it was observed that all Monet paintings were 256x256 pixels in size and the dataset was consistent in terms of size. Using Sobel edge detection, it was observed that soft and irregular brush strokes were dominant instead of distinct edge lines in the paintings. In the color distribution analysis, it was determined that green, blue and red tones were used predominantly in Monet's paintings at certain intensities, and the general tone consisted of pastel colors. Monet's 5 most dominant color tones were extracted with the K-Means clustering method and it was confirmed that tones close to nature were dominant. The histogram graph showing the pixel density distribution shows that Monet's works have a medium level of brightness, very few extremely bright or dark tones, and a balanced art style in terms of tone. In addition, the style similarities of the paintings were measured using the VGG16 model and it was determined that there was a 50-80% similarity between Monet's paintings. In addition, in the sharpness analysis of the images (Laplace Variance), it was confirmed that the brush strokes had soft transitions. When comparing based on pixel differences to analyze uniqueness, it was observed that some Monet paintings were quite similar but not exactly the same. These findings show that preserving elements such as color distribution, style characteristics, and pixel density are critical when producing Monet-style images with the GAN model. In the next step, it would be useful to compare the generated images with these analyses and apply preprocessing techniques when necessary to understand how well the model can capture Monet’s art style.","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport os\nimport glob\nimport time\nfrom PIL import Image\n\n# Defining the Custom InstanceNormalization layer\nclass InstanceNormalization(tf.keras.layers.Layer):\n    def __init__(self, epsilon=1e-5, **kwargs):\n        super(InstanceNormalization, self).__init__(**kwargs)\n        self.epsilon = epsilon\n\n    def build(self, input_shape):\n        # Creating gamma (scale) and beta (shift) parameters for each channel.\n        self.gamma = self.add_weight(\n            name='gamma',\n            shape=(input_shape[-1],),\n            initializer='ones',\n            trainable=True)\n        self.beta = self.add_weight(\n            name='beta',\n            shape=(input_shape[-1],),\n            initializer='zeros',\n            trainable=True)\n        super(InstanceNormalization, self).build(input_shape)\n\n    def call(self, inputs):\n        # Compute mean and variance for each instance (high dimensions: [batch, height, width])\n        mean, variance = tf.nn.moments(inputs, axes=[1, 2], keepdims=True)\n        normalized = (inputs - mean) / tf.sqrt(variance + self.epsilon)\n        return self.gamma * normalized + self.beta\n\n    def get_config(self):\n        config = super(InstanceNormalization, self).get_config()\n        config.update({\"epsilon\": self.epsilon})\n        return config\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_HEIGHT = 256\nIMG_WIDTH = 256\nBATCH_SIZE = 1        # CycleGAN typically works with batch_size=1.\nBUFFER_SIZE = 1000\nEPOCHS = 50           # We can increase this based on training duration.\nLAMBDA = 10           # Weight for cycle consistency and identity loss.\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Image loading and preprocessing function\ndef load_image(image_file):\n    image = tf.io.read_file(image_file)\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = tf.image.resize(image, [IMG_HEIGHT, IMG_WIDTH])\n    # Normalize to the range [-1, 1] for Tanh activation.\n    image = (image / 127.5) - 1\n    return image\n\n# Adjust file paths according to your environment:\nphoto_paths = glob.glob(\"../input/gan-getting-started/photo_jpg/*.jpg\")   # Domain X: Path to your photos\nmonet_paths = glob.glob(\"../input/gan-getting-started/monet_jpg/*.jpg\")   # Domain Y: Path to Monet paintings\n\n# Creating tf.data.Dataset:\ndataset_x = tf.data.Dataset.from_tensor_slices(photo_paths)\ndataset_x = dataset_x.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\ndataset_x = dataset_x.shuffle(BUFFER_SIZE).batch(BATCH_SIZE)\n\ndataset_y = tf.data.Dataset.from_tensor_slices(monet_paths)\ndataset_y = dataset_y.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\ndataset_y = dataset_y.shuffle(BUFFER_SIZE).batch(BATCH_SIZE)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Defining model architectures\n\ndef resnet_block(x, filters, size=3):\n    initializer = tf.random_normal_initializer(0., 0.02)\n    y = tf.keras.layers.Conv2D(filters, size, strides=1, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(x)\n    y = InstanceNormalization()(y)\n    y = tf.keras.layers.ReLU()(y)\n    y = tf.keras.layers.Conv2D(filters, size, strides=1, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(y)\n    y = InstanceNormalization()(y)\n    return tf.keras.layers.add([x, y])\n\ndef build_generator():\n    initializer = tf.random_normal_initializer(0., 0.02)\n    inputs = tf.keras.Input(shape=[IMG_HEIGHT, IMG_WIDTH, 3])\n    \n    # First layer: c7s1-64\n    x = tf.keras.layers.Conv2D(64, 7, strides=1, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(inputs)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.ReLU()(x)\n    \n    # Downsampling (2 layers)\n    x = tf.keras.layers.Conv2D(128, 3, strides=2, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(x)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.ReLU()(x)\n    \n    x = tf.keras.layers.Conv2D(256, 3, strides=2, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(x)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.ReLU()(x)\n    \n    # 9 ResNet blocks\n    for _ in range(9):\n        x = resnet_block(x, 256)\n    \n    # Upsampling (2 layers)\n    x = tf.keras.layers.Conv2DTranspose(128, 3, strides=2, padding='same',\n                                          kernel_initializer=initializer, use_bias=False)(x)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.ReLU()(x)\n    \n    x = tf.keras.layers.Conv2DTranspose(64, 3, strides=2, padding='same',\n                                          kernel_initializer=initializer, use_bias=False)(x)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.ReLU()(x)\n    \n    # Final layer: c7s1-3, output with tanh activation in range [-1,1]\n    x = tf.keras.layers.Conv2D(3, 7, strides=1, padding='same',\n                               kernel_initializer=initializer)(x)\n    outputs = tf.keras.layers.Activation('tanh')(x)\n    \n    return tf.keras.Model(inputs=inputs, outputs=outputs)\n\ndef build_discriminator():\n    initializer = tf.random_normal_initializer(0., 0.02)\n    inputs = tf.keras.Input(shape=[IMG_HEIGHT, IMG_WIDTH, 3])\n    \n    x = tf.keras.layers.Conv2D(64, 4, strides=2, padding='same',\n                               kernel_initializer=initializer)(inputs)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n    \n    x = tf.keras.layers.Conv2D(128, 4, strides=2, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(x)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n    \n    x = tf.keras.layers.Conv2D(256, 4, strides=2, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(x)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n    \n    x = tf.keras.layers.Conv2D(512, 4, strides=1, padding='same',\n                               kernel_initializer=initializer, use_bias=False)(x)\n    x = InstanceNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.2)(x)\n    \n    outputs = tf.keras.layers.Conv2D(1, 4, strides=1, padding='same',\n                                     kernel_initializer=initializer)(x)\n    \n    return tf.keras.Model(inputs=inputs, outputs=outputs)\n\n# Creating model instances:\ngenerator_g = build_generator()  # Photo -> Monet\ngenerator_f = build_generator()  # Monet -> Photo\n\ndiscriminator_x = build_discriminator()  # For Domain X (Photos)\ndiscriminator_y = build_discriminator()  # For Domain Y (Monet)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Loss functions and optimizers\n\n# LSGAN-style loss function\nloss_obj = tf.keras.losses.MeanSquaredError()\n\ndef generator_loss(fake):\n    return loss_obj(tf.ones_like(fake), fake)\n\ndef discriminator_loss(real, fake):\n    real_loss = loss_obj(tf.ones_like(real), real)\n    fake_loss = loss_obj(tf.zeros_like(fake), fake)\n    return 0.5 * (real_loss + fake_loss)\n\ndef calc_cycle_loss(real, cycled):\n    loss = tf.reduce_mean(tf.abs(real - cycled))\n    return LAMBDA * loss\n\ndef identity_loss(real, same):\n    loss = tf.reduce_mean(tf.abs(real - same))\n    return 0.5 * LAMBDA * loss\n\n# Optimizers\ngenerator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ngenerator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ndiscriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ndiscriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Defining the training step function\n@tf.function\ndef train_step(real_x, real_y):\n    with tf.GradientTape(persistent=True) as tape:\n        # Photo to Monet: X -> Y\n        fake_y = generator_g(real_x, training=True)\n        cycled_x = generator_f(fake_y, training=True)\n        \n        # Monet to Photo: Y -> X\n        fake_x = generator_f(real_y, training=True)\n        cycled_y = generator_g(fake_x, training=True)\n        \n        # Identity mapping: Keeping the real input unchanged\n        same_x = generator_f(real_x, training=True)\n        same_y = generator_g(real_y, training=True)\n        \n        # Discriminator outputs\n        disc_real_x = discriminator_x(real_x, training=True)\n        disc_real_y = discriminator_y(real_y, training=True)\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 consistency loss\n        total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)\n        \n        # Identity loss\n        id_loss_g = identity_loss(real_y, same_y)\n        id_loss_f = identity_loss(real_x, same_x)\n        \n        total_gen_g_loss = gen_g_loss + total_cycle_loss + id_loss_g\n        total_gen_f_loss = gen_f_loss + total_cycle_loss + id_loss_f\n        \n        # Discriminator losses\n        disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)\n        disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)\n    \n    # Compute gradients\n    gradients_g = tape.gradient(total_gen_g_loss, generator_g.trainable_variables)\n    gradients_f = tape.gradient(total_gen_f_loss, generator_f.trainable_variables)\n    gradients_disc_x = tape.gradient(disc_x_loss, discriminator_x.trainable_variables)\n    gradients_disc_y = tape.gradient(disc_y_loss, discriminator_y.trainable_variables)\n    \n    # Update weights\n    generator_g_optimizer.apply_gradients(zip(gradients_g, generator_g.trainable_variables))\n    generator_f_optimizer.apply_gradients(zip(gradients_f, generator_f.trainable_variables))\n    discriminator_x_optimizer.apply_gradients(zip(gradients_disc_x, discriminator_x.trainable_variables))\n    discriminator_y_optimizer.apply_gradients(zip(gradients_disc_y, discriminator_y.trainable_variables))\n    \n    return total_gen_g_loss, total_gen_f_loss, disc_x_loss, disc_y_loss\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training loop and generating sample images during training\n\ndef generate_sample_images(real_x, real_y, epoch):\n    # Generate transformations: Real photo to Monet & Real Monet to photo\n    fake_y = generator_g(real_x, training=False)\n    fake_x = generator_f(real_y, training=False)\n    \n    plt.figure(figsize=(12, 12))\n    images = [real_x[0], fake_y[0], real_y[0], fake_x[0]]\n    titles = ['Real Photo', 'Fake Monet', 'Real Monet', 'Fake Photo']\n    \n    for i in range(4):\n        plt.subplot(2, 2, i+1)\n        # Scale images from [-1,1] to [0,1]\n        img = (images[i] + 1) / 2.0\n        plt.imshow(img)\n        plt.title(titles[i])\n        plt.axis('off')\n    plt.suptitle(f'Epoch {epoch}')\n    plt.show()\n\nfor epoch in range(1, EPOCHS + 1):\n    start = time.time()\n    n = 0\n    # Zip both datasets to fetch batches from both domains\n    for real_x, real_y in tf.data.Dataset.zip((dataset_x, dataset_y)):\n        total_gen_g_loss, total_gen_f_loss, disc_x_loss, disc_y_loss = train_step(real_x, real_y)\n        n += 1\n        if n % 100 == 0:\n            print(f\"Epoch {epoch}, Batch {n}: Gen G Loss = {total_gen_g_loss:.4f}, Gen F Loss = {total_gen_f_loss:.4f}\")\n    print(f\"Epoch {epoch} duration: {time.time() - start:.2f} sec\")\n    \n    # Generate and display sample images at the end of each 10 epoch\n    if epoch % 10 == 0:\n        sample_x = next(iter(dataset_x))\n        sample_y = next(iter(dataset_y))\n        generate_sample_images(sample_x, sample_y, epoch)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport shutil\nimport tensorflow as tf\nfrom PIL import Image\n\n# Define output folder (in Kaggle working directory)\noutput_folder = \"/kaggle/working/images\"\nos.makedirs(output_folder, exist_ok=True)\n\n# Set the number of images to generate (e.g., 7000)\nnum_images_to_generate = 7000\ncounter = 0\n\n# Iterate over dataset_x and generate Monet-style images for each photo\nfor photo_batch in dataset_x:  # dataset_x: tf.data.Dataset containing photos (batch_size=1)\n    # Use generator_g to generate fake Monet images from photo batch\n    fake_monet = generator_g(photo_batch, training=False)\n    \n    # Each batch (batch_size=1) may contain one or more images\n    for i in range(fake_monet.shape[0]):\n        # Convert images from [-1,1] range to [0,1] range\n        img = (fake_monet[i] + 1.0) / 2.0\n        # Convert image to uint8 type (0-255 range)\n        img = tf.image.convert_image_dtype(img, tf.uint8)\n        img_array = img.numpy()\n        # Save the image to the specified folder\n        save_path = os.path.join(output_folder, f\"monet_{counter}.jpg\")\n        Image.fromarray(img_array).save(save_path)\n        counter += 1\n        if counter >= num_images_to_generate:\n            break\n    if counter >= num_images_to_generate:\n        break\n\nprint(f\"{counter} Monet-style images saved to '{output_folder}'.\")\n\n# Convert folder to a zip file\nzip_path = output_folder  # Name and path of the zip file\nshutil.make_archive(zip_path, 'zip', output_folder)\nprint(f\"{zip_path}.zip created!\")\n\n# Optional: List the first 10 files in the folder\nprint(\"First 10 files:\")\n!ls \"{output_folder}\" | head -n 10\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}