{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":13836,"databundleVersionId":1718836,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:24.168682Z","iopub.execute_input":"2025-12-01T15:47:24.169016Z","iopub.status.idle":"2025-12-01T15:47:38.732256Z","shell.execute_reply.started":"2025-12-01T15:47:24.16898Z","shell.execute_reply":"2025-12-01T15:47:38.731065Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install tensorflow==2.12 keras --quiet\nimport tensorflow as tf\nimport tensorflow.keras as keras\nfrom tensorflow.keras import layers, models\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report\n\nimport os\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:38.733744Z","iopub.execute_input":"2025-12-01T15:47:38.734008Z","iopub.status.idle":"2025-12-01T15:47:42.338591Z","shell.execute_reply.started":"2025-12-01T15:47:38.733988Z","shell.execute_reply":"2025-12-01T15:47:42.337409Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.340913Z","iopub.execute_input":"2025-12-01T15:47:42.341199Z","iopub.status.idle":"2025-12-01T15:47:42.369819Z","shell.execute_reply.started":"2025-12-01T15:47:42.341173Z","shell.execute_reply":"2025-12-01T15:47:42.369086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, val_df = train_test_split(\n    train_df,\n    test_size=0.2,\n    stratify=train_df['label'],\n    random_state=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.370639Z","iopub.execute_input":"2025-12-01T15:47:42.370889Z","iopub.status.idle":"2025-12-01T15:47:42.38781Z","shell.execute_reply.started":"2025-12-01T15:47:42.370866Z","shell.execute_reply":"2025-12-01T15:47:42.387117Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Image Augmentations and pre-processing  \n","metadata":{}},{"cell_type":"code","source":"IMAGE_SIZE = 224 # resize images to 224 because that's what the models expect\nBATCH_SIZE = 32 # sending 32 images at a time\nTRAIN_DIR = \"/kaggle/input/cassava-leaf-disease-classification/train_images\" \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.388724Z","iopub.execute_input":"2025-12-01T15:47:42.389983Z","iopub.status.idle":"2025-12-01T15:47:42.395248Z","shell.execute_reply.started":"2025-12-01T15:47:42.389963Z","shell.execute_reply":"2025-12-01T15:47:42.394434Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#training  data loader\ntrain_df['label'] = train_df['label'].astype(str)\nval_df['label'] = val_df['label'].astype(str)\ntrain_gen = keras.preprocessing.image.ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=40,\n    zoom_range=0.2,\n    width_shift_range=0.2,\n    height_shift_range=0.2,\n    horizontal_flip=True\n).flow_from_dataframe(\n    train_df,\n    directory=TRAIN_DIR,\n    x_col='image_id',\n    y_col='label',\n    target_size=(IMAGE_SIZE, IMAGE_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='categorical'\n)\n\n#validation data loader\nval_gen = keras.preprocessing.image.ImageDataGenerator(\n    rescale=1./255\n).flow_from_dataframe(\n    val_df,\n    directory=TRAIN_DIR,\n    x_col='image_id',\n    y_col='label',\n    target_size=(IMAGE_SIZE, IMAGE_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:48:53.453844Z","iopub.execute_input":"2025-12-01T15:48:53.454162Z","iopub.status.idle":"2025-12-01T15:49:20.137529Z","shell.execute_reply.started":"2025-12-01T15:48:53.454141Z","shell.execute_reply":"2025-12-01T15:49:20.136592Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## BASE MODEL -- EfficientNetB0","metadata":{}},{"cell_type":"code","source":"def build_base_efficientnet():\n    base = keras.applications.EfficientNetB0(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)\n    )\n    \n    base.trainable = False   # 🔥 FREEZE BACKBONE\n\n    x = layers.GlobalAveragePooling2D()(base.output)\n    x = layers.Dense(256, activation='relu')(x)\n    x = layers.Dropout(0.3)(x)\n    out = layers.Dense(5, activation='softmax')(x)\n\n    return keras.Model(inputs=base.input, outputs=out)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:49:29.252944Z","iopub.execute_input":"2025-12-01T15:49:29.253279Z","iopub.status.idle":"2025-12-01T15:49:31.633491Z","shell.execute_reply.started":"2025-12-01T15:49:29.253256Z","shell.execute_reply":"2025-12-01T15:49:31.632713Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nprint(\"TF version:\", tf.__version__)\nprint(\"GPUs:\", tf.config.list_physical_devices('GPU'))\n# Optionally: !nvidia-smi in a notebook cell to see GPU utilization","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)\n# In the training loop:\ninputs, labels = inputs.to(device), labels.to(device)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:57:03.068407Z","iopub.execute_input":"2025-12-01T15:57:03.068736Z","iopub.status.idle":"2025-12-01T15:57:03.107723Z","shell.execute_reply.started":"2025-12-01T15:57:03.068707Z","shell.execute_reply":"2025-12-01T15:57:03.106162Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_base = base_model.fit(\n    train_gen, validation_data=val_gen, epochs=5\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:49:48.3956Z","iopub.execute_input":"2025-12-01T15:49:48.395918Z","iopub.status.idle":"2025-12-01T15:55:44.536724Z","shell.execute_reply.started":"2025-12-01T15:49:48.395895Z","shell.execute_reply":"2025-12-01T15:55:44.535574Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Base model Results","metadata":{}},{"cell_type":"code","source":"def plot_confusion_matrix(model, val_gen, title):\n    val_gen.reset()\n    preds = model.predict(val_gen)\n    y_pred = np.argmax(preds, axis=1)\n    y_true = val_gen.classes.astype(int)\n\n    cm = confusion_matrix(y_true, y_pred)\n\n    plt.figure(figsize=(8,6))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\n    plt.title(title)\n    plt.xlabel('Predicted')\n    plt.ylabel('True')\n    plt.show()\n\n    print(f\"\\nClassification Report — {title}\")\n    print(classification_report(y_true, y_pred))\n\nplot_confusion_matrix(base_model, val_gen, \"Base Model (EfficientNetB0)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.452935Z","iopub.status.idle":"2025-12-01T15:47:42.453287Z","shell.execute_reply.started":"2025-12-01T15:47:42.453118Z","shell.execute_reply":"2025-12-01T15:47:42.453134Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## TRANSFER LEARNING  :  (MobileNetV2 on top of EfficientNet Features)\n#### EfficientNet - frozen feature extractor \n#### MobileNet - Classification head","metadata":{}},{"cell_type":"code","source":"def build_transfer_from_base(base_model):\n    base_model.trainable = False\n\n    # EfficientNet feature extractor\n    eff_in = base_model.input\n    eff_out = base_model.layers[-3].output  # GAP layer output\n    eff_extractor = keras.Model(inputs=eff_in, outputs=eff_out)\n\n    # MobileNet head\n    mobile = keras.applications.MobileNetV2(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)\n    )\n    mobile.trainable = False\n\n    inputs = layers.Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3))\n    feat_eff = eff_extractor(inputs)       # base model features\n    feat_mob = mobile(inputs)              \n    feat_mob = layers.GlobalAveragePooling2D()(feat_mob)\n\n    merged = layers.Concatenate()([feat_eff, feat_mob])\n    x = layers.Dense(256, activation='relu')(merged)\n    x = layers.Dropout(0.3)(x)\n    out = layers.Dense(5, activation='softmax')(x)\n\n    return keras.Model(inputs, out)\n\ntransfer_model = build_transfer_from_base(base_model)\ntransfer_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])\ntransfer_model.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.454176Z","iopub.status.idle":"2025-12-01T15:47:42.454485Z","shell.execute_reply.started":"2025-12-01T15:47:42.454328Z","shell.execute_reply":"2025-12-01T15:47:42.454342Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_transfer = transfer_model.fit(\n    train_gen, validation_data=val_gen, epochs=5\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.455491Z","iopub.status.idle":"2025-12-01T15:47:42.455781Z","shell.execute_reply.started":"2025-12-01T15:47:42.455627Z","shell.execute_reply":"2025-12-01T15:47:42.455641Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Results for Transfer Learning","metadata":{}},{"cell_type":"code","source":"plot_confusion_matrix(transfer_model, val_gen, \"EfficientNet -> MobileNet\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.457556Z","iopub.status.idle":"2025-12-01T15:47:42.457923Z","shell.execute_reply.started":"2025-12-01T15:47:42.45773Z","shell.execute_reply":"2025-12-01T15:47:42.457746Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Hybrid Model - Fusion of EfficientNet and MobileNet","metadata":{}},{"cell_type":"code","source":"def build_hybrid_model():\n    inputs = layers.Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3))\n\n    eff_base = keras.applications.EfficientNetB0(\n        include_top=False, input_shape=(IMAGE_SIZE,IMAGE_SIZE,3), weights='imagenet'\n    )\n    mob_base = keras.applications.MobileNetV2(\n        include_top=False, input_shape=(IMAGE_SIZE,IMAGE_SIZE,3), weights='imagenet'\n    )\n\n    eff_base.trainable = False\n    mob_base.trainable = False\n\n    f1 = eff_base(inputs)\n    f1 = layers.GlobalAveragePooling2D()(f1)\n\n    f2 = mob_base(inputs)\n    f2 = layers.GlobalAveragePooling2D()(f2)\n\n    merged = layers.Concatenate()([f1, f2])\n    x = layers.Dense(256, activation='relu')(merged)\n    x = layers.Dropout(0.4)(x)\n    out = layers.Dense(5, activation='softmax')(x)\n\n    return keras.Model(inputs, out)\n\nhybrid_model = build_hybrid_model()\nhybrid_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])\nhybrid_model.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.459422Z","iopub.status.idle":"2025-12-01T15:47:42.459634Z","shell.execute_reply.started":"2025-12-01T15:47:42.459535Z","shell.execute_reply":"2025-12-01T15:47:42.459544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_hybrid = hybrid_model.fit(\n    train_gen, validation_data=val_gen, epochs=5\n)","metadata":{"trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.46054Z","iopub.status.idle":"2025-12-01T15:47:42.460759Z","shell.execute_reply.started":"2025-12-01T15:47:42.460653Z","shell.execute_reply":"2025-12-01T15:47:42.460663Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Hybrid Model Results :","metadata":{}},{"cell_type":"code","source":"val_gen.reset()\npreds = hybrid_model.predict(val_gen)\ny_pred = np.argmax(preds, axis=1)\ny_true = val_gen.classes.astype(int)\n\ncm = confusion_matrix(y_true, y_pred)\nplt.figure(figsize=(8,6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\nplt.title(\"Hybrid Model Confusion Matrix\")\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.show()\n\nprint(classification_report(y_true, y_pred))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.462596Z","iopub.status.idle":"2025-12-01T15:47:42.462919Z","shell.execute_reply.started":"2025-12-01T15:47:42.462723Z","shell.execute_reply":"2025-12-01T15:47:42.462735Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualization of Results :","metadata":{}},{"cell_type":"code","source":"def plot_history(history, title):\n    plt.figure(figsize=(12,4))\n\n    plt.subplot(1,2,1)\n    plt.plot(history.history['accuracy'])\n    plt.plot(history.history['val_accuracy'])\n    plt.title(title + \" Accuracy\")\n    plt.legend(['Train','Val'])\n\n    plt.subplot(1,2,2)\n    plt.plot(history.history['loss'])\n    plt.plot(history.history['val_loss'])\n    plt.title(title + \" Loss\")\n    plt.legend(['Train','Val'])\n\n    plt.show()\n\nplot_history(history_base, \"Base Model (EfficientNet)\")\nplot_history(history_transfer, \"Transfer Model (EfficientNet → MobileNet)\")\nplot_history(history_hybrid, \"Hybrid Model (Fusion)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T15:47:42.464055Z","iopub.status.idle":"2025-12-01T15:47:42.46432Z","shell.execute_reply.started":"2025-12-01T15:47:42.464205Z","shell.execute_reply":"2025-12-01T15:47:42.464217Z"}},"outputs":[],"execution_count":null}]}