{"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":"# Requirements\n##Installs\ndef check_dependencies_already_installed() -> bool:\n    try:\n        import gdown\n        return True\n    except ImportError:\n        return False\n\n\ndef install_dependencies():\n    !pip install 'tensorflow_addons' 'tensorflow-determinism' 'gdown'\n\n\ndependencies_already_installed = check_dependencies_already_installed()\nprint(f'dependencies_already_installed: {dependencies_already_installed}')\nif not dependencies_already_installed:\n    install_dependencies()\n\n##Imports\nimport os\nimport random\nimport json\nfrom enum import Enum\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.python.data.ops.dataset_ops import Dataset\nfrom tensorflow.python.distribute.tpu_strategy import TPUStrategy\nimport tensorflow_addons as tfa\nfrom skimage.metrics import structural_similarity\nfrom scipy.stats import wasserstein_distance\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom tqdm import tqdm\nfrom PIL import Image\nfrom pathlib import Path\nfrom datetime import datetime\nfrom zipfile import ZipFile\n\n\n# Environment setup\n##Set tensorflow deterministic mode\ndef set_tf_deterministic_mode():\n    os.environ['TF_DETERMINISTIC_OPS'] = '1'\n    os.environ['TF_CUDNN_DETERMINISTIC'] = '1'\n\n\n##Set random seed\ndef set_training_random_seed(seed: int):\n    random.seed(seed)\n    tf.random.set_seed(seed)\n    np.random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    print(f'set_training_random_seed() - seed value: {seed}')\n\n\n##Connect to strongest available device\nset_tf_deterministic_mode()\n\n\ndef choose_strongest_available_device_strategy():\n    try:\n        tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n        tf.config.experimental_connect_to_cluster(tpu)\n        tf.tpu.experimental.initialize_tpu_system(tpu)\n        selected_strategy = TPUStrategy(tpu)\n    except:\n        selected_strategy = tf.distribute.get_strategy()\n\n    print(f\"choose_strongest_available_device_strategy() - selected strategy type: {type(selected_strategy).__name__}\")\n\n    # todo itay - delete this section so it won't mess up in google colab\n    gpu_is_available = any(tf.config.list_physical_devices('GPU'))\n    if gpu_is_available:\n        !nvidia-smi\n\n    print(f'\\n\\n*** running_on_tpu - {isinstance(selected_strategy, TPUStrategy)} ***\\n\\n')\n    return selected_strategy\n\n\n\nDEVICE_STRATEGY = choose_strongest_available_device_strategy()\n\n# todo itay - delete\nassert tf.__version__ == '2.6.4'\n\n##Download competition dataset\nLOCAL_DATASET_FOLDER_PATH = Path('./train_data/gan-getting-started/')\n\n\ndef download_competition_dataset_if_not_present():\n    dataset_already_downloaded = LOCAL_DATASET_FOLDER_PATH.exists()\n    print(f\"dataset_already_downloaded: {dataset_already_downloaded}\")\n    if not dataset_already_downloaded:\n        # note - this is the untouched competition dataset. just uploaded it to the drive so it'll be\n        # available via colab as well.\n        !gdown '1ZwcoO11NKhYsbuM7hzdSzKjGOnOx6X94'\n        !mkdir -p {LOCAL_DATASET_FOLDER_PATH}\n        !unzip -o -q ./gan-getting-started.zip -d {LOCAL_DATASET_FOLDER_PATH}\n\n\ndownload_competition_dataset_if_not_present()\n\n#Choose 30 monet train images\nclass TrainImagesSelectionMethod(Enum):\n    RandomSelection = 'random_selection'\n    FarthestImagesByPixelDistance = 'farthest_images_by_pixel_distance'\n    ClosestImagesByPixelDistance = 'closest_images_by_pixel_distance'\n    FarthestImagesByStructuralDistance = 'farthest_images_by_structural_distance'\n    ClosestImagesByStructuralDistance = 'closest_images_by_structural_distance'\n    FarthestImagesByEarthMoversDistance = 'farthest_images_by_earth_movers_distance'\n    ClosestImagesByEarthMoversDistance = 'closest_images_by_earth_movers_distance'\n\n\ndef _choose_30_monet_train_images(\n        raw_ordered_monet_dataset: Dataset, method: TrainImagesSelectionMethod,\n        experiment_random_seed: int, use_preprocessed_cache: bool\n) -> Dataset:\n    set_training_random_seed(seed=42)\n\n    try:\n        original_raw_ordered_monet_images = list(raw_ordered_monet_dataset)\n        if use_preprocessed_cache:\n            preprocessed_indices_cache = {\n                TrainImagesSelectionMethod.RandomSelection: [203, 266, 152, 9, 233, 226, 196, 109, 5, 175, 237, 57, 218, 45, 182, 221, 289, 211, 148, 165, 78, 113, 249, 250, 104, 42, 281, 295, 157, 238],\n                TrainImagesSelectionMethod.FarthestImagesByPixelDistance: [57, 299, 113, 74, 160, 193, 139, 283, 93, 56, 90, 196, 108, 34, 45, 40, 249, 87, 240, 106, 218, 208, 2, 289, 16, 11, 155, 52, 292, 272],\n                TrainImagesSelectionMethod.ClosestImagesByPixelDistance: [57, 119, 185, 203, 70, 216, 238, 61, 273, 169, 81, 222, 68, 137, 58, 67, 295, 164, 50, 217, 179, 190, 125, 290, 44, 251, 274, 195, 7, 165],\n                TrainImagesSelectionMethod.FarthestImagesByStructuralDistance: [57, 134, 16, 152, 133, 116, 94, 147, 177, 161, 40, 9, 218, 110, 70, 101, 75, 54, 243, 100, 98, 237, 77, 115, 106, 119, 45, 261, 241, 90],\n                TrainImagesSelectionMethod.ClosestImagesByStructuralDistance: [57, 254, 238, 160, 86, 295, 187, 51, 41, 81, 273, 120, 230, 148, 25, 13, 264, 114, 236, 155, 58, 59, 151, 91, 107, 207, 289, 203, 176, 125],\n                TrainImagesSelectionMethod.FarthestImagesByEarthMoversDistance: [57, 168, 144, 249, 145, 117, 288, 109, 48, 207, 252, 251, 186, 278, 296, 39, 263, 234, 167, 165, 128, 41, 290, 176, 33, 107, 202, 201, 282, 127],\n                TrainImagesSelectionMethod.ClosestImagesByEarthMoversDistance: [57, 196, 13, 131, 148, 97, 187, 74, 11, 162, 147, 240, 55, 70, 102, 76, 213, 139, 256, 253, 247, 17, 227, 108, 86, 133, 19, 77, 42, 1],\n            }\n            assert method in preprocessed_indices_cache, f\"unknown method - '{method}'\"\n            chosen_30_images_indices = preprocessed_indices_cache[method]\n        elif method is TrainImagesSelectionMethod.RandomSelection:\n            chosen_30_images_indices = _pick_random_images(\n                original_raw_ordered_monet_images, images_count=30\n            )\n        elif method is TrainImagesSelectionMethod.FarthestImagesByPixelDistance:\n            chosen_30_images_indices = _pick_images_farthest_from_each_other(\n                original_raw_ordered_monet_images,\n                distance_func=_images_pixel_distance,\n                images_count=30\n            )\n        elif method is TrainImagesSelectionMethod.ClosestImagesByPixelDistance:\n            chosen_30_images_indices = _pick_images_farthest_from_each_other(\n                original_raw_ordered_monet_images,\n                distance_func=_images_pixel_distance,\n                images_count=30,\n                reverse_distance=True\n            )\n        elif method is TrainImagesSelectionMethod.FarthestImagesByStructuralDistance:\n            chosen_30_images_indices = _pick_images_farthest_from_each_other(\n                original_raw_ordered_monet_images,\n                distance_func=_images_structural_distance,\n                images_count=30\n            )\n        elif method is TrainImagesSelectionMethod.ClosestImagesByStructuralDistance:\n            chosen_30_images_indices = _pick_images_farthest_from_each_other(\n                original_raw_ordered_monet_images,\n                distance_func=_images_structural_distance,\n                images_count=30,\n                reverse_distance=True\n            )\n        elif method is TrainImagesSelectionMethod.FarthestImagesByEarthMoversDistance:\n            chosen_30_images_indices = _pick_images_farthest_from_each_other(\n                original_raw_ordered_monet_images,\n                distance_func=_earth_movers_distance,\n                images_count=30\n            )\n        elif method is TrainImagesSelectionMethod.ClosestImagesByEarthMoversDistance:\n            chosen_30_images_indices = _pick_images_farthest_from_each_other(\n                original_raw_ordered_monet_images,\n                distance_func=_earth_movers_distance,\n                images_count=30,\n                reverse_distance=True\n            )\n        else:\n            raise NotImplementedError(f\"unknown method - '{method}'\")\n\n        chosen_30_raw_images_dataset = Dataset.from_tensor_slices([\n            original_raw_ordered_monet_images[image_idx]\n            for image_idx in chosen_30_images_indices\n        ])\n        _plot_chosen_30_images(chosen_30_raw_images_dataset)\n    finally:\n        set_training_random_seed(experiment_random_seed)\n    return chosen_30_raw_images_dataset\n\n\ndef _plot_chosen_30_images(chosen_30_raw_images_dataset):\n    try:\n        images_shape = list(chosen_30_raw_images_dataset)[0].shape\n        print(f'*** Selected 30 train monet photos (shape: {images_shape}) ***')\n        _, ax = plt.subplots(30, 1, figsize=(50, 50))\n        for i, img in enumerate(chosen_30_raw_images_dataset):\n            # img = (img * 127.5 + 127.5).numpy()[0].astype(np.uint8)\n            ax[i].imshow(img)\n        plt.show()\n    finally:\n        plt.close()\n\n\n# Load competition dataset\n##Load full dataset\ndef find_competition_dataset_files(local_dataset_folder_path: Path):\n    if isinstance(DEVICE_STRATEGY, TPUStrategy):\n        from kaggle_datasets import KaggleDatasets\n        dataset_folder_path = Path(KaggleDatasets().get_gcs_path())\n    else:\n        dataset_folder_path = local_dataset_folder_path\n\n    monet_dataset_files = tf.io.gfile.glob(str(dataset_folder_path / 'monet_tfrec/*.tfrec'))\n    photo_dataset_files = tf.io.gfile.glob(str(dataset_folder_path / 'photo_tfrec/*.tfrec'))\n    assert any(monet_dataset_files)\n    assert any(photo_dataset_files)\n    print(f\"found {len(monet_dataset_files)} monet and {len(photo_dataset_files)} photo tfrec files.\")\n\n    return monet_dataset_files, photo_dataset_files\n\n\ndef normalize_images_dataset_for_model(images_dataset: Dataset, batch_size: int = 1) -> Dataset:\n    def _prepare_image_tensor_for_training(image):\n        image = (tf.cast(image, tf.float32) / 127.5) - 1\n        image = tf.reshape(image, [256, 256, 3])\n        image = tf.image.resize(image, (320, 320), method='bilinear')\n        return image\n\n    images_dataset = images_dataset.map(\n        _prepare_image_tensor_for_training, num_parallel_calls=tf.data.experimental.AUTOTUNE\n    ).batch(batch_size)\n    return images_dataset\n\n\n\ndef load_tf_records_raw_dataset(tf_record_files) -> Dataset:\n    def _read_and_normalize_tfrecord(record):\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        record = tf.io.parse_single_example(record, tfrecord_format)\n        image = record['image']\n        image = tf.image.decode_jpeg(image, channels=3)\n        return image\n\n    sorted_tf_record_files = sorted(tf_record_files)\n    raw_dataset = tf.data.TFRecordDataset(sorted_tf_record_files)\n    raw_dataset = raw_dataset.map(_read_and_normalize_tfrecord, num_parallel_calls=tf.data.experimental.AUTOTUNE)\n    return raw_dataset\n\n\n##Pick 30 train monet images strategies\ndef _pick_images_farthest_from_each_other(\n        original_ordered_monet_images: list, distance_func, images_count: int, reverse_distance: bool = False\n) -> list:\n    comparison_images_resize_shape = (100, 100)\n\n    def _pre_comparison_transformation_func(image_tensor):\n        image_array = image_tensor.numpy()\n        resized_image = tf.image.resize(image_array, comparison_images_resize_shape).numpy()\n        resized_image = resized_image\n        return resized_image\n\n    final_distance_func = distance_func\n    if reverse_distance:\n        final_distance_func = lambda image1, image2: -distance_func(image1, image2)\n\n    chosen_30_images_indices = _incremental_farthest_search(\n        original_ordered_monet_images,\n        k=images_count,\n        distance_func=final_distance_func,\n        pre_comparison_transformation_func=_pre_comparison_transformation_func\n    )\n    return chosen_30_images_indices\n\n\ndef _pick_random_images(original_ordered_monet_images: list, images_count: int) -> list:\n    chosen_30_images_indices = list(np.random.choice(\n        list(range(len(original_ordered_monet_images))), size=images_count, replace=False\n    ))\n    return chosen_30_images_indices\n\n\ndef _incremental_farthest_search(\n        ordered_image_tensors_list, k: int, distance_func, pre_comparison_transformation_func\n):\n    remaining_images = [\n        dict(\n            orig_image_index=orig_image_index,\n            img_tensor=img_tensor,\n            img_comparison_array=pre_comparison_transformation_func(img_tensor)\n        )\n        for orig_image_index, img_tensor in enumerate(ordered_image_tensors_list)\n    ]\n\n    chosen_30_images_indices = [remaining_images.pop(random.randint(0, len(remaining_images) - 1))]\n    for _ in tqdm(list(range(k - 1)), desc='incremental_farthest_search() main loop'):\n        distances = [\n            distance_func(\n                i['img_comparison_array'],\n                chosen_30_images_indices[0]['img_comparison_array']\n            )\n            for i in remaining_images\n        ]\n        for i, p in enumerate(remaining_images):\n            for j, s in enumerate(chosen_30_images_indices):\n                distances[i] = min(distances[i], distance_func(\n                    p['img_comparison_array'], s['img_comparison_array']\n                ))\n        chosen_30_images_indices.append(remaining_images.pop(distances.index(max(distances))))\n    chosen_30_images_indices = [i['orig_image_index'] for i in chosen_30_images_indices]\n    return chosen_30_images_indices\n\n\ndef _images_pixel_distance(image1: np.array, image2: np.array) -> float:\n    distance = np.sum((image1.flatten() - image2.flatten()) ** 2)\n    return distance\n\n\ndef _images_structural_distance(image1: np.array, image2: np.array) -> float:\n    similarity_index, *_ = structural_similarity(image1.flatten(), image2.flatten(), full=True)\n    return -similarity_index\n\n\ndef _earth_movers_distance(image1: np.array, image2: np.array) -> float:\n    image1_hist = _calc_image_greyscale_histogram(image1)\n    image2_hist = _calc_image_greyscale_histogram(image2)\n    distance = wasserstein_distance(image1_hist, image2_hist)\n    return distance\n\n\ndef _calc_image_greyscale_histogram(image: np.array) -> np.array:\n    greyscale_image = np.array(Image.fromarray(image).convert('L'))\n    h, w = greyscale_image.shape\n    hist = [0.0] * 256\n    for i in range(h):\n        for j in range(w):\n            hist[greyscale_image[i, j]] += 1\n    return np.array(hist) / (h * w)\n\n\ndef down_sample_layer(filters, size, strides=2, padding='same'):\n    initializer = tf.random_normal_initializer(0., 0.02)\n\n    network = keras.Sequential()\n    network.add(layers.Conv2D(filters, size, strides=strides, padding=padding,\n                              kernel_initializer=initializer, use_bias=False))\n\n    network.add(layers.LeakyReLU())\n\n    return network\n\n\ndef up_sample_layer(filters, size, strides=2, padding='same', apply_dropout=False):\n    network = keras.Sequential()\n    network.add(layers.Conv2DTranspose(\n        filters, size, strides=strides, padding=padding, use_bias=False,\n        kernel_initializer=tf.random_normal_initializer(0., 0.02)\n    ))\n    if apply_dropout:\n        network.add(layers.Dropout(0.5))\n    network.add(layers.ReLU())\n    return network\n\n\nclass GeneratorNetworkStructure(Enum):\n    Baseline = 'baseline'\n    Thin = 'thin'\n    Wide = 'wide'\n    Deep = 'deep'\n\n\nclass DiscriminatorNetworkStructure(Enum):\n    Baseline = 'baseline'\n    ThinNetwork = 'thin_network'\n    WideNetwork = 'wide_network'\n    ThinReceptiveField = 'thin_receptive_field'\n    WideReceptiveField = 'wide_receptive_field'\n\n\ndef build_generator_model(generator_network_structure: GeneratorNetworkStructure):\n    down_stack, up_stack = _build_generator_encoder_decoder_layout(generator_network_structure)\n    inputs = layers.Input(shape=[320, 320, 3])\n\n    layers_to_direct_to_previous = []\n    x = inputs\n    for down in down_stack:\n        x = down(x)\n        layers_to_direct_to_previous.append(x)\n\n    for up, skip in zip(up_stack, reversed(layers_to_direct_to_previous[:-1])):\n        x = up(x)\n        x = layers.Concatenate()([x, skip])\n\n    initializer = tf.random_normal_initializer(0.0, 0.02)\n    last_layer = layers.Conv2DTranspose(3, 4,\n                                  strides=2,\n                                  padding='same',\n                                  kernel_initializer=initializer,\n                                  activation='tanh')  # (bs, 320, 320, 3)\n    generator_model = keras.Model(inputs=inputs, outputs=last_layer(x))\n    return generator_model\n\n\ndef _build_generator_encoder_decoder_layout(network_structure: GeneratorNetworkStructure):\n    # todo itay delete dimensions comments\n\n    if network_structure is GeneratorNetworkStructure.Baseline:\n        # bs = batch size\n        down_stack = [\n            down_sample_layer(64, 4),  # (bs, 160, 160, 64)\n            down_sample_layer(128, 4),  # (bs, 80, 80, 128)\n            down_sample_layer(256, 4),  # (bs, 40, 40, 256)\n            down_sample_layer(512, 4),  # (bs, 20, 20, 512)\n            down_sample_layer(512, 4),  # (bs, 10, 10, 512)\n            down_sample_layer(512, 4),  # (bs, 5, 5, 512)\n            down_sample_layer(512, 4, strides=1, padding='valid'),  # (bs, 2, 2, 512)\n            down_sample_layer(512, 4),  # (bs, 1, 1, 512)\n        ]\n        up_stack = [\n            up_sample_layer(512, 4, apply_dropout=True),  # (bs, 2, 2, 1024)\n            up_sample_layer(512, 4, strides=1, padding='valid', apply_dropout=True),  # (bs, 5, 5, 1024)\n            up_sample_layer(512, 4, apply_dropout=True),  # (bs, 10, 10, 1024)\n            up_sample_layer(512, 4),  # (bs, 20, 20, 1024)\n            up_sample_layer(256, 4),  # (bs, 40, 40, 512)\n            up_sample_layer(128, 4),  # (bs, 80, 80, 256)\n            up_sample_layer(64, 4),  # (bs, 160, 160, 128)\n        ]\n    elif network_structure is GeneratorNetworkStructure.Thin:\n        # bs = batch size\n        down_stack = [\n            down_sample_layer(64, 4),  # (bs, 160, 160, 64)\n            down_sample_layer(128, 4),  # (bs, 80, 80, 128)\n            down_sample_layer(128, 4),  # (bs, 40, 40, 128)\n            down_sample_layer(256, 4),  # (bs, 20, 20, 256)\n            down_sample_layer(256, 4),  # (bs, 10, 10, 256)\n            down_sample_layer(256, 4),  # (bs, 5, 5, 256)\n            down_sample_layer(512, 4, strides=1, padding='valid'),  # (bs, 2, 2, 512)\n            down_sample_layer(512, 4),  # (bs, 1, 1, 512)\n        ]\n        up_stack = [\n            up_sample_layer(512, 4, apply_dropout=True),  # (bs, 2, 2, 1024)\n            up_sample_layer(512, 4, strides=1, padding='valid', apply_dropout=True),  # (bs, 5, 5, 1024)\n            up_sample_layer(256, 4, apply_dropout=True),  # (bs, 10, 10, 512)\n            up_sample_layer(256, 4),  # (bs, 20, 20, 512)\n            up_sample_layer(128, 4),  # (bs, 40, 40, 256)\n            up_sample_layer(128, 4),  # (bs, 80, 80, 256)\n            up_sample_layer(64, 4),  # (bs, 160, 160, 128)\n        ]\n    elif network_structure is GeneratorNetworkStructure.Wide:\n        # bs = batch size\n        down_stack = [\n            down_sample_layer(64, 4),  # (bs, 160, 160, 64)\n            down_sample_layer(256, 4),  # (bs, 80, 80, 256)\n            down_sample_layer(512, 4),  # (bs, 40, 40, 512)\n            down_sample_layer(1_024, 4),  # (bs, 20, 20, 1_024)\n            down_sample_layer(1_024, 4),  # (bs, 10, 10, 1_024)\n            down_sample_layer(1_024, 4),  # (bs, 5, 5, 1_024)\n            down_sample_layer(1_024, 4, strides=1, padding='valid'),  # (bs, 2, 2, 1_024)\n            down_sample_layer(512, 4),  # (bs, 1, 1, 512)\n        ]\n        up_stack = [\n            up_sample_layer(1_024, 4, apply_dropout=True),  # (bs, 2, 2, 2048)\n            up_sample_layer(1_024, 4, strides=1, padding='valid', apply_dropout=True),  # (bs, 5, 5, 2048)\n            up_sample_layer(1_024, 4, apply_dropout=True),  # (bs, 10, 10, 2048)\n            up_sample_layer(1_024, 4),  # (bs, 20, 20, 2048)\n            up_sample_layer(512, 4),  # (bs, 40, 40, 1024)\n            up_sample_layer(256, 4),  # (bs, 80, 80, 512)\n            up_sample_layer(64, 4),  # (bs, 160, 160, 128)\n        ]\n    elif network_structure is GeneratorNetworkStructure.Deep:\n        # bs = batch size\n        down_stack = [\n            down_sample_layer(64, 4),  # (bs, 160, 160, 64)\n            down_sample_layer(128, 4),  # (bs, 80, 80, 128)\n            down_sample_layer(128, 4, strides=1, padding='same'),  # (bs, 80, 80, 128)\n            down_sample_layer(256, 4),  # (bs, 40, 40, 256)\n            down_sample_layer(256, 4, strides=1, padding='same'),  # (bs, 40, 40, 256)\n            down_sample_layer(512, 4),  # (bs, 20, 20, 512)\n            down_sample_layer(512, 4, strides=1, padding='same'),  # (bs, 20, 20, 512)\n            down_sample_layer(512, 4),  # (bs, 10, 10, 512)\n            down_sample_layer(512, 4, strides=1, padding='same'),  # (bs, 10, 10, 512)\n            down_sample_layer(512, 4),  # (bs, 5, 5, 512)\n            down_sample_layer(512, 4, strides=1, padding='valid'),  # (bs, 2, 2, 512)\n            down_sample_layer(512, 4),  # (bs, 1, 1, 512)\n        ]\n        up_stack = [\n            up_sample_layer(512, 4, apply_dropout=True),  # (bs, 2, 2, 1024)\n            up_sample_layer(512, 4, strides=1, padding='valid', apply_dropout=True),  # (bs, 5, 5, 1024)\n            up_sample_layer(512, 4, apply_dropout=True),  # (bs, 10, 10, 1024)\n            up_sample_layer(512, 4, strides=1, padding='same'),  # (bs, 10, 10, 1024)\n            up_sample_layer(512, 4),  # (bs, 20, 20, 1024)\n            up_sample_layer(512, 4, strides=1, padding='same'),  # (bs, 20, 20, 1024)\n            up_sample_layer(256, 4),  # (bs, 40, 40, 512)\n            up_sample_layer(256, 4, strides=1, padding='same'),  # (bs, 40, 40, 512)\n            up_sample_layer(128, 4),  # (bs, 80, 80, 256)\n            up_sample_layer(128, 4, strides=1, padding='same'),  # (bs, 80, 80, 256)\n            up_sample_layer(64, 4),  # (bs, 160, 160, 128)\n        ]\n    else:\n        raise NotImplementedError(f\"unknown network structure - '{network_structure}'\")\n    return down_stack, up_stack\n\n\n\"\"\"# Build the discriminator\n\nThe discriminator takes in the input image and classifies it as real or fake (generated). Instead of outputing a single node, the discriminator outputs a smaller 2D image with higher pixel values indicating a real classification and lower values indicating a fake classification.\n\"\"\"\n\n\ndef build_discriminator_model(discriminator_network_structure: DiscriminatorNetworkStructure):\n    # todo itay delete dimensions comments\n\n    inp = layers.Input(shape=[320, 320, 3], name='input_image')\n    conv_layers_layout = _build_patch_discriminator_conv_layers_layout(discriminator_network_structure)\n    x = inp\n    for down_conv in conv_layers_layout:\n        x = down_conv(x)\n\n    zero_pad1 = layers.ZeroPadding2D()(x)  # (bs, 34, 34, 256)\n    initializer = tf.random_normal_initializer(0.0, 0.02)\n    conv = layers.Conv2D(512, 4, strides=1,\n                         kernel_initializer=initializer,\n                         use_bias=False)(zero_pad1)  # (bs, 31, 31, 512)\n\n    leaky_relu = layers.LeakyReLU()(conv)\n    zero_pad2 = layers.ZeroPadding2D()(leaky_relu)  # (bs, 33, 33, 512)\n    last = layers.Conv2D(1, 4, strides=1,\n                         kernel_initializer=initializer)(zero_pad2)  # (bs, 30, 30, 1)\n\n    discriminator_model = tf.keras.Model(inputs=inp, outputs=last)\n    return discriminator_model\n\n\ndef _build_patch_discriminator_conv_layers_layout(network_structure: DiscriminatorNetworkStructure):\n    # todo itay delete dimensions comments\n\n    if network_structure is DiscriminatorNetworkStructure.Baseline:\n        # final receptive filed size = 123\n        conv_layers_layout = [\n            down_sample_layer(64, 5),  # (bs, 160, 160, 64)\n            down_sample_layer(128, 4),  # (bs, 80, 80, 128)\n            down_sample_layer(256, 3),  # (bs, 40, 40, 256)\n            down_sample_layer(256, 2),  # (bs, 20, 20, 256)\n        ]\n    elif network_structure is DiscriminatorNetworkStructure.ThinNetwork:\n        # final receptive filed size = 123\n        conv_layers_layout = [\n            down_sample_layer(32, 5),  # (bs, 160, 160, 32)\n            down_sample_layer(64, 4),  # (bs, 80, 80, 64)\n            down_sample_layer(128, 3),  # (bs, 40, 40, 128)\n            down_sample_layer(256, 2),  # (bs, 20, 20, 256)\n        ]\n    elif network_structure is DiscriminatorNetworkStructure.WideNetwork:\n        # final receptive filed size = 123\n        conv_layers_layout = [\n            down_sample_layer(128, 5),  # (bs, 160, 160, 64)\n            down_sample_layer(256, 4),  # (bs, 80, 80, 128)\n            down_sample_layer(512, 3),  # (bs, 40, 40, 256)\n            down_sample_layer(256, 2),  # (bs, 20, 20, 256)\n        ]\n    elif network_structure is DiscriminatorNetworkStructure.ThinReceptiveField:\n        # final receptive filed size = 47\n        conv_layers_layout = [\n            down_sample_layer(64, size=3, strides=2),  # (bs, 160, 160, 64)\n            down_sample_layer(128, size=3, strides=2),  # (bs, 80, 80, 128)\n            down_sample_layer(256, size=3, strides=1),  # (bs, 40, 40, 256)\n            down_sample_layer(256, size=3, strides=1),  # (bs, 20, 20, 256)\n        ]\n    elif network_structure is DiscriminatorNetworkStructure.WideReceptiveField:\n        # final receptive filed size = 310\n        conv_layers_layout = [\n            down_sample_layer(64, size=4, strides=3),  # (bs, 160, 160, 64)\n            down_sample_layer(128, size=4, strides=3),  # (bs, 80, 80, 128)\n            down_sample_layer(256, size=4, strides=2),  # (bs, 40, 40, 256)\n            down_sample_layer(256, size=4, strides=2),  # (bs, 20, 20, 256)\n        ]\n    else:\n        raise NotImplementedError(f\"unknown network structure - '{network_structure}'\")\n    return conv_layers_layout\n\n\nclass 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        self._my_hack_history = []\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,\n                                                                                             self.lambda_cycle)\n            total_photo_gen_loss = photo_gen_loss + total_cycle_loss + self.identity_loss_fn(real_photo, same_photo,\n                                                                                             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        rett = {\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        self._my_hack_history.append(rett)\n        return rett\n\n\ndef _build_losses():\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\n\n    def generators_loss(generated):\n        loss = tf.keras.losses.BinaryCrossentropy(\n            from_logits=True,\n            reduction=tf.keras.losses.Reduction.NONE\n        )(tf.ones_like(generated), generated)\n        return loss\n\n    def discriminators_loss(real, generated):\n        real_loss = tf.keras.losses.BinaryCrossentropy(\n            from_logits=True,\n            reduction=tf.keras.losses.Reduction.NONE\n        )(tf.ones_like(real), real)\n\n        generated_loss = tf.keras.losses.BinaryCrossentropy(\n            from_logits=True,\n            reduction=tf.keras.losses.Reduction.NONE\n        )(tf.zeros_like(generated), generated)\n\n        total_disc_loss = real_loss + generated_loss\n        return total_disc_loss * 0.5\n\n    def final_cycle_loss(real_image, cycled_image, lambda_):\n        loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))\n        return lambda_ * loss1\n\n    return identity_loss, generators_loss, discriminators_loss, final_cycle_loss\n\n\ndef _build_cycle_gan_model(train_settings):\n    generator_network_structure = train_settings['generator_network_structure']\n    discriminator_network_structure = train_settings['discriminator_network_structure']\n    monet_generator = build_generator_model(generator_network_structure)\n    photo_generator = build_generator_model(generator_network_structure)\n    monet_discriminator = build_discriminator_model(discriminator_network_structure)\n    photo_discriminator = build_discriminator_model(discriminator_network_structure)\n\n    identity_loss, generators_loss, discriminators_loss, final_cycle_loss = _build_losses()\n\n    optimizer_builder = train_settings['optimizer_builder']\n    monet_generator_optimizer = optimizer_builder()\n    photo_generator_optimizer = optimizer_builder()\n    monet_discriminator_optimizer = optimizer_builder()\n    photo_discriminator_optimizer = optimizer_builder()\n\n    cycle_gan_model = CycleGan(\n        monet_generator, photo_generator, monet_discriminator, photo_discriminator\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=generators_loss,\n        disc_loss_fn=discriminators_loss,\n        cycle_loss_fn=final_cycle_loss,\n        identity_loss_fn=identity_loss\n    )\n    return cycle_gan_model, monet_generator\n\n\n#Add Image augmentations to train dataset\ndef add_random_augmentations(\n        raw_images_dataset: Dataset, random_seed: int, usage_each_image_n_times: int\n) -> Dataset:\n    augmentations_pipe = keras.Sequential([\n        layers.RandomFlip(\n            'horizontal_and_vertical', seed=random_seed\n        ),\n        layers.RandomZoom(\n            height_factor=(0, -0.15), width_factor=(0, -0.15), seed=random_seed\n        ),\n        layers.RandomRotation(\n            0.3, seed=random_seed\n        )\n    ])\n    images_dataset_with_augmentations = raw_images_dataset.map(\n        augmentations_pipe, num_parallel_calls=tf.data.experimental.AUTOTUNE\n    ).repeat(usage_each_image_n_times)\n    return images_dataset_with_augmentations\n\n\n#Plotting utils\ndef plot_cycle_gan_train_losses(train_history):\n    epoch_level_train_history = {\n        loss_name: {\n            epoch: epoch_losses.flatten().mean()\n            for epoch, epoch_losses in enumerate(epochs_losses)\n        }\n        for loss_name, epochs_losses in train_history.history.items()\n    }\n\n    try:\n        fig, (top_ax, bottom_ax) = plt.subplots(2, figsize=(12, 12))\n        fig.suptitle('Loss vs Epoch')\n\n        top_ax.set_title('Generators')\n        plt_lines = []\n        for loss_name in ('monet_gen_loss', 'photo_gen_loss'):\n            loss_epoch_values = epoch_level_train_history[loss_name]\n            line, = top_ax.plot(loss_epoch_values.keys(), loss_epoch_values.values(), label=loss_name)\n            plt_lines.append(line)\n        top_ax.legend(handles=plt_lines)\n\n        bottom_ax.set_title('Discriminators')\n        plt_lines = []\n        for loss_name in ('monet_disc_loss', 'photo_disc_loss'):\n            loss_epoch_values = epoch_level_train_history[loss_name]\n            line, = bottom_ax.plot(loss_epoch_values.keys(), loss_epoch_values.values(), label=loss_name)\n            plt_lines.append(line)\n        bottom_ax.legend(handles=plt_lines)\n\n        plt.tight_layout()\n        plt.show()\n    finally:\n        plt.close()\n\n    final_losses = {\n        loss_name: str(loss_epoch_values[max(loss_epoch_values.keys())])\n        for loss_name, loss_epoch_values in epoch_level_train_history.items()\n    }\n    print(f\"*** trained cycle gan final losses ***\\n{json.dumps(final_losses, indent=4)}\")\n\n\ndef plot_predictions_sample(monet_generator, photo_dataset):\n    print('*** Show trained model predictions sample ***')\n    _, ax = plt.subplots(5, 2, figsize=(12, 12))\n    for i, img in enumerate(photo_dataset.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\n\"\"\"# Define loss functions\n\nThe discriminator loss function below compares real images to a matrix of 1s and fake images to a matrix of 0s. The perfect discriminator will output all 1s for real images and all 0s for fake images. The discriminator loss outputs the average of the real and generated loss.\n\"\"\"\n\n\"\"\"# Create submission file\"\"\"\ndef create_predictions_for_kaggle_submission(monet_generator: tf.keras.Model, photo_dataset: Dataset):\n    wip_images_folder = Path(f\"/tmp/wip_{datetime.now().strftime('%y_%m_%d__%H_%M_%S')}/\")\n    wip_images_folder.mkdir(parents=True, exist_ok=True)\n    output_zip_path = Path('/kaggle/working/images.zip')\n    if output_zip_path.exists():\n        output_zip_path.unlink()\n\n    with ZipFile(output_zip_path, 'w') as output_images_zip, \\\n            tqdm(total=7_038, desc='generating prediction images for kaggle submission') as pbar:\n        for i, img in enumerate(photo_dataset):\n            prediction = monet_generator(img, training=False)[0]\n            prediction = tf.image.resize(prediction, (256, 256), method='bilinear')\n            prediction = prediction.numpy()\n            prediction = (prediction * 127.5 + 127.5).astype(np.uint8)\n            im = Image.fromarray(prediction)\n            wip_image_file_path = wip_images_folder / f'{i + 1}.jpg'\n            im.save(wip_image_file_path)\n            output_images_zip.write(filename=wip_image_file_path, arcname=wip_image_file_path.name)\n            wip_image_file_path.unlink()\n            pbar.update()\n\n\ndef experiment_flow(\n        choose_30_images_method: TrainImagesSelectionMethod,\n        train_settings: dict,\n        experiment_random_seed: int,\n        create_kaggle_predictions_for_submission: bool = False,\n):\n    set_training_random_seed(experiment_random_seed)\n\n    monet_dataset_files, photo_dataset_files = find_competition_dataset_files(LOCAL_DATASET_FOLDER_PATH)\n    train_raw_ordered_monet_dataset = load_tf_records_raw_dataset(monet_dataset_files)\n    train_raw_photo_dataset = load_tf_records_raw_dataset(photo_dataset_files)\n\n    train_selected_raw_monet_dataset = _choose_30_monet_train_images(\n        train_raw_ordered_monet_dataset, choose_30_images_method,\n        experiment_random_seed, use_preprocessed_cache=True\n    )\n\n    train_selected_raw_monet_dataset, train_raw_photo_dataset = _handle_augmentations(\n        train_settings['augmentation_settings'], experiment_random_seed,\n        train_selected_raw_monet_dataset, train_raw_photo_dataset\n    )\n\n    train_selected_monet_dataset = normalize_images_dataset_for_model(train_selected_raw_monet_dataset)\n    train_photo_dataset = normalize_images_dataset_for_model(train_raw_photo_dataset)\n\n    train_pairs_dataset = tf.data.Dataset.zip((train_selected_monet_dataset, train_photo_dataset))\n\n    with DEVICE_STRATEGY.scope():\n        cycle_gan_model, monet_generator = _build_cycle_gan_model(train_settings)\n\n    train_history = cycle_gan_model.fit(\n        train_pairs_dataset,\n        epochs=train_settings['train_epochs'],\n        verbose=2\n    )\n    plot_cycle_gan_train_losses(train_history)\n\n    inference_raw_photo_dataset = load_tf_records_raw_dataset(photo_dataset_files)\n    inference_photo_dataset = normalize_images_dataset_for_model(inference_raw_photo_dataset)\n    plot_predictions_sample(monet_generator, inference_photo_dataset)\n    if create_kaggle_predictions_for_submission:\n        create_predictions_for_kaggle_submission(monet_generator, inference_photo_dataset)\n\n\ndef _handle_augmentations(\n        augmentation_settings: dict, experiment_random_seed: int,\n        train_selected_raw_monet_dataset: Dataset, train_raw_photo_dataset: Dataset\n):\n    if augmentation_settings['monet_dataset']['enabled']:\n        train_selected_raw_monet_dataset = add_random_augmentations(\n            train_selected_raw_monet_dataset, experiment_random_seed,\n            usage_each_image_n_times=augmentation_settings['monet_dataset'].get('usage_each_image_n_times', 1)\n        )\n\n    if augmentation_settings['photo_dataset']['enabled']:\n        train_raw_photo_dataset = add_random_augmentations(\n            train_raw_photo_dataset, experiment_random_seed,\n            usage_each_image_n_times=augmentation_settings['photo_dataset'].get('usage_each_image_n_times', 1)\n        )\n\n    return train_selected_raw_monet_dataset, train_raw_photo_dataset\n\n\n#Experiments\n##Experiment functions\nclass ExperimentsToRunConfig:\n    CHOOSE_30_TRAIN_IMAGES_EXPERIMENT = False\n    GENERATOR_NETWORK_STRUCTURE_EXPERIMENT = False\n    DISCRIMINATOR_NETWORK_STRUCTURE_EXPERIMENT = False\n    ITAY_TO_DELETE_EXPERIMENT = True\n\n\ndef run_choose_30_train_images_experiment():\n    base_desc = 'choose_30_images_methods loop'\n    with tqdm(total=len(TrainImagesSelectionMethod), desc=base_desc) as pbar:\n        for method in TrainImagesSelectionMethod:\n            pbar.set_description(f\"{base_desc} (curr_method = '{method}')\")\n            experiment_flow(\n                choose_30_images_method=method,\n                train_settings=dict(\n                    train_epochs=40,\n                    optimizer_builder=lambda: tf.keras.optimizers.Adam(learning_rate=0.001, decay=0.001),\n                    generator_network_structure=GeneratorNetworkStructure.Baseline,\n                    discriminator_network_structure=DiscriminatorNetworkStructure.Baseline,\n                    augmentation_settings=dict(\n                        monet_dataset=dict(enabled=False),\n                        photo_dataset=dict(enabled=False),\n                    )\n                ),\n                experiment_random_seed=1,\n                create_kaggle_predictions_for_submission=False\n            )\n            pbar.update()\n\n\ndef run_generator_network_structure_experiment():\n    base_desc = 'generator_network_structure_experiment loop'\n    with tqdm(total=len(GeneratorNetworkStructure), desc=base_desc) as pbar:\n        for generator_network_structure in GeneratorNetworkStructure:\n            pbar.set_description(f\"{base_desc} (generator_network_structure={generator_network_structure})\")\n            experiment_flow(\n                choose_30_images_method=TrainImagesSelectionMethod.FarthestImagesByPixelDistance,\n                train_settings=dict(\n                    train_epochs=40,\n                    optimizer_builder=lambda: tf.keras.optimizers.Adam(learning_rate=0.001, decay=0.001),\n                    generator_network_structure=generator_network_structure,\n                    discriminator_network_structure=DiscriminatorNetworkStructure.Baseline,\n                    augmentation_settings=dict(\n                        monet_dataset=dict(enabled=False),\n                        photo_dataset=dict(enabled=False),\n                    )\n                ),\n                experiment_random_seed=1,\n                create_kaggle_predictions_for_submission=False\n            )\n            pbar.update()\n\n\ndef run_discriminator_network_structure_experiment():\n    base_desc = 'run_discriminator_network_structure_experiment loop'\n    with tqdm(total=len(DiscriminatorNetworkStructure), desc=base_desc) as pbar:\n        for discriminator_network_structure in DiscriminatorNetworkStructure:\n            pbar.set_description(f\"{base_desc} (discriminator_network_structure={discriminator_network_structure})\")\n            experiment_flow(\n                choose_30_images_method=TrainImagesSelectionMethod.FarthestImagesByPixelDistance,\n                train_settings=dict(\n                    train_epochs=40,\n                    optimizer_builder=lambda: tf.keras.optimizers.Adam(learning_rate=0.001, decay=0.001),\n                    generator_network_structure=GeneratorNetworkStructure.Thin,\n                    discriminator_network_structure=discriminator_network_structure,\n                    augmentation_settings=dict(\n                        monet_dataset=dict(enabled=False),\n                        photo_dataset=dict(enabled=False),\n                    )\n                ),\n                experiment_random_seed=1,\n                create_kaggle_predictions_for_submission=False\n            )\n            pbar.update()\n\n\ndef run_itay_to_delete_experiment():\n    experiment_flow(\n        choose_30_images_method=TrainImagesSelectionMethod.FarthestImagesByPixelDistance,\n        train_settings=dict(\n            train_epochs=40,\n            optimizer_builder=lambda: tf.keras.optimizers.Adam(learning_rate=0.001, decay=0.001),\n            generator_network_structure=GeneratorNetworkStructure.Thin,\n            discriminator_network_structure=DiscriminatorNetworkStructure.Baseline,\n            augmentation_settings=dict(\n                monet_dataset=dict(\n                    enabled=True,\n                    usage_each_image_n_times=20\n                ),\n                photo_dataset=dict(\n                    enabled=True,\n                    usage_each_image_n_times=1\n                ),\n            )\n        ),\n        experiment_random_seed=1,\n        create_kaggle_predictions_for_submission=True\n    )\n\ncurr_version = 14\nprint(f'*** curr_version: {curr_version} ***')\n\n##Experiment execution\nif ExperimentsToRunConfig.CHOOSE_30_TRAIN_IMAGES_EXPERIMENT:\n    run_choose_30_train_images_experiment()\n\nif ExperimentsToRunConfig.GENERATOR_NETWORK_STRUCTURE_EXPERIMENT:\n    run_generator_network_structure_experiment()\n\nif ExperimentsToRunConfig.DISCRIMINATOR_NETWORK_STRUCTURE_EXPERIMENT:\n    run_discriminator_network_structure_experiment()\n\nif ExperimentsToRunConfig.ITAY_TO_DELETE_EXPERIMENT:\n    run_itay_to_delete_experiment()\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-02-17T22:50:52.954025Z","iopub.execute_input":"2023-02-17T22:50:52.954449Z","iopub.status.idle":"2023-02-17T23:11:27.160182Z","shell.execute_reply.started":"2023-02-17T22:50:52.954365Z","shell.execute_reply":"2023-02-17T23:11:27.158600Z"},"trusted":true},"execution_count":null,"outputs":[]}]}