{"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":"none","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# GAN Mini-Project\n\n#### DTSA 5511 Introduction to Deep Learning, Week 5\n\n&nbsp;\n\n<b>Instructions</b>: <i>We will use this Kaggle competition to practice building and training generative deep learning models (mostly GAN). Kaggle introduces an evaluation metric called MiFID (Memorization-informed Fréchet Inception Distance) score to evaluate the quality of generated images.</i>","metadata":{}},{"cell_type":"markdown","source":"First, I'll import the libraries I need for this project","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport time\n\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, losses, optimizers\nimport matplotlib.pyplot as plt\n\nfrom kaggle_datasets import KaggleDatasets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:04.879945Z","iopub.execute_input":"2025-04-22T15:55:04.880235Z","iopub.status.idle":"2025-04-22T15:55:20.891512Z","shell.execute_reply.started":"2025-04-22T15:55:04.880212Z","shell.execute_reply":"2025-04-22T15:55:20.890703Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Brief description of the problem and data","metadata":{}},{"cell_type":"markdown","source":"In this challenge, the goal is to build a generative adversarial network (GAN) that transfers the style of Monet paintings to photographs of landscapes. Style transfer is an area where GANs excel, because the architecture of these networks force the model to preserve the content of an image, but apply stylistic features from other images to it.\n\nGenerative deep learning models are unique because instead of learning to make a classification or regression prediction, they try to make new data, following the bounds of what they were trained on. A GAN is a type of generative deep learning model, and it is made up of a generator and discriminator. The generator creates new content, and the discriminator tries to decide if what the generator made is real or not.","metadata":{}},{"cell_type":"markdown","source":"Let's take a look at the size and shape of the data. How many images are in both datasets?","metadata":{}},{"cell_type":"code","source":"def count_tfrecord_examples(tfrecord_dir):\n    tfrecord_files = tf.io.gfile.glob(os.path.join(tfrecord_dir, '*.tfrec'))\n    total = 0\n    for tfrec in tfrecord_files:\n        dataset = tf.data.TFRecordDataset(tfrec)\n        count = sum(1 for _ in dataset)\n        print(f\"{tfrec}: {count} images\")\n        total += count\n    return total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:20.892289Z","iopub.execute_input":"2025-04-22T15:55:20.892738Z","iopub.status.idle":"2025-04-22T15:55:20.898149Z","shell.execute_reply.started":"2025-04-22T15:55:20.892717Z","shell.execute_reply":"2025-04-22T15:55:20.897229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Count for Monet\nmonet_count = count_tfrecord_examples(\"/kaggle/input/gan-getting-started/monet_tfrec/\")\nprint(f\"\\nTotal Monet images: {monet_count}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:20.900537Z","iopub.execute_input":"2025-04-22T15:55:20.900856Z","iopub.status.idle":"2025-04-22T15:55:21.356625Z","shell.execute_reply.started":"2025-04-22T15:55:20.900813Z","shell.execute_reply":"2025-04-22T15:55:21.355756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Count for Photo\nphoto_count = count_tfrecord_examples(\"/kaggle/input/gan-getting-started/photo_tfrec/\")\nprint(f\"\\nTotal Photo images: {photo_count}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:21.357362Z","iopub.execute_input":"2025-04-22T15:55:21.357606Z","iopub.status.idle":"2025-04-22T15:55:25.795635Z","shell.execute_reply.started":"2025-04-22T15:55:21.357586Z","shell.execute_reply":"2025-04-22T15:55:25.794657Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Ok, we have 300 images of Monet paintings to work with, and 7,038 landscape photos as well.\n\nWhat size are the photos?","metadata":{}},{"cell_type":"code","source":"# Choose a random tfrecord file\ndirectory = \"/kaggle/input/gan-getting-started/photo_tfrec/\"\ntfrecord_files = tf.io.gfile.glob(os.path.join(directory, '*.tfrec'))\nrandom_file = random.choice(tfrecord_files)\n\n# Open up the tfrecord file\nraw_dataset = tf.data.TFRecordDataset(random_file)\n\n# Function to unpack images from the opened tfrecord file\ndef parse_image(record):\n    feature_description = {'image': tf.io.FixedLenFeature([], tf.string)}\n    parsed = tf.io.parse_single_example(record, feature_description)\n    image = tf.io.decode_jpeg(parsed['image'], channels=3)\n    image = tf.image.resize(image, [256, 256])\n    image = tf.cast(image, tf.float32) / 255.0  # [0, 1] range for display\n    return image\n\n# Parse the dataset\nparsed_dataset = raw_dataset.map(parse_image)\n\n# Display the image\nimage = next(iter(parsed_dataset.take(1)))\nprint(\"Image shape:\", image.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:25.796591Z","iopub.execute_input":"2025-04-22T15:55:25.796875Z","iopub.status.idle":"2025-04-22T15:55:25.930265Z","shell.execute_reply.started":"2025-04-22T15:55:25.796846Z","shell.execute_reply":"2025-04-22T15:55:25.929305Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The images are 256 by 256 pixels, and each pixel has three channels: R, G, and B. This is standard for image data.\n\nWe now have some understanding of the data files that have been provided, and what their dimensions are. In the next section, we take a closer look at the data itself.","metadata":{}},{"cell_type":"markdown","source":"### Exploratory Data Analysis (EDA) — Inspect, Visualize and Clean the Data","metadata":{}},{"cell_type":"markdown","source":"First, let's see what some of these images look like:","metadata":{}},{"cell_type":"code","source":"def random_image_from_tfrecord(directory):\n    # Choose a random tfrecord file\n    tfrecord_files = tf.io.gfile.glob(os.path.join(directory, '*.tfrec'))\n    random_file = random.choice(tfrecord_files)\n    \n    # Open up the tfrecord file\n    raw_dataset = tf.data.TFRecordDataset(random_file)\n    \n    # Function to unpack images from the opened tfrecord file\n    def parse_image(record):\n        feature_description = {'image': tf.io.FixedLenFeature([], tf.string)}\n        parsed = tf.io.parse_single_example(record, feature_description)\n        image = tf.io.decode_jpeg(parsed['image'], channels=3)\n        image = tf.image.resize(image, [256, 256])\n        image = tf.cast(image, tf.float32) / 255.0\n        return image\n    \n    # Parse the dataset\n    parsed_dataset = raw_dataset.map(parse_image)\n    \n    # Display the image\n    image = next(iter(parsed_dataset.take(1)))\n    plt.imshow(image.numpy())\n    plt.axis('off')\n    plt.title(f'Random Image from {directory.split(\"/\")[-2]}')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:25.931223Z","iopub.execute_input":"2025-04-22T15:55:25.931473Z","iopub.status.idle":"2025-04-22T15:55:25.938190Z","shell.execute_reply.started":"2025-04-22T15:55:25.931449Z","shell.execute_reply":"2025-04-22T15:55:25.937323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random_image_from_tfrecord(\"/kaggle/input/gan-getting-started/monet_tfrec/\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:25.939168Z","iopub.execute_input":"2025-04-22T15:55:25.939390Z","iopub.status.idle":"2025-04-22T15:55:26.335643Z","shell.execute_reply.started":"2025-04-22T15:55:25.939373Z","shell.execute_reply":"2025-04-22T15:55:26.334743Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random_image_from_tfrecord(\"/kaggle/input/gan-getting-started/photo_tfrec/\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:26.336596Z","iopub.execute_input":"2025-04-22T15:55:26.337290Z","iopub.status.idle":"2025-04-22T15:55:26.585972Z","shell.execute_reply.started":"2025-04-22T15:55:26.337264Z","shell.execute_reply":"2025-04-22T15:55:26.585096Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"In the above code blocks, I wrote a function to randomly choose an image from the tfrecord files and display it. I ran this a few times to get a sense of the types of images I'm working with.\n\nI also had the values in the image divided by 255, so they should be scaled to [0, 1], but I'll verify that with a histogram of color values for an image.","metadata":{}},{"cell_type":"code","source":"# Choose a random tfrecord file\ndirectory = \"/kaggle/input/gan-getting-started/monet_tfrec/\"\ntfrecord_files = tf.io.gfile.glob(os.path.join(directory, '*.tfrec'))\nrandom_file = random.choice(tfrecord_files)\n\n# Open up the tfrecord file\nraw_dataset = tf.data.TFRecordDataset(random_file)\n\n# Function to unpack images from the opened tfrecord file\ndef parse_image(record):\n    feature_description = {'image': tf.io.FixedLenFeature([], tf.string)}\n    parsed = tf.io.parse_single_example(record, feature_description)\n    image = tf.io.decode_jpeg(parsed['image'], channels=3)\n    image = tf.image.resize(image, [256, 256])\n    image = tf.cast(image, tf.float32) / 255.0  # [0, 1] range for display\n    return image\n\n# Parse the dataset\nparsed_dataset = raw_dataset.map(parse_image)\n\n# Grab the image\nimage = next(iter(parsed_dataset.take(1)))\nimage_np = image.numpy()\n\n# Plot histogram\ncolors = ['red', 'green', 'blue']\n\nfor i, color in enumerate(colors):\n    plt.hist(image_np[:, :, i].ravel(), bins=256, color=color, alpha=0.5, label=color.upper())\n\nplt.title('RGB Histogram of a Random Image')\nplt.xlabel('Pixel value')\nplt.ylabel('Count')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:26.588119Z","iopub.execute_input":"2025-04-22T15:55:26.588356Z","iopub.status.idle":"2025-04-22T15:55:27.943523Z","shell.execute_reply.started":"2025-04-22T15:55:26.588337Z","shell.execute_reply":"2025-04-22T15:55:27.942672Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Looks like all of the data is between 0 and 1 after I transformed it, as expected.\n\nThese images look clean and ready for training, especially since they cam prepackaged in tfrec files. In the next section, I'll build GANs and use these images to train and validate them.","metadata":{}},{"cell_type":"markdown","source":"### Model Architecture","metadata":{}},{"cell_type":"markdown","source":"Parts of this section, specifically the implementation of CycleGAN, is inspired by the [Monet CycleGAN Tutorial](https://www.kaggle.com/code/amyjang/monet-cyclegan-tutorial) notebook that was provided along with the Kaggle challenge. The CycleGAN is a type of Generalized Adversarial Network that is specifically designed for image-to-image changes, like style transfer.\n\nThe architecture generally has two generators, which generate images, and two discriminators, which try to pick which images are fake and which are real. The competition between the generators and discriminators drives the model's improvement.\n\nI created a CycleGAN class that extends the Keras model class, and this is how the model will be built.","metadata":{}},{"cell_type":"code","source":"class CycleGAN(tf.keras.Model):\n    def __init__(self, generator_g, generator_f, discriminator_x, discriminator_y):\n        super(CycleGAN, self).__init__()\n        self.generator_g = generator_g\n        self.generator_f = generator_f\n        self.discriminator_x = discriminator_x\n        self.discriminator_y = discriminator_y\n\n    def compile(self, generator_optimizer, discriminator_optimizer, loss_fn):\n        super(CycleGAN, self).compile()\n        self.generator_optimizer = generator_optimizer\n        self.discriminator_optimizer = discriminator_optimizer\n        self.loss_fn = loss_fn\n\n    def train_step(self, batch_data):\n        real_x, real_y = batch_data\n\n        with tf.GradientTape(persistent=True) as tape:\n            # Apply generators to transform the images\n            fake_y = self.generator_g(real_x)\n            fake_x = self.generator_f(real_y)\n\n            # Discriminator results for real vs fake images\n            disc_real_x = self.discriminator_x(real_x)\n            disc_fake_x = self.discriminator_x(fake_x)\n\n            disc_real_y = self.discriminator_y(real_y)\n            disc_fake_y = self.discriminator_y(fake_y)\n\n            # Losses\n            loss_d_x = self.loss_fn(disc_real_x, disc_fake_x)\n            loss_d_y = self.loss_fn(disc_real_y, disc_fake_y)\n\n            # Generator losses\n            loss_g_x = self.loss_fn(disc_fake_x, disc_real_x)\n            loss_g_y = self.loss_fn(disc_fake_y, disc_real_y)\n\n            # Cycle consistency loss\n            cycle_loss_x = tf.reduce_mean(tf.abs(real_x - self.generator_f(fake_y)))\n            cycle_loss_y = tf.reduce_mean(tf.abs(real_y - self.generator_g(fake_x)))\n\n            # Total loss for generator and discriminator\n            total_loss = loss_g_x + loss_g_y + cycle_loss_x + cycle_loss_y + loss_d_x + loss_d_y\n\n        # Compute gradients\n        generator_gradients = tape.gradient(total_loss, self.generator_g.trainable_variables + self.generator_f.trainable_variables)\n        discriminator_gradients = tape.gradient(total_loss, self.discriminator_x.trainable_variables + self.discriminator_y.trainable_variables)\n\n        # Apply gradients\n        self.generator_optimizer.apply_gradients(zip(generator_gradients, self.generator_g.trainable_variables + self.generator_f.trainable_variables))\n        self.discriminator_optimizer.apply_gradients(zip(discriminator_gradients, self.discriminator_x.trainable_variables + self.discriminator_y.trainable_variables))\n\n        return {\"total_loss\": total_loss}\n\n    def call(self, inputs):\n        return self.generator_g(inputs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:28.015697Z","iopub.execute_input":"2025-04-22T15:55:28.015957Z","iopub.status.idle":"2025-04-22T15:55:28.025622Z","shell.execute_reply.started":"2025-04-22T15:55:28.015937Z","shell.execute_reply":"2025-04-22T15:55:28.024692Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The above CycleGAN class will be trained with the photos that we've been provided for this challenge. Therefore, the next step is to bring the images into the notebook:","metadata":{}},{"cell_type":"code","source":"IMAGE_SIZE = [256, 256]\n\ndef decode_image(image):\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = (tf.cast(image, tf.float32) / 127.5) - 1\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\ndef read_tfrecord(example):\n    tfrecord_format = {\n        \"image_name\": tf.io.FixedLenFeature([], tf.string),\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"target\": tf.io.FixedLenFeature([], tf.string)\n    }\n    example = tf.io.parse_single_example(example, tfrecord_format)\n    image = decode_image(example['image'])\n    return image\n\ndef load_dataset(filenames, labeled=True, ordered=False):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_tfrecord, num_parallel_calls=AUTOTUNE)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:28.026587Z","iopub.execute_input":"2025-04-22T15:55:28.027021Z","iopub.status.idle":"2025-04-22T15:55:28.048503Z","shell.execute_reply.started":"2025-04-22T15:55:28.026999Z","shell.execute_reply":"2025-04-22T15:55:28.047757Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"With the functions above, I'll be able to read in the images from the .tfrec files that come with this Kaggle challenge. In the next code block, I'll bring in the image data:","metadata":{}},{"cell_type":"code","source":"AUTOTUNE = tf.data.experimental.AUTOTUNE\n\nGCS_PATH = KaggleDatasets().get_gcs_path()\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\nmonet_ds = load_dataset(MONET_FILENAMES, labeled=True).batch(1)\nphoto_ds = load_dataset(PHOTO_FILENAMES, labeled=True).batch(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:28.049397Z","iopub.execute_input":"2025-04-22T15:55:28.049641Z","iopub.status.idle":"2025-04-22T15:55:28.908261Z","shell.execute_reply.started":"2025-04-22T15:55:28.049623Z","shell.execute_reply":"2025-04-22T15:55:28.907311Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"I also need to provide the CycleGAN with the architecture for the generator and discriminator. We learned earlier in this course that convolutional layers are particularly well suited for training with image data, so I leverged those in both the generator and discriminator. Since training on image data can be time consuming, I chose to keep the architecture simple, and use 2 convolutional layers.","metadata":{}},{"cell_type":"code","source":"def build_generator():\n    model = tf.keras.Sequential([\n        layers.InputLayer(input_shape=(256, 256, 3)),\n        layers.Conv2D(64, (3, 3), activation='relu', padding='same'),\n        layers.Conv2D(3, (3, 3), activation='tanh', padding='same')\n    ])\n    return model\n\ndef build_discriminator():\n    model = tf.keras.Sequential([\n        layers.InputLayer(input_shape=(256, 256, 3)),\n        layers.Conv2D(64, (3, 3), activation='relu', padding='same'),\n        layers.Conv2D(1, (3, 3), activation='sigmoid', padding='same')\n    ])\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:28.909296Z","iopub.execute_input":"2025-04-22T15:55:28.909615Z","iopub.status.idle":"2025-04-22T15:55:28.915962Z","shell.execute_reply.started":"2025-04-22T15:55:28.909589Z","shell.execute_reply":"2025-04-22T15:55:28.915093Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"In the next code block, I instaniate the discriminators, generators, and cycleGAN. I chose to go with the Adam optimizer, since it was emphasized in the lectures that this is a great optimizer to begin with. Then, I compiled the model, so it is ready to begin training.","metadata":{}},{"cell_type":"code","source":"generator_g = build_generator()\ngenerator_f = build_generator()\ndiscriminator_x = build_discriminator()\ndiscriminator_y = build_discriminator()\n\ncyclegan = CycleGAN(generator_g, generator_f, discriminator_x, discriminator_y)\n\ngenerator_optimizer = tf.keras.optimizers.Adam(learning_rate=2e-4, beta_1=0.5)\ndiscriminator_optimizer = tf.keras.optimizers.Adam(learning_rate=2e-4, beta_1=0.5)\n\nloss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=False)\n\ncyclegan.compile(generator_optimizer, discriminator_optimizer, loss_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:28.916944Z","iopub.execute_input":"2025-04-22T15:55:28.917224Z","iopub.status.idle":"2025-04-22T15:55:29.036272Z","shell.execute_reply.started":"2025-04-22T15:55:28.917201Z","shell.execute_reply":"2025-04-22T15:55:29.035405Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Now, we're ready to begin training. I'll package the data together, provide it to the model. I set the number of epochs to 10.","metadata":{}},{"cell_type":"code","source":"paired_ds = tf.data.Dataset.zip((monet_ds, photo_ds))\n\ncyclegan.fit(paired_ds, epochs=10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T15:55:29.037246Z","iopub.execute_input":"2025-04-22T15:55:29.037871Z","iopub.status.idle":"2025-04-22T17:11:16.094773Z","shell.execute_reply.started":"2025-04-22T15:55:29.037842Z","shell.execute_reply":"2025-04-22T17:11:16.094127Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The model has now been trained. Looking closely at the output shows that training was fairly time consuming, taking about 7 to 8 minutes to complete each epoch. After 10 epochs, that's over 1 hour to complete training. Kaggle's GPU and TPU resources are limited to 30 hours a week, so I had to complete this with a CPU. This contributed to the lengthy training process.\n\nIn the next section, I'll review model output and make a submission to the Kaggle challenge.","metadata":{}},{"cell_type":"markdown","source":"### Results and Analysis","metadata":{}},{"cell_type":"markdown","source":"I'm very curious to see how the model performed. I should be able to give the model a photograph, and it will turn it into something that looks like a Monet painting. In the following code block, I will get five photographs and see how the model transforms them:","metadata":{}},{"cell_type":"code","source":"_, ax = plt.subplots(5, 2, figsize=(12, 12))\nfor i, img in enumerate(photo_ds.take(5)):\n    prediction = generator_g(img, training=False)[0].numpy()\n    prediction = (prediction * 127.5 + 127.5).astype(np.uint8)\n    img = (img[0] * 127.5 + 127.5).numpy().astype(np.uint8)\n\n    ax[i, 0].imshow(img)\n    ax[i, 1].imshow(prediction)\n    ax[i, 0].set_title(\"Input Photo\")\n    ax[i, 1].set_title(\"Monet-esque\")\n    ax[i, 0].axis(\"off\")\n    ax[i, 1].axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T17:18:41.306802Z","iopub.execute_input":"2025-04-22T17:18:41.308112Z","iopub.status.idle":"2025-04-22T17:18:42.623565Z","shell.execute_reply.started":"2025-04-22T17:18:41.308067Z","shell.execute_reply":"2025-04-22T17:18:42.622613Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Recall that we used a fairly simple, two-layer convolutional neural network for the generators and discriminators. The output from the models are certainly transformations of the input photos. I would say that the color resemble the colors you would expect to see in a Monet painting, with heightened saturation. However, I think a future iteration of this project could improve the model so that brushstroke are more visible, and the structure of the image is more painting-like. However, I think the color transformation is impressive.\n\nNow, I'll submit this model to the Kaggle challenge. Since this submission requires images, it's a bit more complicated than a tradition Kaggle challenge, which is often just a .csv submission. First, I want to make a folder for the images that will be generated.","metadata":{}},{"cell_type":"code","source":"import PIL\n! mkdir images","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T17:48:13.747803Z","iopub.execute_input":"2025-04-22T17:48:13.748458Z","iopub.status.idle":"2025-04-22T17:48:13.924593Z","shell.execute_reply.started":"2025-04-22T17:48:13.748434Z","shell.execute_reply":"2025-04-22T17:48:13.923066Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"For every photo in the dataset, I want the model to output it's transformed version of the photo.","metadata":{}},{"cell_type":"code","source":"i = 1\nfor img in photo_ds:\n    prediction = generator_g(img, training=False)[0].numpy()\n    prediction = (prediction * 127.5 + 127.5).astype(np.uint8)\n    im = PIL.Image.fromarray(prediction)\n    im.save(\"/kaggle/working/images/\" + str(i) + \".jpg\")\n    i += 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T17:43:39.815794Z","iopub.execute_input":"2025-04-22T17:43:39.816162Z","iopub.status.idle":"2025-04-22T17:47:21.114254Z","shell.execute_reply.started":"2025-04-22T17:43:39.816133Z","shell.execute_reply":"2025-04-22T17:47:21.113391Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"I'll zip up those images and name the zip file \"images.zip\" (as required).","metadata":{}},{"cell_type":"code","source":"import shutil\nshutil.make_archive(\"/kaggle/working/images\", 'zip', \"/kaggle/working/images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T17:47:21.115610Z","iopub.execute_input":"2025-04-22T17:47:21.115883Z","iopub.status.idle":"2025-04-22T17:47:24.506297Z","shell.execute_reply.started":"2025-04-22T17:47:21.115860Z","shell.execute_reply":"2025-04-22T17:47:24.505429Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Finally, remove the non-zipped version of the images, since they're no longer needed.","metadata":{}},{"cell_type":"code","source":"shutil.rmtree(\"/kaggle/working/images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T17:48:19.658327Z","iopub.execute_input":"2025-04-22T17:48:19.658661Z","iopub.status.idle":"2025-04-22T17:48:19.664072Z","shell.execute_reply.started":"2025-04-22T17:48:19.658634Z","shell.execute_reply":"2025-04-22T17:48:19.663087Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"From here, I used the interface in the Kaggle notebook to make the submission!","metadata":{}},{"cell_type":"markdown","source":"### Conclusion","metadata":{}},{"cell_type":"markdown","source":"I learned that Generative Adversarial Networks (GANs) can accomplish some amazing tasks. The model I built was able to take an image, examine it's parts, bring it back together, and produce a transformed image in a different style. With that, I'll reflect on a few takeaways from this project:\n\nHere's what helped improve the performance of my models:\n * The generator/discriminator architecture is not something I've seen before, but this design obviously allowed the model to apply some amazing transformations to the images.\n * Using convolutional layers in the discriminator and generator was important, especially since the model was training on image data\n * The data was normalized to the range [-1, 1], which helped stabilize training and improve the model's performance\n\nHere's what didn't help improve the performance of my models (and some lessons learned):\n * I would want to spend more time tuning the number of epochs, because I think 10 may not have been enough. More epochs might give the model more time to converge and produce better results\n * The architecture I built for the generator and discriminator were simple, and produced interesting results, but could probably be improved further by adding more layers, or by using a pre-trained model.\n\nHere's what I'd like to try in the future:\n * In the future, I'd like to have more access to GPUs or TPUs to train GANs. These models can produce some amazing results, but are certainly the most resource-intensive models we worked with in this course.\n * Given some additional resources, I think it would be really cool to provide my own images to the model and see what it's able to transform them into.\n * I'd also like to have more of a chance to tweak the learning rate and see how it affects training. With amount of time it took to train the model, I did not get to try many different learning rates.","metadata":{}}]}