{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":13836,"databundleVersionId":1718836},{"sourceType":"kernelVersion","sourceId":124707319}],"dockerImageVersionId":31260,"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":"2026-01-23T10:46:11.833258Z","iopub.execute_input":"2026-01-23T10:46:11.83372Z","iopub.status.idle":"2026-01-23T10:47:06.054626Z","shell.execute_reply.started":"2026-01-23T10:46:11.833682Z","shell.execute_reply":"2026-01-23T10:47:06.053566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.listdir(\"/kaggle/input\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:06.056753Z","iopub.execute_input":"2026-01-23T10:47:06.057341Z","iopub.status.idle":"2026-01-23T10:47:06.066908Z","shell.execute_reply.started":"2026-01-23T10:47:06.057297Z","shell.execute_reply":"2026-01-23T10:47:06.066124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nos.listdir(\"/kaggle/input/cassava-leaf-disease-classification\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:06.067863Z","iopub.execute_input":"2026-01-23T10:47:06.068152Z","iopub.status.idle":"2026-01-23T10:47:06.083452Z","shell.execute_reply.started":"2026-01-23T10:47:06.068124Z","shell.execute_reply":"2026-01-23T10:47:06.082895Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"EDA","metadata":{}},{"cell_type":"code","source":"import os\nimport json \nimport pandas as pd \nimport matplotlib.pyplot as plt \nimport seaborn as sns\nimport cv2 \nfrom PIL import Image\nimport hashlib","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:06.084246Z","iopub.execute_input":"2026-01-23T10:47:06.084484Z","iopub.status.idle":"2026-01-23T10:47:07.766507Z","shell.execute_reply.started":"2026-01-23T10:47:06.084462Z","shell.execute_reply":"2026-01-23T10:47:07.765791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_DIR = \"/kaggle/input/cassava-leaf-disease-classification\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.768523Z","iopub.execute_input":"2026-01-23T10:47:07.768865Z","iopub.status.idle":"2026-01-23T10:47:07.772394Z","shell.execute_reply.started":"2026-01-23T10:47:07.76884Z","shell.execute_reply":"2026-01-23T10:47:07.77169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with open('/kaggle/input/cassava-leaf-disease-classification/label_num_to_disease_map.json')as file:\n    print('yes')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.773262Z","iopub.execute_input":"2026-01-23T10:47:07.773533Z","iopub.status.idle":"2026-01-23T10:47:07.793023Z","shell.execute_reply.started":"2026-01-23T10:47:07.773498Z","shell.execute_reply":"2026-01-23T10:47:07.792325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load and do label map to disease name \n\nwith open('/kaggle/input/cassava-leaf-disease-classification/label_num_to_disease_map.json') as file:\n    map_class = json.loads(file.read())\n    map_class = {int(k) : v for k , v in map_class.items()}\n\nprint('class mapping: ')\nprint(json.dumps(map_class, indent = 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.793909Z","iopub.execute_input":"2026-01-23T10:47:07.794273Z","iopub.status.idle":"2026-01-23T10:47:07.813078Z","shell.execute_reply.started":"2026-01-23T10:47:07.79424Z","shell.execute_reply":"2026-01-23T10:47:07.812547Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images_len = len(os.listdir('/kaggle/input/cassava-leaf-disease-classification/train_images'))\nprint(images_len)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.813965Z","iopub.execute_input":"2026-01-23T10:47:07.814203Z","iopub.status.idle":"2026-01-23T10:47:07.82445Z","shell.execute_reply.started":"2026-01-23T10:47:07.814183Z","shell.execute_reply":"2026-01-23T10:47:07.823741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load train.csv and add human-readable class name based on mapping \ndf_train = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')\ndf_train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.825301Z","iopub.execute_input":"2026-01-23T10:47:07.825547Z","iopub.status.idle":"2026-01-23T10:47:07.899109Z","shell.execute_reply.started":"2026-01-23T10:47:07.825515Z","shell.execute_reply":"2026-01-23T10:47:07.898544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train['class_name'] = df_train['label'].map(map_class)\ndf_train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.899821Z","iopub.execute_input":"2026-01-23T10:47:07.900087Z","iopub.status.idle":"2026-01-23T10:47:07.914154Z","shell.execute_reply.started":"2026-01-23T10:47:07.900044Z","shell.execute_reply":"2026-01-23T10:47:07.913424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train['class_name'].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.915051Z","iopub.execute_input":"2026-01-23T10:47:07.915275Z","iopub.status.idle":"2026-01-23T10:47:07.954873Z","shell.execute_reply.started":"2026-01-23T10:47:07.915254Z","shell.execute_reply":"2026-01-23T10:47:07.954295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# class distribution check \n\nclass_dis = df_train['class_name'].value_counts()\nplt.figure(figsize = (10,6))\nclass_dis.plot(kind = 'bar')\nplt.title('class distribution of leaf disease')\nplt.ylabel('number of images')\nplt.xlabel('disease class')\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:07.955635Z","iopub.execute_input":"2026-01-23T10:47:07.95586Z","iopub.status.idle":"2026-01-23T10:47:08.255953Z","shell.execute_reply.started":"2026-01-23T10:47:07.95584Z","shell.execute_reply":"2026-01-23T10:47:08.255233Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"In transfer learning we dont need to balance the data.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize = (8,4))\nsns.countplot(y = 'class_name', data = df_train)\nplt.title('class distribution of leaf disease')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:08.257418Z","iopub.execute_input":"2026-01-23T10:47:08.257799Z","iopub.status.idle":"2026-01-23T10:47:08.428375Z","shell.execute_reply.started":"2026-01-23T10:47:08.257776Z","shell.execute_reply":"2026-01-23T10:47:08.427782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# check for missing values and duplicates \n\nprint(f\"\\n missing values in each column :\\n{df_train.isnull().sum()}\")\nprint(f\"\\n nomber of duplicate row : { df_train.duplicated().sum()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:08.430961Z","iopub.execute_input":"2026-01-23T10:47:08.431361Z","iopub.status.idle":"2026-01-23T10:47:08.443833Z","shell.execute_reply.started":"2026-01-23T10:47:08.431338Z","shell.execute_reply":"2026-01-23T10:47:08.443231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nimg_dir = \"/kaggle/input/cassava-leaf-disease-classification/train_images\"\n\nimg_shape = {}\n\nfor image_name in os.listdir(img_dir)[:1000]:\n    image_path = os.path.join(img_dir, image_name)\n    image = cv2.imread(image_path)\n\n    if image is not None:\n        shape = image.shape\n        img_shape[shape] = img_shape.get(shape, 0) + 1\n\nprint(\"\\nSample image shapes and their frequency:\")\nprint(img_shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:08.444716Z","iopub.execute_input":"2026-01-23T10:47:08.445062Z","iopub.status.idle":"2026-01-23T10:47:25.34649Z","shell.execute_reply.started":"2026-01-23T10:47:08.445028Z","shell.execute_reply":"2026-01-23T10:47:25.345689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"widths = [600]\nheights = [800]\nfreqs = [1000]\n\n\nplt.figure(figsize=(8, 6))\nplt.scatter(widths, heights, s=freqs, alpha=0.6)\n\nplt.xlabel(\"Image Width\")\nplt.ylabel(\"Image Height\")\nplt.title(\"Image Size Distribution (Cassava Leaf Images)\")\n\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:25.347439Z","iopub.execute_input":"2026-01-23T10:47:25.347721Z","iopub.status.idle":"2026-01-23T10:47:25.50102Z","shell.execute_reply.started":"2026-01-23T10:47:25.347695Z","shell.execute_reply":"2026-01-23T10:47:25.500328Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# plot sample images from a specific class\n\ndef plot_images_from_class(class_id , num_images = 9):\n    \"\"\"\n    plot sample images form the a specific class in a 3x3 grid\n\n    class_id = the class label to filter images\n    num_images = the number of images to plot \n    \"\"\"\n    class_images  = df_train[df_train['label'] == class_id]\n    num_images = min(len(class_images), num_images)\n\n    plt.figure(figsize= (15,15))\n    images = class_images.sample(num_images)\n\n    for i , (_,row) in enumerate(images.iterrows()):\n        img_path = os.path.join('/kaggle/input/cassava-leaf-disease-classification/train_images', row['image_id'])\n        img = Image.open(img_path)\n        plt.subplot(3,3,i+1)\n        plt.imshow(img)\n        plt.title(map_class[class_id])\n        plt.axis('off')\n\n\n    plt.tight_layout()\n    plt.show()\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:25.501961Z","iopub.execute_input":"2026-01-23T10:47:25.502265Z","iopub.status.idle":"2026-01-23T10:47:25.507556Z","shell.execute_reply.started":"2026-01-23T10:47:25.502241Z","shell.execute_reply":"2026-01-23T10:47:25.506862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_from_class(0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:25.508591Z","iopub.execute_input":"2026-01-23T10:47:25.5088Z","iopub.status.idle":"2026-01-23T10:47:27.206648Z","shell.execute_reply.started":"2026-01-23T10:47:25.508781Z","shell.execute_reply":"2026-01-23T10:47:27.205674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_from_class(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:27.207998Z","iopub.execute_input":"2026-01-23T10:47:27.208362Z","iopub.status.idle":"2026-01-23T10:47:28.846522Z","shell.execute_reply.started":"2026-01-23T10:47:27.208322Z","shell.execute_reply":"2026-01-23T10:47:28.845765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_from_class(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:28.847442Z","iopub.execute_input":"2026-01-23T10:47:28.847684Z","iopub.status.idle":"2026-01-23T10:47:30.327942Z","shell.execute_reply.started":"2026-01-23T10:47:28.84766Z","shell.execute_reply":"2026-01-23T10:47:30.327179Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_from_class(3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:30.329015Z","iopub.execute_input":"2026-01-23T10:47:30.329387Z","iopub.status.idle":"2026-01-23T10:47:31.830909Z","shell.execute_reply.started":"2026-01-23T10:47:30.329349Z","shell.execute_reply":"2026-01-23T10:47:31.829255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_from_class(4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:31.831858Z","iopub.execute_input":"2026-01-23T10:47:31.832104Z","iopub.status.idle":"2026-01-23T10:47:33.432326Z","shell.execute_reply.started":"2026-01-23T10:47:31.832082Z","shell.execute_reply":"2026-01-23T10:47:33.430687Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i in range(5):\n    print(f\"Displaying sample images : {map_class[i]}\")\n    plot_images_from_class(i)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:33.433305Z","iopub.execute_input":"2026-01-23T10:47:33.433554Z","iopub.status.idle":"2026-01-23T10:47:41.2559Z","shell.execute_reply.started":"2026-01-23T10:47:33.433523Z","shell.execute_reply":"2026-01-23T10:47:41.254353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nIMG_DIR = \"/kaggle/input/cassava-leaf-disease-classification/train_images\"\n\n# add image_shape column\ndf_train['image_shape'] = df_train['image_id'].apply(\n    lambda x: cv2.imread(os.path.join(IMG_DIR, x)).shape\n)\n\n# group by class and image shape\nshape_class_dist = (\n    df_train\n    .groupby(['class_name', 'image_shape'])\n    .size()\n    .unstack(fill_value=0)\n)\n\nshape_class_dist\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:47:41.257034Z","iopub.execute_input":"2026-01-23T10:47:41.257286Z","iopub.status.idle":"2026-01-23T10:53:22.263596Z","shell.execute_reply.started":"2026-01-23T10:47:41.25726Z","shell.execute_reply":"2026-01-23T10:53:22.262981Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# check image shape for the entire dataset\n\nimg_dir = \"/kaggle/input/cassava-leaf-disease-classification/train_images\"\n\nfirst_shape = None\ndifferent_shapes = False\n\nfor img_name in os.listdir(img_dir):\n    img_path = os.path.join(img_dir, img_name)\n    img = cv2.imread(img_path)\n\n    if img is None:\n        continue\n\n    if first_shape is None:\n        first_shape = img.shape\n    elif img.shape != first_shape:\n        different_shapes = True\n        print(\"Different shape found:\", img.shape)\n        break\n\nif not different_shapes:\n    print(\"All images have the same shape:\", first_shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:53:22.264518Z","iopub.execute_input":"2026-01-23T10:53:22.264714Z","iopub.status.idle":"2026-01-23T10:55:23.217338Z","shell.execute_reply.started":"2026-01-23T10:53:22.264695Z","shell.execute_reply":"2026-01-23T10:55:23.216665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:55:23.218365Z","iopub.execute_input":"2026-01-23T10:55:23.218989Z","iopub.status.idle":"2026-01-23T10:55:23.229089Z","shell.execute_reply.started":"2026-01-23T10:55:23.218962Z","shell.execute_reply":"2026-01-23T10:55:23.228426Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# identifying exact duplicate images\n\ndef get_image_hash(image_path):\n    \"\"\"Generate an MD5 hash for the image\"\"\"\n    with open(image_path, 'rb') as f:\n        file_hash = hashlib.md5(f.read()).hexdigest()\n    return file_hash\n\nimage_hashes = {}\nduplicate_images = []\n\npath = \"/kaggle/input/cassava-leaf-disease-classification/train_images\"\n\nfor image_name in os.listdir(path):\n    image_path = os.path.join(path, image_name)\n\n    # safety check\n    if not os.path.isfile(image_path):\n        continue\n\n    img_hash = get_image_hash(image_path)\n\n    if img_hash in image_hashes:\n        duplicate_images.append((image_name, image_hashes[img_hash]))\n    else:\n        image_hashes[img_hash] = image_name\n\nprint(f\"Exact duplicate images found: {len(duplicate_images)}\")\n\n# optional: view some duplicates\nduplicate_images[:10]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:55:23.229992Z","iopub.execute_input":"2026-01-23T10:55:23.230231Z","iopub.status.idle":"2026-01-23T10:56:20.056294Z","shell.execute_reply.started":"2026-01-23T10:55:23.230202Z","shell.execute_reply":"2026-01-23T10:56:20.055438Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"base model","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers , models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:56:20.057391Z","iopub.execute_input":"2026-01-23T10:56:20.0577Z","iopub.status.idle":"2026-01-23T10:56:43.376986Z","shell.execute_reply.started":"2026-01-23T10:56:20.057675Z","shell.execute_reply":"2026-01-23T10:56:43.376387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = models.Sequential()\n\n# 1st block \nmodel.add(layers.Conv2D(32,(3,3), activation ='relu', input_shape= (224,224,3)))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.MaxPooling2D((2,2)))\n\n#2 block\nmodel.add(layers.Conv2D(64,(3,3), activation ='relu',))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.MaxPooling2D((2,2)))\n\nmodel.add(layers.Conv2D(128,(3,3), activation ='relu',))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.MaxPooling2D((2,2)))\n\nmodel.add(layers.Conv2D(256,(3,3), activation ='relu',))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.MaxPooling2D((2,2)))\n\n# dense layer\n\nmodel.add(layers.Flatten())\n\nmodel.add(layers.Dense(256,activation='relu'))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.Dropout(0.5))\n\nmodel.add(layers.Dense(5,activation = 'softmax'))\n\nmodel.compile(optimizer = 'adam', loss = 'categorical_crossentropy',metrics= ['accuracy'])\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:56:43.377832Z","iopub.execute_input":"2026-01-23T10:56:43.378299Z","iopub.status.idle":"2026-01-23T10:56:47.796042Z","shell.execute_reply.started":"2026-01-23T10:56:43.378275Z","shell.execute_reply":"2026-01-23T10:56:47.795483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_dir = '/kaggle/input/cassava-leaf-disease-classification/train_images'\n\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\ndatagen  = ImageDataGenerator(rescale= 1.0/255, validation_split=0.2)\n\ntrain_dataset = datagen.flow_from_dataframe(\n    dataframe= df_train,\n    directory = img_dir,\n    x_col =\"image_id\",\n    y_col = 'class_name',\n    target_size = (224,224),\n    batch_size = 32,\n    class_mode = \"categorical\",\n    subset =\"training\",\n    shuffle = True\n)\n\nval_dataset = datagen.flow_from_dataframe(\n    dataframe= df_train,\n    directory = img_dir,\n    x_col =\"image_id\",\n    y_col = 'class_name',\n    target_size = (224,224),\n    batch_size = 32,\n    class_mode = \"categorical\",\n    subset =\"validation\",\n    shuffle = True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:56:47.796808Z","iopub.execute_input":"2026-01-23T10:56:47.797086Z","iopub.status.idle":"2026-01-23T10:57:14.421951Z","shell.execute_reply.started":"2026-01-23T10:56:47.797054Z","shell.execute_reply":"2026-01-23T10:57:14.421385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ep = 10","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:57:14.422756Z","iopub.execute_input":"2026-01-23T10:57:14.42303Z","iopub.status.idle":"2026-01-23T10:57:14.426263Z","shell.execute_reply.started":"2026-01-23T10:57:14.423007Z","shell.execute_reply":"2026-01-23T10:57:14.425703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\n\nearly_stop = EarlyStopping(\n    monitor='val_loss',\n    patience=5,\n    restore_best_weights=True\n)\n\ncheckpoint = ModelCheckpoint(\n    'best_cnn_model.h5',\n    monitor='val_loss',\n    save_best_only=True\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:57:14.427119Z","iopub.execute_input":"2026-01-23T10:57:14.42738Z","iopub.status.idle":"2026-01-23T10:57:14.469838Z","shell.execute_reply.started":"2026-01-23T10:57:14.42735Z","shell.execute_reply":"2026-01-23T10:57:14.469236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    train_dataset,\n    validation_data = val_dataset,\n    epochs = ep,\n    callbacks=[early_stop, checkpoint]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T10:57:14.470701Z","iopub.execute_input":"2026-01-23T10:57:14.470972Z","iopub.status.idle":"2026-01-23T11:14:43.110156Z","shell.execute_reply.started":"2026-01-23T10:57:14.470923Z","shell.execute_reply":"2026-01-23T11:14:43.109493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Accuracy plot\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'], label='Train Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.title('Training vs Validation Accuracy')\nplt.legend()\n\n# Loss plot\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.title('Training vs Validation Loss')\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:14:43.111119Z","iopub.execute_input":"2026-01-23T11:14:43.111436Z","iopub.status.idle":"2026-01-23T11:14:43.369086Z","shell.execute_reply.started":"2026-01-23T11:14:43.111413Z","shell.execute_reply":"2026-01-23T11:14:43.368426Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_loss, val_acc = model.evaluate(val_dataset)\nprint(f\"Validation Accuracy: {val_acc:.4f}\")\nprint(f\"Validation Loss: {val_loss:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:14:43.370136Z","iopub.execute_input":"2026-01-23T11:14:43.370446Z","iopub.status.idle":"2026-01-23T11:15:03.959255Z","shell.execute_reply.started":"2026-01-23T11:14:43.370419Z","shell.execute_reply":"2026-01-23T11:15:03.958648Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport numpy as np\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns\n\n# Get true labels and predictions\ny_true = val_dataset.classes\ny_pred = model.predict(val_dataset)\ny_pred_classes = np.argmax(y_pred, axis=1)\n\n# Confusion matrix\ncm = confusion_matrix(y_true, y_pred_classes)\n\nplt.figure(figsize=(6, 5))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\nplt.xlabel('Predicted')\nplt.ylabel('True')\nplt.title('Confusion Matrix')\nplt.show()\n\n# Classification report\nprint(classification_report(y_true, y_pred_classes))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:03.960181Z","iopub.execute_input":"2026-01-23T11:15:03.960468Z","iopub.status.idle":"2026-01-23T11:15:25.332896Z","shell.execute_reply.started":"2026-01-23T11:15:03.960444Z","shell.execute_reply":"2026-01-23T11:15:25.332256Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The baseline CNN achieved 47% accuracy but showed strong bias toward the majority class. Macro F1-score of 0.19 revealed poor generalization across minority classes, indicating the need for class balancing and transfer learning.","metadata":{}},{"cell_type":"code","source":"df_train['image_id'] = df_train['image_id'].apply(lambda x: x.split('/')[-1])\n\nprint(df_train['image_id'][0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T12:06:57.803443Z","iopub.execute_input":"2026-01-23T12:06:57.804006Z","iopub.status.idle":"2026-01-23T12:06:57.819407Z","shell.execute_reply.started":"2026-01-23T12:06:57.803977Z","shell.execute_reply":"2026-01-23T12:06:57.818523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.354066Z","iopub.status.idle":"2026-01-23T11:15:25.354346Z","shell.execute_reply.started":"2026-01-23T11:15:25.354223Z","shell.execute_reply":"2026-01-23T11:15:25.354242Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_DIR = '/kaggle/input/cassava-leaf-disease-classification/train_images'\n\ndf_train['image_id'] = IMG_DIR +'/'+ df_train['image_id']\ndf_train['image_id'][0] ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.3558Z","iopub.status.idle":"2026-01-23T11:15:25.356133Z","shell.execute_reply.started":"2026-01-23T11:15:25.355956Z","shell.execute_reply":"2026-01-23T11:15:25.355979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train['image_id'][1]\n#/kaggle/input/cassava-leaf-disease-classification/train_images/1000015157.jpg","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.357244Z","iopub.status.idle":"2026-01-23T11:15:25.357499Z","shell.execute_reply.started":"2026-01-23T11:15:25.357388Z","shell.execute_reply":"2026-01-23T11:15:25.357403Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\nVALIDATION_SPLIT = 0.2\n\ntrain_df , val_df = train_test_split(\n    df_train,\n    test_size = VALIDATION_SPLIT,\n    stratify = df_train['label'],\n    random_state = 42\n)\n\nprint(f\"training sample : {len(train_df)}\")\nprint(f\"validation sample : {len(val_df)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.358413Z","iopub.status.idle":"2026-01-23T11:15:25.35874Z","shell.execute_reply.started":"2026-01-23T11:15:25.358556Z","shell.execute_reply":"2026-01-23T11:15:25.358576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"training  distribution\")\nprint(train_df['label'].value_counts())\n\nprint(\"\\n validation distribution\")\nprint(train_df['label'].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.359998Z","iopub.status.idle":"2026-01-23T11:15:25.360296Z","shell.execute_reply.started":"2026-01-23T11:15:25.360147Z","shell.execute_reply":"2026-01-23T11:15:25.360166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# image dimensions \nIMG_HEIGHT = 224 \nIMG_WIDTH = 224\nCHANNELS = 3 \n\nBATCH_SIZE = 32\n# autotune fro performance optimization \nAUTOTUNE = tf.data.AUTOTUNE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.361195Z","iopub.status.idle":"2026-01-23T11:15:25.361491Z","shell.execute_reply.started":"2026-01-23T11:15:25.361321Z","shell.execute_reply":"2026-01-23T11:15:25.361342Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_image(file_path, label):\n    # read the image from disk \n    image = tf.io.read_file(file_path)\n\n    # decode the image\n    image = tf.image.decode_jpeg(image, channels = CHANNELS )\n\n    image = tf.image.resize(image, [IMG_HEIGHT, IMG_WIDTH])\n    image = image/255.0\n    label = tf.one_hot(label, depth=5)\n\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.362472Z","iopub.status.idle":"2026-01-23T11:15:25.362879Z","shell.execute_reply.started":"2026-01-23T11:15:25.362735Z","shell.execute_reply":"2026-01-23T11:15:25.362759Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.364191Z","iopub.status.idle":"2026-01-23T11:15:25.364541Z","shell.execute_reply.started":"2026-01-23T11:15:25.364368Z","shell.execute_reply":"2026-01-23T11:15:25.364388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# data Agumentation \n\ndata_augmentation = tf.keras.Sequential([\n    layers.RandomFlip(\"horizontal\"),\n    layers.RandomRotation(0.1, fill_mode=\"nearest\"),\n    layers.RandomZoom(0.1, fill_mode=\"nearest\"),\n    layers.RandomContrast(0.1),\n])\n\ndef augment(image, label):\n    image = data_augmentation(image)\n    return image, label\n\ntrain_ds = tf.data.Dataset.from_tensor_slices(\n    (train_df['image_id'].values, train_df['label'].values)\n)\n\ntrain_ds = (\n    train_ds\n    .map(process_image, num_parallel_calls=AUTOTUNE)\n    .map(augment, num_parallel_calls=AUTOTUNE)\n    .shuffle(1000)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTOTUNE)\n)\n\nval_ds = tf.data.Dataset.from_tensor_slices(\n    (val_df['image_id'].values, val_df['label'].values)\n)\n\nval_ds = (\n    val_ds\n    .map(process_image, num_parallel_calls=AUTOTUNE)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTOTUNE)\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.366216Z","iopub.status.idle":"2026-01-23T11:15:25.366717Z","shell.execute_reply.started":"2026-01-23T11:15:25.366523Z","shell.execute_reply":"2026-01-23T11:15:25.366546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras import regularizers\n\nnum_classes = 5 \ndef create_cnn_model(\n    input_shape=(IMG_HEIGHT, IMG_WIDTH, CHANNELS),\n    num_classes=5\n):\n    model = models.Sequential([\n        layers.Input(shape=input_shape),\n\n        # Block 1\n        layers.Conv2D(32, (3,3), activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D(2,2),\n\n        # Block 2\n        layers.Conv2D(64, (3,3), activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D(2,2),\n\n        # Block 3\n        layers.Conv2D(128, (3,3), activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D(2,2),\n\n        # Block 4\n        layers.Conv2D(256, (3,3), activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D(2,2),\n\n        # Head\n        layers.Flatten(),\n        layers.Dense(\n            256,\n            activation='relu',\n            kernel_regularizer=regularizers.l2(1e-4)\n        ),\n        layers.BatchNormalization(),\n        layers.Dropout(0.5),\n        layers.Dense(num_classes, activation='softmax')\n    ])\n\n    model.compile(\n        optimizer='adam',\n        loss='categorical_crossentropy',  # IMPORTANT\n        metrics=['accuracy']\n    )\n\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.367793Z","iopub.status.idle":"2026-01-23T11:15:25.368258Z","shell.execute_reply.started":"2026-01-23T11:15:25.368073Z","shell.execute_reply":"2026-01-23T11:15:25.368095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = create_cnn_model(input_shape = (IMG_HEIGHT, IMG_WIDTH, CHANNELS),\n    num_classes=5)\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.369532Z","iopub.status.idle":"2026-01-23T11:15:25.369869Z","shell.execute_reply.started":"2026-01-23T11:15:25.369697Z","shell.execute_reply":"2026-01-23T11:15:25.369718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\n\ncallbacks = [\n    EarlyStopping(\n        monitor=\"val_loss\",\n        patience=5,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    ModelCheckpoint(\n        filepath=\"best_cnn_model.keras\",\n        monitor=\"val_loss\",\n        save_best_only=True,\n        verbose=1\n    ),\n    ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.3,\n        patience=3,\n        min_lr=1e-6,\n        verbose=1\n    )\n]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.370605Z","iopub.status.idle":"2026-01-23T11:15:25.370823Z","shell.execute_reply.started":"2026-01-23T11:15:25.37072Z","shell.execute_reply":"2026-01-23T11:15:25.370732Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_ds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.371952Z","iopub.status.idle":"2026-01-23T11:15:25.37223Z","shell.execute_reply.started":"2026-01-23T11:15:25.372113Z","shell.execute_reply":"2026-01-23T11:15:25.372129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 20\n\nhistory = model.fit(\n    train_ds ,\n    validation_data = val_ds,\n    epochs = EPOCHS,\n    callbacks = callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.373213Z","iopub.status.idle":"2026-01-23T11:15:25.373567Z","shell.execute_reply.started":"2026-01-23T11:15:25.373386Z","shell.execute_reply":"2026-01-23T11:15:25.373406Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Accuracy plot\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'], label='Train Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.title('Training vs Validation Accuracy')\nplt.legend()\n\n# Loss plot\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.title('Training vs Validation Loss')\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.374666Z","iopub.status.idle":"2026-01-23T11:15:25.375016Z","shell.execute_reply.started":"2026-01-23T11:15:25.374829Z","shell.execute_reply":"2026-01-23T11:15:25.374851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_loss, val_acc = model.evaluate(val_dataset)\nprint(f\"Validation Accuracy: {val_acc:.4f}\")\nprint(f\"Validation Loss: {val_loss:.4f}\")\n\n\nimport numpy as np\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns\n\n# Get true labels and predictions\ny_true = val_dataset.classes\ny_pred = model.predict(val_dataset)\ny_pred_classes = np.argmax(y_pred, axis=1)\n\n# Confusion matrix\ncm = confusion_matrix(y_true, y_pred_classes)\n\nplt.figure(figsize=(6, 5))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\nplt.xlabel('Predicted')\nplt.ylabel('True')\nplt.title('Confusion Matrix')\nplt.show()\n\n# Classification report\nprint(classification_report(y_true, y_pred_classes))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.376336Z","iopub.status.idle":"2026-01-23T11:15:25.37676Z","shell.execute_reply.started":"2026-01-23T11:15:25.376622Z","shell.execute_reply":"2026-01-23T11:15:25.37664Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"transfer learning ","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout\nfrom tensorflow.keras.models import Model\n\nIMG_SIZE = (224, 224)\nBATCH_SIZE = 32\nNUM_CLASSES = 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.377862Z","iopub.status.idle":"2026-01-23T11:15:25.37817Z","shell.execute_reply.started":"2026-01-23T11:15:25.378005Z","shell.execute_reply":"2026-01-23T11:15:25.378019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_model = tf.keras.applications.ResNet50(\n    weights='imagenet',\n    include_top=False,\n    input_shape=(224, 224, 3)\n)\n\nbase_model.trainable = False  # freeze backbone\n\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\nx = Dense(256, activation='relu')(x)\nx = Dropout(0.5)(x)\noutput = Dense(NUM_CLASSES, activation='softmax')(x)\n\nmodel_resnet = Model(inputs=base_model.input, outputs=output)\n\nmodel_resnet.compile(\n    optimizer='adam',\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\nmodel_resnet.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.37922Z","iopub.status.idle":"2026-01-23T11:15:25.379492Z","shell.execute_reply.started":"2026-01-23T11:15:25.379365Z","shell.execute_reply":"2026-01-23T11:15:25.379388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_resnet = model_resnet.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=10\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.380509Z","iopub.status.idle":"2026-01-23T11:15:25.380849Z","shell.execute_reply.started":"2026-01-23T11:15:25.380671Z","shell.execute_reply":"2026-01-23T11:15:25.380691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef plot_resnet_history(history_resnet):\n    # Accuracy\n    plt.figure()\n    plt.plot(history_resnet.history['accuracy'])\n    plt.plot(history_resnet.history['val_accuracy'])\n    plt.title('ResNet Training vs Validation Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.legend(['Train', 'Validation'])\n    plt.show()\n\n    # Loss\n    plt.figure()\n    plt.plot(history_resnet.history['loss'])\n    plt.plot(history_resnet.history['val_loss'])\n    plt.title('ResNet Training vs Validation Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend(['Train', 'Validation'])\n    plt.show()\nplot_resnet_history(history_resnet)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.381772Z","iopub.status.idle":"2026-01-23T11:15:25.382126Z","shell.execute_reply.started":"2026-01-23T11:15:25.381955Z","shell.execute_reply":"2026-01-23T11:15:25.381975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"resnet_loss, resnet_accuracy = model.evaluate(val_ds)\nprint(f\"ResNet Validation Loss: {resnet_loss:.4f}\")\nprint(f\"ResNet Validation Accuracy: {resnet_accuracy:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.383207Z","iopub.status.idle":"2026-01-23T11:15:25.383556Z","shell.execute_reply.started":"2026-01-23T11:15:25.383374Z","shell.execute_reply":"2026-01-23T11:15:25.383399Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom sklearn.metrics import classification_report, confusion_matrix\n\n# True labels\ny_true = np.concatenate([y for x, y in val_ds], axis=0)\ny_true = np.argmax(y_true, axis=1)\n\n# Predictions\ny_pred = model.predict(val_ds)\ny_pred = np.argmax(y_pred, axis=1)\n\nprint(classification_report(y_true, y_pred, target_names=class_names))\n\ncm = confusion_matrix(y_true, y_pred)\nprint(\"Confusion Matrix:\\n\", cm)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.384436Z","iopub.status.idle":"2026-01-23T11:15:25.384657Z","shell.execute_reply.started":"2026-01-23T11:15:25.384551Z","shell.execute_reply":"2026-01-23T11:15:25.384564Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**MODEL 2: EfficientNetB0**","metadata":{}},{"cell_type":"code","source":"IMG_SIZE = (224, 224)\nBATCH_SIZE = 32\nNUM_CLASSES = 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.385875Z","iopub.status.idle":"2026-01-23T11:15:25.386233Z","shell.execute_reply.started":"2026-01-23T11:15:25.386063Z","shell.execute_reply":"2026-01-23T11:15:25.386083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_model = tf.keras.applications.EfficientNetB0(\n    include_top=False,\n    weights='imagenet',\n    input_shape=(224,224,3)\n)\n\nbase_model.trainable = False  # Transfer learning\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.38809Z","iopub.status.idle":"2026-01-23T11:15:25.388398Z","shell.execute_reply.started":"2026-01-23T11:15:25.388224Z","shell.execute_reply":"2026-01-23T11:15:25.388247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = tf.keras.Sequential([\n    base_model,\n    tf.keras.layers.GlobalAveragePooling2D(),\n    tf.keras.layers.BatchNormalization(),\n    tf.keras.layers.Dense(256, activation='relu'),\n    tf.keras.layers.Dropout(0.5),\n    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.389515Z","iopub.status.idle":"2026-01-23T11:15:25.389852Z","shell.execute_reply.started":"2026-01-23T11:15:25.389683Z","shell.execute_reply":"2026-01-23T11:15:25.389703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.391287Z","iopub.status.idle":"2026-01-23T11:15:25.391629Z","shell.execute_reply.started":"2026-01-23T11:15:25.391448Z","shell.execute_reply":"2026-01-23T11:15:25.391468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.393066Z","iopub.status.idle":"2026-01-23T11:15:25.393394Z","shell.execute_reply.started":"2026-01-23T11:15:25.393226Z","shell.execute_reply":"2026-01-23T11:15:25.393246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.394417Z","iopub.status.idle":"2026-01-23T11:15:25.394757Z","shell.execute_reply.started":"2026-01-23T11:15:25.394576Z","shell.execute_reply":"2026-01-23T11:15:25.394595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom sklearn.metrics import confusion_matrix, classification_report\n\n# Predict on validation data\ny_pred_prob = model.predict(val_ds)\ny_pred = np.argmax(y_pred_prob, axis=1)\n\n# True labels\ny_true = np.concatenate([y.numpy() for _, y in val_ds])\ny_true = np.argmax(y_true, axis=1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.396209Z","iopub.status.idle":"2026-01-23T11:15:25.396795Z","shell.execute_reply.started":"2026-01-23T11:15:25.396661Z","shell.execute_reply":"2026-01-23T11:15:25.396683Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Confusion Matrix**","metadata":{}},{"cell_type":"code","source":"cm = confusion_matrix(y_true, y_pred)\n\nplt.figure(figsize=(6,6))\nplt.imshow(cm)\nplt.title(\"Confusion Matrix\")\nplt.colorbar()\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"Actual\")\nplt.xticks(range(5))\nplt.yticks(range(5))\n\nfor i in range(5):\n    for j in range(5):\n        plt.text(j, i, cm[i, j], ha=\"center\", va=\"center\")\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.397621Z","iopub.status.idle":"2026-01-23T11:15:25.397965Z","shell.execute_reply.started":"2026-01-23T11:15:25.3978Z","shell.execute_reply":"2026-01-23T11:15:25.397821Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Classification Report**","metadata":{}},{"cell_type":"code","source":"print(\"Classification Report:\\n\")\nprint(classification_report(\n    y_true,\n    y_pred,\n    digits=4\n))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.399028Z","iopub.status.idle":"2026-01-23T11:15:25.399243Z","shell.execute_reply.started":"2026-01-23T11:15:25.39914Z","shell.execute_reply":"2026-01-23T11:15:25.399153Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Per-class Accuracy**","metadata":{}},{"cell_type":"code","source":"class_acc = cm.diagonal() / cm.sum(axis=1)\n\nfor i, acc in enumerate(class_acc):\n    print(f\"Class {i} Accuracy: {acc:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.400295Z","iopub.status.idle":"2026-01-23T11:15:25.40058Z","shell.execute_reply.started":"2026-01-23T11:15:25.40045Z","shell.execute_reply":"2026-01-23T11:15:25.400471Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Overall Accuracy**","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Training vs Validation plot**","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(12,4))\n\nplt.subplot(1,2,1)\nplt.plot(history.history['accuracy'], label='Train')\nplt.plot(history.history['val_accuracy'], label='Validation')\nplt.title('Accuracy')\nplt.legend()\n\nplt.subplot(1,2,2)\nplt.plot(history.history['loss'], label='Train')\nplt.plot(history.history['val_loss'], label='Validation')\nplt.title('Loss')\nplt.legend()\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T11:15:25.401687Z","iopub.status.idle":"2026-01-23T11:15:25.401925Z","shell.execute_reply.started":"2026-01-23T11:15:25.401816Z","shell.execute_reply":"2026-01-23T11:15:25.40183Z"}},"outputs":[],"execution_count":null}]}