{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":21755,"databundleVersionId":1475600}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Libraries","metadata":{}},{"cell_type":"code","source":"\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nimport numpy as np\nimport os, shutil, PIL\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:20.283005Z","iopub.execute_input":"2026-04-09T17:57:20.284011Z","iopub.status.idle":"2026-04-09T17:57:48.687219Z","shell.execute_reply.started":"2026-04-09T17:57:20.283971Z","shell.execute_reply":"2026-04-09T17:57:48.686463Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  DATA LOADING","metadata":{}},{"cell_type":"code","source":"\n# --- 1. CONFIGURATION ---\nIMAGE_SIZE = [256, 256]\nAUTO = tf.data.experimental.AUTOTUNE\n# Path fix based on your environment's os.walk output\nBASE_PATH = '/kaggle/input/competitions/gan-getting-started/'\nMONET_FILES = tf.io.gfile.glob(BASE_PATH + 'monet_tfrec/*.tfrec')\nPHOTO_FILES = tf.io.gfile.glob(BASE_PATH + 'photo_tfrec/*.tfrec')\n\nprint(f\"Files found: {len(MONET_FILES)} Monet, {len(PHOTO_FILES)} Photo\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:48.688798Z","iopub.execute_input":"2026-04-09T17:57:48.68937Z","iopub.status.idle":"2026-04-09T17:57:48.721653Z","shell.execute_reply.started":"2026-04-09T17:57:48.689343Z","shell.execute_reply":"2026-04-09T17:57:48.720743Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# --- 2. DATA PIPELINE (Added .repeat() for continuous training) ---\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 = {\"image\": tf.io.FixedLenFeature([], tf.string)}\n    example = tf.io.parse_single_example(example, tfrecord_format)\n    return decode_image(example['image'])\n\ndef load_dataset(filenames):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_tfrecord, num_parallel_calls=AUTO)\n    return dataset\n\n# Crucial: added .repeat() so fit() doesn't exit early\nmonet_ds = load_dataset(MONET_FILES).batch(1).repeat()\nphoto_ds = load_dataset(PHOTO_FILES).batch(1).repeat()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:48.722769Z","iopub.execute_input":"2026-04-09T17:57:48.723171Z","iopub.status.idle":"2026-04-09T17:57:50.309011Z","shell.execute_reply.started":"2026-04-09T17:57:48.723133Z","shell.execute_reply":"2026-04-09T17:57:50.308007Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# MODEL ARCHITECTURE","metadata":{}},{"cell_type":"code","source":"\ndef downsample(filters, size, apply_norm=True):\n    result = keras.Sequential()\n    result.add(layers.Conv2D(filters, size, strides=2, padding='same', use_bias=False))\n    if apply_norm:\n        result.add(layers.GroupNormalization(groups=filters)) # Native InstanceNorm\n    result.add(layers.LeakyReLU())\n    return result\n\ndef upsample(filters, size, apply_dropout=False):\n    result = keras.Sequential()\n    result.add(layers.Conv2DTranspose(filters, size, strides=2, padding='same', use_bias=False))\n    result.add(layers.GroupNormalization(groups=filters))\n    if apply_dropout: result.add(layers.Dropout(0.5))\n    result.add(layers.ReLU())\n    return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:50.31095Z","iopub.execute_input":"2026-04-09T17:57:50.311332Z","iopub.status.idle":"2026-04-09T17:57:50.317068Z","shell.execute_reply.started":"2026-04-09T17:57:50.311308Z","shell.execute_reply":"2026-04-09T17:57:50.316345Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# GENERATOR & DISCRIMINATOR","metadata":{}},{"cell_type":"code","source":"\ndef Generator():\n    inputs = layers.Input(shape=[256,256,3])\n    down_stack = [downsample(64, 4, False), downsample(128, 4), downsample(256, 4), downsample(512, 4)]\n    up_stack = [upsample(256, 4), upsample(128, 4), upsample(64, 4)]\n    x = inputs\n    skips = []\n    for down in down_stack:\n        x = down(x)\n        skips.append(x)\n    skips = reversed(skips[:-1])\n    for up, skip in zip(up_stack, skips):\n        x = up(x)\n        x = layers.Concatenate()([x, skip])\n    last = layers.Conv2DTranspose(3, 4, strides=2, padding='same', activation='tanh')\n    return keras.Model(inputs=inputs, outputs=last(x))\n\ndef Discriminator():\n    inputs = layers.Input(shape=[256,256,3])\n    x = downsample(64, 4, False)(inputs)\n    x = downsample(128, 4)(x)\n    last = layers.Conv2D(1, 4, strides=1, padding='same')(x)\n    return keras.Model(inputs=inputs, outputs=last)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:50.318085Z","iopub.execute_input":"2026-04-09T17:57:50.318493Z","iopub.status.idle":"2026-04-09T17:57:50.333192Z","shell.execute_reply.started":"2026-04-09T17:57:50.318468Z","shell.execute_reply":"2026-04-09T17:57:50.332163Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CYCLEGAN MODEL CLASS","metadata":{}},{"cell_type":"code","source":"\nclass CycleGan(keras.Model):\n    def __init__(self, monet_gen, photo_gen, monet_disc, photo_disc, lambda_cycle=10):\n        super().__init__()\n        self.m_gen, self.p_gen = monet_gen, photo_gen\n        self.m_disc, self.p_disc = monet_disc, photo_disc\n        self.lambda_cycle = lambda_cycle\n\n    def compile(self, m_gen_opt, p_gen_opt, m_disc_opt, p_disc_opt, gen_loss_fn, disc_loss_fn, cycle_loss_fn, identity_loss_fn):\n        super().compile()\n        self.m_gen_opt, self.p_gen_opt = m_gen_opt, p_gen_opt\n        self.m_disc_opt, self.p_disc_opt = m_disc_opt, p_disc_opt\n        self.gen_loss_fn, self.disc_loss_fn = gen_loss_fn, disc_loss_fn\n        self.cycle_loss_fn, self.identity_loss_fn = cycle_loss_fn, identity_loss_fn\n\n    @tf.function\n    def train_step(self, batch_data):\n        real_monet, real_photo = batch_data\n        with tf.GradientTape(persistent=True) as tape:\n            fake_monet = self.m_gen(real_photo, training=True)\n            cycled_photo = self.p_gen(fake_monet, training=True)\n            fake_photo = self.p_gen(real_monet, training=True)\n            cycled_monet = self.m_gen(fake_photo, training=True)\n            \n            m_gen_loss = self.gen_loss_fn(self.m_disc(fake_monet, training=True))\n            p_gen_loss = self.gen_loss_fn(self.p_disc(fake_photo, training=True))\n            \n            cycle_l = self.cycle_loss_fn(real_monet, cycled_monet, self.lambda_cycle) + \\\n                      self.cycle_loss_fn(real_photo, cycled_photo, self.lambda_cycle)\n            \n            total_m_gen_loss = m_gen_loss + cycle_l\n            total_p_gen_loss = p_gen_loss + cycle_l\n            \n            m_disc_loss = self.disc_loss_fn(self.m_disc(real_monet, training=True), self.m_disc(fake_monet, training=True))\n            p_disc_loss = self.disc_loss_fn(self.p_disc(real_photo, training=True), self.p_disc(fake_photo, training=True))\n\n        self.m_gen_opt.apply_gradients(zip(tape.gradient(total_m_gen_loss, self.m_gen.trainable_variables), self.m_gen.trainable_variables))\n        self.p_gen_opt.apply_gradients(zip(tape.gradient(total_p_gen_loss, self.p_gen.trainable_variables), self.p_gen.trainable_variables))\n        self.m_disc_opt.apply_gradients(zip(tape.gradient(m_disc_loss, self.m_disc.trainable_variables), self.m_disc.trainable_variables))\n        self.p_disc_opt.apply_gradients(zip(tape.gradient(p_disc_loss, self.p_disc.trainable_variables), self.p_disc.trainable_variables))\n        \n        return {\"m_loss\": total_m_gen_loss, \"m_disc_loss\": m_disc_loss}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:50.33407Z","iopub.execute_input":"2026-04-09T17:57:50.334391Z","iopub.status.idle":"2026-04-09T17:57:50.347602Z","shell.execute_reply.started":"2026-04-09T17:57:50.33436Z","shell.execute_reply":"2026-04-09T17:57:50.34696Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# INITIALIZE AND TRAIN","metadata":{}},{"cell_type":"code","source":"\n# --- 5. INITIALIZE AND TRAIN ---\nadv_loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)\ndef disc_loss(real, gen): return (adv_loss_fn(tf.ones_like(real), real) + adv_loss_fn(tf.zeros_like(gen), gen)) * 0.5\ndef gen_loss(gen): return adv_loss_fn(tf.ones_like(gen), gen)\ndef cycle_loss(real, cycled, LAMBDA): return LAMBDA * tf.reduce_mean(tf.abs(real - cycled))\n\nmodel = CycleGan(Generator(), Generator(), Discriminator(), Discriminator())\nmodel.compile(m_gen_opt=keras.optimizers.Adam(2e-4, 0.5), p_gen_opt=keras.optimizers.Adam(2e-4, 0.5),\n              m_disc_opt=keras.optimizers.Adam(2e-4, 0.5), p_disc_opt=keras.optimizers.Adam(2e-4, 0.5),\n              gen_loss_fn=gen_loss, disc_loss_fn=disc_loss, cycle_loss_fn=cycle_loss, identity_loss_fn=cycle_loss)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:50.348537Z","iopub.execute_input":"2026-04-09T17:57:50.348851Z","iopub.status.idle":"2026-04-09T17:57:51.992827Z","shell.execute_reply.started":"2026-04-09T17:57:50.34883Z","shell.execute_reply":"2026-04-09T17:57:51.992181Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Fit will now take time and produce history\nhistory = model.fit(tf.data.Dataset.zip((monet_ds, photo_ds)), steps_per_epoch=100, epochs=100)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T17:57:51.993778Z","iopub.execute_input":"2026-04-09T17:57:51.994192Z","iopub.status.idle":"2026-04-09T18:08:16.805319Z","shell.execute_reply.started":"2026-04-09T17:57:51.994166Z","shell.execute_reply":"2026-04-09T18:08:16.804358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 6. LEARNING CURVE ---\nplt.plot(history.history['m_loss'], label='Gen Loss')\nplt.plot(history.history['m_disc_loss'], label='Disc Loss')\nplt.title('CycleGAN Learning Curve')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T18:08:16.806563Z","iopub.execute_input":"2026-04-09T18:08:16.806944Z","iopub.status.idle":"2026-04-09T18:08:17.026176Z","shell.execute_reply.started":"2026-04-09T18:08:16.806912Z","shell.execute_reply":"2026-04-09T18:08:17.025441Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# sAVE IMAGES FOR SUBMISSION","metadata":{}},{"cell_type":"code","source":"# --- 6. SAVE IMAGES FOR SUBMISSION ---\nimport os\nimport shutil\nimport PIL.Image\nimport tensorflow as tf\n\n# 1. FIX THE PATH (Based on your notebook's file structure)\n# Your notebook showed paths inside '/competitions/'\nPHOTO_FILES = tf.io.gfile.glob('/kaggle/input/competitions/gan-getting-started/photo_tfrec/*.tfrec')\n\n# Sanity Check: If this is 0, the loop below will not run!\nprint(f\"Found {len(PHOTO_FILES)} TFRecord files for generation.\")\n\n# 2. CREATE CLEAN DIRECTORY\n# It is safer to use a folder inside /kaggle/working/ to avoid permission issues\nsubmission_dir = '/kaggle/working/temp_images'\nif os.path.exists(submission_dir):\n    shutil.rmtree(submission_dir)\nos.makedirs(submission_dir)\n\n# 3. GENERATION LOOP\n# Ensure photo_ds is loaded correctly from the fixed PHOTO_FILES\nphoto_dataset = load_dataset(PHOTO_FILES).batch(1)\n\nprint(\"Generating images...\")\nfor i, img in enumerate(photo_dataset):\n    # Predict using the Monet Generator\n    prediction = model.m_gen(img, training=False)[0].numpy()\n    \n    # De-normalize from [-1, 1] to [0, 255]\n    prediction = (prediction * 127.5 + 127.5).astype(np.uint8)\n    \n    # Save as .jpg\n    im = PIL.Image.fromarray(prediction)\n    im.save(os.path.join(submission_dir, f'{i+1}.jpg'))\n    \n    if (i+1) % 1000 == 0:\n        print(f\"Progress: {i+1}/7038\")\n    if i + 1 >= 7038: # Safety break to match competition requirements\n        break\n\n# 4. ZIP THE IMAGES\n# CRITICAL: Kaggle requires the images to be at the ROOT of the zip.\n# Using 'base_dir' ensures the zip doesn't contain a nested 'temp_images' folder.\nshutil.make_archive('/kaggle/working/images', 'zip', submission_dir)\n\n# 5. CLEAN UP\nshutil.rmtree(submission_dir)\nprint(f\"Done! Final zip contains {len(os.listdir('/kaggle/working/'))} files in /working/\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T18:08:17.028002Z","iopub.execute_input":"2026-04-09T18:08:17.0284Z","iopub.status.idle":"2026-04-09T18:12:43.386532Z","shell.execute_reply.started":"2026-04-09T18:08:17.028374Z","shell.execute_reply":"2026-04-09T18:12:43.385671Z"}},"outputs":[],"execution_count":null}]}