{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","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"}],"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))\nimport matplotlib.image as mpimg\nimport matplotlib.pyplot as plt\nimport random\nimport shutil\nimport tensorflow as tf\n\n\nfrom os import listdir\nfrom os.path import isfile, join\nfrom PIL import Image\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom keras.models import Sequential\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-09-28T16:54:56.248454Z","iopub.execute_input":"2025-09-28T16:54:56.248688Z","iopub.status.idle":"2025-09-28T16:55:09.678181Z","shell.execute_reply.started":"2025-09-28T16:54:56.248671Z","shell.execute_reply":"2025-09-28T16:55:09.677404Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#!pip install tensorflow-addons","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T01:49:42.138983Z","iopub.execute_input":"2025-08-13T01:49:42.139264Z","iopub.status.idle":"2025-08-13T01:49:42.153940Z","shell.execute_reply.started":"2025-08-13T01:49:42.139238Z","shell.execute_reply":"2025-08-13T01:49:42.153271Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#import tensorflow_addons as tfa\n!pip install tfa-nightly\nimport tensorflow_addons as tfa","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T16:55:27.838065Z","iopub.execute_input":"2025-09-28T16:55:27.838613Z","iopub.status.idle":"2025-09-28T16:55:33.631847Z","shell.execute_reply.started":"2025-09-28T16:55:27.838589Z","shell.execute_reply":"2025-09-28T16:55:33.631174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random.seed(42)\nnp.random.seed(42)\ntf.random.set_seed(42)\nautotune = tf.data.AUTOTUNE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T16:55:41.259406Z","iopub.execute_input":"2025-09-28T16:55:41.260212Z","iopub.status.idle":"2025-09-28T16:55:41.264219Z","shell.execute_reply.started":"2025-09-28T16:55:41.260181Z","shell.execute_reply":"2025-09-28T16:55:41.263658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"monet_dir = '/kaggle/input/gan-getting-started/monet_jpg/'\nphoto_dir = '/kaggle/input/gan-getting-started/photo_jpg/'\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T16:55:44.191768Z","iopub.execute_input":"2025-09-28T16:55:44.192045Z","iopub.status.idle":"2025-09-28T16:55:44.195949Z","shell.execute_reply.started":"2025-09-28T16:55:44.192025Z","shell.execute_reply":"2025-09-28T16:55:44.195228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"monet_filenames = [join(monet_dir, f) for f in listdir(monet_dir) if isfile(join(monet_dir, f))]\nphoto_filenames = [join(photo_dir, f) for f in listdir(photo_dir) if isfile(join(photo_dir, f))]\nmonet_files = [np.array(Image.open(f)) for f in monet_filenames]\nphoto_files = [np.array(Image.open(f)) for f in photo_filenames]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T16:55:46.675586Z","iopub.execute_input":"2025-09-28T16:55:46.675853Z","iopub.status.idle":"2025-09-28T16:56:47.695683Z","shell.execute_reply.started":"2025-09-28T16:55:46.675834Z","shell.execute_reply":"2025-09-28T16:56:47.694865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"monet files: \" + str(len(monet_files)))\nprint(\"photo files: \" + str(len(photo_files)))\nprint(\"image shape: \" + str(monet_files[1].shape))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T16:59:47.029164Z","iopub.execute_input":"2025-09-28T16:59:47.029444Z","iopub.status.idle":"2025-09-28T16:59:47.033727Z","shell.execute_reply.started":"2025-09-28T16:59:47.029423Z","shell.execute_reply":"2025-09-28T16:59:47.033038Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1>Part 1 - Description of Problem and Data</h1>\nThis project will generate images in the style of Monet. By using machine learning techniques, we can identify the different styles such as color pallete or brush strokes of different artists. This rpboe, involved using 2 neural networks: a generator model and a discriminator model. The generator will create the images in the style of Monet. The discriminator will try to accurately classify the real vs generated images. We are tasked to build a GAN to generate thousands of Monet style images.\n\nWe are given 300 monet paintings and 7038 photos of size 256x256.\nhttps://www.kaggle.com/competitions/gan-getting-started\n\nLet's take a quick look of a photo and a Monet Painting","metadata":{}},{"cell_type":"code","source":"plt.subplot(121)\nplt.title('Photo')\nplt.imshow(mpimg.imread(photo_filenames[42]))\n\nplt.subplot(122)\nplt.title('Monet')\nplt.imshow(mpimg.imread(monet_filenames[42]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T16:59:50.523517Z","iopub.execute_input":"2025-09-28T16:59:50.523778Z","iopub.status.idle":"2025-09-28T16:59:50.944779Z","shell.execute_reply.started":"2025-09-28T16:59:50.523760Z","shell.execute_reply":"2025-09-28T16:59:50.944121Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"From a quick glance, it looks like they are distinct enough for machine learning techniques to identify key attributes to generate the Monet paintings","metadata":{}},{"cell_type":"markdown","source":"Load in the filenames of TFRecords","metadata":{}},{"cell_type":"code","source":"monet_tfrec = tf.io.gfile.glob(str('/kaggle/input/gan-getting-started/monet_tfrec/*.tfrec'))\nphoto_tfrec = tf.io.gfile.glob(str('/kaggle/input/gan-getting-started/photo_tfrec/*.tfrec'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:07.339505Z","iopub.execute_input":"2025-09-28T17:00:07.339794Z","iopub.status.idle":"2025-09-28T17:00:07.387254Z","shell.execute_reply.started":"2025-09-28T17:00:07.339769Z","shell.execute_reply":"2025-09-28T17:00:07.386765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Monet TFRecord Files:', len(monet_tfrec))\nprint('Photo TFRecord Files:', len(photo_tfrec))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:09.225978Z","iopub.execute_input":"2025-09-28T17:00:09.226456Z","iopub.status.idle":"2025-09-28T17:00:09.230264Z","shell.execute_reply.started":"2025-09-28T17:00:09.226434Z","shell.execute_reply":"2025-09-28T17:00:09.229692Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"For the generative model, we don't need labels. Only return image from TFRecord","metadata":{}},{"cell_type":"code","source":"IMAGE_SIZE = [256, 256]\n\n# allows to decode an image\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\n# read the tfrec file format\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:12.480612Z","iopub.execute_input":"2025-09-28T17:00:12.481080Z","iopub.status.idle":"2025-09-28T17:00:12.486264Z","shell.execute_reply.started":"2025-09-28T17:00:12.481055Z","shell.execute_reply":"2025-09-28T17:00:12.485509Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Extract image form files","metadata":{}},{"cell_type":"code","source":"def load_dataset(filenames, labeled=True, ordered=False):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_tfrecord)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:16.723421Z","iopub.execute_input":"2025-09-28T17:00:16.724253Z","iopub.status.idle":"2025-09-28T17:00:16.728037Z","shell.execute_reply.started":"2025-09-28T17:00:16.724227Z","shell.execute_reply":"2025-09-28T17:00:16.727373Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Load in datasets","metadata":{}},{"cell_type":"code","source":"monet_ds = load_dataset(monet_tfrec, labeled=True).batch(1)\nphoto_ds = load_dataset(photo_tfrec, labeled=True).batch(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:18.616187Z","iopub.execute_input":"2025-09-28T17:00:18.616452Z","iopub.status.idle":"2025-09-28T17:00:19.601892Z","shell.execute_reply.started":"2025-09-28T17:00:18.616432Z","shell.execute_reply":"2025-09-28T17:00:19.601135Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Visualize photo and Monet example after loading dataset","metadata":{}},{"cell_type":"code","source":"example_monet = next(iter(monet_ds))\nexample_photo = next(iter(photo_ds))\n\nplt.subplot(121)\nplt.title('Photo')\nplt.imshow(example_photo[0] * 0.5 + 0.5)\n\nplt.subplot(122)\nplt.title('Monet')\nplt.imshow(example_monet[0] * 0.5 + 0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:20.610063Z","iopub.execute_input":"2025-09-28T17:00:20.610296Z","iopub.status.idle":"2025-09-28T17:00:21.074213Z","shell.execute_reply.started":"2025-09-28T17:00:20.610280Z","shell.execute_reply":"2025-09-28T17:00:21.073519Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1>Part 2 Model Architecture</h1>\nThis GAN model is using a UNET archtecture.\nThe downsample(encoder) reduces 2D dimensions. We are using a stride of 2, so the image width and height are reduced by 2. The input image is compressed into a compact representation, capturing high-level features.\nThe upsample (decoder) does the opposite of downsample. This reconstructs the image back to 256x256x3. The skip Connections preserve fine details by concatenating features from corresponding downsmapling layers.\nThis is suitable for Monet image generation because paintings typically have different \"levels\". They have local patterns such as brushstrokes and then higher-level composition patterns like water or the sky.\nTHe discriminator takes in the input image and classiffies it as real or generated. The discrminator outputs a smaller 2D images with higher pixel calues for real classifications and lower values indicating a fake/generated classification. ","metadata":{}},{"cell_type":"code","source":"OUTPUT_CHANNELS = 3\n\n# function to reduce the image size\ndef downsample(filters, size, apply_instancenorm=True):\n    initializer = tf.random_normal_initializer(0., 0.02)\n    gamma_init = keras.initializers.RandomNormal(mean=0.0, stddev=0.02)\n\n    result = keras.Sequential()\n    result.add(layers.Conv2D(filters, size, strides=2, padding='same',\n                             kernel_initializer=initializer, use_bias=False))\n\n    if apply_instancenorm:\n        result.add(tfa.layers.InstanceNormalization(gamma_initializer=gamma_init))\n\n    result.add(layers.LeakyReLU())\n\n    return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:31.874735Z","iopub.execute_input":"2025-09-28T17:00:31.875219Z","iopub.status.idle":"2025-09-28T17:00:31.879517Z","shell.execute_reply.started":"2025-09-28T17:00:31.875199Z","shell.execute_reply":"2025-09-28T17:00:31.878934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def upsample(filters, size, apply_dropout=False):\n    initializer = tf.random_normal_initializer(0., 0.02)\n    gamma_init = keras.initializers.RandomNormal(mean=0.0, stddev=0.02)\n\n    result = keras.Sequential()\n    result.add(layers.Conv2DTranspose(filters, size, strides=2,\n                                      padding='same',\n                                      kernel_initializer=initializer,\n                                      use_bias=False))\n\n    result.add(tfa.layers.InstanceNormalization(gamma_initializer=gamma_init))\n\n    if apply_dropout:\n        result.add(layers.Dropout(0.5))\n\n    result.add(layers.ReLU())\n\n    return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:34.471940Z","iopub.execute_input":"2025-09-28T17:00:34.472440Z","iopub.status.idle":"2025-09-28T17:00:34.476980Z","shell.execute_reply.started":"2025-09-28T17:00:34.472417Z","shell.execute_reply":"2025-09-28T17:00:34.476303Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def Generator():\n    inputs = layers.Input(shape=[256,256,3])\n\n    # bs = batch size\n    down_stack = [\n        downsample(64, 4, apply_instancenorm=False), # (bs, 128, 128, 64)\n        downsample(128, 4), # (bs, 64, 64, 128)\n        downsample(256, 4), # (bs, 32, 32, 256)\n        downsample(512, 4), # (bs, 16, 16, 512)\n        downsample(512, 4), # (bs, 8, 8, 512)\n        downsample(512, 4), # (bs, 4, 4, 512)\n        downsample(512, 4), # (bs, 2, 2, 512)\n        downsample(512, 4), # (bs, 1, 1, 512)\n    ]\n\n    up_stack = [\n        upsample(512, 4, apply_dropout=True), # (bs, 2, 2, 1024)\n        upsample(512, 4, apply_dropout=True), # (bs, 4, 4, 1024)\n        upsample(512, 4, apply_dropout=True), # (bs, 8, 8, 1024)\n        upsample(512, 4), # (bs, 16, 16, 1024)\n        upsample(256, 4), # (bs, 32, 32, 512)\n        upsample(128, 4), # (bs, 64, 64, 256)\n        upsample(64, 4), # (bs, 128, 128, 128)\n    ]\n\n    initializer = tf.random_normal_initializer(0., 0.02)\n    last = layers.Conv2DTranspose(OUTPUT_CHANNELS, 4,\n                                  strides=2,\n                                  padding='same',\n                                  kernel_initializer=initializer,\n                                  activation='tanh') # (bs, 256, 256, 3)\n\n    x = inputs\n\n    # Downsampling through the model\n    skips = []\n    for down in down_stack:\n        x = down(x)\n        skips.append(x)\n\n    skips = reversed(skips[:-1])\n\n    # Upsampling and establishing the skip connections\n    for up, skip in zip(up_stack, skips):\n        x = up(x)\n        x = layers.Concatenate()([x, skip])\n\n    x = last(x)\n\n    return keras.Model(inputs=inputs, outputs=x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:37.503907Z","iopub.execute_input":"2025-09-28T17:00:37.504595Z","iopub.status.idle":"2025-09-28T17:00:37.513096Z","shell.execute_reply.started":"2025-09-28T17:00:37.504569Z","shell.execute_reply":"2025-09-28T17:00:37.512428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def Discriminator():\n    initializer = tf.random_normal_initializer(0., 0.02)\n    gamma_init = keras.initializers.RandomNormal(mean=0.0, stddev=0.02)\n\n    inp = layers.Input(shape=[256, 256, 3], name='input_image')\n\n    x = inp\n\n    down1 = downsample(64, 4, False)(x) # (bs, 128, 128, 64)\n    down2 = downsample(128, 4)(down1) # (bs, 64, 64, 128)\n    down3 = downsample(256, 4)(down2) # (bs, 32, 32, 256)\n\n    zero_pad1 = layers.ZeroPadding2D()(down3) # (bs, 34, 34, 256)\n    conv = layers.Conv2D(512, 4, strides=1,\n                         kernel_initializer=initializer,\n                         use_bias=False)(zero_pad1) # (bs, 31, 31, 512)\n\n    norm1 = tfa.layers.InstanceNormalization(gamma_initializer=gamma_init)(conv)\n\n    leaky_relu = layers.LeakyReLU()(norm1)\n\n    zero_pad2 = layers.ZeroPadding2D()(leaky_relu) # (bs, 33, 33, 512)\n\n    last = layers.Conv2D(1, 4, strides=1,\n                         kernel_initializer=initializer)(zero_pad2) # (bs, 30, 30, 1)\n\n    return tf.keras.Model(inputs=inp, outputs=last)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:42.594513Z","iopub.execute_input":"2025-09-28T17:00:42.594768Z","iopub.status.idle":"2025-09-28T17:00:42.600448Z","shell.execute_reply.started":"2025-09-28T17:00:42.594749Z","shell.execute_reply":"2025-09-28T17:00:42.599733Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#!pip install tfa-nightly\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T01:50:43.731570Z","iopub.execute_input":"2025-08-13T01:50:43.731757Z","iopub.status.idle":"2025-08-13T01:50:43.745750Z","shell.execute_reply.started":"2025-08-13T01:50:43.731743Z","shell.execute_reply":"2025-08-13T01:50:43.745134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#import tensorflow as tf\n#import tensorflow_addons as tfa\nmonet_generator = Generator()\nphoto_generator = Generator()\n\nmonet_discriminator = Discriminator()\nphoto_discriminator = Discriminator()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:46.522276Z","iopub.execute_input":"2025-09-28T17:00:46.522535Z","iopub.status.idle":"2025-09-28T17:00:48.895807Z","shell.execute_reply.started":"2025-09-28T17:00:46.522517Z","shell.execute_reply":"2025-09-28T17:00:48.895271Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CycleGan(keras.Model):\n    def __init__(\n        self,\n        monet_generator,\n        photo_generator,\n        monet_discriminator,\n        photo_discriminator,\n        lambda_cycle=10,\n    ):\n        super(CycleGan, self).__init__()\n        self.m_gen = monet_generator\n        self.p_gen = photo_generator\n        self.m_disc = monet_discriminator\n        self.p_disc = photo_discriminator\n        self.lambda_cycle = lambda_cycle\n        \n    def compile(\n        self,\n        m_gen_optimizer,\n        p_gen_optimizer,\n        m_disc_optimizer,\n        p_disc_optimizer,\n        gen_loss_fn,\n        disc_loss_fn,\n        cycle_loss_fn,\n        identity_loss_fn\n    ):\n        super(CycleGan, self).compile()\n        self.m_gen_optimizer = m_gen_optimizer\n        self.p_gen_optimizer = p_gen_optimizer\n        self.m_disc_optimizer = m_disc_optimizer\n        self.p_disc_optimizer = p_disc_optimizer\n        self.gen_loss_fn = gen_loss_fn\n        self.disc_loss_fn = disc_loss_fn\n        self.cycle_loss_fn = cycle_loss_fn\n        self.identity_loss_fn = identity_loss_fn\n        \n    def train_step(self, batch_data):\n        real_monet, real_photo = batch_data\n        \n        with tf.GradientTape(persistent=True) as tape:\n            # photo to monet back to photo\n            fake_monet = self.m_gen(real_photo, training=True)\n            cycled_photo = self.p_gen(fake_monet, training=True)\n\n            # monet to photo back to monet\n            fake_photo = self.p_gen(real_monet, training=True)\n            cycled_monet = self.m_gen(fake_photo, training=True)\n\n            # generating itself\n            same_monet = self.m_gen(real_monet, training=True)\n            same_photo = self.p_gen(real_photo, training=True)\n\n            # discriminator used to check, inputing real images\n            disc_real_monet = self.m_disc(real_monet, training=True)\n            disc_real_photo = self.p_disc(real_photo, training=True)\n\n            # discriminator used to check, inputing fake images\n            disc_fake_monet = self.m_disc(fake_monet, training=True)\n            disc_fake_photo = self.p_disc(fake_photo, training=True)\n\n            # evaluates generator loss\n            monet_gen_loss = self.gen_loss_fn(disc_fake_monet)\n            photo_gen_loss = self.gen_loss_fn(disc_fake_photo)\n\n            # evaluates total cycle consistency loss\n            total_cycle_loss = self.cycle_loss_fn(real_monet, cycled_monet, self.lambda_cycle) + self.cycle_loss_fn(real_photo, cycled_photo, self.lambda_cycle)\n\n            # evaluates total generator loss\n            total_monet_gen_loss = monet_gen_loss + total_cycle_loss + self.identity_loss_fn(real_monet, same_monet, self.lambda_cycle)\n            total_photo_gen_loss = photo_gen_loss + total_cycle_loss + self.identity_loss_fn(real_photo, same_photo, self.lambda_cycle)\n\n            # evaluates discriminator loss\n            monet_disc_loss = self.disc_loss_fn(disc_real_monet, disc_fake_monet)\n            photo_disc_loss = self.disc_loss_fn(disc_real_photo, disc_fake_photo)\n\n        # Calculate the gradients for generator and discriminator\n        monet_generator_gradients = tape.gradient(total_monet_gen_loss, self.m_gen.trainable_variables)\n        photo_generator_gradients = tape.gradient(total_photo_gen_loss, self.p_gen.trainable_variables)\n        monet_discriminator_gradients = tape.gradient(monet_disc_loss, self.m_disc.trainable_variables)\n        photo_discriminator_gradients = tape.gradient(photo_disc_loss, self.p_disc.trainable_variables)\n\n        # Apply the gradients to the optimizer\n        self.m_gen_optimizer.apply_gradients(zip(monet_generator_gradients, self.m_gen.trainable_variables))\n        self.p_gen_optimizer.apply_gradients(zip(photo_generator_gradients, self.p_gen.trainable_variables))\n        self.m_disc_optimizer.apply_gradients(zip(monet_discriminator_gradients, self.m_disc.trainable_variables))\n        self.p_disc_optimizer.apply_gradients(zip(photo_discriminator_gradients, self.p_disc.trainable_variables))\n        \n        return {\n            \"monet_gen_loss\": total_monet_gen_loss,\n            \"photo_gen_loss\": total_photo_gen_loss,\n            \"monet_disc_loss\": monet_disc_loss,\n            \"photo_disc_loss\": photo_disc_loss\n        }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:00:50.994024Z","iopub.execute_input":"2025-09-28T17:00:50.994613Z","iopub.status.idle":"2025-09-28T17:00:51.006786Z","shell.execute_reply.started":"2025-09-28T17:00:50.994590Z","shell.execute_reply":"2025-09-28T17:00:51.006026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def discriminator_loss(real, generated):\n    real_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(tf.ones_like(real), real)\n    generated_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(tf.zeros_like(generated), generated)\n    total_disc_loss = real_loss + generated_loss\n\n    return total_disc_loss * 0.5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:01:00.391798Z","iopub.execute_input":"2025-09-28T17:01:00.392056Z","iopub.status.idle":"2025-09-28T17:01:00.396498Z","shell.execute_reply.started":"2025-09-28T17:01:00.392038Z","shell.execute_reply":"2025-09-28T17:01:00.395826Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generator_loss(generated):\n    return tf.keras.losses.BinaryCrossentropy(from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(tf.ones_like(generated), generated)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:01:02.275282Z","iopub.execute_input":"2025-09-28T17:01:02.275575Z","iopub.status.idle":"2025-09-28T17:01:02.279206Z","shell.execute_reply.started":"2025-09-28T17:01:02.275555Z","shell.execute_reply":"2025-09-28T17:01:02.278615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calc_cycle_loss(real_image, cycled_image, LAMBDA):\n    loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))\n    return LAMBDA * loss1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:01:04.235343Z","iopub.execute_input":"2025-09-28T17:01:04.235859Z","iopub.status.idle":"2025-09-28T17:01:04.239598Z","shell.execute_reply.started":"2025-09-28T17:01:04.235837Z","shell.execute_reply":"2025-09-28T17:01:04.238867Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def identity_loss(real_image, same_image, LAMBDA):\n    loss = tf.reduce_mean(tf.abs(real_image - same_image))\n    return LAMBDA * 0.5 * loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:01:05.710828Z","iopub.execute_input":"2025-09-28T17:01:05.711340Z","iopub.status.idle":"2025-09-28T17:01:05.715164Z","shell.execute_reply.started":"2025-09-28T17:01:05.711317Z","shell.execute_reply":"2025-09-28T17:01:05.714519Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1>Train the model</h1>\nWe can use the keras optimizer to tune our hyperparameters and then train the model.\nUsing the \"Adaptive Moment Estimation\"(Adam) optimizer, we will tune the learning rate and beta_1, which controls exponential decay rate of the first moment estimate.\nAdam is designed ot reduce the need for manual hyper parameter tuning by adatping its learning rate per parameter based on gradient history.\nhttps://pub.aimind.so/keras-optimizers-explained-adam-optimizer-521638f6be0c","metadata":{}},{"cell_type":"code","source":"monet_generator_optimizer = tf.keras.optimizers.Adam(1e-4, beta_1=0.5)\nphoto_generator_optimizer = tf.keras.optimizers.Adam(1e-4, beta_1=0.5)\n\nmonet_discriminator_optimizer = tf.keras.optimizers.Adam(1e-4, beta_1=0.5)\nphoto_discriminator_optimizer = tf.keras.optimizers.Adam(1e-4, beta_1=0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:01:07.674977Z","iopub.execute_input":"2025-09-28T17:01:07.675246Z","iopub.status.idle":"2025-09-28T17:01:07.691662Z","shell.execute_reply.started":"2025-09-28T17:01:07.675226Z","shell.execute_reply":"2025-09-28T17:01:07.690969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cycle_gan_model = CycleGan(\n    monet_generator, photo_generator, monet_discriminator, photo_discriminator\n)\n\n# compile the model\ncycle_gan_model.compile(\n    m_gen_optimizer = monet_generator_optimizer,\n    p_gen_optimizer = photo_generator_optimizer,\n    m_disc_optimizer = monet_discriminator_optimizer,\n    p_disc_optimizer = photo_discriminator_optimizer,\n    gen_loss_fn = generator_loss,\n    disc_loss_fn = discriminator_loss,\n    cycle_loss_fn = calc_cycle_loss,\n    identity_loss_fn = identity_loss\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:01:09.932690Z","iopub.execute_input":"2025-09-28T17:01:09.932951Z","iopub.status.idle":"2025-09-28T17:01:09.945806Z","shell.execute_reply.started":"2025-09-28T17:01:09.932932Z","shell.execute_reply":"2025-09-28T17:01:09.945206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cycle_gan_model.fit(\n    tf.data.Dataset.zip((monet_ds, photo_ds)),\n    epochs=3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:01:17.955018Z","iopub.execute_input":"2025-09-28T17:01:17.955299Z","iopub.status.idle":"2025-09-28T17:05:09.107867Z","shell.execute_reply.started":"2025-09-28T17:01:17.955278Z","shell.execute_reply":"2025-09-28T17:05:09.107264Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Let's visualize our Monet-generated images","metadata":{}},{"cell_type":"code","source":"_, ax = plt.subplots(5, 2, figsize=(12, 12))\nfor i, img in enumerate(photo_ds.take(5)):\n    prediction = monet_generator(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-09-28T17:17:24.877124Z","iopub.execute_input":"2025-09-28T17:17:24.877781Z","iopub.status.idle":"2025-09-28T17:17:26.615182Z","shell.execute_reply.started":"2025-09-28T17:17:24.877754Z","shell.execute_reply":"2025-09-28T17:17:26.614414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import PIL\n!mkdir ./submit_images","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:17:37.014232Z","iopub.execute_input":"2025-09-28T17:17:37.014551Z","iopub.status.idle":"2025-09-28T17:17:37.243895Z","shell.execute_reply.started":"2025-09-28T17:17:37.014529Z","shell.execute_reply":"2025-09-28T17:17:37.243055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"i = 1\nfor img in photo_ds.take(-1): # -1 returns all the samples in the dataset\n    prediction = monet_generator(img)\n    prediction = prediction * 0.5 + 0.5\n    im = tf.keras.utils.array_to_img(prediction[0], data_format=None, scale=True, dtype=None)\n    # im.show()\n    im.save(f'./submit_images/{i}.jpg')\n    plt.close()\n    i += 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:17:39.585909Z","iopub.execute_input":"2025-09-28T17:17:39.586196Z","iopub.status.idle":"2025-09-28T17:25:36.259038Z","shell.execute_reply.started":"2025-09-28T17:17:39.586171Z","shell.execute_reply":"2025-09-28T17:25:36.258378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\nshutil.make_archive(\"./images\", 'zip', \"./submit_images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T17:35:48.031170Z","iopub.execute_input":"2025-09-28T17:35:48.031944Z","iopub.status.idle":"2025-09-28T17:35:50.170146Z","shell.execute_reply.started":"2025-09-28T17:35:48.031917Z","shell.execute_reply":"2025-09-28T17:35:50.169571Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"References:\nhttps://www.kaggle.com/competitions/gan-getting-started\nhttps://www.tensorflow.org/tutorials/generative/cyclegan\nhttps://keras.io/examples/generative/cyclegan/\nhttps://pub.aimind.so/keras-optimizers-explained-adam-optimizer-521638f6be0c","metadata":{}}]}