{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","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":30299,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Introducción\n\nEste código es basado en el tuturial propuesto para esta compentecia para un Cycle Gan. Para este caso no utilizamos los tpu debido a su alta demanda entonces usamos un acelerador de GPU T100. Se importan las librerías necesarias y se cargan los data sets.","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nimport tensorflow_addons as tfa\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__)","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","execution":{"iopub.status.busy":"2023-11-23T15:45:47.806974Z","iopub.execute_input":"2023-11-23T15:45:47.807837Z","iopub.status.idle":"2023-11-23T15:45:53.23284Z","shell.execute_reply.started":"2023-11-23T15:45:47.807797Z","shell.execute_reply":"2023-11-23T15:45:53.231644Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Cargar los datos\n\nCargamos los datos guardados como TFRecords lo que nos permite trabajar en batches y que sea computacionalmente menos costoso el proceso de carga y entrenamiento de la red.","metadata":{}},{"cell_type":"code","source":"GCS_PATH = KaggleDatasets().get_gcs_path()","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:45:53.234638Z","iopub.execute_input":"2023-11-23T15:45:53.235774Z","iopub.status.idle":"2023-11-23T15:45:53.739404Z","shell.execute_reply.started":"2023-11-23T15:45:53.235732Z","shell.execute_reply":"2023-11-23T15:45:53.738574Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"MONET_FILENAMES = tf.io.gfile.glob(str(GCS_PATH + '/monet_tfrec/*.tfrec'))\nprint('Monet TFRecord Files:', len(MONET_FILENAMES))\n\nPHOTO_FILENAMES = tf.io.gfile.glob(str(GCS_PATH + '/photo_tfrec/*.tfrec'))\nprint('Photo TFRecord Files:', len(PHOTO_FILENAMES))","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:45:58.347236Z","iopub.execute_input":"2023-11-23T15:45:58.348132Z","iopub.status.idle":"2023-11-23T15:45:58.528184Z","shell.execute_reply.started":"2023-11-23T15:45:58.348093Z","shell.execute_reply":"2023-11-23T15:45:58.527105Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Todas las imágenes para la competencia ya están dimensionadas a 256x256 píxeles. Dado que estas imágenes son imágenes RGB, establece el canal en 3. Además, necesitamos escalar las imágenes a un rango de [-1, 1]. Debido a que estamos construyendo un modelo generativo, no necesitamos las etiquetas ni el identificador de la imagen, por lo que solo extraeremos la imagen del archivo TFRecord","metadata":{}},{"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":{"execution":{"iopub.status.busy":"2023-11-23T15:46:02.411295Z","iopub.execute_input":"2023-11-23T15:46:02.412281Z","iopub.status.idle":"2023-11-23T15:46:02.420203Z","shell.execute_reply.started":"2023-11-23T15:46:02.412244Z","shell.execute_reply":"2023-11-23T15:46:02.419231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"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":{"execution":{"iopub.status.busy":"2023-11-23T15:46:02.557684Z","iopub.execute_input":"2023-11-23T15:46:02.55855Z","iopub.status.idle":"2023-11-23T15:46:02.563851Z","shell.execute_reply.started":"2023-11-23T15:46:02.558502Z","shell.execute_reply":"2023-11-23T15:46:02.562874Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"photo_ds = load_dataset(PHOTO_FILENAMES, labeled=True).batch(1)\n#monet_ds = load_dataset(MONET_FILENAMES, labeled=True).batch(1)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:02.709532Z","iopub.execute_input":"2023-11-23T15:46:02.709971Z","iopub.status.idle":"2023-11-23T15:46:05.63048Z","shell.execute_reply.started":"2023-11-23T15:46:02.709936Z","shell.execute_reply":"2023-11-23T15:46:05.629532Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Se crea un data image generator manual para poblar el data set con aún más fotos.","metadata":{}},{"cell_type":"code","source":"# Función para cargar un conjunto de datos con transformaciones aleatorias\ndef load_dataset_with_augmentation(filenames, labeled=True, ordered=False):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_tfrecord, num_parallel_calls=AUTOTUNE)\n    \n    # Aplica transformaciones aleatorias a las imágenes\n    def apply_augmentation(image):\n        random_rotation = tf.random.uniform((), minval=0, maxval=4, dtype=tf.int32) * 90\n        # Aplicar la rotación\n        image = tf.image.rot90(image, k=random_rotation // 90)\n\n        # Volteo horizontal aleatorio\n        if tf.random.uniform(()) > 0.5:\n            image = tf.image.flip_left_right(image)\n\n        # Ajuste de brillo y contraste aleatorio\n        image = tf.image.random_brightness(image, max_delta=0.2)\n        image = tf.image.random_contrast(image, lower=0.5, upper=1.5)\n\n        # Recorte y cambio de tamaño aleatorios\n        image = tf.image.random_crop(image, size=[224, 224, 3])\n        image = tf.image.resize(image, [256, 256])\n\n        # Zoom aleatorio\n        zoom_factor = tf.random.uniform((), 0.8, 1.3)\n        new_size = tf.cast(tf.shape(image)[0:2], tf.float32) * zoom_factor\n        image = tf.image.resize(image, tf.cast(new_size, tf.int32))\n        image = tf.image.resize(image, [256, 256])\n\n        return image\n    \n    dataset = dataset.map(lambda x: apply_augmentation(x), num_parallel_calls=AUTOTUNE)\n    \n    return dataset\n\n# Carga el conjunto de datos original\nmonet_ds = load_dataset(MONET_FILENAMES, labeled=True).batch(1)\n\n# Carga el conjunto de datos con transformaciones aleatorias\naugmented_monet_ds = load_dataset_with_augmentation(MONET_FILENAMES, labeled=True).batch(1)\n\n# Combina los dos conjuntos de datos para aumentar la diversidad\nmonet_ds = tf.data.Dataset.concatenate(monet_ds, augmented_monet_ds)\n","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:05.632531Z","iopub.execute_input":"2023-11-23T15:46:05.632806Z","iopub.status.idle":"2023-11-23T15:46:06.11642Z","shell.execute_reply.started":"2023-11-23T15:46:05.63278Z","shell.execute_reply":"2023-11-23T15:46:06.115594Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"example_monet = next(iter(monet_ds))\nexample_photo = next(iter(photo_ds))\n\nimport itertools\n\n# Convierte monet_ds en un iterador\nmonet_ds_iterator = iter(monet_ds)\n\n# Avanza hasta el elemento 325\nindex_to_plot = 301\nfor _ in itertools.islice(monet_ds_iterator, index_to_plot - 1):\n    pass\n\n# Obtén la imagen en la posición 301\nexample_monet = next(monet_ds_iterator)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:06.117686Z","iopub.execute_input":"2023-11-23T15:46:06.118033Z","iopub.status.idle":"2023-11-23T15:46:10.130676Z","shell.execute_reply.started":"2023-11-23T15:46:06.118004Z","shell.execute_reply":"2023-11-23T15:46:10.129496Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's  visualize a photo example and a Monet example.","metadata":{}},{"cell_type":"code","source":"plt.subplot(121)\nplt.title('Photo')\nplt.imshow(example_photo[0] * 0.5 + 0.5)\n\nplt.subplot(122)\nplt.title('Monet')\nplt.imshow(example_monet[0] * 0.5 + 0.5)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:10.137176Z","iopub.execute_input":"2023-11-23T15:46:10.13753Z","iopub.status.idle":"2023-11-23T15:46:10.542191Z","shell.execute_reply.started":"2023-11-23T15:46:10.1375Z","shell.execute_reply":"2023-11-23T15:46:10.541298Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Generador\n\nUtilizaremos una arquitectura UNET para nuestro CycleGAN. Para construir nuestro generador, primero definiremos nuestros métodos downsample (reducción de muestreo) y upsample (aumento de muestreo).\n\nEl método downsample, como sugiere su nombre, reduce las dimensiones 2D, el ancho y la altura, de la imagen mediante la zancada (stride). La zancada es la longitud del paso que toma el filtro. Dado que la zancada es 2, el filtro se aplica a cada otro píxel, reduciendo así el ancho y la altura en 2.\n\nUtilizaremos una normalización por instancia en lugar de una normalización por lotes (batch normalization). Dado que la normalización por instancia no es estándar en la API de TensorFlow, utilizaremos la capa de TensorFlow Add-ons.\n\nUNET es una arquitectura de red neuronal convolucional utilizada principalmente en tareas de segmentación de imágenes y procesamiento de imágenes médicas. Fue propuesta por primera vez por Olaf Ronneberger, Philipp Fischer y Thomas Brox en 2015. La arquitectura UNET se caracteriza por su capacidad para capturar detalles finos en imágenes y su capacidad para realizar segmentación precisa de objetos en imágenes.","metadata":{}},{"cell_type":"code","source":"OUTPUT_CHANNELS = 3\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","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:10.543285Z","iopub.execute_input":"2023-11-23T15:46:10.54358Z","iopub.status.idle":"2023-11-23T15:46:10.550824Z","shell.execute_reply.started":"2023-11-23T15:46:10.543554Z","shell.execute_reply":"2023-11-23T15:46:10.549963Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Upsample hace lo contrario de downsample y aumenta las dimensiones de la imagen. Conv2DTranspose hace básicamente lo opuesto a una capa Conv2D.","metadata":{}},{"cell_type":"code","source":"def 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","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:10.552086Z","iopub.execute_input":"2023-11-23T15:46:10.552477Z","iopub.status.idle":"2023-11-23T15:46:10.562976Z","shell.execute_reply.started":"2023-11-23T15:46:10.552448Z","shell.execute_reply":"2023-11-23T15:46:10.562022Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def Generator():\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)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:10.565428Z","iopub.execute_input":"2023-11-23T15:46:10.566548Z","iopub.status.idle":"2023-11-23T15:46:10.581921Z","shell.execute_reply.started":"2023-11-23T15:46:10.566506Z","shell.execute_reply":"2023-11-23T15:46:10.580982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Discriminator\nEl discriminador recibe la imagen de entrada y la clasifica como real o falsa (generada). En lugar de emitir un solo nodo, el discriminador produce una imagen 2D más pequeña con valores de píxeles más altos que indican una clasificación como real y valores más bajos que indican una clasificación como falsa.\n","metadata":{}},{"cell_type":"code","source":"def Discriminator():\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":"2023-11-23T15:46:10.583155Z","iopub.execute_input":"2023-11-23T15:46:10.583521Z","iopub.status.idle":"2023-11-23T15:46:10.596132Z","shell.execute_reply.started":"2023-11-23T15:46:10.583485Z","shell.execute_reply":"2023-11-23T15:46:10.595279Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with strategy.scope():\n    monet_generator = Generator() # transforms photos to Monet-esque paintings\n    photo_generator = Generator() # transforms Monet paintings to be more like photos\n\n    monet_discriminator = Discriminator() # differentiates real Monet paintings and generated Monet paintings\n    photo_discriminator = Discriminator() # differentiates real photos and generated photos","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:10.597221Z","iopub.execute_input":"2023-11-23T15:46:10.59781Z","iopub.status.idle":"2023-11-23T15:46:13.328479Z","shell.execute_reply.started":"2023-11-23T15:46:10.597782Z","shell.execute_reply":"2023-11-23T15:46:13.327498Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# CycleGAN model\n\nConstruiremos el modelo CycleGAN como una subclase de tf.keras.Model para poder utilizar la función fit() más adelante y entrenar nuestro modelo. Durante el paso de entrenamiento, el modelo transforma una foto en una pintura de Monet y luego de nuevo en una foto. La diferencia entre la foto original y la foto transformada dos veces es la pérdida de consistencia de ciclo. Queremos que la foto original y la foto transformada dos veces sean similares entre sí.","metadata":{}},{"cell_type":"code","source":"class CycleGan(keras.Model):\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(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        }","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.369203Z","iopub.execute_input":"2023-11-23T15:46:20.36988Z","iopub.status.idle":"2023-11-23T15:46:20.391445Z","shell.execute_reply.started":"2023-11-23T15:46:20.369836Z","shell.execute_reply":"2023-11-23T15:46:20.390266Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Loss functions\n\nLa función de pérdida del discriminador que se muestra a continuación compara las imágenes reales con una matriz de 1s y las imágenes falsas con una matriz de 0s. El discriminador perfecto generaría todo 1s para las imágenes reales y todo 0s para las imágenes falsas. La pérdida del discriminador calcula el promedio de la pérdida de las imágenes reales y generadas.","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n    def discriminator_loss(real, generated):\n        real_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(tf.ones_like(real), real)\n\n        generated_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(tf.zeros_like(generated), generated)\n\n        total_disc_loss = real_loss + generated_loss\n\n        return total_disc_loss * 0.5","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.392607Z","iopub.execute_input":"2023-11-23T15:46:20.392971Z","iopub.status.idle":"2023-11-23T15:46:20.406777Z","shell.execute_reply.started":"2023-11-23T15:46:20.392902Z","shell.execute_reply":"2023-11-23T15:46:20.405758Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with strategy.scope():\n    def generator_loss(generated):\n        return tf.keras.losses.BinaryCrossentropy(from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(tf.ones_like(generated), generated)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.40805Z","iopub.execute_input":"2023-11-23T15:46:20.408437Z","iopub.status.idle":"2023-11-23T15:46:20.417572Z","shell.execute_reply.started":"2023-11-23T15:46:20.408397Z","shell.execute_reply":"2023-11-23T15:46:20.41671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with strategy.scope():\n    def calc_cycle_loss(real_image, cycled_image, LAMBDA):\n        loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))\n\n        return LAMBDA * loss1","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.418809Z","iopub.execute_input":"2023-11-23T15:46:20.41917Z","iopub.status.idle":"2023-11-23T15:46:20.427477Z","shell.execute_reply.started":"2023-11-23T15:46:20.419134Z","shell.execute_reply":"2023-11-23T15:46:20.426607Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with strategy.scope():\n    def identity_loss(real_image, same_image, LAMBDA):\n        loss = tf.reduce_mean(tf.abs(real_image - same_image))\n        return LAMBDA * 0.5 * loss","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.428602Z","iopub.execute_input":"2023-11-23T15:46:20.428907Z","iopub.status.idle":"2023-11-23T15:46:20.437972Z","shell.execute_reply.started":"2023-11-23T15:46:20.428881Z","shell.execute_reply":"2023-11-23T15:46:20.437106Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Entrenamiento del CycleGAN","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\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)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.439186Z","iopub.execute_input":"2023-11-23T15:46:20.439491Z","iopub.status.idle":"2023-11-23T15:46:20.449121Z","shell.execute_reply.started":"2023-11-23T15:46:20.439464Z","shell.execute_reply":"2023-11-23T15:46:20.448161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with strategy.scope():\n    cycle_gan_model = CycleGan(\n        monet_generator, photo_generator, monet_discriminator, photo_discriminator\n    )\n\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    )","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.450176Z","iopub.execute_input":"2023-11-23T15:46:20.450481Z","iopub.status.idle":"2023-11-23T15:46:20.473812Z","shell.execute_reply.started":"2023-11-23T15:46:20.450449Z","shell.execute_reply":"2023-11-23T15:46:20.47296Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history = cycle_gan_model.fit(\n    tf.data.Dataset.zip((monet_ds, photo_ds)),\n    epochs= 20\n)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T15:46:20.474995Z","iopub.execute_input":"2023-11-23T15:46:20.475289Z","iopub.status.idle":"2023-11-23T16:37:51.676353Z","shell.execute_reply.started":"2023-11-23T15:46:20.475262Z","shell.execute_reply":"2023-11-23T16:37:51.675392Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Resultados","metadata":{}},{"cell_type":"code","source":"_, ax = plt.subplots(5, 2, figsize=(12, 12))\nfor i, img in enumerate(photo_ds.take(20)):\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\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-11-23T16:37:51.681602Z","iopub.execute_input":"2023-11-23T16:37:51.681897Z","iopub.status.idle":"2023-11-23T16:37:53.287212Z","shell.execute_reply.started":"2023-11-23T16:37:51.68187Z","shell.execute_reply":"2023-11-23T16:37:53.286229Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Iterar sobre las imágenes y guardarlas en el directorio actual\nfor i, img in enumerate(photo_ds.take(10)):\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    # Guardar la imagen generada en el directorio actual\n    plt.imsave(f'monet-esque_{i}.png', prediction)\n\n    # Opcional: También puedes guardar la imagen original si lo deseas\n    plt.imsave(f'input_photo_{i}.png', img)","metadata":{"execution":{"iopub.status.busy":"2023-11-23T17:18:56.912007Z","iopub.execute_input":"2023-11-23T17:18:56.912431Z","iopub.status.idle":"2023-11-23T17:18:58.762236Z","shell.execute_reply.started":"2023-11-23T17:18:56.912395Z","shell.execute_reply":"2023-11-23T17:18:58.761411Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Datos de ejemplo (sustituye con tus datos reales)\nepochs = list(range(1, 21))  # Número de épocas\nmonet_gen_loss = [4.2749, 3.3944, 3.2355, 3.1614, 3.1177, 3.0789, 3.0405, 2.9900, 2.9397, 2.8852, 2.8156, 2.8000, 2.7591, 2.7492, 2.7000, 2.6877, 2.6901, 2.6741, 2.6570, 2.6161]  # Pérdida del generador Monet\nphoto_gen_loss = [4.5267, 3.5803, 3.4291, 3.3502, 3.2554, 3.1770, 3.1356, 3.0802, 3.0435, 2.9939, 2.9354, 2.9144, 2.8786, 2.8809, 2.8255, 2.8134, 2.8060, 2.8049, 2.7949, 2.7760]  # Pérdida del generador Photo\nmonet_disc_loss = [0.6596, 0.6405, 0.6219, 0.6186, 0.6104, 0.6091, 0.6012, 0.6093, 0.6151, 0.6116, 0.6216, 0.6216, 0.6278, 0.6287, 0.6308, 0.6351, 0.6330, 0.6314, 0.6343, 0.6072]  # Pérdida del discriminador Monet\nphoto_disc_loss = [0.6106, 0.6042, 0.5875, 0.5908, 0.6054, 0.6119, 0.6049, 0.6119, 0.6138, 0.6108, 0.6142, 0.6146, 0.6136, 0.6095, 0.6176, 0.6128, 0.6084, 0.6087, 0.6072, 0.6033]  # Pérdida del discriminador Photo\n\n# Crear la gráfica\nplt.figure(figsize=(12, 6))\n\nplt.subplot(1, 2, 1)\nplt.plot(epochs, monet_gen_loss, label='Generador Monet')\nplt.plot(epochs, photo_gen_loss, label='Generador Photo')\nplt.title('Pérdida del Generador')\nplt.xlabel('Épocas')\nplt.ylabel('Pérdida')\nplt.legend()\nplt.grid(True)\n\nplt.subplot(1, 2, 2)\nplt.plot(epochs, monet_disc_loss, label='Discriminador Monet')\nplt.plot(epochs, photo_disc_loss, label='Discriminador Photo')\nplt.title('Pérdida del Discriminador')\nplt.xlabel('Épocas')\nplt.ylabel('Pérdida')\nplt.legend()\nplt.grid(True)\n\n# Guardar la gráfica en un archivo\nplt.savefig('grafica_perdidas.png')\n\n# Mostrar la gráfica\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2023-11-23T17:12:05.213813Z","iopub.execute_input":"2023-11-23T17:12:05.214759Z","iopub.status.idle":"2023-11-23T17:12:05.710898Z","shell.execute_reply.started":"2023-11-23T17:12:05.214721Z","shell.execute_reply":"2023-11-23T17:12:05.709966Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Guarda solo los pesos del modelo\ncycle_gan_model.save_weights(\"modelo_cycle_gan_weights.h5\")\n\n# Cargar los pesos del modelo\n#cycle_gan_model.load_weights(\"cycle_gan_weights.h5\")","metadata":{"execution":{"iopub.status.busy":"2023-11-23T18:04:48.939054Z","iopub.execute_input":"2023-11-23T18:04:48.939436Z","iopub.status.idle":"2023-11-23T18:04:50.033526Z","shell.execute_reply.started":"2023-11-23T18:04:48.939407Z","shell.execute_reply":"2023-11-23T18:04:50.032438Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Este código es basado en el tutorial para CycleGans propuesto para esta competencia.","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}