{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# PETALS TO THE METAL - STRONG FLOWER CLASSIFIER\n# EfficientNetB2 + Data Augmentation + Fine Tuning\n# ============================================================\n\nimport os\nimport glob\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\n\nfrom sklearn.metrics import f1_score\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"GPUs:\", tf.config.list_physical_devices(\"GPU\"))\n\n\n# ============================================================\n# 1. SETTINGS\n# ============================================================\n\nDATA_PATH = \"/kaggle/input/competitions/tpu-getting-started\"\n\nIMG_SIZE = 260\nBATCH_SIZE = 32\nNUM_CLASSES = 104\n\nEPOCHS_HEAD = 5\nEPOCHS_FINE = 5\n\nAUTOTUNE = tf.data.AUTOTUNE\n\n\n# ============================================================\n# 2. FIND DATA\n# ============================================================\n\nTRAIN_FILES = sorted(\n    glob.glob(\n        DATA_PATH +\n        \"/tfrecords-jpeg-224x224/train/*.tfrec\"\n    )\n)\n\nVAL_FILES = sorted(\n    glob.glob(\n        DATA_PATH +\n        \"/tfrecords-jpeg-224x224/val/*.tfrec\"\n    )\n)\n\nTEST_FILES = sorted(\n    glob.glob(\n        DATA_PATH +\n        \"/tfrecords-jpeg-224x224/test/*.tfrec\"\n    )\n)\n\nprint(\"\\nTrain files:\", len(TRAIN_FILES))\nprint(\"Validation files:\", len(VAL_FILES))\nprint(\"Test files:\", len(TEST_FILES))\n\n\n# ============================================================\n# 3. READ TRAIN / VALIDATION TFRECORDS\n# ============================================================\n\ndef read_labeled(example):\n\n    features = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64)\n    }\n\n    example = tf.io.parse_single_example(\n        example,\n        features\n    )\n\n    image = tf.image.decode_jpeg(\n        example[\"image\"],\n        channels=3\n    )\n\n    image = tf.image.resize(\n        image,\n        [IMG_SIZE, IMG_SIZE]\n    )\n\n    image = tf.cast(image, tf.float32)\n\n    label = tf.cast(\n        example[\"class\"],\n        tf.int32\n    )\n\n    return image, label\n\n\ntrain_ds = tf.data.TFRecordDataset(\n    TRAIN_FILES,\n    num_parallel_reads=AUTOTUNE\n)\n\ntrain_ds = train_ds.map(\n    read_labeled,\n    num_parallel_calls=AUTOTUNE\n)\n\ntrain_ds = train_ds.shuffle(\n    4000,\n    reshuffle_each_iteration=True\n)\n\ntrain_ds = train_ds.batch(\n    BATCH_SIZE,\n    drop_remainder=True\n)\n\ntrain_ds = train_ds.prefetch(AUTOTUNE)\n\n\nval_ds = tf.data.TFRecordDataset(\n    VAL_FILES,\n    num_parallel_reads=AUTOTUNE\n)\n\nval_ds = val_ds.map(\n    read_labeled,\n    num_parallel_calls=AUTOTUNE\n)\n\nval_ds = val_ds.batch(\n    BATCH_SIZE\n)\n\nval_ds = val_ds.prefetch(AUTOTUNE)\n\n\n# ============================================================\n# 4. CALCULATE CLASS WEIGHTS\n# ============================================================\n\nprint(\"\\nCalculating class weights...\")\n\nclass_counts = np.zeros(\n    NUM_CLASSES,\n    dtype=np.int64\n)\n\nfor _, labels in train_ds.unbatch():\n\n    label = int(labels.numpy())\n\n    class_counts[label] += 1\n\n\n# Avoid division by zero\nclass_counts = np.maximum(\n    class_counts,\n    1\n)\n\ntotal_samples = np.sum(\n    class_counts\n)\n\nclass_weights = (\n    total_samples /\n    (NUM_CLASSES * class_counts)\n)\n\n# Don't allow extreme weights\nclass_weights = np.clip(\n    class_weights,\n    0.5,\n    3.0\n)\n\nclass_weight_dict = {\n    i: float(class_weights[i])\n    for i in range(NUM_CLASSES)\n}\n\nprint(\"Class weights calculated.\")\n\n\n# ============================================================\n# 5. DATA AUGMENTATION\n# ============================================================\n\naugmentation = tf.keras.Sequential([\n\n    tf.keras.layers.RandomFlip(\n        \"horizontal\"\n    ),\n\n    tf.keras.layers.RandomRotation(\n        0.12\n    ),\n\n    tf.keras.layers.RandomZoom(\n        0.15\n    ),\n\n    tf.keras.layers.RandomContrast(\n        0.10\n    )\n])\n\n\n# ============================================================\n# 6. BUILD EFFICIENTNETB2\n# ============================================================\n\nprint(\"\\nLoading EfficientNetB2...\")\n\nbase_model = tf.keras.applications.EfficientNetB2(\n\n    include_top=False,\n\n    weights=\"imagenet\",\n\n    input_shape=(\n        IMG_SIZE,\n        IMG_SIZE,\n        3\n    )\n)\n\n# First train only the new classification head\nbase_model.trainable = False\n\n\ninputs = tf.keras.Input(\n    shape=(\n        IMG_SIZE,\n        IMG_SIZE,\n        3\n    )\n)\n\nx = augmentation(inputs)\n\nx = base_model(\n    x,\n    training=False\n)\n\nx = tf.keras.layers.GlobalAveragePooling2D()(x)\n\nx = tf.keras.layers.BatchNormalization()(x)\n\nx = tf.keras.layers.Dropout(\n    0.35\n)(x)\n\nx = tf.keras.layers.Dense(\n    256,\n    activation=\"relu\"\n)(x)\n\nx = tf.keras.layers.Dropout(\n    0.25\n)(x)\n\noutputs = tf.keras.layers.Dense(\n    NUM_CLASSES,\n    activation=\"softmax\"\n)(x)\n\nmodel = tf.keras.Model(\n    inputs,\n    outputs\n)\n\n\n# ============================================================\n# 7. COMPILE\n# ============================================================\n\nmodel.compile(\n\n    optimizer=tf.keras.optimizers.Adam(\n        learning_rate=0.001\n    ),\n\n    loss=\"sparse_categorical_crossentropy\",\n\n    metrics=[\n        \"accuracy\"\n    ]\n)\n\nprint(\"\\nModel ready!\")\nmodel.summary()\n\n\n# ============================================================\n# 8. MACRO F1 CALLBACK\n# ============================================================\n\nclass MacroF1Callback(\n    tf.keras.callbacks.Callback\n):\n\n    def __init__(self, validation_data):\n\n        super().__init__()\n\n        self.validation_data = validation_data\n\n        self.best_f1 = 0.0\n\n\n    def on_epoch_end(\n        self,\n        epoch,\n        logs=None\n    ):\n\n        y_true = []\n        y_pred = []\n\n        for images, labels in self.validation_data:\n\n            predictions = self.model.predict(\n                images,\n                verbose=0\n            )\n\n            predictions = np.argmax(\n                predictions,\n                axis=1\n            )\n\n            y_true.extend(\n                labels.numpy()\n            )\n\n            y_pred.extend(\n                predictions\n            )\n\n\n        score = f1_score(\n            y_true,\n            y_pred,\n            average=\"macro\"\n        )\n\n        print(\n            \"\\n🌸 Validation Macro F1:\",\n            round(score, 5)\n        )\n\n\n        if score > self.best_f1:\n\n            self.best_f1 = score\n\n            self.model.save(\n                \"/kaggle/working/best_flower_model.keras\"\n            )\n\n            print(\n                \"⭐ New best model saved!\"\n            )\n\n\nf1_callback = MacroF1Callback(\n    val_ds\n)\n\n\n# ============================================================\n# 9. TRAIN CLASSIFICATION HEAD\n# ============================================================\n\nprint(\"\\n======================================\")\nprint(\"PHASE 1: TRAINING CLASSIFICATION HEAD\")\nprint(\"======================================\")\n\nhistory1 = model.fit(\n\n    train_ds,\n\n    validation_data=val_ds,\n\n    epochs=EPOCHS_HEAD,\n\n    class_weight=class_weight_dict,\n\n    callbacks=[\n        f1_callback,\n\n        tf.keras.callbacks.ReduceLROnPlateau(\n            monitor=\"val_loss\",\n            factor=0.5,\n            patience=1,\n            min_lr=1e-6\n        )\n    ]\n)\n\n\n# ============================================================\n# 10. FINE-TUNE EFFICIENTNET\n# ============================================================\n\nprint(\"\\n======================================\")\nprint(\"PHASE 2: FINE-TUNING\")\nprint(\"======================================\")\n\n\nbase_model.trainable = True\n\n\n# Freeze early layers\n# Train only the later layers\n\nfor layer in base_model.layers[:-50]:\n\n    layer.trainable = False\n\n\nmodel.compile(\n\n    optimizer=tf.keras.optimizers.Adam(\n        learning_rate=0.00005\n    ),\n\n    loss=\"sparse_categorical_crossentropy\",\n\n    metrics=[\n        \"accuracy\"\n    ]\n)\n\n\nhistory2 = model.fit(\n\n    train_ds,\n\n    validation_data=val_ds,\n\n    epochs=EPOCHS_FINE,\n\n    class_weight=class_weight_dict,\n\n    callbacks=[\n\n        f1_callback,\n\n        tf.keras.callbacks.ReduceLROnPlateau(\n            monitor=\"val_loss\",\n            factor=0.5,\n            patience=1,\n            min_lr=1e-7\n        )\n    ]\n)\n\n\n# ============================================================\n# 11. LOAD BEST MODEL\n# ============================================================\n\nprint(\"\\nLoading best model...\")\n\nmodel = tf.keras.models.load_model(\n    \"/kaggle/working/best_flower_model.keras\"\n)\n\n\n# ============================================================\n# 12. FINAL VALIDATION F1\n# ============================================================\n\nprint(\"\\nCalculating final validation score...\")\n\ny_true = []\ny_pred = []\n\nfor images, labels in val_ds:\n\n    predictions = model.predict(\n        images,\n        verbose=0\n    )\n\n    predictions = np.argmax(\n        predictions,\n        axis=1\n    )\n\n    y_true.extend(\n        labels.numpy()\n    )\n\n    y_pred.extend(\n        predictions\n    )\n\n\nfinal_f1 = f1_score(\n    y_true,\n    y_pred,\n    average=\"macro\"\n)\n\nprint(\n    \"\\n======================================\"\n)\n\nprint(\n    \"FINAL VALIDATION MACRO F1:\",\n    round(final_f1, 5)\n)\n\nprint(\n    \"======================================\"\n)\n\n\n# ============================================================\n# 13. READ TEST DATA\n# ============================================================\n\ndef read_test(example):\n\n    features = {\n\n        \"image\": tf.io.FixedLenFeature(\n            [],\n            tf.string\n        ),\n\n        \"id\": tf.io.FixedLenFeature(\n            [],\n            tf.string\n        )\n    }\n\n    example = tf.io.parse_single_example(\n        example,\n        features\n    )\n\n    image = tf.image.decode_jpeg(\n        example[\"image\"],\n        channels=3\n    )\n\n    image = tf.image.resize(\n        image,\n        [IMG_SIZE, IMG_SIZE]\n    )\n\n    image = tf.cast(\n        image,\n        tf.float32\n    )\n\n    return image, example[\"id\"]\n\n\ntest_ds = tf.data.TFRecordDataset(\n    TEST_FILES,\n    num_parallel_reads=AUTOTUNE\n)\n\ntest_ds = test_ds.map(\n    read_test,\n    num_parallel_calls=AUTOTUNE\n)\n\ntest_ds = test_ds.batch(\n    BATCH_SIZE\n)\n\ntest_ds = test_ds.prefetch(\n    AUTOTUNE\n)\n\n\n# ============================================================\n# 14. TEST PREDICTIONS\n# ============================================================\n\nprint(\"\\nCreating test predictions...\")\n\nids = []\npredicted_labels = []\n\n\nfor images, image_ids in test_ds:\n\n    probabilities = model.predict(\n        images,\n        verbose=0\n    )\n\n    labels = np.argmax(\n        probabilities,\n        axis=1\n    )\n\n    predicted_labels.extend(\n        labels\n    )\n\n    ids.extend([\n\n        x.decode(\"utf-8\")\n\n        for x in image_ids.numpy()\n\n    ])\n\n\n# ============================================================\n# 15. CREATE SUBMISSION\n# ============================================================\n\nsubmission = pd.DataFrame({\n\n    \"id\": ids,\n\n    \"label\": predicted_labels\n\n})\n\n\nsubmission.to_csv(\n\n    \"/kaggle/working/submission.csv\",\n\n    index=False\n\n)\n\n\nprint(\"\\n======================================\")\nprint(\"🎉 SUBMISSION CREATED\")\nprint(\"======================================\")\n\nprint(\n    \"Predictions:\",\n    len(submission)\n)\n\nprint(\n    \"\\nValidation Macro F1:\",\n    round(final_f1, 5)\n)\n\nprint(\n    \"\\nFirst 10 predictions:\"\n)\n\nprint(\n    submission.head(10)\n)\n\nprint(\n    \"\\nFile:\"\n)\n\nprint(\n    \"/kaggle/working/submission.csv\"\n)\n\nprint(\n    \"\\nREADY FOR KAGGLE SUBMISSION 🚀\"\n)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-08-27T15:05:44.470698Z","iopub.execute_input":"2026-08-27T15:05:44.471417Z","iopub.status.idle":"2026-08-27T15:29:34.869717Z","shell.execute_reply.started":"2026-08-27T15:05:44.471391Z","shell.execute_reply":"2026-08-27T15:29:34.868903Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}