{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Importing necessary libraries"},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install efficientnet_pytorch #download pretrained effieceint model","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd #\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nfrom skimage import io\nfrom sklearn.model_selection import train_test_split\nimport torch\nimport torch.nn as nn  \nimport torch.optim as optim \nimport torch.nn.functional as F \nimport torchvision\nimport torchvision.transforms as transforms \nfrom tqdm import tqdm\nfrom efficientnet_pytorch import EfficientNet\nfrom torch.utils.data import (\n    Dataset,\n    DataLoader,\n)\nimport plotly.express as px\nimport seaborn as sns\nimport time\nimport json\nimport os\nimport sys\nimport copy\nimport math\n%matplotlib inline\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#setting up device\ndevice = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\ndevice","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"base_dir = '../input/cassava-leaf-disease-classification'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Exploring dataset"},{"metadata":{"trusted":true},"cell_type":"code","source":"with open(os.path.join(base_dir,'label_num_to_disease_map.json')) as file:\n    map_classes = json.loads(file.read())\n    map_classes = {int(k): v for k, v in map_classes.items()}\n    \nprint(json.dumps(map_classes, indent=4))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_img_dir = os.path.join(base_dir, 'train_images' )\ntest_img_dir = os.path.join(base_dir, 'test_images' )\ntrain_img_dir, test_img_dir","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = pd.read_csv(os.path.join(base_dir,'train.csv'))\nprint(df_train.head())\nprint(df_train.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(8, 6))\nsns.countplot(x=\"label\", data=df_train, palette=\"Set3\").set_title(\"Images distribution in Dataset\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pie_df = df_train['label'].value_counts().reset_index()\npie_df.columns = ['label', 'count']\nfig = px.pie(pie_df, values = 'count', names = 'label', color_discrete_sequence = px.colors.qualitative.Pastel)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n<p> Here, we have unbalanced training data. This is one of the biggest probelm that we face when apllying machine learning. Three main ways of solving this problem are: <p/>\n<ul>\n    <li>Under sampling</li>\n    <li>Under sampling</li>\n    <li>Synthetic sampling(SMOTE)</li>\n\n</ul>\n</p>\n\n[more_details](https://towardsdatascience.com/deep-learning-unbalanced-training-data-solve-it-like-this-6c528e9efea6)"},{"metadata":{},"cell_type":"markdown","source":"## Visualize images :"},{"metadata":{"trusted":true},"cell_type":"code","source":"start_index = 25\nend_index = 37\nncols = 4\nnrows = math.ceil((end_index - start_index)/ncols)\nfig = plt.gcf()\nfig.set_size_inches(ncols*6, nrows*6)\nsample_imgs = [os.path.join(train_img_dir, fname) for fname in os.listdir(train_img_dir)[start_index:end_index] ]\n\nfor i, img_path in enumerate(sample_imgs) :\n    # Set up subplot; subplot indices start at 1\n    sp = plt.subplot(nrows, ncols, i + 1)\n    sp.axis('Off') # Don't show axes (or gridlines)\n\n    img = mpimg.imread(img_path)\n    plt.imshow(img)\n    label = df_train['label'].iloc[start_index+i]\n    plt.title(f\"Class: {map_classes[label]}\")\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Initialize hyperparameters"},{"metadata":{"trusted":true},"cell_type":"code","source":"input_size = 300\nnum_classes = 5\nlearning_rate = 0.001\ntrain_bs = 32\nvalid_bs = 32\nnum_epochs = 20\nnum_workers = 4","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Image Data Generator"},{"metadata":{"trusted":true},"cell_type":"code","source":"class CassavaDataset(Dataset):\n    def __init__(\n        self, df, data_root, transforms=None, output_label=True\n    ):\n        \n        super().__init__()\n        self.df = df.reset_index(drop=True).copy()\n        self.transforms = transforms\n        self.data_root = data_root\n        self.output_label = output_label\n    \n    def __len__(self):\n        return self.df.shape[0]\n    \n    def __getitem__(self, index: int):\n        \n        # get labels\n        if self.output_label:\n            target = self.df.iloc[index]['label']\n          \n        path = \"{}/{}\".format(self.data_root, self.df.iloc[index]['image_id'])\n        \n        img  = io.imread(path)\n        \n        if self.transforms:\n            img = self.transforms(img)\n            \n        # do label smoothing\n        if self.output_label == True:\n            return img, target\n        else:\n            return img","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Applying image data augumentation"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((input_size,input_size)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomPerspective(distortion_scale=0.5, p=0.5, interpolation=2, fill=0),\n    transforms.RandomRotation(degrees=45),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n])\ntest_transform =  transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((input_size,input_size)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomPerspective(distortion_scale=0.5, p=0.5, interpolation=2, fill=0),\n    transforms.RandomRotation(degrees=45),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load Data\ndataset = CassavaDataset(\n    df = df_train,\n    data_root = train_img_dir,\n    transforms = train_transforms,\n    output_label=True\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Splitting data into train set and validation set\n<p> Here 20% of training images is reserved for validation.  </p>"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_set, valid_set = torch.utils.data.random_split(dataset, [17118, 4279])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_loader = DataLoader(dataset=train_set, batch_size=train_bs, shuffle=True, num_workers=num_workers)\nvalid_loader = DataLoader(dataset=valid_set, batch_size=train_bs, shuffle=True, num_workers=num_workers)\ndataloaders_dict = {'train': train_loader, 'val': valid_loader}","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h1 style=\"text-align:center;\"> Visualizing augumented Images</h1>"},{"metadata":{},"cell_type":"markdown","source":"## Image before augumentation"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"train_set2, valid_set = torch.utils.data.random_split(dataset, [1, 21396])\ntrain_loader2 = DataLoader(dataset=train_set, batch_size=4, shuffle=True, num_workers=num_workers)\ncurrent_img = 0\nfor i in train_set2:\n    current_img = i[1]\n    img = mpimg.imread(os.path.join(train_img_dir, df_train['image_id'].iloc[current_img] ))\n    plt.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Images after applying augumentation"},{"metadata":{"trusted":true,"_kg_hide-output":false,"_kg_hide-input":true},"cell_type":"code","source":"for i in range(8):\n    fig = plt.gcf()\n    fig.set_size_inches(ncols*5, nrows*6)\n    for batch_idx, (inputs, labels) in enumerate(train_set2):\n        inputs = inputs.permute(1, 2, 0).numpy()\n        sp = plt.subplot(4, 4, batch_idx+ 1+i)\n\n        plt.imshow(inputs)\n        label = df_train['label'].iloc[start_index+i]\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Defining Model "},{"metadata":{"trusted":true},"cell_type":"code","source":"class CassavaClassifier(nn.Module):\n    def __init__(self, classes_to_predict=5):\n        super(CassavaClassifier, self).__init__()\n        self.model = EfficientNet.from_pretrained('efficientnet-b3')\n        self.classifier_layer = nn.Sequential(\n            nn.Linear(1536 , 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Linear(512 , 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Linear(256 , classes_to_predict)\n        )\n        # forward function of Efficient-Net model \n    def forward(self, inputs):\n        x = self.model.extract_features(inputs)\n        x = self.model._avg_pooling(x)\n        x = x.flatten(start_dim=1)\n        x = self.model._dropout(x)\n        x = self.classifier_layer(x)\n        return x\n    \nmodel = CassavaClassifier().to(device)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Defining loss function "},{"metadata":{"trusted":true},"cell_type":"code","source":"def train_model(model, dataloaders, criterion, optimizer, num_epochs=25):\n    since = time.time()\n    history = {\n        'train_acc':[],\n        'train_loss':[],\n        'val_acc':[],\n        'val_loss': []\n    }\n    val_acc_history = []\n    num_samples = 0\n    best_model_wts = copy.deepcopy(model.state_dict())\n    best_acc = 0.0\n\n    for epoch in range(num_epochs):\n        # Each epoch has a training and validation phase\n        for phase in ['train', 'val']:\n            if phase == 'train':\n                model.train()  # Set model to training mode\n            else:\n                model.eval()   # Set model to evaluate mode\n\n            running_loss = 0.0\n            running_corrects = 0\n\n            # Iterate over data.\n            loop = tqdm(enumerate(dataloaders[phase]), total=len(dataloaders[phase]))\n    \n            for batch_idx, (inputs, labels) in loop:\n                inputs = inputs.to(device)\n                labels = labels.to(device)\n\n                # zero the parameter gradients\n                optimizer.zero_grad()\n\n                # forward\n                # track history if only in train\n                with torch.set_grad_enabled(phase == 'train'):\n                    outputs = model(inputs)\n                    loss = criterion(outputs, labels)\n                    _, preds = torch.max(outputs, 1)\n                    \n                    # backward + optimize only if in training phase\n                    if phase == 'train':\n                        loss.backward()\n                        optimizer.step()\n\n                # statistics\n                running_loss += loss.item() * inputs.size(0)\n                running_corrects += torch.sum(preds == labels.data)\n                num_samples += preds.size(0)\n                loop.set_description(f\"Epoch [{epoch}/{num_epochs-1}]\")\n                loop.set_postfix({\n                    \"phase\":phase,\n                    \"loss\" :\"{:.4f} \".format(loss.item())\n                })\n\n            epoch_loss = running_loss / len(dataloaders[phase].dataset)\n            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)\n            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))\n            \n            \n            \n            # deep copy the model\n            if phase == 'val' and epoch_acc > best_acc:\n                best_acc = epoch_acc\n                best_model_wts = copy.deepcopy(model.state_dict())\n            if phase == 'val':\n                val_acc_history.append(epoch_acc)\n                history['val_acc'].append(epoch_acc)\n                history['val_loss'].append(epoch_loss)\n                \n            elif phase == 'train':\n                history['train_acc'].append(epoch_acc)\n                history['train_loss'].append(epoch_loss)\n\n    time_elapsed = time.time() - since\n    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))\n    print('Best val Acc: {:4f}'.format(best_acc))\n\n    # load best model weights\n    model.load_state_dict(best_model_wts)\n    return model, history","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Initializing loss function and optimizer "},{"metadata":{"trusted":true},"cell_type":"code","source":"# Loss and optimizer\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=learning_rate)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Training the model "},{"metadata":{"trusted":true},"cell_type":"code","source":"# Train and evaluate\nmodel_ft, history = train_model(model, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Visualizing model performance"},{"metadata":{"trusted":true},"cell_type":"code","source":"def performance_plot(hist, epochs=20):\n    acc = hist['train_acc']\n    val_acc = hist['val_acc']\n    loss = hist['train_loss']\n    val_loss = hist['val_loss']\n    x_range = range(epochs)\n    \n    plt.figure(figsize=(8,6))\n    plt.plot(x_range, acc, 'b-', label='Training accuracy')\n    plt.plot(x_range, val_acc, 'r-', label='Validation accuracy')\n    plt.title('Training and validation accuracy')\n    plt.ylim(0, 1)\n    plt.legend()\n\n    plt.figure(figsize=(8,6))\n\n    plt.plot(x_range, loss, 'b-', label='Training Loss')\n    plt.plot(x_range, val_loss, 'r-', label='Validation Loss')\n    plt.title('Training and validation loss')\n    plt.ylim(0, 1)\n    plt.legend()\n\n    plt.show()\n\nperformance_plot(history, epochs=num_epochs)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Our model has lots of room for improvement. Things that you can do to improve the performance are:\n<ul>\n    <li>Hyperparameters tuining</li>\n    <li>Cross-validation</li>\n    <li>Try different Data augumentation technique</li>\n    <li>Handel unbalanced training dataset</li>\n    <li>Ensampling</li>\n    <li>Try different architectures</li>\n</ul>"},{"metadata":{},"cell_type":"markdown","source":"**Work in Progress!**"}],"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":4,"nbformat_minor":4}