{"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":"markdown","source":"<h1>Cassava Leaf Disease Detection Using Image Classification (Final Model)</h1>\n<h4>Jack Gage</h4>\n<h4>Teka Kubrom</h4>\n<h4>Capstone Project</h4>\n<h4>14 December 2025</h4>","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nfrom sklearn.model_selection import train_test_split\nimport pickle\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import *\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.utils import class_weight\nimport seaborn as sns\nimport torch\nfrom torch.utils.data import DataLoader, WeightedRandomSampler\nfrom torchvision import datasets\nfrom sklearn.utils.class_weight import compute_class_weight\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-14T22:59:04.931243Z","iopub.execute_input":"2025-12-14T22:59:04.932013Z","iopub.status.idle":"2025-12-14T22:59:04.936666Z","shell.execute_reply.started":"2025-12-14T22:59:04.931988Z","shell.execute_reply":"2025-12-14T22:59:04.936005Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h3>Importing Data From Kaggle</h3>","metadata":{}},{"cell_type":"code","source":"cassava_leaf_train = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')\ncassava_leaf_train.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:15:56.997579Z","iopub.execute_input":"2025-12-14T21:15:56.998193Z","iopub.status.idle":"2025-12-14T21:15:57.027692Z","shell.execute_reply.started":"2025-12-14T21:15:56.998163Z","shell.execute_reply":"2025-12-14T21:15:57.026862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cassava_leaf_train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:15:58.664687Z","iopub.execute_input":"2025-12-14T21:15:58.665478Z","iopub.status.idle":"2025-12-14T21:15:58.68642Z","shell.execute_reply.started":"2025-12-14T21:15:58.665448Z","shell.execute_reply":"2025-12-14T21:15:58.685852Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h4>Class imbalanced determined.</h4>","metadata":{}},{"cell_type":"code","source":"cassava_leaf_proportion = (cassava_leaf_train.label.value_counts() / len(cassava_leaf_train)).to_frame()\ncassava_leaf_proportion","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:06.561373Z","iopub.execute_input":"2025-12-14T21:16:06.562073Z","iopub.status.idle":"2025-12-14T21:16:06.576076Z","shell.execute_reply.started":"2025-12-14T21:16:06.562043Z","shell.execute_reply":"2025-12-14T21:16:06.575408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cassava_leaf_train['label'].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:08.665772Z","iopub.execute_input":"2025-12-14T21:16:08.666401Z","iopub.status.idle":"2025-12-14T21:16:08.673144Z","shell.execute_reply.started":"2025-12-14T21:16:08.666368Z","shell.execute_reply":"2025-12-14T21:16:08.672225Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cassava_classes = cassava_leaf_train['label'].value_counts()\nprint(cassava_classes)\ncassava_classes.plot(kind=\"bar\")\nplt.xlabel('label')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:10.68097Z","iopub.execute_input":"2025-12-14T21:16:10.681253Z","iopub.status.idle":"2025-12-14T21:16:10.904916Z","shell.execute_reply.started":"2025-12-14T21:16:10.681232Z","shell.execute_reply":"2025-12-14T21:16:10.904328Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1>Oversampling</h1>\n<h4>With this attempt to improve the class balance, class 0 is duplicated several time while classes 1, 2, and 4 are duplicated at a lesser quantity. Class 3 is left unaltered.</h4>","metadata":{}},{"cell_type":"code","source":"cassava_leaf_train_3 = cassava_leaf_train.loc[cassava_leaf_train['label'] == 3]\nprint(\"Class 3: \", cassava_leaf_train_3.shape)\n\ncassava_leaf_train_4 = cassava_leaf_train.loc[cassava_leaf_train['label'] == 4]\nprint(\"Class 4 Before: \", cassava_leaf_train_4.shape)\ncassava_leaf_train_4 = cassava_leaf_train_4.loc[cassava_leaf_train_4.index.repeat(5)].reset_index(drop=True)\nprint(\"Class 4 After: \", cassava_leaf_train_4.shape)\n\ncassava_leaf_train_2 = cassava_leaf_train.loc[cassava_leaf_train['label'] == 2]\nprint(\"Class 2 Before: \", cassava_leaf_train_2.shape)\ncassava_leaf_train_2 = cassava_leaf_train_2.loc[cassava_leaf_train_2.index.repeat(5)].reset_index(drop=True)\nprint(\"Class 2 After: \", cassava_leaf_train_2.shape)\n\ncassava_leaf_train_1 = cassava_leaf_train.loc[cassava_leaf_train['label'] == 1]\nprint(\"Class 1 Before: \", cassava_leaf_train_1.shape)\ncassava_leaf_train_1 = cassava_leaf_train_1.loc[cassava_leaf_train_1.index.repeat(6)].reset_index(drop=True)\nprint(\"Class 1 After: \", cassava_leaf_train_1.shape)\n\ncassava_leaf_train_0 = cassava_leaf_train.loc[cassava_leaf_train['label'] == 0]\nprint(\"Class 0 Before: \", cassava_leaf_train_0.shape)\ncassava_leaf_train_0 = cassava_leaf_train_0.loc[cassava_leaf_train_0.index.repeat(11)].reset_index(drop=True)\nprint(\"Class 0 After: \", cassava_leaf_train_0.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:23.857366Z","iopub.execute_input":"2025-12-14T21:16:23.857675Z","iopub.status.idle":"2025-12-14T21:16:23.878668Z","shell.execute_reply.started":"2025-12-14T21:16:23.857653Z","shell.execute_reply":"2025-12-14T21:16:23.878076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"osamp_cassava_leaf_train = pd.concat([cassava_leaf_train_3, cassava_leaf_train_4, cassava_leaf_train_2, cassava_leaf_train_1, cassava_leaf_train_0], axis=0)\nprint(osamp_cassava_leaf_train.shape)\nosamp_cassava_leaf_proportion = (osamp_cassava_leaf_train.label.value_counts() / len(osamp_cassava_leaf_train)).to_frame()\nosamp_cassava_leaf_proportion","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:32.021239Z","iopub.execute_input":"2025-12-14T21:16:32.021545Z","iopub.status.idle":"2025-12-14T21:16:32.034353Z","shell.execute_reply.started":"2025-12-14T21:16:32.021524Z","shell.execute_reply":"2025-12-14T21:16:32.033711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"osamp_cassava_leaf_train['label'] = osamp_cassava_leaf_train['label'].astype(str)\nosamp_train_cassava_df, osamp_valid_cassava_df = train_test_split(osamp_cassava_leaf_train, test_size=0.2, random_state=1, stratify=osamp_cassava_leaf_train.label)\nprint(osamp_train_cassava_df.shape)\nprint(osamp_valid_cassava_df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:41.28104Z","iopub.execute_input":"2025-12-14T21:16:41.281621Z","iopub.status.idle":"2025-12-14T21:16:41.364372Z","shell.execute_reply.started":"2025-12-14T21:16:41.281599Z","shell.execute_reply":"2025-12-14T21:16:41.363555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"osamp_train_dg = ImageDataGenerator(rescale=1/255, validation_split=0.2)\nosamp_valid_dg = ImageDataGenerator(rescale=1/255)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:48.145258Z","iopub.execute_input":"2025-12-14T21:16:48.146051Z","iopub.status.idle":"2025-12-14T21:16:48.149691Z","shell.execute_reply.started":"2025-12-14T21:16:48.146016Z","shell.execute_reply":"2025-12-14T21:16:48.149127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"osamp_train_loader = osamp_train_dg.flow_from_dataframe(\n    dataframe = osamp_train_cassava_df,\n    directory = '/kaggle/input/cassava-leaf-disease-classification/train_images',\n    x_col = 'image_id',\n    y_col = 'label',\n    batch_size = 64,\n    seed = 1,\n    shuffle = True,\n    class_mode = 'categorical',\n    target_size = (75,100)\n)\n\nosamp_valid_loader = osamp_valid_dg.flow_from_dataframe(\n    dataframe = osamp_valid_cassava_df,\n    directory = '/kaggle/input/cassava-leaf-disease-classification/train_images',\n    x_col = 'image_id',\n    y_col = 'label',\n    batch_size = 64,\n    seed = 1,\n    shuffle = True,\n    class_mode = 'categorical',\n    target_size = (75,100)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:16:54.232908Z","iopub.execute_input":"2025-12-14T21:16:54.2336Z","iopub.status.idle":"2025-12-14T21:17:20.808557Z","shell.execute_reply.started":"2025-12-14T21:16:54.233574Z","shell.execute_reply":"2025-12-14T21:17:20.807842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"osamp_train_steps = len(osamp_train_loader)\nosamp_valid_steps = len(osamp_valid_loader)\n\nprint(osamp_train_steps)\nprint(osamp_valid_steps)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:17:26.642005Z","iopub.execute_input":"2025-12-14T21:17:26.64255Z","iopub.status.idle":"2025-12-14T21:17:26.646797Z","shell.execute_reply.started":"2025-12-14T21:17:26.642524Z","shell.execute_reply":"2025-12-14T21:17:26.646034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"np.random.seed(1)\ntf.random.set_seed(1)\n\nosamp_cassava_model = Sequential([\n    Conv2D(32, (3,3), activation='relu', padding='same', input_shape=(75,100,3)),\n    MaxPooling2D(2,2),\n    Dropout(0.5),\n    BatchNormalization(),\n    Conv2D(64, (3,3), activation='relu', padding='same', input_shape=(75,100,3)),\n    MaxPooling2D(2,2),\n    Dropout(0.5),\n    BatchNormalization(),\n    Conv2D(64, (3,3), activation='relu', padding='same', input_shape=(75,100,3)),\n    Flatten(),\n    Dense(64, activation='softmax'),\n    Dropout(0.5),\n    BatchNormalization(),\n    Dense(5, activation='softmax')\n])\n\nosamp_cassava_model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:17:32.602355Z","iopub.execute_input":"2025-12-14T21:17:32.603031Z","iopub.status.idle":"2025-12-14T21:17:34.325405Z","shell.execute_reply.started":"2025-12-14T21:17:32.603007Z","shell.execute_reply":"2025-12-14T21:17:34.324554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"osamp_cassava_optimizer = tf.keras.optimizers.Adam(0.0001)\nosamp_cassava_model.compile(loss='categorical_crossentropy', optimizer=osamp_cassava_optimizer, metrics=['accuracy', tf.keras.metrics.AUC()])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:17:42.958619Z","iopub.execute_input":"2025-12-14T21:17:42.959004Z","iopub.status.idle":"2025-12-14T21:17:42.978394Z","shell.execute_reply.started":"2025-12-14T21:17:42.958979Z","shell.execute_reply":"2025-12-14T21:17:42.977689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\nosamp_h1 = osamp_cassava_model.fit(\n    x = osamp_train_loader,\n    steps_per_epoch = osamp_train_steps,\n    epochs = 25,\n    validation_data = osamp_valid_loader,\n    validation_steps = osamp_valid_steps,\n    verbose = 1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T21:17:50.281403Z","iopub.execute_input":"2025-12-14T21:17:50.282129Z","iopub.status.idle":"2025-12-14T22:57:03.931852Z","shell.execute_reply.started":"2025-12-14T21:17:50.282101Z","shell.execute_reply":"2025-12-14T22:57:03.93123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"training_hist = pd.DataFrame(osamp_h1.history)\ntraining_hist['epoch'] = osamp_h1.epoch\nsns.lineplot(x='epoch', y='loss', data=training_hist)\nsns.lineplot(x='epoch', y='val_loss', data=training_hist)\nplt.legend(labels=['train_loss', 'val_loss'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T22:57:36.494193Z","iopub.execute_input":"2025-12-14T22:57:36.49511Z","iopub.status.idle":"2025-12-14T22:57:36.686139Z","shell.execute_reply.started":"2025-12-14T22:57:36.495073Z","shell.execute_reply":"2025-12-14T22:57:36.685378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sns.lineplot(x='epoch', y='accuracy', data=training_hist)\nsns.lineplot(x='epoch', y='val_accuracy', data=training_hist)\nplt.legend(labels=['train_accuracy', 'val_accuracy'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T22:58:11.381628Z","iopub.execute_input":"2025-12-14T22:58:11.382383Z","iopub.status.idle":"2025-12-14T22:58:11.572373Z","shell.execute_reply.started":"2025-12-14T22:58:11.382355Z","shell.execute_reply":"2025-12-14T22:58:11.57176Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h4>Result: The model is well fit as the training accuracy hits 75% and the validation metrics closely rememble the training metrics. Nonetheless, there are still concerns with how zig-zaggy the validation metrics are and the fact that the validation metrics surpass the training metrics.</h4>","metadata":{}},{"cell_type":"code","source":"osamp_cassava_model.save('jmg_cassava_leaf.h5')\npickle.dump(osamp_h1, open(f'jmg_cassava_leaf.pk1', 'wb'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T23:01:43.321635Z","iopub.execute_input":"2025-12-14T23:01:43.32215Z","iopub.status.idle":"2025-12-14T23:01:43.538943Z","shell.execute_reply.started":"2025-12-14T23:01:43.322122Z","shell.execute_reply":"2025-12-14T23:01:43.538129Z"}},"outputs":[],"execution_count":null}]}