{"cells":[{"metadata":{},"cell_type":"markdown","source":"## General description\n\nIn this competition we work on developing solutions for identifying common diseases of cassava (plant). There are 4 different diseases and, of course, a plant could be healhy. As a resutl we have a classification problem with 5 classes.\n\nIn this notebook I'll explore the data, train a model using pytorch lightning and analyse the predictions.\n\n![](http://www.naro.go.ug/files/images/crops-naro.jpg)"},{"metadata":{},"cell_type":"markdown","source":"#### import libraries"},{"metadata":{"trusted":true},"cell_type":"code","source":"package_paths = [\n                 '../input/efficientnet-pytorrch-offline',\n                 '../input/mlflow/mlflow-master'\n                ]\nimport sys\nfor package_path in package_paths:\n    sys.path.append(package_path)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#!pip install mlflow\n#!pip install efficientnet_pytorch\n#!pip install --quiet /kaggle/input/EfficientNet-PyTorch/EfficientNet-PyTorch-master\n#!pip install --quiet /kaggle/input/mlflow/mlflow-master","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"from PIL import Image\nfrom albumentations.core.composition import Compose\nfrom albumentations.pytorch import ToTensorV2\nfrom collections import defaultdict, deque\nfrom efficientnet_pytorch import EfficientNet\nfrom pytorch_lightning import Callback\nfrom pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping\nfrom sklearn import metrics\nfrom sklearn.model_selection import StratifiedKFold\nfrom torch import nn\nfrom torch.autograd import Variable\nfrom torch.utils.data import Dataset, TensorDataset, DataLoader\nfrom torchvision.transforms import functional as F\nfrom typing import Any, Dict, List, Union, Optional\nimport albumentations as A\nimport ast\nimport collections\nimport copy\nimport cv2\nimport datetime\nimport importlib\nimport json\nimport math\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport os\nimport pandas as pd\nimport pickle\n# import pretrainedmodels\nimport pytorch_lightning as pl\n%matplotlib inline\nimport random\nimport seaborn as sns\nimport shutil\nimport tempfile\nimport time\nimport torch\nimport torch.distributed as dist\nimport torch.nn.functional as F\nimport torch.utils.data\nimport torchvision\nfrom tqdm.notebook import tqdm\nsns.set_style('darkgrid')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#from pytorch_lightning.loggers.mlflow import MLFlowLogger\n#import mlflow\n#mlflow.__version__","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### helper functions"},{"metadata":{"trusted":true},"cell_type":"code","source":"def visualize(images, transform):\n    \"\"\"\n    Plot images and their transformations\n    \"\"\"\n    fig = plt.figure(figsize=(32, 16))\n    \n    for i, im in enumerate(images):\n        ax = fig.add_subplot(2, 5, i + 1, xticks=[], yticks=[])\n        plt.imshow(im)\n        \n    for i, im in enumerate(images):\n        ax = fig.add_subplot(2, 5, i + 6, xticks=[], yticks=[])\n        plt.imshow(transform(image=im)['image'])\n\ndef set_seed(seed: int = 42) -> None:\n    np.random.seed(seed)\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    torch.backends.cudnn.benchmark = False\n    torch.backends.cudnn.deterministic = True\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\nset_seed()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Data Exploration"},{"metadata":{"trusted":true},"cell_type":"code","source":"path = \"../input/cassava-leaf-disease-classification/\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv(f'{path}train.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"with open(f'{path}/label_num_to_disease_map.json', 'r') as f:\n    name_mapping = json.load(f)\n    \nname_mapping = {int(k): v for k, v in name_mapping.items()}\nname_mapping","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As per description, there are 4 diseases and one class for healthy plants."},{"metadata":{"trusted":true},"cell_type":"code","source":"train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.countplot(y=train['label'].map(name_mapping), orient='v')\nplt.title('Target distribution');","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Interestingly the most common class belongs to one of diseases and not to healthy plants."},{"metadata":{},"cell_type":"markdown","source":"### Let's have a look at cassava\n\nAt first, lets have a look at images belonging to different classes"},{"metadata":{"trusted":true},"cell_type":"code","source":"selected_images = []\nfig = plt.figure(figsize=(16, 16))\nfor class_id, class_name in name_mapping.items():\n    for i, (idx, row) in enumerate(train.loc[train['label'] == class_id].sample(4).iterrows()):\n        ax = fig.add_subplot(5, 4, class_id * 4 + i + 1, xticks=[], yticks=[])\n        img = cv2.imread(f\"{path}train_images/{row['image_id']}\")\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        plt.imshow(img)\n        ax.set_title(f\"Image: {row['image_id']}. Label: {row['label']}\")\n        if i == 0:\n            selected_images.append(img)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As far as I can see, one of the common symptoms of desease is a change in color - usually yellow color with different patterns. We will need to be careful with augmentations.\n\nBy the way, let's see how different augmentation change the images.\n\nIn the first ror where are original images, in the second row there are augmented images. I selected one random image from each class."},{"metadata":{"trusted":true},"cell_type":"code","source":"#visualize(selected_images, A.HorizontalFlip(p=1))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#visualize(selected_images, A.ShiftScaleRotate(p=1))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#visualize(selected_images, A.Cutout(max_h_size=64, max_w_size=64, p=1))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Bi-Tempered Logistic Loss function"},{"metadata":{"trusted":true},"cell_type":"code","source":"def log_t(u, t):\n    \"\"\"Compute log_t for `u`.\"\"\"\n\n    if t == 1.0:\n        return torch.log(u)\n    else:\n        return (u ** (1.0 - t) - 1.0) / (1.0 - t)\n\n\ndef exp_t(u, t):\n    \"\"\"Compute exp_t for `u`.\"\"\"\n\n    if t == 1.0:\n        return torch.exp(u)\n    else:\n        return torch.relu(1.0 + (1.0 - t) * u) ** (1.0 / (1.0 - t))\n\n\ndef compute_normalization_fixed_point(activations, t, num_iters=5):\n    \"\"\"Returns the normalization value for each example (t > 1.0).\n    Args:\n    activations: A multi-dimensional tensor with last dimension `num_classes`.\n    t: Temperature 2 (> 1.0 for tail heaviness).\n    num_iters: Number of iterations to run the method.\n    Return: A tensor of same rank as activation with the last dimension being 1.\n    \"\"\"\n\n    mu = torch.max(activations, dim=-1).values.view(-1, 1)\n    normalized_activations_step_0 = activations - mu\n\n    normalized_activations = normalized_activations_step_0\n    i = 0\n    while i < num_iters:\n        i += 1\n        logt_partition = torch.sum(exp_t(normalized_activations, t), dim=-1).view(-1, 1)\n        normalized_activations = normalized_activations_step_0 * (logt_partition ** (1.0 - t))\n\n    logt_partition = torch.sum(exp_t(normalized_activations, t), dim=-1).view(-1, 1)\n\n    return -log_t(1.0 / logt_partition, t) + mu\n\n\ndef compute_normalization(activations, t, num_iters=5):\n    \"\"\"Returns the normalization value for each example.\n    Args:\n    activations: A multi-dimensional tensor with last dimension `num_classes`.\n    t: Temperature 2 (< 1.0 for finite support, > 1.0 for tail heaviness).\n    num_iters: Number of iterations to run the method.\n    Return: A tensor of same rank as activation with the last dimension being 1.\n    \"\"\"\n\n    if t < 1.0:\n        return None # not implemented as these values do not occur in the authors experiments...\n    else:\n        return compute_normalization_fixed_point(activations, t, num_iters)\n\n\ndef tempered_softmax(activations, t, num_iters=5):\n    \"\"\"Tempered softmax function.\n    Args:\n    activations: A multi-dimensional tensor with last dimension `num_classes`.\n    t: Temperature tensor > 0.0.\n    num_iters: Number of iterations to run the method.\n    Returns:\n    A probabilities tensor.\n    \"\"\"\n\n    if t == 1.0:\n        normalization_constants = torch.log(torch.sum(torch.exp(activations), dim=-1))\n        activations = activations.transpose(0, 1)\n    else:\n        normalization_constants = compute_normalization(activations, t, num_iters)\n        normalization_constants = normalization_constants.transpose(0, 1)\n        activations = activations.transpose(0, 1)\n\n\n    return exp_t(activations - normalization_constants, t)\n\n\ndef bi_tempered_logistic_loss(activations, labels, t1, t2, label_smoothing=0.0, num_iters=5):\n\n    \"\"\"Bi-Tempered Logistic Loss with custom gradient.\n    Args:\n    activations: A multi-dimensional tensor with last dimension `num_classes`.\n    labels: A tensor with shape and dtype as activations.\n    t1: Temperature 1 (< 1.0 for boundedness).\n    t2: Temperature 2 (> 1.0 for tail heaviness, < 1.0 for finite support).\n    label_smoothing: Label smoothing parameter between [0, 1).\n    num_iters: Number of iterations to run the method.\n    Returns:\n    A loss tensor.\n    \"\"\"\n\n    if label_smoothing > 0.0:\n        num_classes = labels.shape[-1]\n        labels = (1 - num_classes / (num_classes - 1) * label_smoothing) * labels + label_smoothing / (num_classes - 1)\n\n    probabilities = tempered_softmax(activations, t2, num_iters)\n    probabilities = probabilities.transpose(0, 1)\n\n    temp1 = (log_t(labels + 1e-10, t1) - log_t(probabilities, t1)) * labels\n    temp2 = (1 / (2 - t1)) * (torch.pow(labels, 2 - t1) - torch.pow(probabilities, 2 - t1))\n    loss_values = temp1 - temp2\n\n    return torch.sum(loss_values, dim=-1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Preparing classes for pytorch-lightning\n\nTraining neural nets in pytorch-lightning requires writing several classes. Some of them are pure Pytorch classes, some are from pl."},{"metadata":{},"cell_type":"markdown","source":"### Dataset class"},{"metadata":{"trusted":true},"cell_type":"code","source":"class ImageClassificationDataset(Dataset):\n    def __init__(\n        self,\n        image_names: List,\n        transforms: Compose,\n        labels: Optional[List[int]],\n        img_path: str = '',\n        mode: str = 'train',\n        labels_to_ohe: bool = False,\n        n_classes: int = 5,\n    ):\n        \"\"\"\n        Image classification dataset.\n\n        Args:\n            df: dataframe with image id and bboxes\n            mode: train/val/test\n            img_path: path to images\n            transforms: albumentations\n        \"\"\"\n\n        self.mode = mode\n        self.transforms = transforms\n        self.img_path = img_path\n        self.image_names = image_names\n        if labels is not None:\n            if not labels_to_ohe:\n                self.labels = np.array(labels)\n            else:\n                self.labels = np.zeros((len(labels), n_classes))\n                self.labels[np.arange(len(labels)), np.array(labels)] = 1\n\n    def __getitem__(self, idx: int) -> Dict[str, np.array]:\n        image_path = self.img_path + self.image_names[idx]\n        image = cv2.imread(f'{image_path}', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        if image is None:\n            raise FileNotFoundError(image_path)\n        target = self.labels[idx]\n\n        img = self.transforms(image=image)['image']\n        sample = {'image_path': image_path, 'image': img, 'target': np.array(target).astype('int64')}\n\n        return sample\n\n    def __len__(self) -> int:\n        return len(self.image_names)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Augmentations\n\nFor now I chose some augmentations at random."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_augs = A.Compose([\n        A.RandomResizedCrop(512, 512),\n        A.Transpose(p=0.5),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.ShiftScaleRotate(p=0.5),\n        A.HueSaturationValue(hue_shift_limit=0.2, sat_shift_limit=0.2, val_shift_limit=0.2, p=0.5),\n        A.RandomBrightnessContrast(brightness_limit=(-0.1,0.1), contrast_limit=(-0.1, 0.1), p=0.5),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),\n        A.CoarseDropout(p=0.5),\n        A.Cutout(p=0.5),\n        ToTensorV2(p=1.0),\n    ], p=1.)\nvalid_augs = A.Compose([\n        A.CenterCrop(512, 512, p=1.),\n        A.Resize(512, 512),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),\n        ToTensorV2(p=1.0),\n    ], p=1.)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### PL datamodule\n\nThis class prepares data. Here we initialize data classes and write code for dataloaders. Notice that in `setup` I split data into train and valid."},{"metadata":{"trusted":true},"cell_type":"code","source":"class CassavaDataModule(pl.LightningDataModule):\n    def __init__(self,\n                 df,\n                 train_augs,\n                 valid_augs,\n                 path):\n        super().__init__()\n        self.df = df\n        self.train_augs = train_augs\n        self.valid_augs = valid_augs\n        self.path = path\n\n    def prepare_data(self):\n        pass\n\n    def setup(self, stage=None):\n        \n        folds = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n        \n        train_indexes, valid_indexes = list(folds.split(self.df, self.df['label']))[0]\n        \n        train_df = self.df.iloc[train_indexes]\n        valid_df = self.df.iloc[valid_indexes]\n\n        \n        self.train_dataset = ImageClassificationDataset(image_names=train_df['image_id'].values,\n                                                        transforms=train_augs,\n                                                        labels=train_df['label'].values,\n                                                        img_path=self.path,\n                                                        mode='train',\n                                                        labels_to_ohe=False,\n                                                        n_classes=5)\n        self.valid_dataset = ImageClassificationDataset(image_names=valid_df['image_id'].values,\n                                                        transforms=valid_augs,\n                                                        labels=valid_df['label'].values,\n                                                        img_path=self.path,\n                                                        mode='valid',\n                                                        labels_to_ohe=False,\n                                                        n_classes=5)\n\n    def train_dataloader(self):\n        train_loader = torch.utils.data.DataLoader(\n            self.train_dataset,\n            batch_size=16,\n            num_workers=4,\n            shuffle=True,\n        )\n        return train_loader\n\n    def val_dataloader(self):\n        valid_loader = torch.utils.data.DataLoader(\n            self.valid_dataset,\n            batch_size=32,\n            num_workers=4,\n            shuffle=False,\n        )\n\n        return valid_loader\n\n    def test_dataloader(self):\n        return None","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Defining the model."},{"metadata":{"trusted":true},"cell_type":"code","source":"class CassvaImgClassifier(nn.Module):\n    def __init__(self, model_arch, n_class):\n        super().__init__()\n        self.model = EfficientNet.from_pretrained(model_arch)\n        n_features = self.model._fc.in_features\n        self.model._fc = nn.Linear(n_features, n_class)\n    \n    def forward(self, x, targets):\n        logits = self.model(x)\n        batch_size = targets.size()[0]\n        targets = nn.functional.one_hot(targets, num_classes=5)\n        loss = bi_tempered_logistic_loss(activations=logits, labels=targets, t1=0.4, t2=2.0)\n        loss = loss.sum() / batch_size\n        return logits, loss","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Main pl training class\n\nIn this class we define optimizers, schedulers and training itself."},{"metadata":{"trusted":true},"cell_type":"code","source":"class LitCassava(pl.LightningModule):\n    def __init__(self, model):\n        super(LitCassava, self).__init__()\n        self.model = model\n        self.metric = pl.metrics.Accuracy()\n        self.learning_rate = 1e-4\n\n    def forward(self, x, targets, *args, **kwargs):\n        return self.model(x, targets)\n\n    def configure_optimizers(self):\n        optimizer = torch.optim.AdamW(self.model.parameters(), lr=self.learning_rate, weight_decay=0.001)\n        scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.1, patience=2)\n\n        return (\n            [optimizer],\n            [{'scheduler': scheduler, 'interval': 'epoch', 'monitor': 'valid_loss'}],\n        )\n\n    def training_step(\n        self, batch: torch.Tensor, batch_idx: int\n    ) -> Union[int, Dict[str, Union[torch.Tensor, Dict[str, torch.Tensor]]]]:\n        image = batch['image']\n        target = batch['target']\n        logits, loss = self(image, target)\n        score = self.metric(logits.argmax(1), target)\n        self.log('train_loss', loss, on_step=False, on_epoch=True, logger=True)\n        logs = {'train_loss': loss, f'train_accuracy': score}\n        return {\n            'loss': loss,\n            'log': logs,\n            'progress_bar': logs,\n            'logits': logits,\n            'target': target,\n            f'train_accuracy': score,\n        }\n\n    def training_epoch_end(self, outputs):\n        avg_loss = torch.stack([x['loss'] for x in outputs]).mean()\n        y_true = torch.cat([x['target'] for x in outputs])\n        y_pred = torch.cat([x['logits'] for x in outputs])\n        score = self.metric(y_pred.argmax(1), y_true)\n        self.log('train_score', score, logger=True)\n        logs = {'train_loss': avg_loss, 'train_accuracy': score}\n        return {'log': logs, 'progress_bar': logs}\n\n    def validation_step(\n        self, batch: torch.Tensor, batch_idx: int\n    ) -> Union[int, Dict[str, Union[torch.Tensor, Dict[str, torch.Tensor]]]]:\n        image = batch['image']\n        target = batch['target']\n        logits, loss = self(image, target)\n        score = self.metric(logits.argmax(1), target)\n        self.log('val_loss', loss, on_step=False, on_epoch=True, logger=True)\n        logs = {'valid_loss': loss, f'valid_accuracy': score}\n\n        return {\n            'loss': loss,\n            'log': logs,\n            'progress_bar': logs,\n            'logits': logits,\n            'target': target,\n            f'valid_accuracy': score,\n        }\n\n    def validation_epoch_end(self, outputs):\n        avg_loss = torch.stack([x['loss'] for x in outputs]).mean()\n        y_true = torch.cat([x['target'] for x in outputs])\n        y_pred = torch.cat([x['logits'] for x in outputs])\n        score = self.metric(y_pred.argmax(1), y_true)\n        self.log('val_score', score, logger=True)\n        logs = {'valid_loss': avg_loss, f'valid_accuracy': score, 'accuracy': score}\n        return {'valid_loss': avg_loss, 'log': logs, 'progress_bar': logs}","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Training the model"},{"metadata":{"trusted":true},"cell_type":"code","source":"#mlflow_logger = MLFlowLogger(experiment_name='cassava-hatanor',\n#                             tracking_uri=\"http://54.238.161.51:5000\",\n#                             tags={\"machine\":\"kaggle-notebook\",\n#                                   \"argu\":\"v2\",\n#                                   \"pretrain\":\"yes\",\n#                                   \"arch\":\"efficientnet-b3\"\n#                                 })\n#tracking_uri = mlflow.get_tracking_uri()\n#print(\"Current tracking uri: {}\".format(tracking_uri))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = CassvaImgClassifier(model_arch=\"efficientnet-b3\", n_class=5)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dm = CassavaDataModule(train, train_augs, valid_augs, f'{path}train_images/')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"trainer = pl.Trainer(\n        checkpoint_callback=ModelCheckpoint(monitor='val_loss',\n                                            save_top_k=1, filepath='{epoch}_{val_loss:.4f}_{val_score:.4f}', mode='min'),\n        gpus=1,\n        max_epochs=50,\n        num_sanity_val_steps=0,\n        weights_summary='top',\n        callbacks = [EarlyStopping(monitor='val_loss', patience=10, mode='min')],\n        #logger=mlflow_logger,\n        fast_dev_run=False,\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"lit_model = LitCassava(model)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"trainer.fit(lit_model, dm)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ---------------------\n# Log best scores\n# ---------------------\n#val_losses = mlflow_logger.experiment.get_metric_history(run_id=mlflow_logger.run_id, key=\"val_loss\")\n#best_val_loss = np.min([v.value for v in val_losses])\n#best_epoch = np.argmin([v.value for v in val_losses])\n#best_val_loss = val_losses[best_epoch].value\n#mlflow_logger.experiment.log_metric(key=\"best_val_loss\", value=best_val_loss, run_id=mlflow_logger.run_id)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Analyzing the predictions\n\nFirst of all, let's make predictions on validation data and collect them."},{"metadata":{"trusted":true},"cell_type":"code","source":"lit_model.model.eval();","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"image_paths = []\ntargets = []\npredictions = []\nfor i, batch in tqdm(enumerate(dm.val_dataloader())):\n    with torch.no_grad():\n        targets.append(batch['target'].detach().cpu().numpy())\n        image_paths.append(batch['image_path'])\n        pred = lit_model.model(batch['image'], batch['target'])[0]\n        predictions.append(pred.detach().cpu().numpy())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds_df = pd.DataFrame({'target': np.concatenate(targets),\n              'prediction': np.concatenate(predictions).argmax(1),\n              'logits': np.concatenate(predictions).max(1),\n              'image_paths': [i for j in image_paths for i in j]})\npreds_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.countplot(y=preds_df['prediction'].map(name_mapping), orient='v')\nplt.title('Prediction distribution');","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(metrics.classification_report(preds_df['target'], preds_df['prediction']))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"metrics.confusion_matrix(preds_df['target'], preds_df['prediction'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"selected_images = []\nfig = plt.figure(figsize=(16, 16))\nc = 1\nfor class_id1, class_name1 in name_mapping.items():\n    for class_id2, class_name2 in name_mapping.items():\n        if class_id1 != class_id2:\n            img_path = preds_df.loc[(preds_df['target'] == class_id1)\n                                    & (preds_df['prediction'] == class_id2)].sort_values('logits', ascending=False)['image_paths'].values[0]\n\n            ax = fig.add_subplot(5, 4, c, xticks=[], yticks=[])\n            img = cv2.imread(img_path)\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            plt.imshow(img)\n            ax.set_title(f\"Correct class: {class_id1}. Predicted class: {class_id2}\")\n            c += 1","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We can see that the distribution of predictions is similar to the distribution original classes.\n\nRare classes have more errors in predictions, we will need to find a way to tacke that."},{"metadata":{},"cell_type":"markdown","source":"## Prediction"},{"metadata":{"trusted":true},"cell_type":"code","source":"sub = pd.read_csv(f'{path}/sample_submission.csv')\nsub.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dataset = ImageClassificationDataset(image_names=sub['image_id'].values,\n                                                        transforms=valid_augs,\n                                                        labels=sub['label'].values,\n                                                        img_path=f'{path}test_images/',\n                                                        mode='test',\n                                                        labels_to_ohe=False,\n                                                        n_classes=5)\n\ntest_loader = torch.utils.data.DataLoader(\n            test_dataset,\n            batch_size=4,\n            num_workers=4,\n            shuffle=False,\n        )\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"lit_model.model.cuda()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = []\n\nfor batch in test_loader:\n\n    image = batch['image'].to('cuda')\n    target = batch['target'].to('cuda')\n    with torch.no_grad():\n        outputs = lit_model.model(image, target)[0]\n        preds = outputs.argmax(1).detach().cpu().numpy()\n\n        predictions.append(preds)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub['label'] = np.concatenate(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]}],"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}