{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.16","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"tpu1vmV38","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"}],"dockerImageVersionId":30920,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Install and Import Libraries","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport math\nimport numpy as np\nimport pandas as pd\nfrom tensorflow.keras import layers, models, callbacks\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:05.181743Z","iopub.execute_input":"2025-03-14T23:26:05.182046Z","iopub.status.idle":"2025-03-14T23:26:25.817339Z","shell.execute_reply.started":"2025-03-14T23:26:05.182019Z","shell.execute_reply":"2025-03-14T23:26:25.816087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_FEATURES = {\n    'image': tf.io.FixedLenFeature([], tf.string),\n    'class': tf.io.FixedLenFeature([], tf.int64)\n}\n\nTEST_FEATURES = {\n    'image': tf.io.FixedLenFeature([], tf.string),\n    'id': tf.io.FixedLenFeature([], tf.string)\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:25.818290Z","iopub.execute_input":"2025-03-14T23:26:25.818732Z","iopub.status.idle":"2025-03-14T23:26:25.822676Z","shell.execute_reply.started":"2025-03-14T23:26:25.818707Z","shell.execute_reply":"2025-03-14T23:26:25.821938Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# TPU Setup","metadata":{}},{"cell_type":"code","source":"try:\n    resolver = tf.distribute.cluster_resolver.TPUClusterResolver.connect(tpu='local')\n    strategy = tf.distribute.TPUStrategy(resolver)\n    print(\"✅ TPU inicializada | Réplicas:\", strategy.num_replicas_in_sync)\nexcept ValueError:\n    strategy = tf.distribute.get_strategy()\n    print(\"❌ Usando CPU/GPU | Dispositivos:\", strategy.num_replicas_in_sync)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:25.823886Z","iopub.execute_input":"2025-03-14T23:26:25.824114Z","iopub.status.idle":"2025-03-14T23:26:33.682428Z","shell.execute_reply.started":"2025-03-14T23:26:25.824091Z","shell.execute_reply":"2025-03-14T23:26:33.681177Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Configurations","metadata":{}},{"cell_type":"code","source":"BASE_PATH = '/kaggle/input/tpu-getting-started/tfrecords-jpeg-224x224'\nTRAIN_FILES = tf.io.gfile.glob(f'{BASE_PATH}/train/*.tfrec')\nVAL_FILES = tf.io.gfile.glob(f'{BASE_PATH}/val/*.tfrec') \nTEST_FILES = tf.io.gfile.glob(f'{BASE_PATH}/test/*.tfrec')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.683523Z","iopub.execute_input":"2025-03-14T23:26:33.683772Z","iopub.status.idle":"2025-03-14T23:26:33.718245Z","shell.execute_reply.started":"2025-03-14T23:26:33.683746Z","shell.execute_reply":"2025-03-14T23:26:33.717189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMAGE_SIZE = [224, 224]\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync\nNUM_CLASSES = 104\nEPOCHS = 15","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.719483Z","iopub.execute_input":"2025-03-14T23:26:33.719735Z","iopub.status.idle":"2025-03-14T23:26:33.723462Z","shell.execute_reply.started":"2025-03-14T23:26:33.719710Z","shell.execute_reply":"2025-03-14T23:26:33.722619Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Preprocessing","metadata":{}},{"cell_type":"code","source":"class WarmUp(tf.keras.optimizers.schedules.LearningRateSchedule):\n    def __init__(self, initial_learning_rate, decay_schedule_fn, warmup_steps, warmup_learning_rate=0.0, name=None):\n        super(WarmUp, self).__init__()\n        self.initial_learning_rate = initial_learning_rate\n        self.warmup_learning_rate = warmup_learning_rate\n        self.warmup_steps = warmup_steps\n        self.decay_schedule_fn = decay_schedule_fn\n        self.name = name\n\n    def __call__(self, step):\n        with tf.name_scope(self.name or \"WarmUp\"):\n            step = tf.cast(step, tf.float32)\n            warmup_steps = tf.cast(self.warmup_steps, tf.float32)\n            # Linear warmup: gradually increase LR over warmup_steps\n            warmup_lr = self.warmup_learning_rate + (self.initial_learning_rate - self.warmup_learning_rate) * (step / warmup_steps)\n            # After warmup, use the decay schedule (shift step count by warmup_steps)\n            return tf.cond(step < warmup_steps,\n                           lambda: warmup_lr,\n                           lambda: self.decay_schedule_fn(step - warmup_steps))\n\n    def get_config(self):\n        return {\n            \"initial_learning_rate\": self.initial_learning_rate,\n            \"warmup_learning_rate\": self.warmup_learning_rate,\n            \"warmup_steps\": self.warmup_steps,\n            \"decay_schedule_fn\": self.decay_schedule_fn,\n            \"name\": self.name\n        }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.724642Z","iopub.execute_input":"2025-03-14T23:26:33.724889Z","iopub.status.idle":"2025-03-14T23:26:33.733236Z","shell.execute_reply.started":"2025-03-14T23:26:33.724849Z","shell.execute_reply":"2025-03-14T23:26:33.732047Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"decay_schedule = tf.keras.optimizers.schedules.ExponentialDecay(\n    initial_learning_rate=1e-3,\n    decay_steps=1000,\n    decay_rate=0.96,\n    staircase=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.734202Z","iopub.execute_input":"2025-03-14T23:26:33.734561Z","iopub.status.idle":"2025-03-14T23:26:33.743477Z","shell.execute_reply.started":"2025-03-14T23:26:33.734535Z","shell.execute_reply":"2025-03-14T23:26:33.742592Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lr_schedule = WarmUp(\n    initial_learning_rate=1e-3,\n    decay_schedule_fn=decay_schedule,\n    warmup_steps=500,\n    warmup_learning_rate=0.0\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.744231Z","iopub.execute_input":"2025-03-14T23:26:33.744470Z","iopub.status.idle":"2025-03-14T23:26:33.752548Z","shell.execute_reply.started":"2025-03-14T23:26:33.744442Z","shell.execute_reply":"2025-03-14T23:26:33.751161Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_SAMPLES = 12753\nVAL_SAMPLES = 3712","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.753039Z","iopub.execute_input":"2025-03-14T23:26:33.753231Z","iopub.status.idle":"2025-03-14T23:26:33.760804Z","shell.execute_reply.started":"2025-03-14T23:26:33.753212Z","shell.execute_reply":"2025-03-14T23:26:33.760015Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.image.resize(image, IMAGE_SIZE, method=tf.image.ResizeMethod.BICUBIC)\n    image = tf.cast(image, tf.float32)\n    \n    # Normalize using EfficientNet's specific preprocessing\n    image = tf.keras.applications.efficientnet.preprocess_input(image)\n    \n    # Add sanity checks\n    image = tf.debugging.assert_all_finite(image, \"Invalid pixel values\")\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:32:00.234165Z","iopub.execute_input":"2025-03-14T23:32:00.234502Z","iopub.status.idle":"2025-03-14T23:32:00.240034Z","shell.execute_reply.started":"2025-03-14T23:32:00.234472Z","shell.execute_reply":"2025-03-14T23:32:00.238492Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def parse_train(example):\n    example = tf.io.parse_single_example(example, TRAIN_FEATURES)\n    image = decode_image(example['image'])\n    label = tf.cast(example['class'], tf.int32)\n    tf.debugging.assert_non_negative(label, message=\"Etiqueta negativa detectada\")\n    tf.debugging.assert_less(label, NUM_CLASSES, message=f\"Etiqueta excede {NUM_CLASSES-1}\")\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.770742Z","iopub.execute_input":"2025-03-14T23:26:33.771117Z","iopub.status.idle":"2025-03-14T23:26:33.781413Z","shell.execute_reply.started":"2025-03-14T23:26:33.771094Z","shell.execute_reply":"2025-03-14T23:26:33.780326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def parse_test(example):\n    example = tf.io.parse_single_example(example, TEST_FEATURES)\n    image = decode_image(example['image'])\n    # No se vuelve a dividir entre 255, ya se hizo en decode_image\n    return image, example['id']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.782515Z","iopub.execute_input":"2025-03-14T23:26:33.782721Z","iopub.status.idle":"2025-03-14T23:26:33.789676Z","shell.execute_reply.started":"2025-03-14T23:26:33.782701Z","shell.execute_reply":"2025-03-14T23:26:33.788808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def augment_data(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_brightness(image, 0.2)\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.790976Z","iopub.execute_input":"2025-03-14T23:26:33.791182Z","iopub.status.idle":"2025-03-14T23:26:33.798476Z","shell.execute_reply.started":"2025-03-14T23:26:33.791162Z","shell.execute_reply":"2025-03-14T23:26:33.797480Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_dataset(filenames, is_training=True, shuffle=False, augment=False, drop_remainder=None):\n    dataset = tf.data.Dataset.from_tensor_slices(filenames)\n    dataset = dataset.interleave(\n        lambda x: tf.data.TFRecordDataset(x),\n        cycle_length=tf.data.AUTOTUNE,\n        num_parallel_calls=tf.data.AUTOTUNE\n    )\n    dataset = dataset.map(\n        parse_train if is_training else parse_test,\n        num_parallel_calls=tf.data.AUTOTUNE\n    )\n    \n    if shuffle:\n        dataset = dataset.shuffle(2048)\n    \n    if is_training:\n        dataset = dataset.repeat()\n    \n    if augment and is_training:\n        dataset = dataset.map(augment_data, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    if drop_remainder is None:\n        drop_remainder = is_training  # Por defecto\n    \n    dataset = dataset.batch(BATCH_SIZE, drop_remainder=drop_remainder)\n    dataset = dataset.prefetch(tf.data.AUTOTUNE)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.799599Z","iopub.execute_input":"2025-03-14T23:26:33.799805Z","iopub.status.idle":"2025-03-14T23:26:33.808323Z","shell.execute_reply.started":"2025-03-14T23:26:33.799784Z","shell.execute_reply":"2025-03-14T23:26:33.807174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = create_dataset(TRAIN_FILES, is_training=True, shuffle=True, augment=True)\nval_dataset = create_dataset(VAL_FILES, is_training=True, drop_remainder=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:26:33.809286Z","iopub.execute_input":"2025-03-14T23:26:33.809499Z","iopub.status.idle":"2025-03-14T23:26:34.079008Z","shell.execute_reply.started":"2025-03-14T23:26:33.809479Z","shell.execute_reply":"2025-03-14T23:26:34.077820Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Build & Compile Model","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n    # === ¡Faltaba esta parte! ===\n    base_model = tf.keras.applications.EfficientNetB0(\n        include_top=False, \n        weights='imagenet',\n        input_shape=(*IMAGE_SIZE, 3)\n    )\n    \n    model = tf.keras.Sequential([\n        layers.Input(shape=(*IMAGE_SIZE, 3)),\n        base_model,\n        layers.GlobalAveragePooling2D(),\n        layers.BatchNormalization(),\n        layers.Dense(512, activation='swish'),\n        layers.Dropout(0.5),\n        layers.Dense(NUM_CLASSES, activation='softmax')\n    ])\n    \n    optimizer = tf.keras.optimizers.Adam(\n        learning_rate=lr_schedule,\n        global_clipnorm=1.0 \n    )\n    \n    model.compile(\n        optimizer=optimizer,\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy'],\n        steps_per_execution=10\n    )\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:32:43.298613Z","iopub.execute_input":"2025-03-14T23:32:43.298958Z","iopub.status.idle":"2025-03-14T23:32:48.940258Z","shell.execute_reply.started":"2025-03-14T23:32:43.298931Z","shell.execute_reply":"2025-03-14T23:32:48.938969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"training_callbacks = [\n    EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True),\n    ModelCheckpoint('best_model.h5', save_best_only=True, monitor='val_loss'),\n    # ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, min_lr=1e-7)\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:32:52.031201Z","iopub.execute_input":"2025-03-14T23:32:52.031526Z","iopub.status.idle":"2025-03-14T23:32:52.035666Z","shell.execute_reply.started":"2025-03-14T23:32:52.031499Z","shell.execute_reply":"2025-03-14T23:32:52.034805Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"code","source":"train_steps = TRAIN_SAMPLES // BATCH_SIZE\nval_steps = VAL_SAMPLES // BATCH_SIZE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:32:53.370685Z","iopub.execute_input":"2025-03-14T23:32:53.370990Z","iopub.status.idle":"2025-03-14T23:32:53.374621Z","shell.execute_reply.started":"2025-03-14T23:32:53.370962Z","shell.execute_reply":"2025-03-14T23:32:53.373941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    train_dataset,\n    epochs=EPOCHS,\n    steps_per_epoch=train_steps,\n    validation_data=val_dataset,\n    validation_steps=val_steps,\n    callbacks=training_callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:32:53.703774Z","iopub.execute_input":"2025-03-14T23:32:53.704058Z","iopub.status.idle":"2025-03-14T23:35:37.340324Z","shell.execute_reply.started":"2025-03-14T23:32:53.704033Z","shell.execute_reply":"2025-03-14T23:35:37.339350Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Prediction & Submission","metadata":{}},{"cell_type":"code","source":"if history.epoch and history.epoch[-1] >= 5:\n    with strategy.scope():\n        base_model.trainable = True\n        optimizer_finetune = tf.keras.optimizers.Adam(\n            learning_rate=1e-5,  # Fixed LR\n            clipnorm=1.0\n        )\n        model.compile(\n            optimizer=optimizer_finetune,\n            loss='sparse_categorical_crossentropy',\n            metrics=['accuracy']\n        )\n        \n    model.fit(\n        train_dataset,\n        epochs=20,\n        initial_epoch=history.epoch[-1] + 1,\n        steps_per_epoch=train_steps,\n        validation_data=val_dataset,\n        validation_steps=val_steps,\n        callbacks=training_callbacks\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:35:37.341841Z","iopub.execute_input":"2025-03-14T23:35:37.342218Z","iopub.status.idle":"2025-03-14T23:39:12.401124Z","shell.execute_reply.started":"2025-03-14T23:35:37.342185Z","shell.execute_reply":"2025-03-14T23:39:12.399459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = create_dataset(TEST_FILES, is_training=False)\ntest_ids = []\npreds = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:39:12.403997Z","iopub.execute_input":"2025-03-14T23:39:12.404290Z","iopub.status.idle":"2025-03-14T23:39:12.531815Z","shell.execute_reply.started":"2025-03-14T23:39:12.404261Z","shell.execute_reply":"2025-03-14T23:39:12.530935Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for images, ids in test_dataset:\n    batch_preds = model.predict(images)\n    preds.extend(np.argmax(batch_preds, axis=1).tolist())\n    test_ids.extend([id.numpy().decode('utf-8') for id in ids])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:39:12.533245Z","iopub.execute_input":"2025-03-14T23:39:12.533509Z","iopub.status.idle":"2025-03-14T23:40:37.957128Z","shell.execute_reply.started":"2025-03-14T23:39:12.533484Z","shell.execute_reply":"2025-03-14T23:40:37.955268Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df = pd.DataFrame({'id': test_ids, 'label': preds})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:40:37.958585Z","iopub.execute_input":"2025-03-14T23:40:37.958902Z","iopub.status.idle":"2025-03-14T23:40:37.967989Z","shell.execute_reply.started":"2025-03-14T23:40:37.958838Z","shell.execute_reply":"2025-03-14T23:40:37.966229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df.to_csv('/kaggle/working/submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:40:37.968853Z","iopub.execute_input":"2025-03-14T23:40:37.969108Z","iopub.status.idle":"2025-03-14T23:40:37.996978Z","shell.execute_reply.started":"2025-03-14T23:40:37.969083Z","shell.execute_reply":"2025-03-14T23:40:37.996042Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Submission shape:\", submission_df.shape)\n\nprint(\"Submission head:\")\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T23:40:37.997691Z","iopub.execute_input":"2025-03-14T23:40:37.998099Z","iopub.status.idle":"2025-03-14T23:40:38.007889Z","shell.execute_reply.started":"2025-03-14T23:40:37.998074Z","shell.execute_reply":"2025-03-14T23:40:38.006683Z"}},"outputs":[],"execution_count":null}]}