{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"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)\nimport matplotlib.pyplot as plt\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom keras.layers import Dense, Conv2D, Flatten, Conv2DTranspose, MaxPool2D\n\nimport tensorflow_addons as tfa\n\nimport PIL\nimport PIL.Image\n\nautotune = tf.data.AUTOTUNE\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\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        pass\n       # print(os.path.join(dirname, filename))\n\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","execution":{"iopub.status.busy":"2022-09-14T18:37:18.088135Z","iopub.execute_input":"2022-09-14T18:37:18.089100Z","iopub.status.idle":"2022-09-14T18:37:19.587624Z","shell.execute_reply.started":"2022-09-14T18:37:18.089062Z","shell.execute_reply":"2022-09-14T18:37:19.586647Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"input_image_size = (256, 256, 3)\n\ndef preprocess_image(img):\n    img = (tf.cast(img, dtype=tf.float32) / 127.5) - 1\n    return img\n    \nmonet_filepath = '../input/gan-getting-started/monet_jpg'\nphoto_filepath = '../input/gan-getting-started/photo_jpg'\n\nmonet_data = tf.keras.utils.image_dataset_from_directory(monet_filepath, labels = None, image_size= (256, 256), batch_size = 1)\nphoto_data = tf.keras.utils.image_dataset_from_directory(photo_filepath, labels = None, image_size= (256, 256), batch_size = 1)\n    \n\n\nmonet_ds = monet_data.map(preprocess_image, num_parallel_calls = autotune)\nphoto_ds = photo_data.map(preprocess_image, num_parallel_calls = autotune)\n\nmonet_ds","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:19.589906Z","iopub.execute_input":"2022-09-14T18:37:19.590263Z","iopub.status.idle":"2022-09-14T18:37:19.969157Z","shell.execute_reply.started":"2022-09-14T18:37:19.590229Z","shell.execute_reply":"2022-09-14T18:37:19.968090Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"_, ax = plt.subplots(4, 2, figsize=(10, 15))\nfor i, samples in enumerate(zip(photo_ds.take(4), monet_ds.take(4))):\n    photo = (((samples[0][0] * 127.5) + 127.5).numpy()).astype(np.uint8)\n    monet = (((samples[1][0] * 127.5) + 127.5).numpy()).astype(np.uint8)\n    ax[i, 0].imshow(photo)\n    ax[i, 1].imshow(monet)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:19.971826Z","iopub.execute_input":"2022-09-14T18:37:19.972140Z","iopub.status.idle":"2022-09-14T18:37:20.922018Z","shell.execute_reply.started":"2022-09-14T18:37:19.972112Z","shell.execute_reply":"2022-09-14T18:37:20.921161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"MODEL BUILDING BLOCKS","metadata":{}},{"cell_type":"code","source":"kernel_init = tf.random_normal_initializer(0., 0.02)\ngamma_init = keras.initializers.RandomNormal(mean=0.0, stddev=0.02)\n\ndef downsample(\n    filters, \n    kernel_size,\n    activation, \n    strides = 2,\n    kernel_initializer = kernel_init, \n    gamma_initializer = gamma_init,\n    apply_instancenorm=True\n):\n    result = tf.keras.models.Sequential()\n    \n    result.add(Conv2D(\n        filters, \n        kernel_size, \n        strides=strides, \n        padding='same',\n        kernel_initializer = kernel_initializer, \n        use_bias = False\n    ))\n    \n    if apply_instancenorm:\n        result.add(tfa.layers.InstanceNormalization(gamma_initializer = gamma_initializer))\n    \n    result.add(activation)\n    \n    return result\n\ndef upsample(\n    filters, \n    kernel_size, \n    activation,\n    strides = 2,\n    kernel_initializer = kernel_init, \n    gamma_initializer = gamma_init,\n    apply_instancenorm = True, \n    apply_dropout=False\n):\n    result = tf.keras.models.Sequential()\n    \n    result.add(Conv2DTranspose(\n        filters, \n        kernel_size, \n        strides=strides, \n        padding='same', \n        kernel_initializer = kernel_initializer, \n        use_bias = False\n    ))\n    \n    if apply_instancenorm:\n        result.add(tfa.layers.InstanceNormalization(gamma_initializer = gamma_initializer))\n        \n    if apply_dropout:\n        result.add(layers.Dropout(0.5))\n    \n    result.add(activation)\n    \n    return result\n\ndef residual_block(\n    dim, \n    activation,\n    kernel_size = (3,3),\n    strides = 1,\n    kernel_initializer = kernel_init, \n    gamma_initializer = gamma_init, \n    apply_instancenorm = True\n):\n    \n    result = tf.keras.models.Sequential()\n    \n    result.add(Conv2D(\n        dim,\n        kernel_size = kernel_size,\n        strides = strides,\n        padding = 'same',\n        kernel_initializer = kernel_initializer,\n        use_bias = False\n    ))\n    \n    if apply_instancenorm:\n        result.add(tfa.layers.InstanceNormalization(gamma_initializer = gamma_initializer))\n        \n    result.add(activation)\n    \n    return result","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:20.923580Z","iopub.execute_input":"2022-09-14T18:37:20.924190Z","iopub.status.idle":"2022-09-14T18:37:20.938516Z","shell.execute_reply.started":"2022-09-14T18:37:20.924155Z","shell.execute_reply":"2022-09-14T18:37:20.937720Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# GENERATOR","metadata":{}},{"cell_type":"code","source":"def get_gen_model(\n    filters = 64, \n    num_downsample = 2, \n    num_residual = 9, \n    num_upsample = 2, \n    gamma_initializer = gamma_init, \n    activation = layers.Activation('relu')\n):\n    img_input = layers.Input(shape=[256,256,3])\n    \n    #1st Conv Block with kernel_size 7\n    x = layers.Conv2D(filters, (7,7), kernel_initializer = kernel_init, padding = 'same')(img_input)\n    x = tfa.layers.InstanceNormalization(gamma_initializer=gamma_initializer)(x)\n    x = activation(x)\n    \n    #Downsampling\n    for _ in range(num_downsample):\n        filters *= 2\n        x = downsample(filters = filters, kernel_size = (3,3), activation = activation)(x)\n        \n    #Residual\n    for i in range(num_residual):\n        dim = filters\n        x = residual_block(dim, activation = activation)(x)\n        \n    #Upsampling\n    for _ in range(num_upsample):\n        filters //= 2\n        x = upsample(filters, kernel_size = (3,3), activation = activation)(x)\n    \n    #Output Conv Block\n    x = layers.Conv2D(3, (7,7), padding='same')(x)\n    x = layers.Activation('tanh')(x)\n    \n    model = keras.Model(img_input, x)\n    return model","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:20.941335Z","iopub.execute_input":"2022-09-14T18:37:20.941950Z","iopub.status.idle":"2022-09-14T18:37:20.955421Z","shell.execute_reply.started":"2022-09-14T18:37:20.941916Z","shell.execute_reply":"2022-09-14T18:37:20.954074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# DISCRIMINATOR","metadata":{}},{"cell_type":"code","source":"def get_disc_model(\n    filters = 64,\n    num_downsample = 4,\n    gamma_initializer = gamma_init,\n    activation = layers.Activation('relu')\n):\n    #Input layer\n    img_input = layers.Input(shape = [256,256,3])\n    \n    #1st Layer downsample\n    x = downsample(filters, kernel_size = 4, activation=activation)(img_input)\n    \n    #Downsampling\n    for _ in range(num_downsample - 1):\n        filters *= 2\n        x = downsample(filters, kernel_size = 4, activation = activation)(x)\n    \n    #Output\n    x = layers.Conv2D(1, kernel_size = 4, strides = 1, padding = 'same', activation='sigmoid')(x)\n    \n    model = keras.Model(img_input, x)\n    return model","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:20.956741Z","iopub.execute_input":"2022-09-14T18:37:20.958459Z","iopub.status.idle":"2022-09-14T18:37:20.970308Z","shell.execute_reply.started":"2022-09-14T18:37:20.958430Z","shell.execute_reply":"2022-09-14T18:37:20.969330Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Build the CycleGAN Model","metadata":{}},{"cell_type":"code","source":"class CycleGan(keras.Model):\n    def __init__(\n        self,\n        monet_gen,\n        photo_gen,\n        monet_disc,\n        photo_disc,\n        lambda_cycle = 10,\n        lambda_identity = 0.5,\n    ):\n        super().__init__()\n        self.monet_gen = monet_gen\n        self.photo_gen = photo_gen\n        self.monet_disc = monet_disc\n        self.photo_disc = photo_gen\n        self.lambda_cycle = lambda_cycle\n        self.lambda_identity = lambda_identity\n        \n    def compile(\n        self,\n        monet_gen_optimizer,\n        photo_gen_optimizer,\n        monet_disc_optimizer,\n        photo_disc_optimizer,\n        gen_loss_fn,\n        disc_loss_fn,\n        cycle_loss_fn,\n        identity_loss_fn,\n    ):\n        super().compile()\n        self.monet_gen_optimizer = monet_gen_optimizer\n        self.photo_gen_optimizer = photo_gen_optimizer\n        self.monet_disc_optimizer = monet_disc_optimizer\n        self.photo_disc_optimizer = photo_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            #Generate fake images from real images\n            fake_monet = self.monet_gen(real_photo, training = True)\n            fake_photo = self.photo_gen(real_monet, training = True)\n            \n            #Cycle Consistency: pass fake photos back through generators to check if we can get back the original image\n            cycled_photo = self.photo_gen(fake_monet, training = True)\n            cycled_monet = self.monet_gen(fake_photo, training = True)\n            \n            #Identity Mapping: pass real monet photo into monet generator to check if it changes the image\n            identity_monet = self.monet_gen(real_monet, training = True)\n            identity_photo = self.photo_gen(real_photo, training = True)\n            \n            #Discriminators recieving real and fake images\n            disc_fake_monet = self.monet_disc(fake_monet, training = True)\n            disc_fake_photo = self.photo_disc(fake_photo, training = True)\n            \n            disc_real_monet = self.monet_disc(real_monet, training = True)\n            disc_real_photo = self.photo_disc(real_photo, training = True)\n            \n            #Generators adverserial loss\n            monet_adv_loss = self.gen_loss_fn(disc_fake_monet)\n            photo_adv_loss = self.gen_loss_fn(disc_fake_photo)\n            \n            #Generator cycle loss\n            monet_cycle_loss = self.cycle_loss_fn(real_monet, cycled_monet) *self.lambda_cycle\n            photo_cycle_loss = self.cycle_loss_fn(real_photo, cycled_photo) *self.lambda_cycle\n            \n            #Generator identity loss\n            monet_identity_loss = self.identity_loss_fn(real_monet, identity_monet) * self.lambda_identity\n            photo_identity_loss = self.identity_loss_fn(real_photo, identity_photo) * self.lambda_identity\n            \n            #Total Generator loss\n            total_monet_gen_loss = monet_adv_loss + monet_cycle_loss + monet_identity_loss\n            total_photo_gen_loss = photo_adv_loss + photo_cycle_loss + photo_identity_loss\n            \n            #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            #Update weights\n            monet_gen_grads = tape.gradient(total_monet_gen_loss, self.monet_gen.trainable_variables)\n            photo_gen_grads = tape.gradient(total_photo_gen_loss, self.photo_gen.trainable_variables)\n            \n            monet_disc_grads = tape.gradient(monet_disc_loss, self.monet_disc.trainable_variables)\n            photo_disc_grads = tape.gradient(photo_disc_loss, self.photo_disc.trainable_variables)\n            \n            self.monet_gen_optimizer.apply_gradients(zip(monet_gen_grads, self.monet_gen.trainable_variables))\n            self.photo_gen_optimizer.apply_gradients(zip(photo_gen_grads, self.photo_gen.trainable_variables))\n            \n            self.monet_disc_optimizer.apply_gradients(zip(monet_disc_grads, self.monet_disc.trainable_variables))\n            self.photo_disc_optimizer.apply_gradients(zip(photo_disc_grads, self.photo_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            }\n            ","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:20.971834Z","iopub.execute_input":"2022-09-14T18:37:20.972388Z","iopub.status.idle":"2022-09-14T18:37:20.990729Z","shell.execute_reply.started":"2022-09-14T18:37:20.972352Z","shell.execute_reply":"2022-09-14T18:37:20.989771Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Loss Functions","metadata":{}},{"cell_type":"code","source":"l1_loss_fn = tf.keras.losses.MeanAbsoluteError()\nl2_loss_fn = tf.keras.losses.MeanSquaredError()\n\ndef gen_loss_fn(fake):\n    fake_loss = l2_loss_fn(tf.ones_like(fake), fake)\n    return fake_loss\n\ndef disc_loss_fn(real, fake):\n    real_loss = l2_loss_fn(tf.ones_like(real), real)\n    fake_loss = l2_loss_fn(tf.zeros_like(fake), fake)\n    return (real_loss + fake_loss) * 0.5\n\ndef cycle_loss_fn(real_image, cycled_image):\n    cycle_loss = l1_loss_fn(real_image, cycled_image)\n    return cycle_loss\n\ndef identity_loss_fn(real, identity):\n    identity_loss = l1_loss_fn(real, identity)\n    return identity_loss","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:20.992239Z","iopub.execute_input":"2022-09-14T18:37:20.993427Z","iopub.status.idle":"2022-09-14T18:37:21.004391Z","shell.execute_reply.started":"2022-09-14T18:37:20.993398Z","shell.execute_reply":"2022-09-14T18:37:21.003510Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class GANMonitor(keras.callbacks.Callback):\n    \"\"\"A callback to generate and save images after each epoch\"\"\"\n\n    def __init__(self, num_img=4):\n        self.num_img = num_img\n\n    def on_epoch_end(self, epoch, logs=None):\n        _, ax = plt.subplots(4, 2, figsize=(12, 12))\n        for i, img in enumerate(photo_ds.take(self.num_img)):\n            prediction = self.model.monet_gen(img)[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 image\")\n            ax[i, 1].set_title(\"Translated image\")\n            ax[i, 0].axis(\"off\")\n            ax[i, 1].axis(\"off\")\n            \n        plt.show()\n        plt.close()","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:21.006232Z","iopub.execute_input":"2022-09-14T18:37:21.006639Z","iopub.status.idle":"2022-09-14T18:37:21.016549Z","shell.execute_reply.started":"2022-09-14T18:37:21.006605Z","shell.execute_reply":"2022-09-14T18:37:21.015543Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LEARNING_RATE = 2e-4\nBETA_1 = 0.5\nEPOCHS = 50\n\n#Get Generator and Disc models\nmonet_gen = get_gen_model(num_residual = 3)\nphoto_gen = get_gen_model(num_residual = 3)\nmonet_disc = get_disc_model()\nphoto_disc = get_disc_model()\n\n#Create CycleGan Object\ncycle_gan = CycleGan(monet_gen, photo_gen, monet_disc, photo_disc)\noptimizer = tf.keras.optimizers.Adam(learning_rate = LEARNING_RATE, beta_1 = BETA_1)\n\n#Compile CycleGan\ncycle_gan.compile(\n    monet_gen_optimizer = optimizer,\n    photo_gen_optimizer = optimizer, \n    monet_disc_optimizer = optimizer, \n    photo_disc_optimizer = optimizer, \n    gen_loss_fn = gen_loss_fn, \n    disc_loss_fn = disc_loss_fn, \n    cycle_loss_fn = cycle_loss_fn, \n    identity_loss_fn = identity_loss_fn,\n)\n\n#Callback\nplotter = GANMonitor()\n    \ncycle_gan.fit(tf.data.Dataset.zip((monet_ds, photo_ds)), epochs=EPOCHS, callbacks =[plotter])\n    ","metadata":{"execution":{"iopub.status.busy":"2022-09-14T18:37:21.017989Z","iopub.execute_input":"2022-09-14T18:37:21.018458Z","iopub.status.idle":"2022-09-14T19:44:10.561219Z","shell.execute_reply.started":"2022-09-14T18:37:21.018424Z","shell.execute_reply":"2022-09-14T19:44:10.560237Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n_, ax = plt.subplots(4, 2, figsize=(10, 15))\nfor i, img in enumerate(photo_ds.take(4)):\n    prediction = monet_gen(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":{"execution":{"iopub.status.busy":"2022-09-14T19:44:10.563135Z","iopub.execute_input":"2022-09-14T19:44:10.563782Z","iopub.status.idle":"2022-09-14T19:44:11.656052Z","shell.execute_reply.started":"2022-09-14T19:44:10.563746Z","shell.execute_reply":"2022-09-14T19:44:11.654660Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import PIL\n! mkdir '../images'\n\ni = 1\nfor img in photo_ds:\n    prediction = monet_gen(img, training = False)[0].numpy()\n    prediction = (prediction * 127.5 +127.5).astype(np.uint8)\n    im = PIL.Image.fromarray(prediction)\n    im.save('../images/' + str(i) + '.jpg')\n    i += 1\n\n","metadata":{"execution":{"iopub.status.busy":"2022-09-14T19:56:53.989820Z","iopub.execute_input":"2022-09-14T19:56:53.990192Z","iopub.status.idle":"2022-09-14T20:00:17.106773Z","shell.execute_reply.started":"2022-09-14T19:56:53.990158Z","shell.execute_reply":"2022-09-14T20:00:17.105096Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import shutil\nshutil.make_archive('/kaggle/working/images', 'zip', '/kaggle/images')","metadata":{"execution":{"iopub.status.busy":"2022-09-14T20:01:23.962821Z","iopub.execute_input":"2022-09-14T20:01:23.963604Z","iopub.status.idle":"2022-09-14T20:01:29.031550Z","shell.execute_reply.started":"2022-09-14T20:01:23.963545Z","shell.execute_reply":"2022-09-14T20:01:29.030540Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}