{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install fastai sklearn ","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-12-01T08:06:25.145491Z","iopub.execute_input":"2022-12-01T08:06:25.146574Z","iopub.status.idle":"2022-12-01T08:06:58.328091Z","shell.execute_reply.started":"2022-12-01T08:06:25.146468Z","shell.execute_reply":"2022-12-01T08:06:58.326691Z"},"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Intro\n\nFor this project, we are going to use fast.ai to help us classify diseases given an image of a leaf. We have been given 4 different diseases and an option of 'Healthy' to clasiffy our images. ","metadata":{}},{"cell_type":"code","source":"vocab = [\n    \"Cassava Bacterial Blight (CBB)\",\n    \"Cassava Brown Streak Disease (CBSD)\",\n    \"Cassava Green Mottle (CGM)\",\n    \"Cassava Mosaic Disease (CMD)\",\n    \"Healthy\"\n]","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Because internet usage is not allowed for competitions, we are going to use a pre-downloaded version of resnet34.","metadata":{}},{"cell_type":"code","source":"!mkdir -p /root/.cache/torch/hub/checkpoints/\n!cp /kaggle/input/resnet34/resnet34.pth /root/.cache/torch/hub/checkpoints/resnet34-b627a593.pth","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:06:58.331795Z","iopub.execute_input":"2022-12-01T08:06:58.332796Z","iopub.status.idle":"2022-12-01T08:07:02.052935Z","shell.execute_reply.started":"2022-12-01T08:06:58.332761Z","shell.execute_reply":"2022-12-01T08:07:02.051507Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"By copying over our downloaded resnet34 to the spot where pytorch likes to store it, we can override their version and avoid downloading.","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nfrom PIL import Image\nfrom fastai.vision.all import *\nimport gc\nimport matplotlib.pyplot as plt\nfrom matplotlib import image as mpimg","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:17:47.158022Z","iopub.execute_input":"2022-12-01T08:17:47.159281Z","iopub.status.idle":"2022-12-01T08:17:47.168852Z","shell.execute_reply.started":"2022-12-01T08:17:47.159227Z","shell.execute_reply":"2022-12-01T08:17:47.167084Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = Path('/kaggle/input/cassava-leaf-disease-classification')\ntrain_images = get_image_files(path / 'train_images')\nlen(train_images)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:07:55.227608Z","iopub.execute_input":"2022-12-01T08:07:55.228223Z","iopub.status.idle":"2022-12-01T08:07:59.396224Z","shell.execute_reply.started":"2022-12-01T08:07:55.228176Z","shell.execute_reply":"2022-12-01T08:07:59.395137Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Before we start, let's check out these leaves, and pick a random image to view.","metadata":{}},{"cell_type":"code","source":"img = Image.open(train_images[random.randint(0,len(train_images))])\nplt.imshow(img)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:08:13.575762Z","iopub.execute_input":"2022-12-01T08:08:13.576303Z","iopub.status.idle":"2022-12-01T08:08:14.103003Z","shell.execute_reply.started":"2022-12-01T08:08:13.576261Z","shell.execute_reply":"2022-12-01T08:08:14.101888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Each image is sized to 600x800, and we are fine keeping it at these dimensions as long as all images are in the same shape.","metadata":{}},{"cell_type":"code","source":"def displayTrainingData():\n    fig = plt.figure(figsize=(5,10))\n    \n    for rowIndex in range(1, 10):\n        subdirectory = str(rowIndex)\n        p = path/'train_images'\n        images = os.listdir(p)\n        for sampleIndex in range(1, 6):\n            randomNumber = random.randint(0, len(images)-1)\n            image = Image.open(p/images[randomNumber])\n            ax = fig.add_subplot(10, 5, 5*rowIndex + sampleIndex)\n            ax.axis(\"off\")\n            \n            plt.imshow(image, cmap='gray')\n        \n    plt.show()\n    \ndef displayTestingData():\n    fig = plt.figure(figsize=(5, 10))\n    \n    paths = path/'test_images'\n    \n        \n    for i in range(1, 51):\n        randomNumber = random.randint(0, len(paths)-1)\n        image = Image.open(paths[randomNumber])\n        \n        ax = fig.add_subplot(10, 5, i)\n        ax.axis(\"off\")\n        \n        plt.imshow(image, cmap='gray')\n    plt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-12-01T08:10:32.612289Z","iopub.execute_input":"2022-12-01T08:10:32.612741Z","iopub.status.idle":"2022-12-01T08:10:32.640934Z","shell.execute_reply.started":"2022-12-01T08:10:32.612701Z","shell.execute_reply":"2022-12-01T08:10:32.639719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's have a look at the rest of the leaves!","metadata":{}},{"cell_type":"code","source":"displayTrainingData()","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:10:32.995501Z","iopub.execute_input":"2022-12-01T08:10:32.995974Z","iopub.status.idle":"2022-12-01T08:10:39.548308Z","shell.execute_reply.started":"2022-12-01T08:10:32.995936Z","shell.execute_reply":"2022-12-01T08:10:39.546931Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv(path / 'train.csv')\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:19:56.273774Z","iopub.execute_input":"2022-12-01T08:19:56.276302Z","iopub.status.idle":"2022-12-01T08:19:56.314786Z","shell.execute_reply.started":"2022-12-01T08:19:56.276258Z","shell.execute_reply":"2022-12-01T08:19:56.313804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## DataLoaders!\n\nFor this project, we are going to use fast.ai's ImageDataLoaders to augment and prep our images for training. I like to write out the documentation from fast.ai so I don't have to keep tabbing back and forth from their documentation.","metadata":{}},{"cell_type":"code","source":"\"\"\" \nImageDataLoaders.from_df (df, path='.', valid_pct=0.2, seed=None,\n                           fn_col=0, folder=None, suff='', label_col=1,\n                           label_delim=None, y_block=None, valid_col=None,\n                           item_tfms=None, batch_tfms=None, img_cls=<class\n                           'fastai.vision.core.PILImage'>, bs:int=64,\n                           val_bs:int=None, shuffle:bool=True,\n                           device=None)\n\"\"\"\ndls = ImageDataLoaders.from_df(\n    df,\n    valid_pct=0.2,\n    folder=path / 'train_images',\n    y_block=CategoryBlock(),\n    item_tfms=[FlipItem(),Resize((600,800), method='squish')],\n    batch_tfms=aug_transforms(size=(600,800)),\n    bs=16\n)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:21:47.032753Z","iopub.execute_input":"2022-12-01T08:21:47.033408Z","iopub.status.idle":"2022-12-01T08:22:00.852022Z","shell.execute_reply.started":"2022-12-01T08:21:47.033361Z","shell.execute_reply":"2022-12-01T08:22:00.850784Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Breakdown\n\nFast.ai allows us to create dataloaders from a Pandas dataframe; so, we will feed in the training dataframe and pair it with the `folder` parameter to help label our images.\n\nFor the y_block, we are going to choose CategoryBlock because we are trying to classify a certain disease for the leaves.\n\nWe are going to use FlipItem to randomly flip images and Resize to ensure that all images are squished to the same size for processing.\n\nWe are going to use a batch size of 16 as Kaggle does not provide us with crazy hardware to train our model. If we were to use 32 or 64, we would quickly see that our training will stall out and run out of memory.","metadata":{}},{"cell_type":"code","source":"dls.show_batch(max_n=9)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:22:00.858454Z","iopub.execute_input":"2022-12-01T08:22:00.860995Z","iopub.status.idle":"2022-12-01T08:22:03.341436Z","shell.execute_reply.started":"2022-12-01T08:22:00.860951Z","shell.execute_reply":"2022-12-01T08:22:03.340285Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training the model\nNow that we know our data has been loaded nicely and looks good, let's get to creating our learner. We are going to use resnet34 as it is very fast for training quickly and we want to get to iterating as soon as possible.","metadata":{}},{"cell_type":"code","source":"learn = vision_learner(dls, arch=models.resnet34, metrics=accuracy)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:25:51.424143Z","iopub.execute_input":"2022-12-01T08:25:51.424643Z","iopub.status.idle":"2022-12-01T08:25:52.536591Z","shell.execute_reply.started":"2022-12-01T08:25:51.424601Z","shell.execute_reply":"2022-12-01T08:25:52.535328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now let's try and train our model!","metadata":{}},{"cell_type":"code","source":"learn.fit_one_cycle(4)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T08:25:55.437282Z","iopub.execute_input":"2022-12-01T08:25:55.437905Z","iopub.status.idle":"2022-12-01T09:03:28.168132Z","shell.execute_reply.started":"2022-12-01T08:25:55.437859Z","shell.execute_reply":"2022-12-01T09:03:28.162681Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Note: Through testing, I've personally found that 4 epochs has been sufficient in getting a solid accuracy in a timely fashion with Kaggle's GPUs.","metadata":{}},{"cell_type":"markdown","source":"## Interpretation\n\nAfter we are done testing, we can interpret our results to see which images had the highest loss and see where our model had hiccups.","metadata":{}},{"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.261416Z","iopub.status.idle":"2022-12-01T09:03:29.264004Z","shell.execute_reply.started":"2022-12-01T09:03:29.263712Z","shell.execute_reply":"2022-12-01T09:03:29.263741Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"interp.plot_top_losses(9, figsize=(7, 7))","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.268345Z","iopub.status.idle":"2022-12-01T09:03:29.271155Z","shell.execute_reply.started":"2022-12-01T09:03:29.270870Z","shell.execute_reply":"2022-12-01T09:03:29.270899Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"interp.plot_confusion_matrix()","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.272720Z","iopub.status.idle":"2022-12-01T09:03:29.277735Z","shell.execute_reply.started":"2022-12-01T09:03:29.277426Z","shell.execute_reply":"2022-12-01T09:03:29.277479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Testing our model\n\nFirst we are going to just do a quick check on our validation set to see how our model performed.","metadata":{}},{"cell_type":"code","source":"valid = learn.dls.valid\npreds,targs = learn.get_preds(dl=valid)\nerror_rate(preds,targs)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.279185Z","iopub.status.idle":"2022-12-01T09:03:29.279982Z","shell.execute_reply.started":"2022-12-01T09:03:29.279697Z","shell.execute_reply":"2022-12-01T09:03:29.279723Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Loading in the test data\n\nIn this competition, our test data is not revealed to us until we submit. So we are going to have to pretend like the test data is here. After we create the test files, we are going to create a test dataloaders for our test data.","metadata":{}},{"cell_type":"code","source":"tst_files = get_image_files(path / 'test_images')\ntst_dl = learn.dls.test_dl(tst_files)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.281411Z","iopub.status.idle":"2022-12-01T09:03:29.282426Z","shell.execute_reply.started":"2022-12-01T09:03:29.282156Z","shell.execute_reply":"2022-12-01T09:03:29.282182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we can get predictions on the test data and get predictions for each disease (or lack thereof).","metadata":{}},{"cell_type":"code","source":"preds,targets = learn.get_preds(dl=tst_dl)\nidxs = preds.argmax(dim=1)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.283884Z","iopub.status.idle":"2022-12-01T09:03:29.291209Z","shell.execute_reply.started":"2022-12-01T09:03:29.290935Z","shell.execute_reply":"2022-12-01T09:03:29.290961Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We are going to use `argmax` to grab the highest probability option for each prediction.","metadata":{}},{"cell_type":"markdown","source":"## Submission\n\nFor submission, this competition wants the `image_id` of each prediction and `label` which is a numerical version of the vocab mentioned before.\n\nTo make sure let's just have a quick look at what a sample submission looks like:","metadata":{}},{"cell_type":"code","source":"pd.read_csv(path / 'sample_submission.csv').head()","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.292639Z","iopub.status.idle":"2022-12-01T09:03:29.293494Z","shell.execute_reply.started":"2022-12-01T09:03:29.293190Z","shell.execute_reply":"2022-12-01T09:03:29.293217Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ImageId = [path for path in os.listdir(path / 'test_images')]\nsubmission  = pd.DataFrame({\n    \"image_id\": ImageId,\n    \"label\": idxs\n})\nsubmission = submission.sort_values(by=['image_id'])\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.295158Z","iopub.status.idle":"2022-12-01T09:03:29.295964Z","shell.execute_reply.started":"2022-12-01T09:03:29.295672Z","shell.execute_reply":"2022-12-01T09:03:29.295698Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And a quick peak to make sure everything looks right...","metadata":{}},{"cell_type":"code","source":"!head submission.csv","metadata":{"execution":{"iopub.status.busy":"2022-12-01T09:03:29.297586Z","iopub.status.idle":"2022-12-01T09:03:29.298513Z","shell.execute_reply.started":"2022-12-01T09:03:29.298231Z","shell.execute_reply":"2022-12-01T09:03:29.298258Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We aren't going to be able to see the actual results yet as we aren't given the real test data yet.","metadata":{}},{"cell_type":"markdown","source":"## Final notes\n\nThis notebook is meant to help fast.ai people or beginners that are looking for a simple and concise implementation of image classification using fast.ai.\n\nIf you haven't noticed this is my first public notebook and I just started this about 2 weeks ago so I'm just trying to document my progress and practice by explaining my thoughts. I likely left out a lot of details; however, I did some of this intentionally as I don't want to spread misinformation. Please give me any feedback on how to improve my notebooks and overall organization as well!\n\n\nNote: On my first run, I scored about an 86% with this simple implementation.\n\n","metadata":{}}]}