{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **Libraries**","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, Model\nfrom tensorflow.keras.layers import GroupNormalization\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Layer, Conv2D, Conv2DTranspose, ReLU, Activation, Lambda, GroupNormalization\nimport numpy as np \nimport pandas as pd\nimport os\nimport time\nimport matplotlib.pyplot as plt\nfrom IPython.display import clear_output\nimport cv2\n\nAUTOTUNE = tf.data.AUTOTUNE","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:37.009775Z","iopub.execute_input":"2025-07-05T08:15:37.010458Z","iopub.status.idle":"2025-07-05T08:15:50.497953Z","shell.execute_reply.started":"2025-07-05T08:15:37.010425Z","shell.execute_reply":"2025-07-05T08:15:50.497238Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Configurations**","metadata":{}},{"cell_type":"code","source":"monet_path = \"/kaggle/input/gan-getting-started/monet_jpg\"\nphotos_path = \"/kaggle/input/gan-getting-started/photo_jpg\"\n\nBUFFER_SIZE = 1000\nIMG_WIDTH = 256\nIMG_HEIGHT = 256\nAUG_PER_IMAGE = 5\nBATCH_SIZE = 8","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:50.498957Z","iopub.execute_input":"2025-07-05T08:15:50.499390Z","iopub.status.idle":"2025-07-05T08:15:50.503233Z","shell.execute_reply.started":"2025-07-05T08:15:50.499371Z","shell.execute_reply":"2025-07-05T08:15:50.502517Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Data loading and Preprocessing**","metadata":{}},{"cell_type":"code","source":"def count_images(directory):\n    return sum(1 for file in os.scandir(directory) \n               if file.name.lower().endswith(('.jpg', '.jpeg', '.png')))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:50.503981Z","iopub.execute_input":"2025-07-05T08:15:50.504218Z","iopub.status.idle":"2025-07-05T08:15:50.536278Z","shell.execute_reply.started":"2025-07-05T08:15:50.504196Z","shell.execute_reply":"2025-07-05T08:15:50.535682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f'Total Monet images: {count_images(monet_path)}')\nprint(f'Total photos: {count_images(photos_path)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:50.537986Z","iopub.execute_input":"2025-07-05T08:15:50.538190Z","iopub.status.idle":"2025-07-05T08:15:50.595767Z","shell.execute_reply.started":"2025-07-05T08:15:50.538176Z","shell.execute_reply":"2025-07-05T08:15:50.595206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''transform = A.Compose(\n    [\n        A.Resize(width = 256, height = 256),\n        A.HorizontalFlip(p = 0.5),\n        A.ColorJitter(p = 0.1),\n        A.Normalize(mean = [0.5, 0.5, 0.5], \n                    std = [0.5, 0.5, 0.5], \n                    max_pixel_value = 255),\n        ToTensorV2(),\n    ],\n    additional_targets = {\"image0\": \"image\"}\n)'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:50.596379Z","iopub.execute_input":"2025-07-05T08:15:50.596618Z","iopub.status.idle":"2025-07-05T08:15:50.602069Z","shell.execute_reply.started":"2025-07-05T08:15:50.596602Z","shell.execute_reply":"2025-07-05T08:15:50.601402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"RANDOM_SEED = 42\ndef load_and_preprocess(file_path, augmentations_per_image=AUG_PER_IMAGE, seed=42):\n    image = tf.io.read_file(file_path)\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = tf.image.resize(image, [286, 286])\n\n    def augment_fn(i):\n        # Split the global seed into two parts: one per augmentation + image index\n        aug_seed = tf.stack([seed, i])\n        aug = tf.image.stateless_random_crop(image, [256, 256, 3], seed=aug_seed)\n        aug = tf.image.stateless_random_flip_left_right(aug, seed=aug_seed + 1)\n        aug = tf.cast(aug, tf.float32) / 127.5 - 1.0\n        return aug\n\n    return tf.data.Dataset.range(augmentations_per_image).map(augment_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:50.602648Z","iopub.execute_input":"2025-07-05T08:15:50.602852Z","iopub.status.idle":"2025-07-05T08:15:50.620130Z","shell.execute_reply.started":"2025-07-05T08:15:50.602835Z","shell.execute_reply":"2025-07-05T08:15:50.619541Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Create datasets of Monet and normal photos**","metadata":{}},{"cell_type":"code","source":"tf_monet = tf.data.Dataset.list_files(monet_path + \"/*.jpg\", shuffle=True)\ntf_photos = tf.data.Dataset.list_files(photos_path + \"/*.jpg\", shuffle=True)\n\nmonet_dataset = (\n    tf_monet\n    .flat_map(lambda x: load_and_preprocess(x, seed=RANDOM_SEED))\n    .shuffle(BUFFER_SIZE, seed=RANDOM_SEED)\n    .batch(BATCH_SIZE)\n    .prefetch(tf.data.AUTOTUNE)\n)\n\nphoto_dataset = (\n    tf_photos\n    .flat_map(lambda x: load_and_preprocess(x, seed=RANDOM_SEED))\n    .shuffle(BUFFER_SIZE, seed=RANDOM_SEED)\n    .batch(BATCH_SIZE)\n    .prefetch(tf.data.AUTOTUNE)\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:50.620800Z","iopub.execute_input":"2025-07-05T08:15:50.621118Z","iopub.status.idle":"2025-07-05T08:15:55.398284Z","shell.execute_reply.started":"2025-07-05T08:15:50.621101Z","shell.execute_reply":"2025-07-05T08:15:55.397741Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Counting data, can be excluded and deleted","metadata":{}},{"cell_type":"code","source":"'''monet_count = sum(1 for _ in monet_dataset)\nphoto_count = sum(1 for _ in photo_dataset)\n\nprint(\"Monet dataset size:\", \n      monet_count)\nprint(\"Photo dataset size:\", photo_count)\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:55.398975Z","iopub.execute_input":"2025-07-05T08:15:55.399241Z","iopub.status.idle":"2025-07-05T08:15:55.403976Z","shell.execute_reply.started":"2025-07-05T08:15:55.399219Z","shell.execute_reply":"2025-07-05T08:15:55.403218Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Visualizing dataset in batches**","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef display_images_from_dataset(dataset, title='Images', num_batches=4) -> None:\n    for batch in dataset.take(num_batches):\n        batch = (batch + 1.0) / 2.0  # convert from [-1, 1] to [0, 1] for display\n        batch = tf.clip_by_value(batch, 0.0, 1.0)  # just in case there are errors, but quite unnecessary\n        batch_np = batch.numpy() # to numpy to display as the datasets are in form of tensors\n\n        plt.figure(figsize=(15, 5))\n        for i in range(batch_np.shape[0]):\n            plt.subplot(1, batch_np.shape[0], i + 1)\n            plt.imshow(batch_np[i])\n            plt.axis(\"off\")\n        plt.suptitle(title)\n        plt.show()\ndisplay_images_from_dataset(monet_dataset, title=\"Monet Augmented Batch\")\ndisplay_images_from_dataset(photo_dataset, title=\"Photo Augmented Batch\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:15:55.404681Z","iopub.execute_input":"2025-07-05T08:15:55.404978Z","iopub.status.idle":"2025-07-05T08:16:03.166796Z","shell.execute_reply.started":"2025-07-05T08:15:55.404963Z","shell.execute_reply":"2025-07-05T08:16:03.166099Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Reflection Padding**","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.layers import Layer, InputSpec\n\nclass ReflectionPadding2D(Layer):\n    def __init__(self, padding=(1, 1), **kwargs):\n        self.padding = tuple(padding)\n        self.input_spec = [InputSpec(ndim=4)]\n        super(ReflectionPadding2D, self).__init__(**kwargs)\n\n    def call(self, x, mask=None):\n        w_pad, h_pad = self.padding\n        return tf.pad(x, [[0, 0], [h_pad, h_pad], [w_pad, w_pad], [0, 0]], mode='REFLECT')\n\n    def compute_output_shape(self, input_shape):\n        return (input_shape[0],\n                input_shape[1] + 2 * self.padding[1],\n                input_shape[2] + 2 * self.padding[0],\n                input_shape[3])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.170366Z","iopub.execute_input":"2025-07-05T08:16:03.170679Z","iopub.status.idle":"2025-07-05T08:16:03.178586Z","shell.execute_reply.started":"2025-07-05T08:16:03.170652Z","shell.execute_reply":"2025-07-05T08:16:03.177783Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Instance Normalization by Group Normalization**","metadata":{}},{"cell_type":"code","source":"class InstanceLikeGroupNorm(tf.keras.layers.Layer):\n    def __init__(self, **kwargs):\n        super().__init__()\n        self.kwargs = kwargs\n        self.gn = None\n\n    def build(self, input_shape):\n        channels = input_shape[-1]\n        self.gn = tf.keras.layers.GroupNormalization(groups=channels, axis=-1, **self.kwargs)\n\n    def call(self, inputs):\n        return self.gn(inputs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.179518Z","iopub.execute_input":"2025-07-05T08:16:03.179859Z","iopub.status.idle":"2025-07-05T08:16:03.198657Z","shell.execute_reply.started":"2025-07-05T08:16:03.179834Z","shell.execute_reply":"2025-07-05T08:16:03.197909Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Downsample and Upsample**","metadata":{}},{"cell_type":"code","source":"class DownsampleBlock(tf.keras.layers.Layer):\n    def __init__(self, filters, kernel_size, apply_norm=True):\n        super().__init__()\n        self.model = tf.keras.Sequential()\n        self.model.add(layers.Conv2D(filters, kernel_size, strides=2, padding='same',\n                                     kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                                     use_bias=False))\n        if apply_norm:\n            self.model.add(InstanceLikeGroupNorm())\n        self.model.add(layers.LeakyReLU())\n\n    def call(self, x):\n        return self.model(x)\n\n\nclass UpsampleBlock(tf.keras.layers.Layer):\n    def __init__(self, filters, kernel_size):\n        super().__init__()\n        self.model = tf.keras.Sequential([\n            layers.Conv2DTranspose(filters, kernel_size, strides=2, padding='same',\n                                   kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                                   use_bias=False),\n            InstanceLikeGroupNorm(),\n            layers.ReLU()\n        ])\n\n    def call(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.199389Z","iopub.execute_input":"2025-07-05T08:16:03.199578Z","iopub.status.idle":"2025-07-05T08:16:03.214560Z","shell.execute_reply.started":"2025-07-05T08:16:03.199564Z","shell.execute_reply":"2025-07-05T08:16:03.213906Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Residual Block**","metadata":{}},{"cell_type":"code","source":"class ResBlock(tf.keras.layers.Layer):\n    def __init__(self, filters):\n        super().__init__()\n        self.pad1 = ReflectionPadding2D((1, 1))\n        self.conv1 = layers.Conv2D(filters, 3, padding='valid',\n                                   kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                                   use_bias=False)\n        self.norm1 = InstanceLikeGroupNorm()\n        self.relu = layers.ReLU()\n\n        self.pad2 = ReflectionPadding2D((1, 1))\n        self.conv2 = layers.Conv2D(filters, 3, padding='valid',\n                                   kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                                   use_bias=False)\n        self.norm2 = InstanceLikeGroupNorm()\n\n    def call(self, x_input):\n        x = self.pad1(x_input)\n        x = self.conv1(x)\n        x = self.norm1(x)\n        x = self.relu(x)\n\n        x = self.pad2(x)\n        x = self.conv2(x)\n        x = self.norm2(x)\n\n        return layers.Add()([x_input, x])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.215241Z","iopub.execute_input":"2025-07-05T08:16:03.215476Z","iopub.status.idle":"2025-07-05T08:16:03.232671Z","shell.execute_reply.started":"2025-07-05T08:16:03.215450Z","shell.execute_reply":"2025-07-05T08:16:03.231982Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **ResNet Generator**","metadata":{}},{"cell_type":"code","source":"class ResNetGenerator(tf.keras.Model):\n    def __init__(self, output_channels=3, n_blocks=9):\n        super().__init__()\n        self.initializer = tf.random_normal_initializer(0., 0.02)\n\n        self.pad1 = ReflectionPadding2D((3, 3))\n        self.conv1 = layers.Conv2D(64, kernel_size=7, padding='valid',\n                                   kernel_initializer=self.initializer, use_bias=False)\n        self.norm1 = InstanceLikeGroupNorm()\n        self.relu1 = layers.ReLU()\n\n        self.down1 = DownsampleBlock(128, 3)\n        self.down2 = DownsampleBlock(256, 3)\n\n        self.res_blocks = [ResBlock(256) for _ in range(n_blocks)]\n\n        self.up1 = UpsampleBlock(128, 3)\n        self.up2 = UpsampleBlock(64, 3)\n\n        self.pad2 = ReflectionPadding2D((3, 3))\n        self.out_conv = layers.Conv2D(output_channels, kernel_size=7, padding='valid',\n                                      kernel_initializer=self.initializer, activation='tanh')\n\n    def call(self, inputs, training=None):  # Added training\n        x = self.pad1(inputs)\n        x = self.conv1(x)\n        x = self.norm1(x, training=training)  # Important if norm uses training\n        x = self.relu1(x)\n\n        x = self.down1(x, training=training)\n        x = self.down2(x, training=training)\n\n        for block in self.res_blocks:\n            x = block(x, training=training)\n\n        x = self.up1(x, training=training)\n        x = self.up2(x, training=training)\n\n        x = self.pad2(x)\n        return self.out_conv(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.233459Z","iopub.execute_input":"2025-07-05T08:16:03.233667Z","iopub.status.idle":"2025-07-05T08:16:03.247398Z","shell.execute_reply.started":"2025-07-05T08:16:03.233649Z","shell.execute_reply":"2025-07-05T08:16:03.246896Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **PatchGAN Discriminator**","metadata":{}},{"cell_type":"code","source":"class PatchGANDiscriminator(tf.keras.Model):\n    def __init__(self, target=False):\n        super().__init__()\n        self.initializer = tf.random_normal_initializer(0., 0.02)\n        self.target = target\n\n        self.down1 = DownsampleBlock(64, 4, apply_norm=False)\n        self.down2 = DownsampleBlock(128, 4)\n        self.down3 = DownsampleBlock(256, 4)\n\n        self.pad1 = ReflectionPadding2D((1, 1))\n        self.conv = layers.Conv2D(512, 4, strides=1, padding='valid',\n                                  kernel_initializer=self.initializer, use_bias=False)\n        self.norm = InstanceLikeGroupNorm()\n        self.leaky_relu = layers.LeakyReLU()\n\n        self.pad2 = ReflectionPadding2D((1, 1))\n        self.final = layers.Conv2D(1, 4, strides=1, padding='valid',\n                                   kernel_initializer=self.initializer)\n\n    def call(self, input_image, target_image=None, training=None):  # Added training\n        x = tf.concat([input_image, target_image], axis=-1) if self.target else input_image\n        x = self.down1(x, training=training)\n        x = self.down2(x, training=training)\n        x = self.down3(x, training=training)\n\n        x = self.pad1(x)\n        x = self.conv(x)\n        x = self.norm(x, training=training)\n        x = self.leaky_relu(x)\n\n        x = self.pad2(x)\n        return self.final(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.248063Z","iopub.execute_input":"2025-07-05T08:16:03.248222Z","iopub.status.idle":"2025-07-05T08:16:03.266979Z","shell.execute_reply.started":"2025-07-05T08:16:03.248210Z","shell.execute_reply":"2025-07-05T08:16:03.266262Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Generators and Discriminators initialize**","metadata":{}},{"cell_type":"code","source":"# Generator: Translates images from Photo → Monet\ngenerator_photo_to_monet = ResNetGenerator(output_channels=3, n_blocks=9)\n\n# Generator: Translates images from Monet → Photo\ngenerator_monet_to_photo = ResNetGenerator(output_channels=3, n_blocks=9)\n\n# Discriminator: Judges real vs fake Monet images\ndiscriminator_monet = PatchGANDiscriminator(target=False)\n\n# Discriminator: Judges real vs fake Photo images\ndiscriminator_photo = PatchGANDiscriminator(target=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.268036Z","iopub.execute_input":"2025-07-05T08:16:03.268244Z","iopub.status.idle":"2025-07-05T08:16:03.670370Z","shell.execute_reply.started":"2025-07-05T08:16:03.268227Z","shell.execute_reply":"2025-07-05T08:16:03.669826Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef visualize_results(input_img, fake_img, reconstructed_img, title_prefix=\"\"):\n    input_img = (input_img + 1) / 2  # from [-1, 1] → [0, 1]\n    fake_img = (fake_img + 1) / 2\n    reconstructed_img = (reconstructed_img + 1) / 2\n\n    plt.figure(figsize=(12, 4))\n\n    titles = [f\"{title_prefix}Input\", f\"{title_prefix}Fake\", f\"{title_prefix}Reconstructed\"]\n    images = [input_img[0], fake_img[0], reconstructed_img[0]]\n\n    for i in range(3):\n        plt.subplot(1, 3, i + 1)\n        plt.imshow(images[i])\n        plt.title(titles[i])\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.671439Z","iopub.execute_input":"2025-07-05T08:16:03.671647Z","iopub.status.idle":"2025-07-05T08:16:03.676743Z","shell.execute_reply.started":"2025-07-05T08:16:03.671631Z","shell.execute_reply":"2025-07-05T08:16:03.676036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_input = tf.random.normal([1, 256, 256, 3])\n\nfake_monet = generator_photo_to_monet(sample_input)\nreconstructed_photo = generator_monet_to_photo(fake_monet)\n\ndecision_photo = discriminator_photo(sample_input)\ndecision_monet = discriminator_monet(fake_monet)\n\nprint(\"Fake Monet shape:         \", fake_monet.shape)\nprint(\"Reconstructed Photo shape:\", reconstructed_photo.shape)\nprint(\"Disc Photo output shape:  \", decision_photo.shape)\nprint(\"Disc Monet output shape:  \", decision_monet.shape)\n\nvisualize_results(sample_input, fake_monet, reconstructed_photo, title_prefix=\"Photo → Monet → Photo: \")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:03.677527Z","iopub.execute_input":"2025-07-05T08:16:03.677759Z","iopub.status.idle":"2025-07-05T08:16:08.533178Z","shell.execute_reply.started":"2025-07-05T08:16:03.677734Z","shell.execute_reply":"2025-07-05T08:16:08.532087Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Optimizers**","metadata":{}},{"cell_type":"code","source":"# Generator: Translates images from Photo → Monet\ngenerator_photo_to_monet_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n\n# Generator: Translates images from Monet → Photo\ngenerator_monet_to_photo_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n\n# Discriminator: Judges real vs fake Monet images\ndiscriminator_monet_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n# Discriminator: Judges real vs fake Photo images\ndiscriminator_photo_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.534212Z","iopub.execute_input":"2025-07-05T08:16:08.534480Z","iopub.status.idle":"2025-07-05T08:16:08.553781Z","shell.execute_reply.started":"2025-07-05T08:16:08.534450Z","shell.execute_reply":"2025-07-05T08:16:08.553114Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Loss Module**","metadata":{}},{"cell_type":"code","source":"LAMBDA = 10\nloss_func1 = tf.keras.losses.BinaryCrossentropy(from_logits=True)\nloss_func2 = tf.keras.losses.MeanSquaredError()\nloss_object = loss_func1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.554539Z","iopub.execute_input":"2025-07-05T08:16:08.554737Z","iopub.status.idle":"2025-07-05T08:16:08.580588Z","shell.execute_reply.started":"2025-07-05T08:16:08.554722Z","shell.execute_reply":"2025-07-05T08:16:08.580021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Loss:\n    def __init__(self, real_image = None, synthetic_image = None, mode=None, LAMBDA=10, loss_object=None):\n        self.real_img = real_image\n        self.synth_img = synthetic_image\n        self.mode = mode\n        self.LAMBDA = LAMBDA\n        self.loss_obj = loss_object\n\n        if self.mode == 'Identity':\n            self.value = self.identity_loss(self.real_img, self.synth_img)\n        elif self.mode == 'Cycle':\n            self.value = self.calc_cycle_loss(self.real_img, self.synth_img)\n        elif self.mode == 'Generator':\n            self.value = self.generator_loss(self.synth_img)\n        elif self.mode == 'Discriminator':\n            self.value = self.discriminator_loss(self.real_img, self.synth_img)\n        else:\n            raise ValueError(f\"Unknown mode: {self.mode}\")\n\n    def discriminator_loss(self, real, generated):\n        real_loss = self.loss_obj(tf.ones_like(real), real)\n        generated_loss = self.loss_obj(tf.zeros_like(generated), generated)\n        total_disc_loss = real_loss + generated_loss\n        return total_disc_loss * 0.5\n\n    def generator_loss(self, generated):\n        return self.loss_obj(tf.ones_like(generated), generated)\n\n    def calc_cycle_loss(self, real_image, cycled_image):\n        loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))\n        return self.LAMBDA * loss1\n\n    def identity_loss(self, real_image, same_image):\n        loss = tf.reduce_mean(tf.abs(real_image - same_image))\n        return self.LAMBDA * 0.5 * loss\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.581304Z","iopub.execute_input":"2025-07-05T08:16:08.581544Z","iopub.status.idle":"2025-07-05T08:16:08.597520Z","shell.execute_reply.started":"2025-07-05T08:16:08.581528Z","shell.execute_reply":"2025-07-05T08:16:08.596987Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"When calling Loss, put in the mode and use .value\nExample:\nidentity_loss = Loss(real_image, same_image, mode=\"Identity\", LAMBDA=10, loss_object=loss_obj).value\n","metadata":{}},{"cell_type":"markdown","source":"# **Checkpoint saver**","metadata":{}},{"cell_type":"code","source":"class CheckPoint:\n    def __init__(self, checkpoint_path, max_ckpts,\n                 generators, discriminators, optimizers):\n        self.path = checkpoint_path\n        self.keep = max_ckpts\n\n        self.checkpoint = tf.train.Checkpoint(\n            generator_G=generators[\"G\"],\n            generator_F=generators[\"F\"],\n            discriminator_X=discriminators[\"X\"],\n            discriminator_Y=discriminators[\"Y\"],\n            generator_G_opt=optimizers[\"G\"],\n            generator_F_opt=optimizers[\"F\"],\n            discriminator_X_opt=optimizers[\"X\"],\n            discriminator_Y_opt=optimizers[\"Y\"],\n        )\n\n        self.ckpt_manager = tf.train.CheckpointManager(\n            self.checkpoint, self.path, max_to_keep=self.keep\n        )\n\n    def restore_latest(self, expect_partial=True):\n        if self.ckpt_manager.latest_checkpoint:\n            print(f\"Restoring from {self.ckpt_manager.latest_checkpoint}\")\n            restore_obj = self.checkpoint.restore(self.ckpt_manager.latest_checkpoint)\n            if expect_partial:\n                restore_obj.expect_partial()\n            else:\n                restore_obj.assert_consumed()\n        else:\n            print(\"No checkpoint found at the given path.\")\n\n\n    def save(self):\n        ckpt_path = self.ckpt_manager.save()\n        print(f\"Checkpoint saved at {ckpt_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.598373Z","iopub.execute_input":"2025-07-05T08:16:08.598841Z","iopub.status.idle":"2025-07-05T08:16:08.616199Z","shell.execute_reply.started":"2025-07-05T08:16:08.598819Z","shell.execute_reply":"2025-07-05T08:16:08.615507Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''generators = {\n    \"G\": generator_G,  # maps X -> Y\n    \"F\": generator_F,  # maps Y -> X\n}\n\ndiscriminators = {\n    \"X\": discriminator_X,  # discriminates real vs fake X\n    \"Y\": discriminator_Y,  # discriminates real vs fake Y\n}\n\noptimizers = {\n    \"G\": generator_G_opt,\n    \"F\": generator_F_opt,\n    \"X\": discriminator_X_opt,\n    \"Y\": discriminator_Y_opt,\n}\n\n\nckpt_manager = CheckPoint(\"checkpoints/cyclegan\", 10, generators, discriminators, optimizers)\n\nckpt_manager.restore_latest()\n\n# during training:\n# ckpt_manager.save()\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.617026Z","iopub.execute_input":"2025-07-05T08:16:08.617270Z","iopub.status.idle":"2025-07-05T08:16:08.635418Z","shell.execute_reply.started":"2025-07-05T08:16:08.617240Z","shell.execute_reply":"2025-07-05T08:16:08.634818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@tf.function\ndef train_step1(image_X, image_Y):\n    # persistent is set to True because the tape is used more than once to calculate the gradients.\n    with tf.GradientTape(persistent=True) as tape:\n        # generator G translates X -> Y\n        # generator F translates Y -> X.\n\n        fake_Y = generator_G(image_X, training=True)\n        cycled_Y = generator_F(fake_X, training=True)\n\n        fake_X = generator_F(image_Y, training=True)\n        cycled_X = generator_G(fake_Y, training=True)\n\n        # same_x and same_y are used for identity loss.\n        same_X = generator_F(image_X, training=True)\n        same_Y = generator_G(image_Y, training=True)\n\n        discriminator_real_X = discriminator_X(image_X, training=True)\n        discriminator_real_Y = discriminator_Y(image_Y, training=True)\n\n        discriminator_fake_X = discriminator_X(fake_X, training=True)\n        discriminator_fake_Y = discriminator_Y(fake_Y, training=True)\n\n        # calculate the loss\n        # generator loss\n        generator_G_loss = Loss(discriminator_fake_Y, mode='Generator', loss_object=loss_obj)\n        generator_F_loss = Loss(discriminator_fake_X, mode='Generator', loss_object=loss_obj)\n\n        # cycle loss\n        total_cycle_loss = (\n            Loss(image_Y, cycled_Y, mode='Cycle', loss_object=loss_obj) +\n            Loss(image_X, cycled_X, mode='Cycle', loss_object=loss_obj)\n        )\n\n        # identity loss\n        gen_G_identity_loss = Loss(image_Y, same_Y, mode='Identity', loss_object=loss_obj)\n        gen_F_identity_loss = Loss(image_X, same_X, mode='Identity', loss_object=loss_obj)\n\n        # discriminator loss\n        discriminator_X_loss = Loss(discriminator_real_X, discriminator_fake_X, mode='Discriminator', loss_object=loss_obj)\n        discriminator_Y_loss = Loss(discriminator_real_Y, discriminator_fake_Y, mode='Discriminator', loss_object=loss_obj)\n\n        # total generator loss\n        total_generator_G_loss = generator_G_loss + total_cycle_loss + gen_G_identity_loss\n        total_generator_F_loss = generator_F_loss + total_cycle_loss + gen_F_identity_loss\n\n    # Calculate the gradients for generator and discriminator\n    generator_G_grad = tape.gradient(total_generator_G_loss, generator_G.trainable_variables)\n    generator_F_grad = tape.gradient(total_generator_F_loss, generator_F.trainable_variables)\n\n    discriminator_X_grad = tape.gradient(discriminator_X_loss, discriminator_X.trainable_variables)\n    discriminator_Y_grad = tape.gradient(discriminator_Y_loss, discriminator_Y.trainable_variables)\n\n    # Apply the gradients to the optimizer\n    generator_G_opt.apply_gradients(zip(generator_G_grad, generator_G.trainable_variables))\n    generator_F_opt.apply_gradients(zip(generator_F_grad, generator_F.trainable_variables))\n\n    discriminator_X_opt.apply_gradients(zip(discriminator_X_grad, discriminator_X.trainable_variables))\n    discriminator_Y_opt.apply_gradients(zip(discriminator_Y_grad, discriminator_Y.trainable_variables))\n","metadata":{"trusted":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-05T08:16:08.636041Z","iopub.execute_input":"2025-07-05T08:16:08.636269Z","iopub.status.idle":"2025-07-05T08:16:08.651225Z","shell.execute_reply.started":"2025-07-05T08:16:08.636235Z","shell.execute_reply":"2025-07-05T08:16:08.650597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_loop1(EPOCHS, sample_image = None):\n    for epoch in range(EPOCHS):\n        start = time.time()\n        n = 0\n        for image_X, image_Y in tf.data.Dataset.zip((dataA, dataB)): # IT IS BETTER TO REPEAT THE MONET DATA \n                                                                    # to obtain the same shape for the 2 datasets\n            train_step(image_x, image_y)\n            if n % 10 == 0:\n                print('.', end='')\n            n += 1\n\n        clear_output(wait=True)\n        # Using a consistent image so that the progress of the model\n        # is clearly visible.\n        generate_images(generator_g, sample_image)\n\n        if (epoch + 1) % 5 == 0:\n            ckpt_save_path = ckpt_manager.save()\n            print('Saving checkpoint for epoch {} at {}'.format(epoch + 1,\n                                                                ckpt_save_path))\n\n        print('Time taken for epoch {} is {} sec\\n'.format(epoch + 1,\n                                                           time.time() - start))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.651834Z","iopub.execute_input":"2025-07-05T08:16:08.652129Z","iopub.status.idle":"2025-07-05T08:16:08.670203Z","shell.execute_reply.started":"2025-07-05T08:16:08.652105Z","shell.execute_reply":"2025-07-05T08:16:08.669620Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CycleGAN1():\n    def __init__(self, EPOCHS, sample_image, save_path, generators, discriminators, optimizers, image_X, image_Y):\n        self.EPOCHS = EPOCHS\n        self.sample_image = sample_image\n        self.image_X = image_X\n        self.image_Y = image_Y\n\n        # Assign generators\n        self.generator_G = generators[\"G\"]\n        self.generator_F = generators[\"F\"]\n\n        # Assign discriminators\n        self.discriminator_X = discriminators[\"X\"]\n        self.discriminator_Y = discriminators[\"Y\"]\n\n        # Assign optimizers\n        self.generator_G_opt = optimizers[\"G\"]\n        self.generator_F_opt = optimizers[\"F\"]\n        self.discriminator_X_opt = optimizers[\"X\"]\n        self.discriminator_Y_opt = optimizers[\"Y\"]\n\n        # Create and restore checkpoint manager\n        self.ckpt_manager = CheckPoint(save_path, 10, generators, discriminators, optimizers)\n        self.ckpt_manager.restore_latest()\n\n    @tf.function\n    def train_step(self, image_X, image_Y):\n        # persistent is set to True because the tape is used more than once to calculate the gradients.\n        with tf.GradientTape(persistent=True) as tape:\n            # generator G translates X -> Y\n            # generator F translates Y -> X.\n\n            fake_Y = self.generator_G(image_X, training=True)\n            fake_X = self.generator_F(image_Y, training=True)\n\n            cycled_Y = self.generator_F(fake_X, training=True)\n            cycled_X = self.generator_G(fake_Y, training=True)\n\n            # same_x and same_y are used for identity loss.\n            same_X = self.generator_F(image_X, training=True)\n            same_Y = self.generator_G(image_Y, training=True)\n\n            discriminator_real_X = self.discriminator_X(image_X, training=True)\n            discriminator_real_Y = self.discriminator_Y(image_Y, training=True)\n\n            discriminator_fake_X = self.discriminator_X(fake_X, training=True)\n            discriminator_fake_Y = self.discriminator_Y(fake_Y, training=True)\n\n            # calculate the loss\n            # generator loss\n            generator_G_loss = Loss(discriminator_fake_Y, mode='Generator', loss_object=loss_obj)\n            generator_F_loss = Loss(discriminator_fake_X, mode='Generator', loss_object=loss_obj)\n\n            # cycle loss\n            total_cycle_loss = (\n                Loss(image_Y, cycled_Y, mode='Cycle', loss_object=loss_obj) +\n                Loss(image_X, cycled_X, mode='Cycle', loss_object=loss_obj)\n            )\n\n            # identity loss\n            gen_G_identity_loss = Loss(image_Y, same_Y, mode='Identity', loss_object=loss_obj)\n            gen_F_identity_loss = Loss(image_X, same_X, mode='Identity', loss_object=loss_obj)\n\n            # discriminator loss\n            discriminator_X_loss = Loss(discriminator_real_X, discriminator_fake_X, mode='Discriminator', loss_object=loss_obj)\n            discriminator_Y_loss = Loss(discriminator_real_Y, discriminator_fake_Y, mode='Discriminator', loss_object=loss_obj)\n\n            # total generator loss\n            total_generator_G_loss = generator_G_loss + total_cycle_loss + gen_G_identity_loss\n            total_generator_F_loss = generator_F_loss + total_cycle_loss + gen_F_identity_loss\n\n        # Calculate the gradients for generator and discriminator\n        generator_G_grad = tape.gradient(total_generator_G_loss, self.generator_G.trainable_variables)\n        generator_F_grad = tape.gradient(total_generator_F_loss, self.generator_F.trainable_variables)\n\n        discriminator_X_grad = tape.gradient(discriminator_X_loss, self.discriminator_X.trainable_variables)\n        discriminator_Y_grad = tape.gradient(discriminator_Y_loss, self.discriminator_Y.trainable_variables)\n\n        # Apply the gradients to the optimizer\n        self.generator_G_opt.apply_gradients(zip(generator_G_grad, self.generator_G.trainable_variables))\n        self.generator_F_opt.apply_gradients(zip(generator_F_grad, self.generator_F.trainable_variables))\n\n        self.discriminator_X_opt.apply_gradients(zip(discriminator_X_grad, self.discriminator_X.trainable_variables))\n        self.discriminator_Y_opt.apply_gradients(zip(discriminator_Y_grad, self.discriminator_Y.trainable_variables))\n\n    def train(self):\n        for epoch in range(self.EPOCHS):\n            start = time.time()\n            n = 0\n            for image_X, image_Y in tf.data.Dataset.zip((self.image_X, self.image_Y)): \n                # IT IS BETTER TO REPEAT THE MONET DATA \n                # to obtain the same shape for the 2 datasets\n                self.train_step(image_X, image_Y)\n                if n % 10 == 0:\n                    print('.', end='')\n                n += 1\n\n            clear_output(wait=True)\n            # Using a consistent image so that the progress of the model\n            # is clearly visible.\n            generate_images(self.generator_G, self.sample_image)\n\n            if (epoch + 1) % 5 == 0:\n                ckpt_save_path = self.ckpt_manager.save()\n                print('Saving checkpoint for epoch {} at {}'.format(epoch + 1,\n                                                                    ckpt_save_path))\n\n            print('Time taken for epoch {} is {} sec\\n'.format(epoch + 1,\n                                                               time.time() - start))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.671037Z","iopub.execute_input":"2025-07-05T08:16:08.671232Z","iopub.status.idle":"2025-07-05T08:16:08.693613Z","shell.execute_reply.started":"2025-07-05T08:16:08.671219Z","shell.execute_reply":"2025-07-05T08:16:08.692984Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CycleGAN(tf.keras.Model):\n    def __init__(self, generator_G, generator_F, discriminator_X, discriminator_Y):\n        super().__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_G_opt, generator_F_opt, discriminator_X_opt, discriminator_Y_opt, loss_obj, lambda_cycle=10):\n        super().compile()\n        self.generator_G_opt = generator_G_opt\n        self.generator_F_opt = generator_F_opt\n        self.discriminator_X_opt = discriminator_X_opt\n        self.discriminator_Y_opt = discriminator_Y_opt\n        self.loss_obj = loss_obj\n        self.lambda_cycle = lambda_cycle\n\n    def call(self, inputs):\n        return self.generator_G(inputs)\n\n    def train_step(self, batch):\n        image_X, image_Y = batch\n\n        with tf.GradientTape(persistent=True) as tape:\n            # Forward passes\n            fake_Y = self.generator_G(image_X, training=True)\n            fake_X = self.generator_F(image_Y, training=True)\n\n            cycled_Y = self.generator_F(fake_X, training=True)\n            cycled_X = self.generator_G(fake_Y, training=True)\n\n            same_X = self.generator_F(image_X, training=True)\n            same_Y = self.generator_G(image_Y, training=True)\n\n            disc_real_X = self.discriminator_X(image_X, training=True)\n            disc_real_Y = self.discriminator_Y(image_Y, training=True)\n            disc_fake_X = self.discriminator_X(fake_X, training=True)\n            disc_fake_Y = self.discriminator_Y(fake_Y, training=True)\n\n            # Use your Loss class for all calculations\n            gen_G_loss = Loss(synthetic_image=disc_fake_Y, mode='Generator', loss_object=self.loss_obj).value\n            gen_F_loss = Loss(synthetic_image=disc_fake_X, mode='Generator', loss_object=self.loss_obj).value\n\n            cycle_loss = (\n                Loss(real_image=image_Y, synthetic_image=cycled_Y, mode='Cycle', LAMBDA=self.lambda_cycle).value +\n                Loss(real_image=image_X, synthetic_image=cycled_X, mode='Cycle', LAMBDA=self.lambda_cycle).value\n            )\n\n            idt_G_loss = Loss(real_image=image_Y, synthetic_image=same_Y, mode='Identity', LAMBDA=self.lambda_cycle).value\n            idt_F_loss = Loss(real_image=image_X, synthetic_image=same_X, mode='Identity', LAMBDA=self.lambda_cycle).value\n\n            disc_X_loss = Loss(real_image=disc_real_X, synthetic_image=disc_fake_X, mode='Discriminator', loss_object=self.loss_obj).value\n            disc_Y_loss = Loss(real_image=disc_real_Y, synthetic_image=disc_fake_Y, mode='Discriminator', loss_object=self.loss_obj).value\n\n            total_gen_G_loss = gen_G_loss + cycle_loss + idt_G_loss\n            total_gen_F_loss = gen_F_loss + cycle_loss + idt_F_loss\n\n        # Apply gradients\n        self.generator_G_opt.apply_gradients(zip(\n            tape.gradient(total_gen_G_loss, self.generator_G.trainable_variables),\n            self.generator_G.trainable_variables\n        ))\n        self.generator_F_opt.apply_gradients(zip(\n            tape.gradient(total_gen_F_loss, self.generator_F.trainable_variables),\n            self.generator_F.trainable_variables\n        ))\n        self.discriminator_X_opt.apply_gradients(zip(\n            tape.gradient(disc_X_loss, self.discriminator_X.trainable_variables),\n            self.discriminator_X.trainable_variables\n        ))\n        self.discriminator_Y_opt.apply_gradients(zip(\n            tape.gradient(disc_Y_loss, self.discriminator_Y.trainable_variables),\n            self.discriminator_Y.trainable_variables\n        ))\n\n        return {\n            \"gen_G_loss\": total_gen_G_loss,\n            \"gen_F_loss\": total_gen_F_loss,\n            \"disc_X_loss\": disc_X_loss,\n            \"disc_Y_loss\": disc_Y_loss,\n        }\n\n    def generate_images(self, input_image):\n        prediction = self.generator_G(input_image, training=False)\n\n        fig, axes = plt.subplots(1, 2, figsize=(12, 6))\n        images = [input_image[0], prediction[0]]\n        titles = ['Input Image', 'Generated Image (X→Y)']\n\n        for ax, img, title in zip(axes, images, titles):\n            ax.imshow((img + 1) / 2)\n            ax.set_title(title)\n            ax.axis('off')\n\n        plt.tight_layout()\n        plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.694334Z","iopub.execute_input":"2025-07-05T08:16:08.694579Z","iopub.status.idle":"2025-07-05T08:16:08.713350Z","shell.execute_reply.started":"2025-07-05T08:16:08.694559Z","shell.execute_reply":"2025-07-05T08:16:08.712644Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CycleGANCallback(tf.keras.callbacks.Callback):\n    def __init__(self, sample_image, ckpt_manager, save_freq=5):\n        super().__init__()\n        self.sample_image = sample_image\n        self.ckpt_manager = ckpt_manager  # <- this is now your CheckPoint instance\n        self.save_freq = save_freq\n\n    def on_epoch_end(self, epoch, logs=None):\n        print(f\"\\nEpoch {epoch + 1} finished.\")\n\n        # Generate sample images\n        if self.sample_image is not None:\n            self.model.generate_images(self.sample_image)\n\n        # Save checkpoint every `save_freq` epochs\n        if (epoch + 1) % self.save_freq == 0:\n            self.ckpt_manager.save()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.716564Z","iopub.execute_input":"2025-07-05T08:16:08.716756Z","iopub.status.idle":"2025-07-05T08:16:08.733779Z","shell.execute_reply.started":"2025-07-05T08:16:08.716742Z","shell.execute_reply":"2025-07-05T08:16:08.733116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''class CycleGANCallback(tf.keras.callbacks.Callback):\n    def __init__(self, sample_batch, ckpt_manager, save_freq=5):\n        super().__init__()\n        self.sample_batch = sample_batch  # Expecting a batch of images (batch_size, H, W, C)\n        self.ckpt_manager = ckpt_manager\n        self.save_freq = save_freq\n\n    def on_epoch_end(self, epoch, logs=None):\n        print(f\"\\nEpoch {epoch + 1} finished.\")\n\n        # Generate sample images on the entire batch\n        if self.sample_batch is not None:\n            # Assuming your model has a generate_images() method accepting a batch\n            self.model.generate_images(self.sample_batch)\n\n        # Save checkpoint\n        if (epoch + 1) % self.save_freq == 0:\n            self.ckpt_manager.save()\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.734406Z","iopub.execute_input":"2025-07-05T08:16:08.734621Z","iopub.status.idle":"2025-07-05T08:16:08.753511Z","shell.execute_reply.started":"2025-07-05T08:16:08.734607Z","shell.execute_reply":"2025-07-05T08:16:08.752989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''cyclegan = CycleGAN(generator_G, generator_F, discriminator_X, discriminator_Y) # predefine here\ncyclegan.compile(\n    generator_G_opt=generator_G_opt,\n    generator_F_opt=generator_F_opt,\n    discriminator_X_opt=discriminator_X_opt,\n    discriminator_Y_opt=discriminator_Y_opt, # redefine\n    loss_obj=tf.keras.losses.BinaryCrossentropy(from_logits=True), # use the created loss_obj\n)\n\ncallback = CycleGANCallback(\n    sample_image=sample_image,        # your consistent image for monitoring\n    ckpt_manager=ckpt_manager,        # your CheckPoint instance\n    generator_G=generator_G,          # so callback can generate images\n    save_interval=5                   # save every 5 epochs\n)\n\ncyclegan.fit(\n    tf.data.Dataset.zip((train_dataset_X, train_dataset_Y)),\n    epochs=EPOCHS,\n    callbacks=[callback],\n)\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.754099Z","iopub.execute_input":"2025-07-05T08:16:08.754259Z","iopub.status.idle":"2025-07-05T08:16:08.767839Z","shell.execute_reply.started":"2025-07-05T08:16:08.754242Z","shell.execute_reply":"2025-07-05T08:16:08.767279Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Training**","metadata":{}},{"cell_type":"code","source":"# Generator G: Translates images from Photo → Monet\ngenerator_G = ResNetGenerator(output_channels=3, n_blocks=9)\n\n# Generator F: Translates images from Monet → Photo\ngenerator_F = ResNetGenerator(output_channels=3, n_blocks=9)\n\n# Discriminator Y: Judges real vs fake Monet images\ndiscriminator_Y = PatchGANDiscriminator(target=False)\n\n# Discriminator X: Judges real vs fake Photo images\ndiscriminator_X = PatchGANDiscriminator(target=False)\n\ngenerator_G_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ngenerator_F_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ndiscriminator_X_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ndiscriminator_Y_opt = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n\ngenerators = {\n    \"G\": generator_G,  # maps X -> Y\n    \"F\": generator_F,  # maps Y -> X\n}\n\ndiscriminators = {\n    \"X\": discriminator_X,  # discriminates real vs fake X\n    \"Y\": discriminator_Y,  # discriminates real vs fake Y\n}\n\noptimizers = {\n    \"G\": generator_G_opt,\n    \"F\": generator_F_opt,\n    \"X\": discriminator_X_opt,\n    \"Y\": discriminator_Y_opt,\n}\n\nckpt_manager = CheckPoint(\n    checkpoint_path='/kaggle/working/',\n    max_ckpts=3,\n    generators=generators,\n    discriminators=discriminators,\n    optimizers=optimizers\n)\n\nckpt_manager.restore_latest()\n\ncyclegan = CycleGAN(generator_G, generator_F, discriminator_X, discriminator_Y)\n\ncyclegan.compile(\n    generator_G_opt=generator_G_opt,\n    generator_F_opt=generator_F_opt,\n    discriminator_X_opt=discriminator_X_opt,\n    discriminator_Y_opt=discriminator_Y_opt,\n    loss_obj=tf.keras.losses.MeanSquaredError()\n)\n\ndatasetX = photo_dataset\ndatasetY = monet_dataset\n\ntrain_dataset = tf.data.Dataset.zip((datasetX, datasetY)).prefetch(tf.data.AUTOTUNE)\n\nsample_image = next(iter(datasetX.take(1)))\n\ncyclegan.fit(\n    train_dataset,\n    epochs=50,\n    callbacks=[CycleGANCallback(sample_image, ckpt_manager)]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-05T08:16:08.768604Z","iopub.execute_input":"2025-07-05T08:16:08.768833Z","execution_failed":"2025-07-05T08:16:23.555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''# test_input should be a preprocessed image (e.g., shape (1, 256, 256, 3))\n# you must preprocess it just like your training data: normalize to [-1,1]\n\ngenerated_Y = cyclegan.generator_G(test_input, training=False)  # X -> Y\n\n# Convert back from [-1,1] to [0,255] for visualization\ngenerated_Y_image = (generated_Y[0].numpy() + 1.0) * 127.5\ngenerated_Y_image = generated_Y_image.astype(np.uint8)\n\nplt.imshow(generated_Y_image)\nplt.axis('off')\nplt.title(\"Generated Image (X→Y)\")\nplt.show()'''\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-05T08:16:23.555Z"}},"outputs":[],"execution_count":null}]}