{"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"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Monet Image CycleGAN Project","metadata":{}},{"cell_type":"code","source":"pip install git+https://github.com/tensorflow/examples.git","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:34.767341Z","iopub.execute_input":"2025-07-30T18:30:34.768087Z","iopub.status.idle":"2025-07-30T18:30:44.773642Z","shell.execute_reply.started":"2025-07-30T18:30:34.768060Z","shell.execute_reply":"2025-07-30T18:30:44.772729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n\nimport os\n\n\n\n\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow_examples.models.pix2pix import pix2pix\n\nfrom kaggle_datasets import KaggleDatasets\nimport matplotlib.pyplot as plt\nimport numpy as np\n\ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    print('Device:', tpu.master())\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nexcept:\n    strategy = tf.distribute.get_strategy()\nprint('Number of replicas:', strategy.num_replicas_in_sync)\n\nAUTOTUNE = tf.data.experimental.AUTOTUNE\n    \nprint(tf.__version__)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:44.775357Z","iopub.execute_input":"2025-07-30T18:30:44.775583Z","iopub.status.idle":"2025-07-30T18:30:44.797493Z","shell.execute_reply.started":"2025-07-30T18:30:44.775562Z","shell.execute_reply":"2025-07-30T18:30:44.796900Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# GAN Monet Project","metadata":{}},{"cell_type":"markdown","source":"In this project, we are going to be using a Generative Adverserial Network (GAN) to create images that look as much like a Monet painting as possible. In order to accomplish this, we will train a generator using a dataset that consists of 300 images of paintings by Monet and 7,028 other photos. We will use the Monet images to train our model, produce new images and then mix those in with the 7,028 other images to be submitted. \n    Each image is a 256 by 256 RGB .tfrecord or .jpeg file. We will us the tfrecord image files. This will allow us to use the TCU accelerator\n    A GAN model takes random \"noise\" data, runs it through a generator, creates an image, then sends that image to a discriminator where it is judged to be real or not real based on the training the discriminator has recieved from the training images. ","metadata":{}},{"cell_type":"markdown","source":"Note: this notebook draws heavily from the sources listed in references.","metadata":{}},{"cell_type":"markdown","source":"## Load Dataset","metadata":{}},{"cell_type":"code","source":"path = '/kaggle/input/gan-getting-started'\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:44.798059Z","iopub.execute_input":"2025-07-30T18:30:44.798272Z","iopub.status.idle":"2025-07-30T18:30:44.808888Z","shell.execute_reply.started":"2025-07-30T18:30:44.798247Z","shell.execute_reply":"2025-07-30T18:30:44.808283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MONET_FILENAMES = tf.io.gfile.glob(str(path + '/monet_tfrec/*.tfrec'))\nprint('Monet TFRecord Files:', len(MONET_FILENAMES))\n\nPHOTO_FILENAMES = tf.io.gfile.glob(str(path + '/photo_tfrec/*.tfrec'))\nprint('Photo TFRecord Files:', len(PHOTO_FILENAMES))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:44.810137Z","iopub.execute_input":"2025-07-30T18:30:44.810445Z","iopub.status.idle":"2025-07-30T18:30:44.852785Z","shell.execute_reply.started":"2025-07-30T18:30:44.810429Z","shell.execute_reply":"2025-07-30T18:30:44.852256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def count_tfrecord_examples(tfrecord_path):\n    count = 0\n    for _ in tf.data.TFRecordDataset(tfrecord_path):\n        count += 1\n    return count\ntfrecord_dir = '/kaggle/input/gan-getting-started/monet_tfrec'\ntfrecord_files = [os.path.join(tfrecord_dir, f) for f in os.listdir(tfrecord_dir) if f.endswith('.tfrec')]\n\nnum_examples = count_tfrecord_examples(tfrecord_files)\nprint(f\"Total images in monet_tfrec: {num_examples}\")\n\ntfrecord_dir = '/kaggle/input/gan-getting-started/photo_tfrec'\ntfrecord_files = [os.path.join(tfrecord_dir, f) for f in os.listdir(tfrecord_dir) if f.endswith('.tfrec')]\n\nnum_examples = count_tfrecord_examples(tfrecord_files)\nprint(f\"Total images in photo_tfrec: {num_examples}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:44.853384Z","iopub.execute_input":"2025-07-30T18:30:44.853629Z","iopub.status.idle":"2025-07-30T18:30:49.752317Z","shell.execute_reply.started":"2025-07-30T18:30:44.853607Z","shell.execute_reply":"2025-07-30T18:30:49.751549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMAGE_SIZE = [256, 256]\n\ndef decode_image(image):\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = (tf.cast(image, tf.float32) / 127.5) - 1\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\ndef read_tfrecord(example):\n    tfrecord_format = {\n        \"image_name\": tf.io.FixedLenFeature([], tf.string),\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"target\": tf.io.FixedLenFeature([], tf.string)\n    }\n    example = tf.io.parse_single_example(example, tfrecord_format)\n    image = decode_image(example['image'])\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:49.754104Z","iopub.execute_input":"2025-07-30T18:30:49.754518Z","iopub.status.idle":"2025-07-30T18:30:49.759349Z","shell.execute_reply.started":"2025-07-30T18:30:49.754498Z","shell.execute_reply":"2025-07-30T18:30:49.758672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_dataset(filenames, labeled=True, ordered=False):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_tfrecord, num_parallel_calls=AUTOTUNE)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:49.760152Z","iopub.execute_input":"2025-07-30T18:30:49.760435Z","iopub.status.idle":"2025-07-30T18:30:49.793183Z","shell.execute_reply.started":"2025-07-30T18:30:49.760412Z","shell.execute_reply":"2025-07-30T18:30:49.792536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"monet_ds = load_dataset(MONET_FILENAMES, labeled=True).batch(1)\nphoto_ds = load_dataset(PHOTO_FILENAMES, labeled=True).batch(1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:49.794082Z","iopub.execute_input":"2025-07-30T18:30:49.794293Z","iopub.status.idle":"2025-07-30T18:30:49.946306Z","shell.execute_reply.started":"2025-07-30T18:30:49.794279Z","shell.execute_reply":"2025-07-30T18:30:49.945584Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for batch in monet_ds.take(1):\n    print(batch.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:49.947103Z","iopub.execute_input":"2025-07-30T18:30:49.947326Z","iopub.status.idle":"2025-07-30T18:30:49.990358Z","shell.execute_reply.started":"2025-07-30T18:30:49.947309Z","shell.execute_reply":"2025-07-30T18:30:49.989740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_images(dataset, num_images=9):\n    plt.figure(figsize=(10, 10))\n    for i, image in enumerate(dataset.unbatch().take(num_images)):\n        plt.subplot(3, 3, i + 1)\n        plt.imshow((image + 1) / 2)  # Rescale from [-1,1] to [0,1]\n        plt.axis('off')\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:49.991802Z","iopub.execute_input":"2025-07-30T18:30:49.992008Z","iopub.status.idle":"2025-07-30T18:30:49.996277Z","shell.execute_reply.started":"2025-07-30T18:30:49.991993Z","shell.execute_reply":"2025-07-30T18:30:49.995631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Monet Sample Images')\nshow_images(monet_ds)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:50.016961Z","iopub.execute_input":"2025-07-30T18:30:50.017283Z","iopub.status.idle":"2025-07-30T18:30:51.003143Z","shell.execute_reply.started":"2025-07-30T18:30:50.017262Z","shell.execute_reply":"2025-07-30T18:30:51.002429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Sample Non-Monet Images\")\nshow_images(photo_ds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:51.004198Z","iopub.execute_input":"2025-07-30T18:30:51.004548Z","iopub.status.idle":"2025-07-30T18:30:51.865992Z","shell.execute_reply.started":"2025-07-30T18:30:51.004518Z","shell.execute_reply":"2025-07-30T18:30:51.865011Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Build the Model","metadata":{}},{"cell_type":"markdown","source":"To build our CycleGAN model, first we load the generators and discriminators. As the authors of the CycleGAN tutorial show us, we use the preloaded pix2pix models. These are preloaded unet architectures. Next we define the loss functions for each (binary crossentropy).","metadata":{}},{"cell_type":"code","source":"OUTPUT_CHANNELS = 3\n\ngenerator_g = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')  # photo → Monet\ngenerator_f = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')  # Monet → photo\n\ndiscriminator_x = pix2pix.discriminator(norm_type='instancenorm', target=False)  # Monet\ndiscriminator_y = pix2pix.discriminator(norm_type='instancenorm', target=False)  # Photo\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:55.134252Z","iopub.execute_input":"2025-07-30T18:30:55.134940Z","iopub.status.idle":"2025-07-30T18:30:55.932747Z","shell.execute_reply.started":"2025-07-30T18:30:55.134919Z","shell.execute_reply":"2025-07-30T18:30:55.932048Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n\nLAMBDA = 10\n\ndef discriminator_loss(real, generated):\n    real_loss = loss_obj(tf.ones_like(real), real)\n    generated_loss = loss_obj(tf.zeros_like(generated), generated)\n    return (real_loss + generated_loss) * 0.5\n\ndef generator_loss(generated):\n    return loss_obj(tf.ones_like(generated), generated)\n\ndef calc_cycle_loss(real_image, cycled_image):\n    return tf.reduce_mean(tf.abs(real_image - cycled_image)) * LAMBDA\n\ndef identity_loss(real_image, same_image):\n    return tf.reduce_mean(tf.abs(real_image - same_image)) * LAMBDA * 0.5\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:57.832590Z","iopub.execute_input":"2025-07-30T18:30:57.833184Z","iopub.status.idle":"2025-07-30T18:30:57.838416Z","shell.execute_reply.started":"2025-07-30T18:30:57.833162Z","shell.execute_reply":"2025-07-30T18:30:57.837652Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ngenerator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n\ndiscriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\ndiscriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:30:58.812251Z","iopub.execute_input":"2025-07-30T18:30:58.812786Z","iopub.status.idle":"2025-07-30T18:30:58.828890Z","shell.execute_reply.started":"2025-07-30T18:30:58.812763Z","shell.execute_reply":"2025-07-30T18:30:58.828349Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train the Model","metadata":{}},{"cell_type":"markdown","source":"Using the CycleGAN training function, we train our model.","metadata":{}},{"cell_type":"code","source":"@tf.function\ndef train_step(real_x, real_y):\n    # real_x: photos, real_y: Monet paintings\n    with tf.GradientTape(persistent=True) as tape:\n        # Generate fake images\n        fake_y = generator_g(real_x, training=True)  # photo → Monet\n        cycled_x = generator_f(fake_y, training=True)  # Monet → photo → photo\n\n        fake_x = generator_f(real_y, training=True)  # Monet → photo\n        cycled_y = generator_g(fake_x, training=True)  # photo → Monet → Monet\n\n        # Identity mapping\n        same_x = generator_f(real_x, training=True)\n        same_y = generator_g(real_y, training=True)\n\n        # Discriminator outputs\n        disc_real_x = discriminator_x(real_x, training=True)\n        disc_real_y = discriminator_y(real_y, training=True)\n\n        disc_fake_x = discriminator_x(fake_x, training=True)\n        disc_fake_y = discriminator_y(fake_y, training=True)\n\n        # Generator losses\n        gen_g_loss = generator_loss(disc_fake_y)\n        gen_f_loss = generator_loss(disc_fake_x)\n\n        total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)\n\n        # Total generator loss\n        total_gen_g_loss = gen_g_loss + total_cycle_loss + identity_loss(real_y, same_y)\n        total_gen_f_loss = gen_f_loss + total_cycle_loss + identity_loss(real_x, same_x)\n\n        # Discriminator losses\n        disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)\n        disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)\n\n    # Apply gradients\n    generator_g_gradients = tape.gradient(total_gen_g_loss, generator_g.trainable_variables)\n    generator_f_gradients = tape.gradient(total_gen_f_loss, generator_f.trainable_variables)\n\n    discriminator_x_gradients = tape.gradient(disc_x_loss, discriminator_x.trainable_variables)\n    discriminator_y_gradients = tape.gradient(disc_y_loss, discriminator_y.trainable_variables)\n\n    generator_g_optimizer.apply_gradients(zip(generator_g_gradients, generator_g.trainable_variables))\n    generator_f_optimizer.apply_gradients(zip(generator_f_gradients, generator_f.trainable_variables))\n\n    discriminator_x_optimizer.apply_gradients(zip(discriminator_x_gradients, discriminator_x.trainable_variables))\n    discriminator_y_optimizer.apply_gradients(zip(discriminator_y_gradients, discriminator_y.trainable_variables))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:31:00.422030Z","iopub.execute_input":"2025-07-30T18:31:00.422318Z","iopub.status.idle":"2025-07-30T18:31:00.430445Z","shell.execute_reply.started":"2025-07-30T18:31:00.422295Z","shell.execute_reply":"2025-07-30T18:31:00.429611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\nEPOCHS = 100\n\nfor epoch in range(EPOCHS):\n    start = time.time()\n    print(f\"Epoch {epoch+1}/{EPOCHS}\")\n\n    for image_x, image_y in tf.data.Dataset.zip((photo_ds, monet_ds)):\n        train_step(image_x, image_y)\n\n    print(f\"Time taken for epoch {epoch+1}: {time.time()-start:.2f} sec\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:38:21.849484Z","iopub.execute_input":"2025-07-30T18:38:21.849760Z","iopub.status.idle":"2025-07-30T18:50:36.814810Z","shell.execute_reply.started":"2025-07-30T18:38:21.849739Z","shell.execute_reply":"2025-07-30T18:50:36.813954Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_images(model, input_image):\n    prediction = model(input_image, training=False)\n    plt.figure(figsize=(12, 6))\n\n    display_list = [input_image[0], prediction[0]]\n    title = ['Input Image', 'Stylized Output']\n\n    for i in range(2):\n        plt.subplot(1, 2, i+1)\n        plt.title(title[i])\n        # Convert from [-1, 1] to [0, 1]\n        plt.imshow((display_list[i] + 1) / 2.0)\n        plt.axis('off')\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:50:52.106774Z","iopub.execute_input":"2025-07-30T18:50:52.107403Z","iopub.status.idle":"2025-07-30T18:50:52.112251Z","shell.execute_reply.started":"2025-07-30T18:50:52.107378Z","shell.execute_reply":"2025-07-30T18:50:52.111339Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for image in photo_ds.take(5):\n    generate_images(generator_g, image)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:51:53.020555Z","iopub.execute_input":"2025-07-30T18:51:53.020829Z","iopub.status.idle":"2025-07-30T18:51:55.075223Z","shell.execute_reply.started":"2025-07-30T18:51:53.020806Z","shell.execute_reply":"2025-07-30T18:51:55.074486Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create Submission File","metadata":{}},{"cell_type":"code","source":"import PIL\n! mkdir ../images\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:55:41.863692Z","iopub.execute_input":"2025-07-30T18:55:41.864262Z","iopub.status.idle":"2025-07-30T18:55:42.036186Z","shell.execute_reply.started":"2025-07-30T18:55:41.864229Z","shell.execute_reply":"2025-07-30T18:55:42.035121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport PIL.Image\nimport shutil\n\n# Create output directory\nos.makedirs(\"/kaggle/working/images\", exist_ok=True)\n\n# Generate Monet-style images from photo_ds and save\ni = 1\nfor img in photo_ds:\n    prediction = generator_g(img, training=False)[0].numpy()\n    prediction = (prediction * 127.5 + 127.5).astype(np.uint8)  # Scale back to [0, 255]\n    im = PIL.Image.fromarray(prediction)\n    im.save(f\"/kaggle/working/images/{i}.jpg\")  # Save to correct path\n    i += 1\n\n# Zip the folder for submission\nshutil.make_archive(\"/kaggle/working/images\", 'zip', \"/kaggle/working/images\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-30T18:56:12.426920Z","iopub.execute_input":"2025-07-30T18:56:12.427198Z","iopub.status.idle":"2025-07-30T19:01:44.419301Z","shell.execute_reply.started":"2025-07-30T18:56:12.427176Z","shell.execute_reply":"2025-07-30T19:01:44.418556Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Next Steps","metadata":{}},{"cell_type":"markdown","source":"Because of time and computing contraints, I was only able to run 25 epochs of training. Even with that few number of training epochs, I was able to score a 64.54 on the Kaggle leaderboard.  However, with many more epochs, results would certainly improve. There are also other architectures that I could try, including a ResNet architecture rather than the unet used in the pix2pix model. ","metadata":{}},{"cell_type":"markdown","source":"## References","metadata":{}},{"cell_type":"markdown","source":"1. https://www.kaggle.com/code/amyjang/monet-cyclegan-tutorial\n2. https://www.tensorflow.org/tutorials/generative/cyclegan","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}