{"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":"import tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nimport tensorflow_addons as tfa\nimport matplotlib.pyplot as plt\nimport os\nimport PIL\nimport shutil\nimport numpy as np\nimport keras\nimport cv2\nfrom tensorflow.keras.applications.inception_v3 import InceptionV3\nfrom tensorflow.keras.preprocessing import image\nfrom tensorflow.keras.applications.inception_v3 import preprocess_input","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:28:06.232825Z","iopub.execute_input":"2021-08-22T14:28:06.233439Z","iopub.status.idle":"2021-08-22T14:28:12.670454Z","shell.execute_reply.started":"2021-08-22T14:28:06.233350Z","shell.execute_reply":"2021-08-22T14:28:12.669397Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"try:\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)","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:28:12.671947Z","iopub.execute_input":"2021-08-22T14:28:12.672242Z","iopub.status.idle":"2021-08-22T14:28:12.684608Z","shell.execute_reply.started":"2021-08-22T14:28:12.672215Z","shell.execute_reply":"2021-08-22T14:28:12.683257Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Utils**","metadata":{}},{"cell_type":"code","source":"class TfrecCreator:\n    @staticmethod\n    def _bytes_feature(value):\n        \"\"\"Returns a bytes_list from a string / byte.\"\"\"\n        if isinstance(value, type(tf.constant(0))):\n            value = value.numpy()  # BytesList won't unpack a string from an EagerTensor.\n        return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))\n\n    @staticmethod\n    def _float_feature(value):\n        \"\"\"Returns a float_list from a float / double.\"\"\"\n        return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))\n\n    @staticmethod\n    def _int64_feature(value):\n        \"\"\"Returns an int64_list from a bool / enum / int / uint.\"\"\"\n        return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))\n\n    @staticmethod\n    def serialize_example(feature0, feature1, feature2):\n        feature = {\n            'image_name': TfrecCreator._bytes_feature(feature0),\n            'image': TfrecCreator._bytes_feature(feature1),\n            'target': TfrecCreator._bytes_feature(feature2)\n        }\n        example_proto = tf.train.Example(features=tf.train.Features(feature=feature))\n        return example_proto.SerializeToString()\n\n    @staticmethod\n    def create_tfrec(jpeg_path, tfrecords_filepath):\n        # PATHS TO IMAGES\n        PATH = jpeg_path\n        IMGS = os.listdir(PATH);\n        print(f'There are %i train images and {len(IMGS)} test images')\n\n        SIZE = 30\n        CT = len(IMGS) // SIZE + int(len(IMGS) % SIZE != 0)\n        for j in range(CT):\n            print();\n            print('Writing TFRecord %i of %i...' % (j, CT))\n            CT2 = min(SIZE, len(IMGS) - j * SIZE)\n            with tf.io.TFRecordWriter(tfrecords_filepath + ('%.2i-%i.tfrec' % (j, CT2))) as writer:\n                for k in range(CT2):\n                    img = cv2.imread(os.path.join(PATH,IMGS[SIZE * j + k]))\n                    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)  # Fix incorrect colors\n                    img = cv2.imencode('.jpg', img, (cv2.IMWRITE_JPEG_QUALITY, 94))[1].tostring()\n                    name = IMGS[SIZE * j + k].split('.')[0]\n                    example = TfrecCreator.serialize_example(\n                        str.encode(name),\n                        img,\n                        str.encode('monet'))\n                    writer.write(example)\n                    if k % 100 == 0: print(k, ', ', end='')\n\n\ndef create_submission_file(photo_ds, monet_generator):\n\n    if not os.path.exists('original_images'):\n        os.mkdir('original_images')\n\n    if not os.path.exists('images'):\n        os.mkdir('images')\n    i = 1\n    for img in photo_ds:\n\n        original_img = img.numpy()\n        original_img = (original_img * 127.5 + 127.5).astype(np.uint8)\n        original_img = PIL.Image.fromarray(original_img.squeeze(0))\n        original_img.save(\"./original_images/\" + str(i) + \".jpg\")\n\n        prediction = monet_generator(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    shutil.make_archive(\"./images\", 'zip', \"./images\")\n    \n\ndef visualize_our_monet_photos(photo_ds, monet_generator):\n    def close_event():\n        plt.close()  # timer calls this function after 10 seconds and closes the window\n\n    fig, ax = plt.subplots(5, 2, figsize=(12, 12))\n    # creating a timer object and setting an interval of 10000 milliseconds\n    timer = fig.canvas.new_timer(interval=10000)\n    timer.add_callback(close_event)\n    timer.start()\n\n    for i, img in enumerate(photo_ds.take(5)):\n        prediction = monet_generator(img, training=False)[0].numpy()\n        prediction = (prediction * 127.5 + 127.5).astype(np.uint8)\n        img = (img[0] * 127.5 + 127.5).numpy().astype(np.uint8)\n\n        ax[i, 0].imshow(img)\n        ax[i, 1].imshow(prediction)\n        ax[i, 0].set_title(\"Input Photo\")\n        ax[i, 1].set_title(\"Monet-esque\")\n        ax[i, 0].axis(\"off\")\n        ax[i, 1].axis(\"off\")\n    plt.show()\n\n\nclass CustomCallback(keras.callbacks.Callback):\n\n    def __init__(self, photo_ds, monet_generator):\n        self.photo_ds = photo_ds\n        self.monet_generator = monet_generator\n\n    def on_epoch_end(self, epoch, logs=None):\n        keys = list(logs.keys())\n        print(f\"End epoch {epoch} of training; got log keys: {keys}\")\n        visualize_our_monet_photos(photo_ds=self.photo_ds, monet_generator=self.monet_generator)\n","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:28:12.687376Z","iopub.execute_input":"2021-08-22T14:28:12.687825Z","iopub.status.idle":"2021-08-22T14:28:12.716667Z","shell.execute_reply.started":"2021-08-22T14:28:12.687780Z","shell.execute_reply":"2021-08-22T14:28:12.715336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get all monet file names -----------------------------------------------------------------------------------------\nsource_dir_path = '../input/gan-getting-started/photo_jpg'\nall_files = os.listdir(source_dir_path)\nimages = []\nfor file in all_files:\n    if file.endswith('.jpg'):\n        images.append(file)","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:28:12.718806Z","iopub.execute_input":"2021-08-22T14:28:12.719230Z","iopub.status.idle":"2021-08-22T14:28:12.978240Z","shell.execute_reply.started":"2021-08-22T14:28:12.719185Z","shell.execute_reply":"2021-08-22T14:28:12.977118Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Choose 30 images**","metadata":{}},{"cell_type":"code","source":"# Inception Net for Feature-Extraction\nclass InceptionFE:\n    def __init__(self, pooling='max', trainable=False):\n        self.inception_v3_net = InceptionV3(\n            include_top=False,\n            weights=\"imagenet\",\n            input_tensor=None,\n            input_shape=None,\n            pooling=pooling,\n            classes=1000,\n            classifier_activation=\"softmax\",\n        )\n        self.inception_v3_net.trainable = trainable\n","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:28:12.979850Z","iopub.execute_input":"2021-08-22T14:28:12.980258Z","iopub.status.idle":"2021-08-22T14:28:12.986173Z","shell.execute_reply.started":"2021-08-22T14:28:12.980214Z","shell.execute_reply":"2021-08-22T14:28:12.985369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_top_similar_and_different_euclidean_distance(model, dir_path, images):\n    features = None\n    for img_name in images:\n        img = image.load_img(os.path.join(dir_path,  img_name), target_size=(256, 256))\n        x = image.img_to_array(img)\n        x = np.expand_dims(x, axis=0)\n        x = preprocess_input(x)\n        x = model.inception_v3_net.predict(x)\n        if features is None:\n            features = x\n        else:\n            features = tf.concat([features, x], 0)\n\n    # Euclidean distance matrix\n    distance_matrix = np.zeros((300, 300))\n    for i in range(300):\n        j = 0\n        while j <= i:\n            distance = tf.norm(features[i] - features[j])\n            distance_matrix[i][j] = float(distance)\n            distance_matrix[j][i] = float(distance)\n            j += 1\n\n    distance_matrix = tf.convert_to_tensor(distance_matrix, dtype=tf.float32)\n    distance_sum_from_all = tf.reduce_sum(distance_matrix, axis=0)\n\n    # Extract top similar and top different 30 indices\n    top_different_30_indices = tf.math.top_k(distance_sum_from_all, k=30).indices\n    top_similar_30_indices = tf.math.top_k(-distance_sum_from_all, k=30).indices\n\n    return top_similar_30_indices, top_different_30_indices","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:28:12.987562Z","iopub.execute_input":"2021-08-22T14:28:12.987856Z","iopub.status.idle":"2021-08-22T14:28:13.000668Z","shell.execute_reply.started":"2021-08-22T14:28:12.987828Z","shell.execute_reply":"2021-08-22T14:28:12.999480Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Parameters -------------------------------------------------------------------------------------------------------\ndir_path = '../input/gan-getting-started/monet_jpg'\npooling = 'max'\n# pooling = 'avg'\n\n# Create directories & Set paths -----------------------------------------------------------------------------------\nchosen30dir = './chosen30'\ndestination_dir_name = 'top_different_30'\ndestination_full_path = os.path.join(chosen30dir, destination_dir_name)\nif not os.path.isdir(chosen30dir):\n    os.mkdir(chosen30dir)\nif not os.path.isdir(destination_full_path):\n    os.mkdir(destination_full_path)\n\n# Create model for features extraction------------------------------------------------------------------------------\nmodel = InceptionFE(pooling=pooling)\n\n# Get all monet file names -----------------------------------------------------------------------------------------\nall_files = os.listdir(dir_path)\nimages = []\nfor file in all_files:\n    if file.endswith('.jpg'):\n        images.append(file)\n\n# Find 30 most different images ----=================---------------------------------------------------------------\n_, top_different_30_indices = get_top_similar_and_different_euclidean_distance(model=model,\n                                                                               dir_path=dir_path,\n                                                                               images=images)\n# Copy 30 most different images to separate directory --------------------------------------------------------------\nfor index in top_different_30_indices:\n    shutil.copy2(os.path.join(dir_path, images[index]), os.path.join(destination_full_path, images[index]))\n","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:28:13.002171Z","iopub.execute_input":"2021-08-22T14:28:13.002714Z","iopub.status.idle":"2021-08-22T14:29:17.386005Z","shell.execute_reply.started":"2021-08-22T14:28:13.002679Z","shell.execute_reply":"2021-08-22T14:29:17.384887Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Create TFrec file**","metadata":{}},{"cell_type":"code","source":"# Create TFrec file ------------------------------------------------------------------------------------------------\nif not os.path.isdir('./tfrec'):\n    os.mkdir('./tfrec')\n\nmonet_tfrecords_dir_path = './tfrec/monet_tfrec_' + destination_dir_name\nif not os.path.isdir(monet_tfrecords_dir_path):\n    os.mkdir(monet_tfrecords_dir_path)\n\nTfrecCreator.create_tfrec(jpeg_path=destination_full_path,\n                          tfrecords_filepath=monet_tfrecords_dir_path + '/monet')","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:29:17.388713Z","iopub.execute_input":"2021-08-22T14:29:17.389136Z","iopub.status.idle":"2021-08-22T14:29:17.523187Z","shell.execute_reply.started":"2021-08-22T14:29:17.389090Z","shell.execute_reply":"2021-08-22T14:29:17.520961Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **DataLoader**","metadata":{}},{"cell_type":"code","source":"class TFRecordsDataLoader:\n\n    def __init__(self, directory_path, monet_dir_path='monet_tfrec'):\n        self.AUTOTUNE = tf.data.experimental.AUTOTUNE\n\n        # Get file names of all dataset\n        self.MONET_FILENAMES = tf.io.gfile.glob(os.path.join(monet_dir_path, '*.tfrec'))\n        print(f'Monet TFRecord Files: {len(self.MONET_FILENAMES)}')\n\n        self.PHOTO_FILENAMES = tf.io.gfile.glob(os.path.join(directory_path, 'photo_tfrec/*.tfrec'))\n        print(f'Photo TFRecord Files: {len(self.PHOTO_FILENAMES)}')\n\n        self.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  \n        self.monet_splits = {\n            \"monet00-30\": 1,\n        }\n\n        self.photo_splits = {\n            \"photo00-352\": 0.05,\n            \"photo01-352\": 0.05,\n            \"photo02-352\": 0.05,\n            \"photo03-352\": 0.05,\n            \"photo04-352\": 0.05,\n            \"photo05-352\": 0.05,\n            \"photo06-352\": 0.05,\n            \"photo07-352\": 0.05,\n            \"photo08-352\": 0.05,\n            \"photo09-352\": 0.05,\n            \"photo10-352\": 0.05,\n            \"photo11-352\": 0.05,\n            \"photo12-352\": 0.05,\n            \"photo13-352\": 0.05,\n            \"photo14-352\": 0.05,\n            \"photo15-352\": 0.05,\n            \"photo16-352\": 0.05,\n            \"photo17-352\": 0.05,\n            \"photo18-352\": 0.05,\n            \"photo19-350\": 0.05,\n        }\n\n        self.IMAGE_SIZE = [256, 256]\n\n    def decode_image(self, image):\n        # Decode a JPEG-encoded image to a uint8 tensor\n        image = tf.image.decode_jpeg(image, channels=3)\n        # Scale the images to a [-1, 1] scale.\n        image = (tf.cast(image, tf.float32) / 127.5) - 1\n        # Reshape to [255, 255, 3]\n        image = tf.reshape(image, [*self.IMAGE_SIZE, 3])\n        return image\n\n    def read_tfrecord(self, example):\n        example = tf.io.parse_single_example(serialized=example, features=self.tfrecord_format)\n        image = self.decode_image(image=example['image'])\n        return image\n\n    def load_dataset(self, filenames, labeled=True, ordered=False):\n        \"\"\"\n        Define the function to extract the image from the files.\n        :param filenames:\n        :param labeled:\n        :param ordered:\n        :return:\n        \"\"\"\n        dataset = tf.data.TFRecordDataset(filenames)\n        dataset = dataset.map(self.read_tfrecord, num_parallel_calls=self.AUTOTUNE)\n        return dataset","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:29:17.524784Z","iopub.execute_input":"2021-08-22T14:29:17.525063Z","iopub.status.idle":"2021-08-22T14:29:17.540313Z","shell.execute_reply.started":"2021-08-22T14:29:17.525037Z","shell.execute_reply":"2021-08-22T14:29:17.539074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Nets**","metadata":{}},{"cell_type":"code","source":"OUTPUT_CHANNELS = 3\n\n\ndef downsample(filters, size, apply_instancenorm=True):\n    initializer = tf.random_normal_initializer(0., 0.02)\n    gamma_init = keras.initializers.RandomNormal(mean=0.0, stddev=0.02)\n\n    result = keras.Sequential()\n    result.add(layers.Conv2D(filters, size, strides=2, padding='same',\n                             kernel_initializer=initializer, use_bias=False))\n\n    if apply_instancenorm:\n        result.add(tfa.layers.InstanceNormalization(gamma_initializer=gamma_init))\n\n    result.add(layers.LeakyReLU())\n\n    return result\n\n\ndef upsample(filters, size, apply_dropout=False):\n    initializer = tf.random_normal_initializer(0., 0.02)\n    gamma_init = keras.initializers.RandomNormal(mean=0.0, stddev=0.02)\n\n    result = keras.Sequential()\n    result.add(layers.Conv2DTranspose(filters, size, strides=2,\n                                      padding='same',\n                                      kernel_initializer=initializer,\n                                      use_bias=False))\n\n    result.add(tfa.layers.InstanceNormalization(gamma_initializer=gamma_init))\n\n    if apply_dropout:\n        result.add(layers.Dropout(0.5))\n\n    result.add(layers.ReLU())\n\n    return result\n\n\ndef Generator():\n    \"\"\"\n    The generator first downsamples the input image and then upsample while establishing long skip connections.\n    Skip connections are a way to help bypass the vanishing gradient problem by concatenating the output of a layer\n    to multiple layers instead of only one.\n    Here we concatenate the output of the downsample layer to the upsample layer in a symmetrical fashion.\n    :return:\n    \"\"\"\n    inputs = layers.Input(shape=[256,256,3])\n\n    # bs = batch size\n    down_stack = [\n        downsample(64, 4, apply_instancenorm=False), # (bs, 128, 128, 64)\n        downsample(128, 4), # (bs, 64, 64, 128)\n        downsample(256, 4), # (bs, 32, 32, 256)\n        downsample(512, 4), # (bs, 16, 16, 512)\n        downsample(512, 4), # (bs, 8, 8, 512)\n        downsample(512, 4), # (bs, 4, 4, 512)\n        downsample(512, 4), # (bs, 2, 2, 512)\n        downsample(512, 4), # (bs, 1, 1, 512)\n    ]\n\n    up_stack = [\n        upsample(512, 4, apply_dropout=True), # (bs, 2, 2, 1024)\n        upsample(512, 4, apply_dropout=True), # (bs, 4, 4, 1024)\n        upsample(512, 4, apply_dropout=True), # (bs, 8, 8, 1024)\n        upsample(512, 4), # (bs, 16, 16, 1024)\n        upsample(256, 4), # (bs, 32, 32, 512)\n        upsample(128, 4), # (bs, 64, 64, 256)\n        upsample(64, 4), # (bs, 128, 128, 128)\n    ]\n\n    initializer = tf.random_normal_initializer(0., 0.02)\n    last = layers.Conv2DTranspose(OUTPUT_CHANNELS, 4,\n                                  strides=2,\n                                  padding='same',\n                                  kernel_initializer=initializer,\n                                  activation='tanh') # (bs, 256, 256, 3)\n\n    x = inputs\n\n    # Downsampling through the model\n    skips = []\n    for down in down_stack:\n        x = down(x)\n        skips.append(x)\n\n    skips = reversed(skips[:-1])\n\n    # Upsampling and establishing the skip connections\n    for up, skip in zip(up_stack, skips):\n        x = up(x)\n        x = layers.Concatenate()([x, skip])\n\n    x = last(x)\n\n    return keras.Model(inputs=inputs, outputs=x)\n\n\ndef Discriminator():\n    \"\"\"\n    The discriminator takes in the input image and classifies it as real or fake (generated).\n    Instead of outputing a single node, the discriminator outputs a smaller 2D image with higher pixel values\n    indicating a real classification and lower values indicating a fake classification.\n    :return:\n    \"\"\"\n    initializer = tf.random_normal_initializer(0., 0.02)\n    gamma_init = keras.initializers.RandomNormal(mean=0.0, stddev=0.02)\n\n    inp = layers.Input(shape=[256, 256, 3], name='input_image')\n\n    x = inp\n\n    down1 = downsample(64, 4, False)(x) # (bs, 128, 128, 64)\n    down2 = downsample(128, 4)(down1) # (bs, 64, 64, 128)\n    down3 = downsample(256, 4)(down2) # (bs, 32, 32, 256)\n\n    zero_pad1 = layers.ZeroPadding2D()(down3) # (bs, 34, 34, 256)\n    conv = layers.Conv2D(512, 4, strides=1,\n                         kernel_initializer=initializer,\n                         use_bias=False)(zero_pad1) # (bs, 31, 31, 512)\n\n    norm1 = tfa.layers.InstanceNormalization(gamma_initializer=gamma_init)(conv)\n\n    leaky_relu = layers.LeakyReLU()(norm1)\n\n    zero_pad2 = layers.ZeroPadding2D()(leaky_relu) # (bs, 33, 33, 512)\n\n    last = layers.Conv2D(1, 4, strides=1,\n                         kernel_initializer=initializer)(zero_pad2) # (bs, 30, 30, 1)\n\n    return tf.keras.Model(inputs=inp, outputs=last)","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:29:17.542028Z","iopub.execute_input":"2021-08-22T14:29:17.542691Z","iopub.status.idle":"2021-08-22T14:29:17.573856Z","shell.execute_reply.started":"2021-08-22T14:29:17.542643Z","shell.execute_reply":"2021-08-22T14:29:17.573073Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **CycleGan Model**","metadata":{}},{"cell_type":"code","source":"class CycleGan(keras.Model):\n    \"\"\"\n    subclass a tf.keras.Model so that we can run fit() later to train our model.\n    During the training step, the model transforms a photo to a Monet painting and then back to a photo.\n    The difference between the original photo and the twice-transformed photo is the cycle-consistency loss.\n    We want the original photo and the twice-transformed photo to be similar to one another.\n    \"\"\"\n    def __init__(\n            self,\n            monet_generator,\n            photo_generator,\n            monet_discriminator,\n            photo_discriminator,\n            lambda_cycle=10,\n    ):\n        super(CycleGan, self).__init__()\n        self.m_gen = monet_generator\n        self.p_gen = photo_generator\n        self.m_disc = monet_discriminator\n        self.p_disc = photo_discriminator\n        self.lambda_cycle = lambda_cycle\n\n    def compile(\n            self,\n            m_gen_optimizer,\n            p_gen_optimizer,\n            m_disc_optimizer,\n            p_disc_optimizer,\n            gen_loss_fn,\n            disc_loss_fn,\n            cycle_loss_fn,\n            identity_loss_fn\n    ):\n        super(CycleGan, self).compile()\n        self.m_gen_optimizer = m_gen_optimizer\n        self.p_gen_optimizer = p_gen_optimizer\n        self.m_disc_optimizer = m_disc_optimizer\n        self.p_disc_optimizer = p_disc_optimizer\n        self.gen_loss_fn = gen_loss_fn\n        self.disc_loss_fn = disc_loss_fn\n        self.cycle_loss_fn = cycle_loss_fn\n        self.identity_loss_fn = identity_loss_fn\n\n    def train_step(self, batch_data):\n        real_monet, real_photo = batch_data\n\n        with tf.GradientTape(persistent=True) as tape:\n            # photo to monet back to photo\n            fake_monet = self.m_gen(real_photo, training=True)\n            cycled_photo = self.p_gen(fake_monet, training=True)\n\n            # monet to photo back to monet\n            fake_photo = self.p_gen(real_monet, training=True)\n            cycled_monet = self.m_gen(fake_photo, training=True)\n\n            # generating itself\n            same_monet = self.m_gen(real_monet, training=True)\n            same_photo = self.p_gen(real_photo, training=True)\n\n            # discriminator used to check, inputing real images\n            disc_real_monet = self.m_disc(real_monet, training=True)\n            disc_real_photo = self.p_disc(real_photo, training=True)\n\n            # discriminator used to check, inputing fake images\n            disc_fake_monet = self.m_disc(fake_monet, training=True)\n            disc_fake_photo = self.p_disc(fake_photo, training=True)\n\n            # evaluates generator loss\n            monet_gen_loss = self.gen_loss_fn(disc_fake_monet)\n            photo_gen_loss = self.gen_loss_fn(disc_fake_photo)\n\n            # evaluates total cycle consistency loss\n            total_cycle_loss = self.cycle_loss_fn(real_monet, cycled_monet, self.lambda_cycle) + self.cycle_loss_fn \\\n                (real_photo, cycled_photo, self.lambda_cycle)\n\n            # evaluates total generator loss\n            total_monet_gen_loss = monet_gen_loss + total_cycle_loss + self.identity_loss_fn(real_monet, same_monet, self.lambda_cycle)\n            total_photo_gen_loss = photo_gen_loss + total_cycle_loss + self.identity_loss_fn(real_photo, same_photo, self.lambda_cycle)\n\n            # evaluates discriminator loss\n            monet_disc_loss = self.disc_loss_fn(disc_real_monet, disc_fake_monet)\n            photo_disc_loss = self.disc_loss_fn(disc_real_photo, disc_fake_photo)\n\n        # Calculate the gradients for generator and discriminator\n        monet_generator_gradients = tape.gradient(total_monet_gen_loss,\n                                                  self.m_gen.trainable_variables)\n        photo_generator_gradients = tape.gradient(total_photo_gen_loss,\n                                                  self.p_gen.trainable_variables)\n\n        monet_discriminator_gradients = tape.gradient(monet_disc_loss,\n                                                      self.m_disc.trainable_variables)\n        photo_discriminator_gradients = tape.gradient(photo_disc_loss,\n                                                      self.p_disc.trainable_variables)\n\n        # Apply the gradients to the optimizer\n        self.m_gen_optimizer.apply_gradients(zip(monet_generator_gradients,\n                                                 self.m_gen.trainable_variables))\n\n        self.p_gen_optimizer.apply_gradients(zip(photo_generator_gradients,\n                                                 self.p_gen.trainable_variables))\n\n        self.m_disc_optimizer.apply_gradients(zip(monet_discriminator_gradients,\n                                                  self.m_disc.trainable_variables))\n\n        self.p_disc_optimizer.apply_gradients(zip(photo_discriminator_gradients,\n                                                  self.p_disc.trainable_variables))\n\n        return {\n            \"monet_gen_loss\": total_monet_gen_loss,\n            \"photo_gen_loss\": total_photo_gen_loss,\n            \"monet_disc_loss\": monet_disc_loss,\n            \"photo_disc_loss\": photo_disc_loss\n        }\n","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:29:17.575400Z","iopub.execute_input":"2021-08-22T14:29:17.575998Z","iopub.status.idle":"2021-08-22T14:29:17.601972Z","shell.execute_reply.started":"2021-08-22T14:29:17.575949Z","shell.execute_reply":"2021-08-22T14:29:17.601110Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Loss Functions**","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n    def discriminator_loss(real, generated):\n        \"\"\"\n        The discriminator loss function below compares real images to a matrix of 1s and fake images to a matrix of 0s.\n        The perfect discriminator will output all 1s for real images and all 0s for fake images.\n        The discriminator loss outputs the average of the real and generated loss.\n        :param real: real image\n        :param generated: generated image\n        :return: (real_loss + generated_loss) / 2. i.e real and generated loss avg\n        \"\"\"\n        real_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True,\n                                                       reduction=tf.keras.losses.Reduction.NONE)(tf.ones_like(real),\n                                                                                                 real)\n        generated_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True,\n                                                            reduction=tf.keras.losses.Reduction.NONE)(\n            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(generated):\n        \"\"\"\n        The generator wants to fool the discriminator into thinking the generated image is real.\n        The perfect generator will have the discriminator output only 1s.\n        Thus, it compares the generated image to a matrix of 1s to find the loss.\n        :param generated: generated image\n        :return: generator loss\n        \"\"\"\n        return tf.keras.losses.BinaryCrossentropy(from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(\n            tf.ones_like(generated), generated)\n\n    def calc_cycle_loss(real_image, cycled_image, LAMBDA):\n        \"\"\"\n        We want our original photo and the twice transformed photo to be similar to one another.\n        Thus, we can calculate the cycle consistency loss be finding the average of their difference.\n        :param real_image:\n        :param cycled_image:\n        :param LAMBDA: the cycle loss factor\n        :return: cycle loss\n        \"\"\"\n        loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))\n        return LAMBDA * loss1\n\n    def identity_loss(real_image, same_image, LAMBDA):\n        \"\"\"\n        The identity loss compares the image with its generator (i.e. photo with photo generator).\n        If given a photo as input, we want it to generate the same image as the image was originally a photo.\n        The identity loss compares the input with the output of the generator.\n        :param real_image:\n        :param same_image:\n        :param LAMBDA: loss factor\n        :return:\n        \"\"\"\n        loss = tf.reduce_mean(tf.abs(real_image - same_image))\n        return LAMBDA * 0.5 * loss","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:29:17.603371Z","iopub.execute_input":"2021-08-22T14:29:17.604025Z","iopub.status.idle":"2021-08-22T14:29:17.617238Z","shell.execute_reply.started":"2021-08-22T14:29:17.603979Z","shell.execute_reply":"2021-08-22T14:29:17.616393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset_path = '../input/gan-getting-started'\nbatch = 1\nepochs = 25\n    \nwith strategy.scope():\n    # Create Generators and Discriminators -------------------------------------------------------------------------\n    # Create Generators\n    monet_generator = Generator()  # transforms photos to Monet-esque paintings\n    photo_generator = Generator()  # transforms Monet paintings to be more like photos\n\n    # Create Discriminators\n    monet_discriminator = Discriminator()  # differentiates real Monet paintings and generated Monet paintings\n    photo_discriminator = Discriminator()  # differentiates real photos and generated photos\n\n    # Data loader --------------------------------------------------------------------------------------------------\n    # Create our data-loader\n    print(f'monet tfrec path: {monet_tfrecords_dir_path}')\n    data_loader = TFRecordsDataLoader(directory_path=dataset_path, monet_dir_path=monet_tfrecords_dir_path)\n\n    # Load datasets (monet and photo) To tensorflow tensor\n    monet_ds = data_loader.load_dataset(data_loader.MONET_FILENAMES, labeled=True).batch(batch)\n    photo_ds = data_loader.load_dataset(data_loader.PHOTO_FILENAMES, labeled=True).batch(batch)\n\n    # # DEBUG\n    # # Iterate on datasets (sample batch) -------------------------------------------------------------------------\n    # \"\"\"\n    # Since our generators are not trained yet, the generated Monet-esque photo does not show what is expected at\n    # this point.\n    # \"\"\"\n    # example_monet = next(iter(monet_ds))\n    # example_photo = next(iter(photo_ds))\n    #\n    # to_monet = monet_generator(example_photo)\n    #\n    # plt.subplot(1, 2, 1)\n    # plt.title(\"Original Photo\")\n    # plt.imshow(example_photo[0] * 0.5 + 0.5)\n    #\n    # plt.subplot(1, 2, 2)\n    # plt.title(\"Monet-esque Photo\")\n    # plt.imshow(to_monet[0] * 0.5 + 0.5)\n    # plt.show()\n\n    # Train the CycleGAN -------------------------------------------------------------------------------------------\n    # Create Optimizers\n    monet_generator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n    photo_generator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n\n    monet_discriminator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n    photo_discriminator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n\n    # Create CycleGAN model\n    cycle_gan_model = CycleGan(\n        monet_generator=monet_generator,\n        photo_generator=photo_generator,\n        monet_discriminator=monet_discriminator,\n        photo_discriminator=photo_discriminator,\n        lambda_cycle=10\n        )\n\n    # Compile. i.e Configures the model for training\n    cycle_gan_model.compile(\n        m_gen_optimizer=monet_generator_optimizer,\n        p_gen_optimizer=photo_generator_optimizer,\n        m_disc_optimizer=monet_discriminator_optimizer,\n        p_disc_optimizer=photo_discriminator_optimizer,\n        gen_loss_fn=generator_loss,\n        disc_loss_fn=discriminator_loss,\n        cycle_loss_fn=calc_cycle_loss,\n        identity_loss_fn=identity_loss\n        )\n\n    # # Create a callback to visualize generated monet images after every epoch\n    # customCallback = CustomCallback(photo_ds=photo_ds, monet_generator=monet_generator)\n    #\n    # # Visualize our monet generated photos before training is started\n    # visualize_our_monet_photos(photo_ds=photo_ds, monet_generator=monet_generator)","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:29:17.618724Z","iopub.execute_input":"2021-08-22T14:29:17.619356Z","iopub.status.idle":"2021-08-22T14:29:22.491773Z","shell.execute_reply.started":"2021-08-22T14:29:17.619315Z","shell.execute_reply":"2021-08-22T14:29:22.490787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Train CycleGAN model**","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n    cycle_gan_model.fit(\n        tf.data.Dataset.zip((monet_ds, photo_ds)),\n        # callbacks=[customCallback],\n        epochs=epochs\n    )","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:29:22.493182Z","iopub.execute_input":"2021-08-22T14:29:22.493613Z","iopub.status.idle":"2021-08-22T14:40:29.481517Z","shell.execute_reply.started":"2021-08-22T14:29:22.493572Z","shell.execute_reply":"2021-08-22T14:40:29.479545Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Create a submit zip file**","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n    create_submission_file(photo_ds=photo_ds, monet_generator=monet_generator)","metadata":{"execution":{"iopub.status.busy":"2021-08-22T14:40:29.482465Z","iopub.status.idle":"2021-08-22T14:40:29.482908Z"},"trusted":true},"execution_count":null,"outputs":[]}]}