{"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":"markdown","source":"# Leaf Disease Classification using Pytorch with ResNet34 Pretrained Model\n\nBinary Classification","metadata":{}},{"cell_type":"markdown","source":"### Importing the packages","metadata":{}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nfrom torch.nn import functional as F\n\nfrom sklearn import metrics, model_selection, preprocessing","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Loading","metadata":{}},{"cell_type":"code","source":"dfx = pd.read_csv('../input/cassava-leaf-disease-classification/train.csv')\n\ndf_train, df_valid = model_selection.train_test_split(\n        dfx, test_size=0.1, random_state=42, stratify=dfx.label.values\n)\n\ndf_train = df_train.reset_index(drop=True)\ndf_valid = df_valid.reset_index(drop=True)\n\nimage_path = \"../input/cassava-leaf-disease-classification/train_images/\"\ntrain_image_paths = [os.path.join(image_path, x) for x in df_train.image_id.values]\nvalid_image_paths = [os.path.join(image_path, x) for x in df_valid.image_id.values]\ntrain_targets = df_train.label.values\nvalid_targets = df_valid.label.values","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Shape of train set","metadata":{}},{"cell_type":"code","source":"len(train_image_paths),len(train_targets)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Shape of test set","metadata":{}},{"cell_type":"code","source":"len(valid_image_paths),len(valid_targets)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Building Dataset Class","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import Dataset\n\nclass CustomDataset(Dataset):\n    def __init__(self, data, targets, transform):\n        super().__init__()\n        self.files = data\n        self.targets = targets\n        self.classes = list(set(targets))\n        self.transform = transform\n    \n    def __len__(self):\n        return len(self.files)\n\n    def __getitem__(self, i):\n        name = self.files[i]\n        fpath = os.path.join(name)\n        img = self.transform(open_image(fpath))\n        class_idx = self.targets[i]\n        return img, class_idx","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Augmentation step","metadata":{}},{"cell_type":"code","source":"import torchvision.transforms as T\n\nimg_size = 224\nimagenet_stats = ([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\ntrain_dataset = CustomDataset(train_image_paths,train_targets, T.Compose([T.Resize(img_size), \n                                           T.Pad(8, padding_mode='reflect'),\n                                           T.RandomCrop(img_size), \n                                           T.ToTensor(), \n                                           T.Normalize(*imagenet_stats)]))\n\nvalid_dataset = CustomDataset(valid_image_paths,valid_targets, T.Compose([T.Resize(img_size), \n                                           T.Pad(8, padding_mode='reflect'),\n                                           T.RandomCrop(img_size), \n                                           T.ToTensor(), \n                                           T.Normalize(*imagenet_stats)]))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Plotting util functions","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n%matplotlib inline\n\nfrom PIL import Image\n\ndef open_image(path):\n    with open(path, 'rb') as f:\n        img = Image.open(f)\n        return img.convert('RGB')\n    \ndef denormalize(images, means, stds):\n    if len(images.shape) == 3:\n        images = images.unsqueeze(0)\n    means = torch.tensor(means).reshape(1, 3, 1, 1)\n    stds = torch.tensor(stds).reshape(1, 3, 1, 1)\n    return images * stds + means\n\ndef show_image(img_tensor, label):\n    print('Label:', train_dataset.classes[label], '(' + str(label) + ')')\n    img_tensor = denormalize(img_tensor, *imagenet_stats)[0].permute((1, 2, 0))\n    plt.imshow(img_tensor)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train set images","metadata":{}},{"cell_type":"code","source":"show_image(*train_dataset[0])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Validation images","metadata":{}},{"cell_type":"code","source":"# valid_image_paths[2139], valid_targets[2139]\nshow_image(*valid_dataset[2139])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Building DataLoader","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader\nbatch_size = 256\n\ntrain_dl = DataLoader(train_dataset, batch_size, shuffle=True, num_workers=4, pin_memory=True)\nvalid_dl = DataLoader(valid_dataset, batch_size*2, num_workers=4, pin_memory=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Displaying Batch data","metadata":{}},{"cell_type":"code","source":"from torchvision.utils import make_grid\n\ndef show_batch(dl):\n    for images, labels in dl:\n        fig, ax = plt.subplots(figsize=(16, 16))\n        ax.set_xticks([]); ax.set_yticks([])\n        images = denormalize(images[:64], *imagenet_stats)\n        ax.imshow(make_grid(images, nrow=8).permute(1, 2, 0))\n        break","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_batch(train_dl) #takes 2 min","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Defining Model and its Metrics","metadata":{}},{"cell_type":"code","source":"def accuracy(outputs, labels):\n    _, preds = torch.max(outputs, dim=1)\n    return torch.tensor(torch.sum(preds == labels).item() / len(preds))\n\n\nclass ImageClassificationBase(nn.Module):\n    def training_step(self, batch):\n        images, labels = batch\n        out = self(images)                  # Generate predictions\n        loss = F.cross_entropy(out, labels)  # Calculate loss\n        return loss\n\n    def validation_step(self, batch):\n        images, labels = batch\n        out = self(images)                    # Generate predictions\n        loss = F.cross_entropy(out, labels)   # Calculate loss\n        acc = accuracy(out, labels)           # Calculate accuracy\n        return {'val_loss': loss.detach(), 'val_acc': acc}\n\n    def validation_epoch_end(self, outputs):\n        batch_losses = [x['val_loss'] for x in outputs]\n        epoch_loss = torch.stack(batch_losses).mean()   # Combine losses\n        batch_accs = [x['val_acc'] for x in outputs]\n        epoch_acc = torch.stack(batch_accs).mean()      # Combine accuracies\n        return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}\n\n    def epoch_end(self, epoch, result):\n        print(\"Epoch [{}],{} train_loss: {:.4f}, val_loss: {:.4f}, val_acc: {:.4f}\".format(\n            epoch, \"last_lr: {:.5f},\".format(result['lrs'][-1]) if 'lrs' in result else '', \n            result['train_loss'], result['val_loss'], result['val_acc']))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Building Model","metadata":{}},{"cell_type":"code","source":"from torchvision import models\n\nclass LeafModel(ImageClassificationBase):\n    def __init__(self, num_classes, pretrained=True):\n        super().__init__()\n        # Use a pretrained model\n        self.network = models.resnet34(pretrained=pretrained)\n        # Replace last layer\n        self.network.fc = nn.Linear(self.network.fc.in_features, num_classes)\n\n    def forward(self, xb):\n        return self.network(xb)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Clearing gpu cached memory","metadata":{}},{"cell_type":"code","source":"torch.cuda.empty_cache()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Defining GPU Functions","metadata":{}},{"cell_type":"code","source":"def get_default_device():\n    \"\"\"Pick GPU if available, else CPU\"\"\"\n    if torch.cuda.is_available():\n        return torch.device('cuda')\n    else:\n        return torch.device('cpu')\n\n\ndef to_device(data, device):\n    \"\"\"Move tensor(s) to chosen device\"\"\"\n    if isinstance(data, (list, tuple)):\n        return [to_device(x, device) for x in data]\n    return data.to(device, non_blocking=True)\n\n\nclass DeviceDataLoader():\n    \"\"\"Wrap a dataloader to move data to a device\"\"\"\n\n    def __init__(self, dl, device):\n        self.dl = dl\n        self.device = device\n\n    def __iter__(self):\n        \"\"\"Yield a batch of data after moving it to device\"\"\"\n        for b in self.dl:\n            yield to_device(b, self.device)\n\n    def __len__(self):\n        \"\"\"Number of batches\"\"\"\n        return len(self.dl)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Model Fitting","metadata":{}},{"cell_type":"code","source":"from tqdm.notebook import tqdm\n\n@torch.no_grad()\ndef evaluate(model, val_loader):\n    model.eval()\n    outputs = [model.validation_step(batch) for batch in val_loader]\n    return model.validation_epoch_end(outputs)\n\n\ndef fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD):\n    history = []\n    optimizer = opt_func(model.parameters(), lr)\n    for epoch in range(epochs):\n        # Training Phase\n        model.train()\n        train_losses = []\n        for batch in tqdm(train_loader):\n            loss = model.training_step(batch)\n            train_losses.append(loss)\n            loss.backward()\n            optimizer.step()\n            optimizer.zero_grad()\n        # Validation phase\n        result = evaluate(model, val_loader)\n        result['train_loss'] = torch.stack(train_losses).mean().item()\n        model.epoch_end(epoch, result)\n        history.append(result)\n    return history\n\ndef get_lr(optimizer):\n    for param_group in optimizer.param_groups:\n        return param_group['lr']\n\ndef fit_one_cycle(epochs, max_lr, model, train_loader, val_loader,\n                  weight_decay=0, grad_clip=None, opt_func=torch.optim.SGD):\n    torch.cuda.empty_cache()\n    history = []\n\n    # Set up custom optimizer with weight decay\n    optimizer = opt_func(model.parameters(), max_lr, weight_decay=weight_decay)\n    # Set up one-cycle learning rate scheduler\n    sched = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr, epochs=epochs,\n                                                steps_per_epoch=len(train_loader))\n\n    for epoch in range(epochs):\n        # Training Phase\n        model.train()\n        train_losses = []\n        lrs = []\n        for batch in tqdm(train_loader):\n            loss = model.training_step(batch)\n            train_losses.append(loss)\n            loss.backward()\n\n            # Gradient clipping\n            if grad_clip:\n                nn.utils.clip_grad_value_(model.parameters(), grad_clip)\n\n            optimizer.step()\n            optimizer.zero_grad()\n\n            # Record & update learning rate\n            lrs.append(get_lr(optimizer))\n            sched.step()\n\n        # Validation phase\n        result = evaluate(model, val_loader)\n        result['train_loss'] = torch.stack(train_losses).mean().item()\n        result['lrs'] = lrs\n        model.epoch_end(epoch, result)\n        history.append(result)\n    return history","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = get_default_device()\ndevice","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Putting data into GPU","metadata":{}},{"cell_type":"code","source":"train_dl = DeviceDataLoader(train_dl, device)\nvalid_dl = DeviceDataLoader(valid_dl, device)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Downloading Pretrained Resnet34 model","metadata":{}},{"cell_type":"code","source":"from os import listdir, makedirs, getcwd, remove\nfrom os.path import isfile, join, abspath, exists, isdir, expanduser\n\ncache_dir = expanduser(join('~', '.torch'))\nif not exists(cache_dir):\n    makedirs(cache_dir)\n    \ncache_dir = expanduser(join('~', '.cache/torch/hub/checkpoints/'))\nif not exists(cache_dir):\n    makedirs(cache_dir)\n    \nmodels_dir = join(cache_dir, 'models')\nif not exists(models_dir):\n    makedirs(models_dir)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!cp -f ../input/resnet34/resnet34.pth /root/.cache/torch/hub/checkpoints/resnet34-333f7ec4.pth","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Model to GPU","metadata":{}},{"cell_type":"code","source":"model = LeafModel(len(train_dataset.classes))\nto_device(model, device);","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Evaluate base model","metadata":{}},{"cell_type":"code","source":"history = [evaluate(model, valid_dl)]\n# history","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Defining training parameters","metadata":{}},{"cell_type":"code","source":"epochs = 6\nmax_lr = 0.01\ngrad_clip = 0.1\nweight_decay = 1e-4\nopt_func = torch.optim.Adam","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### TRaining","metadata":{}},{"cell_type":"code","source":"%%time\nhistory += fit_one_cycle(epochs, max_lr, model, train_dl, valid_dl, \n                         grad_clip=grad_clip, \n                         weight_decay=weight_decay, \n                         opt_func=opt_func)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# history","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Predict","metadata":{}},{"cell_type":"code","source":"def predict_image(img, model):\n    # Convert to a batch of 1\n    xb = to_device(img.unsqueeze(0), device)\n    # Get predictions from model\n    yb = model(xb)\n    # Pick index with highest probability\n    _, preds  = torch.max(yb, dim=1)\n    # Retrieve the class label\n    return int(preds)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_tensor, label = valid_dataset[2]\nshow_image(*(img_tensor,label))\nprint('Label:', label, ', Predicted:', predict_image(img_tensor, model))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_tensor, label = valid_dataset[5]\nshow_image(*(img_tensor,label))\nprint('Label:', label, ', Predicted:', predict_image(img_tensor, model))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_tensor, label = valid_dataset[8]\nshow_image(*(img_tensor,label))\nprint('Label:', label, ', Predicted:', predict_image(img_tensor, model))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Submission Ready","metadata":{}},{"cell_type":"code","source":"test_dfx = pd.read_csv('../input/cassava-leaf-disease-classification/sample_submission.csv')\nimage_path = \"../input/cassava-leaf-disease-classification/test_images\"\n\n\ntest_image_paths = [os.path.join(image_path, x) for x in test_dfx.image_id.values]\ntest_targets = test_dfx.label.values\n\n\ntest_dataset = CustomDataset(test_image_paths,test_targets, T.Compose([T.Resize(img_size), \n                                           T.Pad(8, padding_mode='reflect'),\n                                           T.RandomCrop(img_size), \n                                           T.ToTensor(), \n                                           T.Normalize(*imagenet_stats)]))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(test_image_paths)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"final_preds = []\nfor i in test_dataset:\n    img_tensor, label = i\n    pred = predict_image(img_tensor, model)\n    final_preds.append(pred)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_tensor, label = test_dataset[0]\nshow_image(*(img_tensor,label))\npred = predict_image(img_tensor, model)\nprint('Label:', label, ', Predicted:', pred)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_dfx.label = final_preds\ntest_dfx.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Please upvote if you like the work. :)","metadata":{}}]}