{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.12"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":21154,"databundleVersionId":1243559}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":389.392975,"end_time":"2026-04-20T04:17:18.319909+00:00","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-04-20T04:10:48.926934+00:00","version":"2.7.0"},"title":"A4 Petals Submission 04 MobileNetV2 More Epochs"},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Submission 15","metadata":{"papermill":{"duration":0.005261,"end_time":"2026-04-20T04:10:51.604658+00:00","exception":false,"start_time":"2026-04-20T04:10:51.599397+00:00","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"## 1. Carga de librerías","metadata":{"papermill":{"duration":0.004005,"end_time":"2026-04-20T04:10:51.612866+00:00","exception":false,"start_time":"2026-04-20T04:10:51.608861+00:00","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"## 2. Configuración del entorno","metadata":{"papermill":{"duration":0.004262,"end_time":"2026-04-20T04:11:23.851101+00:00","exception":false,"start_time":"2026-04-20T04:11:23.846839+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nimport re\nimport math\nimport random\nimport glob\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    f1_score,\n    precision_score,\n    recall_score,\n    confusion_matrix\n)\n\nSEED = 1000\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nprint(\"TensorFlow version:\", tf.__version__)\nprint(\"GPUs disponibles:\", tf.config.list_physical_devices(\"GPU\"))\n","metadata":{"papermill":{"duration":32.224711,"end_time":"2026-04-20T04:11:23.842491+00:00","exception":false,"start_time":"2026-04-20T04:10:51.617780+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:01.048342Z","iopub.execute_input":"2026-04-24T02:47:01.048842Z","iopub.status.idle":"2026-04-24T02:47:25.672744Z","shell.execute_reply.started":"2026-04-24T02:47:01.048806Z","shell.execute_reply":"2026-04-24T02:47:25.671894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gpus = tf.config.list_physical_devices(\"GPU\")\n\nif len(gpus) > 1:\n    strategy = tf.distribute.MirroredStrategy()\n    print(\"Ejecutando con múltiples GPU\")\nelse:\n    strategy = tf.distribute.get_strategy()\n    print(\"Ejecutando con una GPU o CPU\")\n\nprint(\"REPLICAS:\", strategy.num_replicas_in_sync)\n","metadata":{"papermill":{"duration":0.016171,"end_time":"2026-04-20T04:11:23.871490+00:00","exception":false,"start_time":"2026-04-20T04:11:23.855319+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:25.674536Z","iopub.execute_input":"2026-04-24T02:47:25.675198Z","iopub.status.idle":"2026-04-24T02:47:25.681404Z","shell.execute_reply.started":"2026-04-24T02:47:25.675152Z","shell.execute_reply":"2026-04-24T02:47:25.680441Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Carga de datos","metadata":{"papermill":{"duration":0.004362,"end_time":"2026-04-20T04:11:23.880366+00:00","exception":false,"start_time":"2026-04-20T04:11:23.876004+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"IMAGE_SIZE = [512, 512]\nAUTO = tf.data.AUTOTUNE\n\nprint(\"Contenido de /kaggle/input:\")\nprint(os.listdir(\"/kaggle/input\"))\n\ncandidate_paths = sorted(glob.glob(\n    f\"/kaggle/input/**/tfrecords-jpeg-{IMAGE_SIZE[0]}x{IMAGE_SIZE[1]}\",\n    recursive=True\n))\n\nprint(\"\\nRutas candidatas encontradas:\")\nfor path in candidate_paths:\n    print(path)\n\nif len(candidate_paths) == 0:\n    raise FileNotFoundError(\n        \"No se encontró la carpeta de TFRecords. \"\n        \"Agrega la competencia Petals to the Metal como Input en Kaggle.\"\n    )\n\nDATA_PATH = candidate_paths[0]\n\nTRAINING_FILENAMES = sorted(tf.io.gfile.glob(DATA_PATH + \"/train/*.tfrec\"))\nVALIDATION_FILENAMES = sorted(tf.io.gfile.glob(DATA_PATH + \"/val/*.tfrec\"))\nTEST_FILENAMES = sorted(tf.io.gfile.glob(DATA_PATH + \"/test/*.tfrec\"))\n\nif len(TRAINING_FILENAMES) == 0 or len(VALIDATION_FILENAMES) == 0 or len(TEST_FILENAMES) == 0:\n    raise FileNotFoundError(\"No se encontraron archivos train/val/test en la ruta seleccionada.\")\n\nprint(\"\\nRuta usada:\", DATA_PATH)\nprint(\"Archivos train:\", len(TRAINING_FILENAMES))\nprint(\"Archivos val:\", len(VALIDATION_FILENAMES))\nprint(\"Archivos test:\", len(TEST_FILENAMES))\n","metadata":{"papermill":{"duration":0.145928,"end_time":"2026-04-20T04:11:24.030501+00:00","exception":false,"start_time":"2026-04-20T04:11:23.884573+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:25.682768Z","iopub.execute_input":"2026-04-24T02:47:25.683138Z","iopub.status.idle":"2026-04-24T02:47:25.799091Z","shell.execute_reply.started":"2026-04-24T02:47:25.683073Z","shell.execute_reply":"2026-04-24T02:47:25.798325Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Clases","metadata":{"papermill":{"duration":0.004402,"end_time":"2026-04-20T04:11:24.039696+00:00","exception":false,"start_time":"2026-04-20T04:11:24.035294+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"CLASSES = ['pink primrose',\n 'hard-leaved pocket orchid',\n 'canterbury bells',\n 'sweet pea',\n 'wild geranium',\n 'tiger lily',\n 'moon orchid',\n 'bird of paradise',\n 'monkshood',\n 'globe thistle',\n 'snapdragon',\n \"colt's foot\",\n 'king protea',\n 'spear thistle',\n 'yellow iris',\n 'globe-flower',\n 'purple coneflower',\n 'peruvian lily',\n 'balloon flower',\n 'giant white arum lily',\n 'fire lily',\n 'pincushion flower',\n 'fritillary',\n 'red ginger',\n 'grape hyacinth',\n 'corn poppy',\n 'prince of wales feathers',\n 'stemless gentian',\n 'artichoke',\n 'sweet william',\n 'carnation',\n 'garden phlox',\n 'love in the mist',\n 'cosmos',\n 'alpine sea holly',\n 'ruby-lipped cattleya',\n 'cape flower',\n 'great masterwort',\n 'siam tulip',\n 'lenten rose',\n 'barberton daisy',\n 'daffodil',\n 'sword lily',\n 'poinsettia',\n 'bolero deep blue',\n 'wallflower',\n 'marigold',\n 'buttercup',\n 'daisy',\n 'common dandelion',\n 'petunia',\n 'wild pansy',\n 'primula',\n 'sunflower',\n 'lilac hibiscus',\n 'bishop of llandaff',\n 'gaura',\n 'geranium',\n 'orange dahlia',\n 'pink-yellow dahlia',\n 'cautleya spicata',\n 'japanese anemone',\n 'black-eyed susan',\n 'silverbush',\n 'californian poppy',\n 'osteospermum',\n 'spring crocus',\n 'iris',\n 'windflower',\n 'tree poppy',\n 'gazania',\n 'azalea',\n 'water lily',\n 'rose',\n 'thorn apple',\n 'morning glory',\n 'passion flower',\n 'lotus',\n 'toad lily',\n 'anthurium',\n 'frangipani',\n 'clematis',\n 'hibiscus',\n 'columbine',\n 'desert-rose',\n 'tree mallow',\n 'magnolia',\n 'cyclamen ',\n 'watercress',\n 'canna lily',\n 'hippeastrum ',\n 'bee balm',\n 'pink quill',\n 'foxglove',\n 'bougainvillea',\n 'camellia',\n 'mallow',\n 'mexican petunia',\n 'bromelia',\n 'blanket flower',\n 'trumpet creeper',\n 'blackberry lily',\n 'common tulip',\n 'wild rose']\n\nNUM_CLASSES = len(CLASSES)\n\nprint(\"Número de clases:\", NUM_CLASSES)\nprint(\"Primeras 10 clases:\", CLASSES[:10])\n\nNUM_CLASSES = len(CLASSES)\nprint('Número de clases:', NUM_CLASSES)\n","metadata":{"papermill":{"duration":0.016043,"end_time":"2026-04-20T04:11:24.060053+00:00","exception":false,"start_time":"2026-04-20T04:11:24.044010+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:25.800179Z","iopub.execute_input":"2026-04-24T02:47:25.800555Z","iopub.status.idle":"2026-04-24T02:47:25.809515Z","shell.execute_reply.started":"2026-04-24T02:47:25.800529Z","shell.execute_reply":"2026-04-24T02:47:25.808585Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Lectura de TFRecords","metadata":{"papermill":{"duration":0.004353,"end_time":"2026-04-20T04:11:24.068936+00:00","exception":false,"start_time":"2026-04-20T04:11:24.064583+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\n\ndef read_labeled_tfrecord(example):\n    labeled_format = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n    }\n    example = tf.io.parse_single_example(example, labeled_format)\n    image = decode_image(example[\"image\"])\n    label = tf.cast(example[\"class\"], tf.int32)\n    return image, label\n\n\ndef read_unlabeled_tfrecord(example):\n    unlabeled_format = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    }\n    example = tf.io.parse_single_example(example, unlabeled_format)\n    image = decode_image(example[\"image\"])\n    image_id = example[\"id\"]\n    return image, image_id\n\n\ndef load_dataset(filenames, labeled=True, ordered=False):\n    options = tf.data.Options()\n    options.experimental_deterministic = ordered\n\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTO)\n    dataset = dataset.with_options(options)\n    dataset = dataset.map(\n        read_labeled_tfrecord if labeled else read_unlabeled_tfrecord,\n        num_parallel_calls=AUTO\n    )\n    return dataset\n\n\ndef count_data_items(filenames):\n    total = 0\n    for filename in filenames:\n        match = re.search(r\"-([0-9]+)\\.tfrec$\", os.path.basename(filename))\n        if match:\n            total += int(match.group(1))\n    return total\n\n\nNUM_TRAINING_IMAGES = count_data_items(TRAINING_FILENAMES)\nNUM_VALIDATION_IMAGES = count_data_items(VALIDATION_FILENAMES)\nNUM_TEST_IMAGES = count_data_items(TEST_FILENAMES)\n\nprint(\"Training images:\", NUM_TRAINING_IMAGES)\nprint(\"Validation images:\", NUM_VALIDATION_IMAGES)\nprint(\"Test images:\", NUM_TEST_IMAGES)\n","metadata":{"papermill":{"duration":0.018872,"end_time":"2026-04-20T04:11:24.092102+00:00","exception":false,"start_time":"2026-04-20T04:11:24.073230+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:25.811607Z","iopub.execute_input":"2026-04-24T02:47:25.811994Z","iopub.status.idle":"2026-04-24T02:47:25.833898Z","shell.execute_reply.started":"2026-04-24T02:47:25.811944Z","shell.execute_reply":"2026-04-24T02:47:25.832699Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Datasets","metadata":{"papermill":{"duration":0.004203,"end_time":"2026-04-20T04:11:24.100997+00:00","exception":false,"start_time":"2026-04-20T04:11:24.096794+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def data_augment(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_brightness(image, max_delta=0.08)\n    image = tf.image.random_contrast(image, lower=0.90, upper=1.10)\n    image = tf.clip_by_value(image, 0.0, 1.0)\n    return image, label\n\n\nBASE_BATCH_SIZE = 2\nBATCH_SIZE = BASE_BATCH_SIZE * strategy.num_replicas_in_sync\n\nTRAIN_STEPS = math.ceil(NUM_TRAINING_IMAGES / BATCH_SIZE)\nVALID_STEPS = math.ceil(NUM_VALIDATION_IMAGES / BATCH_SIZE)\n\nprint(\"Batch size:\", BATCH_SIZE)\nprint(\"Train steps:\", TRAIN_STEPS)\nprint(\"Validation steps:\", VALID_STEPS)\n\n\ndef get_training_dataset():\n    dataset = load_dataset(TRAINING_FILENAMES, labeled=True, ordered=False)\n    dataset = dataset.shuffle(2048, seed=SEED, reshuffle_each_iteration=True)\n    dataset = dataset.map(data_augment, num_parallel_calls=AUTO)\n    dataset = dataset.repeat()\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\n\ndef get_validation_dataset(ordered=True, repeat=False):\n    dataset = load_dataset(VALIDATION_FILENAMES, labeled=True, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    if repeat:\n        dataset = dataset.repeat()\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\n\ndef get_test_dataset(ordered=True):\n    dataset = load_dataset(TEST_FILENAMES, labeled=False, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\n\nds_train = get_training_dataset()\nds_valid_fit = get_validation_dataset(ordered=True, repeat=True)\nds_valid_eval = get_validation_dataset(ordered=True, repeat=False)\nds_test = get_test_dataset(ordered=True)\n\nprint(\"Dataset de entrenamiento:\", ds_train)\nprint(\"Dataset de validación para entrenamiento:\", ds_valid_fit)\nprint(\"Dataset de validación para evaluación:\", ds_valid_eval)\nprint(\"Dataset de test:\", ds_test)\n","metadata":{"papermill":{"duration":2.29001,"end_time":"2026-04-20T04:11:26.395376+00:00","exception":false,"start_time":"2026-04-20T04:11:24.105366+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:25.835074Z","iopub.execute_input":"2026-04-24T02:47:25.835442Z","iopub.status.idle":"2026-04-24T02:47:28.041955Z","shell.execute_reply.started":"2026-04-24T02:47:25.835405Z","shell.execute_reply":"2026-04-24T02:47:28.041013Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Visualización inicial","metadata":{"papermill":{"duration":0.004407,"end_time":"2026-04-20T04:11:26.404443+00:00","exception":false,"start_time":"2026-04-20T04:11:26.400036+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def show_batch(dataset, n=12):\n    images, labels = next(iter(dataset.unbatch().batch(n)))\n\n    plt.figure(figsize=(12, 8))\n    for i in range(n):\n        plt.subplot(3, 4, i + 1)\n        plt.imshow(images[i].numpy())\n        plt.title(CLASSES[int(labels[i])], fontsize=8)\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n\nshow_batch(ds_valid_eval, n=12)\n","metadata":{"papermill":{"duration":1.224486,"end_time":"2026-04-20T04:11:27.632884+00:00","exception":false,"start_time":"2026-04-20T04:11:26.408398+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:28.043249Z","iopub.execute_input":"2026-04-24T02:47:28.043585Z","iopub.status.idle":"2026-04-24T02:47:29.764421Z","shell.execute_reply.started":"2026-04-24T02:47:28.043558Z","shell.execute_reply":"2026-04-24T02:47:29.763274Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Modelo","metadata":{"papermill":{"duration":0.020063,"end_time":"2026-04-20T04:11:27.673192+00:00","exception":false,"start_time":"2026-04-20T04:11:27.653129+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"tf.keras.backend.clear_session()\n\nwith strategy.scope():\n    inputs = tf.keras.Input(shape=(*IMAGE_SIZE, 3), name=\"image\")\n\n    x = tf.keras.layers.Rescaling(255.0, name=\"efficientnet_input_rescale\")(inputs)\n\n    base_model = tf.keras.applications.EfficientNetB0(\n        input_shape=(*IMAGE_SIZE, 3),\n        include_top=False,\n        weights=\"imagenet\"\n    )\n\n    base_model.trainable = False\n\n    x = base_model(x, training=False)\n    x = tf.keras.layers.GlobalAveragePooling2D(name=\"global_average_pooling\")(x)\n    x = tf.keras.layers.Dropout(0.40, name=\"dropout\")(x)\n    outputs = tf.keras.layers.Dense(\n        NUM_CLASSES,\n        activation=\"softmax\",\n        dtype=\"float32\",\n        name=\"classifier\"\n    )(x)\n\n    model = tf.keras.Model(\n        inputs=inputs,\n        outputs=outputs,\n        name=\"submission15_efficientnetb0_512_tta\"\n    )\n\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=3e-4),\n        loss=\"sparse_categorical_crossentropy\",\n        metrics=[\"sparse_categorical_accuracy\"]\n    )\n\nmodel.summary()\n","metadata":{"papermill":{"duration":2.436382,"end_time":"2026-04-20T04:11:30.129935+00:00","exception":false,"start_time":"2026-04-20T04:11:27.693553+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:29.765880Z","iopub.execute_input":"2026-04-24T02:47:29.766324Z","iopub.status.idle":"2026-04-24T02:47:32.371431Z","shell.execute_reply.started":"2026-04-24T02:47:29.766274Z","shell.execute_reply":"2026-04-24T02:47:32.370528Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Entrenamiento","metadata":{"papermill":{"duration":0.023776,"end_time":"2026-04-20T04:11:30.178678+00:00","exception":false,"start_time":"2026-04-20T04:11:30.154902+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"EPOCHS = 15\n\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_sparse_categorical_accuracy\",\n        patience=3,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=1,\n        min_lr=1e-6,\n        verbose=1\n    )\n]\n\nhistory = model.fit(\n    ds_train,\n    validation_data=ds_valid_fit,\n    epochs=EPOCHS,\n    steps_per_epoch=TRAIN_STEPS,\n    validation_steps=VALID_STEPS,\n    callbacks=callbacks,\n    verbose=1\n)\n\n# Fine-tuning parcial de EfficientNetB0\n\nbase_model.trainable = True\n\n# Congelar la mayoría de capas y entrenar solo las últimas 30\nfor layer in base_model.layers[:-30]:\n    layer.trainable = False\n\n# Mantener BatchNormalization congeladas para mayor estabilidad\nfor layer in base_model.layers:\n    if isinstance(layer, tf.keras.layers.BatchNormalization):\n        layer.trainable = False\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),\n    loss=\"sparse_categorical_crossentropy\",\n    metrics=[\"sparse_categorical_accuracy\"]\n)\n\nfine_tune_callbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_sparse_categorical_accuracy\",\n        patience=2,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=1,\n        min_lr=1e-7,\n        verbose=1\n    )\n]\n\nfine_tune_epochs = 5\n\nhistory_fine = model.fit(\n    ds_train,\n    validation_data=ds_valid_fit,\n    epochs=fine_tune_epochs,\n    steps_per_epoch=TRAIN_STEPS,\n    validation_steps=VALID_STEPS,\n    callbacks=fine_tune_callbacks,\n    verbose=1\n)","metadata":{"papermill":{"duration":301.00982,"end_time":"2026-04-20T04:16:31.209523+00:00","exception":false,"start_time":"2026-04-20T04:11:30.199703+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T02:47:32.373523Z","iopub.execute_input":"2026-04-24T02:47:32.373853Z","iopub.status.idle":"2026-04-24T03:34:05.161547Z","shell.execute_reply.started":"2026-04-24T02:47:32.373826Z","shell.execute_reply":"2026-04-24T03:34:05.159504Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10. Curvas","metadata":{"papermill":{"duration":0.191929,"end_time":"2026-04-20T04:16:31.596931+00:00","exception":false,"start_time":"2026-04-20T04:16:31.405002+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def plot_history(history):\n    history_df = pd.DataFrame(history.history)\n\n    plt.figure(figsize=(8, 5))\n    plt.plot(history_df[\"loss\"], label=\"train_loss\")\n    plt.plot(history_df[\"val_loss\"], label=\"val_loss\")\n    plt.title(\"Loss\")\n    plt.xlabel(\"Época\")\n    plt.ylabel(\"Loss\")\n    plt.legend()\n    plt.grid(True)\n    plt.show()\n\n    plt.figure(figsize=(8, 5))\n    plt.plot(history_df[\"sparse_categorical_accuracy\"], label=\"train_accuracy\")\n    plt.plot(history_df[\"val_sparse_categorical_accuracy\"], label=\"val_accuracy\")\n    plt.title(\"Accuracy\")\n    plt.xlabel(\"Época\")\n    plt.ylabel(\"Accuracy\")\n    plt.legend()\n    plt.grid(True)\n    plt.show()\n\n    return history_df\n\n\nhistory_df = plot_history(history)\nhistory_df\n","metadata":{"papermill":{"duration":0.527301,"end_time":"2026-04-20T04:16:32.303659+00:00","exception":false,"start_time":"2026-04-20T04:16:31.776358+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T03:34:05.167673Z","iopub.execute_input":"2026-04-24T03:34:05.173463Z","iopub.status.idle":"2026-04-24T03:34:05.645356Z","shell.execute_reply.started":"2026-04-24T03:34:05.173418Z","shell.execute_reply":"2026-04-24T03:34:05.644394Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 11. Evaluación","metadata":{"papermill":{"duration":0.20086,"end_time":"2026-04-20T04:16:32.691070+00:00","exception":false,"start_time":"2026-04-20T04:16:32.490210+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"y_true = []\n\nfor _, labels in ds_valid_eval:\n    y_true.extend(labels.numpy().tolist())\n\ny_true = np.array(y_true)\n\nvalid_images_ds = ds_valid_eval.map(lambda image, label: image)\n\nprint(\"Generando predicciones en validación...\")\nvalid_probabilities = model.predict(valid_images_ds, verbose=1)\ny_pred = np.argmax(valid_probabilities, axis=-1)[:len(y_true)]\n\nmetrics = {\n    \"accuracy\": accuracy_score(y_true, y_pred),\n    \"macro_precision\": precision_score(y_true, y_pred, average=\"macro\", zero_division=0),\n    \"macro_recall\": recall_score(y_true, y_pred, average=\"macro\", zero_division=0),\n    \"macro_f1\": f1_score(y_true, y_pred, average=\"macro\", zero_division=0),\n}\n\nmetrics_df = pd.DataFrame([metrics])\nmetrics_df\n","metadata":{"papermill":{"duration":10.405559,"end_time":"2026-04-20T04:16:43.307199+00:00","exception":false,"start_time":"2026-04-20T04:16:32.901640+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T03:34:05.646648Z","iopub.execute_input":"2026-04-24T03:34:05.647019Z","iopub.status.idle":"2026-04-24T03:34:53.013379Z","shell.execute_reply.started":"2026-04-24T03:34:05.646973Z","shell.execute_reply":"2026-04-24T03:34:53.012210Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 12. Matriz de confusión","metadata":{"papermill":{"duration":0.188116,"end_time":"2026-04-20T04:16:43.698755+00:00","exception":false,"start_time":"2026-04-20T04:16:43.510639+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"cmat = confusion_matrix(y_true, y_pred, labels=list(range(NUM_CLASSES)))\ncmat_norm = (cmat.T / np.maximum(cmat.sum(axis=1), 1)).T\n\nplt.figure(figsize=(12, 12))\nplt.imshow(cmat_norm, interpolation=\"nearest\")\nplt.title(\"Matriz de confusión normalizada\")\nplt.xlabel(\"Clase predicha\")\nplt.ylabel(\"Clase real\")\nplt.colorbar()\nplt.tight_layout()\nplt.show()\n","metadata":{"papermill":{"duration":0.65373,"end_time":"2026-04-20T04:16:44.537513+00:00","exception":false,"start_time":"2026-04-20T04:16:43.883783+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T03:34:53.015787Z","iopub.execute_input":"2026-04-24T03:34:53.016082Z","iopub.status.idle":"2026-04-24T03:34:53.412237Z","shell.execute_reply.started":"2026-04-24T03:34:53.016054Z","shell.execute_reply":"2026-04-24T03:34:53.411266Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 13. Revisión visual","metadata":{"papermill":{"duration":0.209366,"end_time":"2026-04-20T04:16:44.957343+00:00","exception":false,"start_time":"2026-04-20T04:16:44.747977+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def show_predictions(dataset, n=12):\n    images, labels = next(iter(dataset.unbatch().batch(n)))\n\n    probabilities = model.predict(images, verbose=0)\n    predictions = np.argmax(probabilities, axis=1)\n\n    plt.figure(figsize=(12, 8))\n\n    for i in range(n):\n        plt.subplot(3, 4, i + 1)\n        plt.imshow(images[i].numpy())\n\n        real_class = CLASSES[int(labels[i])]\n        pred_class = CLASSES[int(predictions[i])]\n\n        plt.title(f\"Real: {real_class}\\nPred: {pred_class}\", fontsize=7)\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n\nshow_predictions(ds_valid_eval, n=12)\n","metadata":{"papermill":{"duration":9.204562,"end_time":"2026-04-20T04:16:54.374030+00:00","exception":false,"start_time":"2026-04-20T04:16:45.169468+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T03:34:53.413524Z","iopub.execute_input":"2026-04-24T03:34:53.413918Z","iopub.status.idle":"2026-04-24T03:35:08.033101Z","shell.execute_reply.started":"2026-04-24T03:34:53.413878Z","shell.execute_reply":"2026-04-24T03:35:08.031837Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 14. Submission","metadata":{"papermill":{"duration":0.209135,"end_time":"2026-04-20T04:16:54.787122+00:00","exception":false,"start_time":"2026-04-20T04:16:54.577987+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def predict_with_tta(model, dataset):\n    test_images_ds = dataset.map(\n        lambda image, image_id: image,\n        num_parallel_calls=AUTO\n    )\n\n    print(\"Predicción original...\")\n    preds_original = model.predict(test_images_ds, verbose=1)\n\n    test_images_flip_ds = test_images_ds.map(\n        lambda image: tf.image.flip_left_right(image),\n        num_parallel_calls=AUTO\n    )\n\n    print(\"Predicción con horizontal flip...\")\n    preds_flip = model.predict(test_images_flip_ds, verbose=1)\n\n    final_predictions = (preds_original + preds_flip) / 2.0\n    return final_predictions\n\n\ntest_ids = []\nfor _, image_ids in ds_test:\n    test_ids.extend([image_id.decode(\"utf-8\") for image_id in image_ids.numpy()])\n\nprint(\"Generando predicciones sobre test con TTA...\")\ntest_probabilities = predict_with_tta(model, ds_test)\ntest_predictions = np.argmax(test_probabilities, axis=-1)[:len(test_ids)]\n\nsubmission = pd.DataFrame({\n    \"id\": test_ids,\n    \"label\": test_predictions\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\n\nprint(\"Archivo submission.csv generado con TTA.\")\nprint(\"Filas:\", len(submission))\nsubmission.head()","metadata":{"papermill":{"duration":17.887819,"end_time":"2026-04-20T04:17:12.877822+00:00","exception":false,"start_time":"2026-04-20T04:16:54.990003+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T03:35:08.037261Z","iopub.execute_input":"2026-04-24T03:35:08.037671Z","iopub.status.idle":"2026-04-24T03:36:22.034904Z","shell.execute_reply.started":"2026-04-24T03:35:08.037639Z","shell.execute_reply":"2026-04-24T03:36:22.033906Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 15. Distribución de predicciones","metadata":{"papermill":{"duration":0.240572,"end_time":"2026-04-20T04:17:13.342029+00:00","exception":false,"start_time":"2026-04-20T04:17:13.101457+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"prediction_counts = submission[\"label\"].value_counts().sort_index()\n\nplt.figure(figsize=(14, 5))\nplt.bar(prediction_counts.index, prediction_counts.values)\nplt.title(\"Distribución de clases predichas\")\nplt.xlabel(\"Clase\")\nplt.ylabel(\"Número de imágenes\")\nplt.tight_layout()\nplt.show()\n","metadata":{"papermill":{"duration":0.490788,"end_time":"2026-04-20T04:17:14.041401+00:00","exception":false,"start_time":"2026-04-20T04:17:13.550613+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T03:36:22.036431Z","iopub.execute_input":"2026-04-24T03:36:22.036835Z","iopub.status.idle":"2026-04-24T03:36:22.344643Z","shell.execute_reply.started":"2026-04-24T03:36:22.036801Z","shell.execute_reply":"2026-04-24T03:36:22.343568Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 16. Registro del experimento","metadata":{"papermill":{"duration":0.294708,"end_time":"2026-04-20T04:17:14.541761+00:00","exception":false,"start_time":"2026-04-20T04:17:14.247053+00:00","status":"completed"},"tags":[]}},{"cell_type":"code","source":"experiment_log = pd.DataFrame([\n    {\n        \"submission\": 15,\n        \"model\": \"EfficientNetB0\",\n        \"image_size\": \"512x512\",\n        \"epochs\": \"15 + 5 fine-tuning\",\n        \"batch_size\": BATCH_SIZE,\n        \"augmentation\": \"moderada\",\n        \"fine_tuning\": \"Sí, últimas 30 capas\",\n        \"tta\": \"Si, original mas horizontal flip\",\n        \"validation_macro_f1\": metrics[\"macro_f1\"],\n        \"kaggle_macro_f1\": \"Completar después del submission\",\n        \"main_change\": \"Aplicación de TTA sobre el mejor modelo EfficientNetB0 512x512\",\n    }\n])\n\nexperiment_log.to_csv(\"experiment_log_submission_15.csv\", index=False)\nexperiment_log\n","metadata":{"papermill":{"duration":0.235476,"end_time":"2026-04-20T04:17:14.977710+00:00","exception":false,"start_time":"2026-04-20T04:17:14.742234+00:00","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2026-04-24T03:36:22.346025Z","iopub.execute_input":"2026-04-24T03:36:22.346606Z","iopub.status.idle":"2026-04-24T03:36:22.365379Z","shell.execute_reply.started":"2026-04-24T03:36:22.346517Z","shell.execute_reply":"2026-04-24T03:36:22.364472Z"}},"outputs":[],"execution_count":null}]}