{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":13836,"databundleVersionId":1718836,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# CELL 1 – Imports, constants, and seeding (ONLINE version).\n\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\n\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.callbacks import (\n    EarlyStopping,\n    ReduceLROnPlateau,\n    ModelCheckpoint\n)\nfrom tensorflow.keras.applications import EfficientNetB3\n\nprint(\"TensorFlow version:\", tf.__version__)\n\n# Paths\nBASE_PATH = \"/kaggle/input/cassava-leaf-disease-classification\"\nTRAIN_CSV = os.path.join(BASE_PATH, \"train.csv\")\nTRAIN_DIR = os.path.join(BASE_PATH, \"train_images\")\nTEST_DIR  = os.path.join(BASE_PATH, \"test_images\")\n\n# Hyperparameters\nIMG_SIZE   = 380        # 380x380 works well for EfficientNetB3\nBATCH_SIZE = 32\nEPOCHS_WARMUP = 5       # first stage (frozen backbone)\nEPOCHS_FINETUNE = 20    # second stage (fine-tuning)\nSEED = 42\n\n# Reproducibility\ntf.keras.utils.set_random_seed(SEED)\nnp.random.seed(SEED)\nrandom.seed(SEED)\n\nAUTOTUNE = tf.data.AUTOTUNE\nNUM_CLASSES = 5","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 2 – Load train.csv and split into train/validation sets.\n\ndf = pd.read_csv(TRAIN_CSV)\nprint(df.head())\nprint(\"Total samples:\", len(df))\n\ntrain_df, val_df = train_test_split(\n    df,\n    test_size=0.2,\n    stratify=df[\"label\"],\n    random_state=SEED\n)\n\nprint(\"Train samples:\", len(train_df))\nprint(\"Val samples:\", len(val_df))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 3 – Functions to decode images and create tf.data pipelines (no Sequential aug).\n\ndef decode_image(filename, label=None, img_size=IMG_SIZE):\n    img = tf.io.read_file(filename)\n    img = tf.io.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, (img_size, img_size))\n    img = tf.cast(img, tf.float32) / 255.0\n    if label is None:\n        return img\n    else:\n        return img, label\n\ndef simple_augment(img, label):\n    # light augmentations only in the dataset\n    img = tf.image.random_flip_left_right(img)\n    img = tf.image.random_brightness(img, 0.1)\n    img = tf.image.random_contrast(img, 0.9, 1.1)\n    return img, label\n\ndef get_train_ds(df):\n    paths = [os.path.join(TRAIN_DIR, img_id) for img_id in df[\"image_id\"].values]\n    labels = df[\"label\"].values\n    ds = tf.data.Dataset.from_tensor_slices((paths, labels))\n    ds = ds.shuffle(2048, seed=SEED, reshuffle_each_iteration=True)\n    ds = ds.map(lambda x, y: decode_image(x, y), num_parallel_calls=AUTOTUNE)\n    ds = ds.map(simple_augment, num_parallel_calls=AUTOTUNE)\n    ds = ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n    return ds\n\ndef get_val_ds(df):\n    paths = [os.path.join(TRAIN_DIR, img_id) for img_id in df[\"image_id\"].values]\n    labels = df[\"label\"].values\n    ds = tf.data.Dataset.from_tensor_slices((paths, labels))\n    ds = ds.map(lambda x, y: decode_image(x, y), num_parallel_calls=AUTOTUNE)\n    ds = ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n    return ds\n\ndef get_test_ds():\n    test_files = sorted(os.listdir(TEST_DIR))\n    test_paths = [os.path.join(TEST_DIR, f) for f in test_files]\n    ds = tf.data.Dataset.from_tensor_slices(test_paths)\n    ds = ds.map(lambda x: decode_image(x, None), num_parallel_calls=AUTOTUNE)\n    ds = ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n    return ds, test_files\n\ntrain_ds = get_train_ds(train_df)\nval_ds   = get_val_ds(val_df)\n\nprint(train_ds, val_ds)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 4 – Build EfficientNetB3 model WITHOUT internet (no pretrained weights).\n\ndef build_model():\n    # Input layer\n    inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n\n    # EfficientNetB3 backbone – NO ImageNet weights (no download, internet-safe)\n    base_model = EfficientNetB3(\n        include_top=False,\n        weights=None,                 # <- IMPORTANT: do NOT use \"imagenet\" here\n        input_shape=(IMG_SIZE, IMG_SIZE, 3),\n        pooling=\"avg\",\n    )\n\n    # We want to train the whole network since weights start random\n    base_model.trainable = True\n\n    # Forward pass through EfficientNetB3\n    x = base_model(inputs)\n\n    # Classification head\n    x = layers.Dropout(0.4)(x)\n    outputs = layers.Dense(NUM_CLASSES, activation=\"softmax\")(x)\n\n    # Final model\n    model = models.Model(inputs, outputs, name=\"EffNetB3_cassava\")\n    return model, base_model\n\nmodel, base_model = build_model()\nmodel.summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 5 – Define callbacks for training.\n\ncheckpoint_path = \"best_model_online.h5\"\n\ncallbacks = [\n    ModelCheckpoint(\n        checkpoint_path, \n        monitor=\"val_accuracy\",\n        mode=\"max\",\n        save_best_only=True,\n        verbose=1\n    ),\n    ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=2,\n        min_lr=1e-6,\n        verbose=1\n    ),\n    EarlyStopping(\n        monitor=\"val_loss\",\n        patience=5,\n        restore_best_weights=True,\n        verbose=1\n    ),\n]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 6 – Warmup training with frozen EfficientNet backbone.\n\n# Loss function (no label smoothing in this TF version)\nloss_fn = tf.keras.losses.SparseCategoricalCrossentropy()\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(1e-3),\n    loss=loss_fn,\n    metrics=[\"accuracy\"],\n)\n\nhistory_warmup = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS_WARMUP,\n    callbacks=callbacks,\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 7 – Fine-tune top 30% of EfficientNetB3 layers.\n\n# Unfreeze the base model\nbase_model.trainable = True\n\n# Freeze bottom ~70% of layers, train the rest\nfine_tune_at = int(len(base_model.layers) * 0.7)\nfor i, layer in enumerate(base_model.layers):\n    layer.trainable = i >= fine_tune_at\n\nprint(\"Total layers in base model:\", len(base_model.layers))\nprint(\"Fine-tuning from layer index:\", fine_tune_at)\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(1e-5),  # small LR for fine-tuning\n    loss=loss_fn,\n    metrics=[\"accuracy\"],\n)\n\nhistory_ft = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS_FINETUNE,\n    callbacks=callbacks,\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 8 – Load best checkpoint and evaluate on validation set.\n\nmodel.load_weights(checkpoint_path)\n\nval_loss, val_acc = model.evaluate(val_ds)\nprint(\"Final validation accuracy:\", val_acc)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 9 – Predict on test set with simple TTA and create submission DataFrame.\n\ntest_ds, test_files = get_test_ds()\n\n# Standard predictions\npred_probs_1 = model.predict(test_ds, verbose=1)\n\n# Simple TTA: horizontal flip\ndef flip_dataset(ds):\n    return ds.map(lambda x: tf.image.flip_left_right(x), num_parallel_calls=AUTOTUNE)\n\ntest_ds_flipped = flip_dataset(test_ds)\npred_probs_2 = model.predict(test_ds_flipped, verbose=1)\n\n# Average predictions\npred_probs = (pred_probs_1 + pred_probs_2) / 2.0\npred_labels = np.argmax(pred_probs, axis=1)\n\nsubmission = pd.DataFrame({\n    \"image_id\": test_files,\n    \"label\": pred_labels.astype(int)\n})\n\nsubmission.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 10 – Save submission.csv to working directory.\n\nsubmission_path = \"/kaggle/working/submission.csv\"\nsubmission.to_csv(submission_path, index=False)\nprint(\"Saved:\", submission_path)\n\nprint(\"Files in /kaggle/working:\")\nprint(os.listdir(\"/kaggle/working\"))","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}