{"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":"## Import Packages","metadata":{}},{"cell_type":"code","source":"from typing import List, Dict\nimport os\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport seaborn as sns\n\nimport tqdm\n\nimport cv2\nimport albumentations as A\nfrom albumentations.core.composition import Compose\nfrom albumentations.pytorch import ToTensorV2\n\nfrom torch.utils.data import Dataset, TensorDataset, DataLoader\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn import metrics\n\nimport pytorch_lightning as pl\nfrom pytorch_lightning import Trainer\nfrom pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping\nfrom pytorch_lightning.metrics import FBeta\nfrom pytorch_lightning.loggers import CSVLogger\n\nimport torch\nimport torchvision.models as models\nfrom torch import nn\nfrom torch.optim import AdamW, Adam\nimport torch.nn.functional as F\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Project Directories","metadata":{}},{"cell_type":"code","source":"ROOT_DIR = '../input/plant-pathology-2021-fgvc8/'\nTRAIN_CSV = 'train.csv'\nTRAIN_IMAGES_FOLDER = 'train_images'\nTEST_IMAGES_FOLDER = 'test_images'\nSAMPLE_SUBMISSION_CSV = 'sample_submission.csv'","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Configurations","metadata":{}},{"cell_type":"code","source":"RANDOM_SEED = 42\n# Set seed for everythin(numpy, torch and python)\n\nfrom pytorch_lightning import seed_everything\nseed_everything(RANDOM_SEED)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"configurations = {\n    \"BATCH_SIZE\": 128,\n    \"NUM_WORKERS\": 4,\n    \"IMAGE_HEIGHT\": 334, \n    \"IMAGE_WIDTH\": 334,\n    \"LEARNING_RATE\": 0.003,\n    \"MAX_EPOCHS\": 6,\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Preparation","metadata":{}},{"cell_type":"code","source":"dataset_df = pd.read_csv(os.path.join(ROOT_DIR, TRAIN_CSV))\ndataset_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset_df.info()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset_df.labels.value_counts()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's visualize the label count distribution...","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(16,10))\nbase_color = sns.color_palette()[0]\n\nax = sns.countplot(x='labels', data=dataset_df, color=base_color);\nax.set_xticklabels(ax.get_xticklabels(), rotation=60);","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_single_labels(unique_labels) -> List[str]:\n    \"\"\"Splitting multi-labels and returning a list of classes\"\"\"\n    single_labels = []\n    for label in unique_labels:\n        single_labels += label.split()\n        \n    single_labels = set(single_labels)\n    \n    return list(single_labels)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_one_hot_encoded_dataframe(dataset_df):\n    # copy dataframe\n    dataset_df_copy = dataset_df.copy()\n    \n    unique_labels = dataset_df_copy.labels.unique()\n    \n    new_column_names = get_single_labels(unique_labels)\n    # initialize columns with zero\n    dataset_df_copy[new_column_names] = 0        \n    \n    # one-hot-encoding using the column names\n    for label in unique_labels:                \n        label_indices = dataset_df_copy[dataset_df_copy['labels'] == label].index\n        splited_labels = label.split()\n        dataset_df_copy.loc[label_indices, splited_labels] = 1\n    \n    return dataset_df_copy","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset_df_copy = get_one_hot_encoded_dataframe(dataset_df)\ndataset_df_copy.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Visualize the Images with Different Diseases","metadata":{}},{"cell_type":"code","source":"def show_images(dataset_df: pd.DataFrame, label_column: str, sample: int=4) -> None:\n    fig, axs = plt.subplots(1, sample, figsize=(18, 12))\n\n    df_sample = dataset_df[dataset_df[label_column] == 1].sample(n=sample, random_state=RANDOM_SEED)\n\n    for idx, ax in enumerate(axs):\n        image_path = os.path.join(ROOT_DIR, TRAIN_IMAGES_FOLDER, df_sample.iloc[idx, 0])        \n        image = cv2.imread(image_path, cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        ax.imshow(image)\n        ax.set_title(f\"Label: {df_sample.iloc[idx, 1]}\")\n        ax.axis('off')\n\n    plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_images(dataset_df=dataset_df_copy, label_column='rust')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_images(dataset_df=dataset_df_copy, label_column='frog_eye_leaf_spot')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_images(dataset_df=dataset_df_copy, label_column='complex')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_images(dataset_df=dataset_df_copy, label_column='healthy')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_images(dataset_df=dataset_df_copy, label_column='powdery_mildew')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_images(dataset_df=dataset_df_copy, label_column='scab')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Learn About the Image Shapes\n\n> There are around 18k images, going through each of them is time consuming. So, I decided to select 500 images randomly to get an idea about the image shapes. This helps to decide the image height and width for model training.","metadata":{}},{"cell_type":"code","source":"image_names = dataset_df_copy.image.values\n\nrandom_indices = np.random.randint(low=0, high=len(image_names), size=500)\n\nimages_heights = []\nimages_widths = []\nfor idx in random_indices:\n    image_path = os.path.join(ROOT_DIR, TRAIN_IMAGES_FOLDER, image_names[idx])\n    image = cv2.imread(image_path)    \n    height, width, _ = image.shape\n    images_heights.append(height)\n    images_widths.append(width)     \n        \n    \n        \nmax_height = max(images_heights)\nmax_width = max(images_widths)\nprint(f\"Max Height: {max_height} || Max Width: {max_width}\")\n\nmin_height = min(images_heights)\nmin_width = min(images_widths)\nprint(f\"Min Height: {min_height} || Min Width: {min_width}\")\n\navg_height = sum(images_heights)/len(images_heights)\navg_width = sum(images_widths)/len(images_widths)\nprint(f\"Avg Height: {avg_height} || Avg Width: {avg_width}\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare Image Dataset for Training","metadata":{}},{"cell_type":"code","source":"class ImageDataset(Dataset):\n    \"\"\" Leaf Disease Dataset \"\"\"\n    def __init__(self,\n                image_names: List[str],\n                labels: List[List[int]],\n                image_dir: str, \n                transforms):        \n        self.image_names = image_names\n        self.image_dir = image_dir\n        self.transforms = transforms                \n        self.labels = labels\n\n\n    def __len__(self) -> int:\n        return len(self.image_names)\n\n    def __getitem__(self, idx: int):\n        image_path = os.path.join(self.image_dir, self.image_names[idx])           \n        image = cv2.imread(image_path, cv2.IMREAD_COLOR)                \n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)    \n\n        target = self.labels[idx]\n\n        transformed_image = self.transforms(image=image)['image']\n        sample = {'image_path': image_path, 'image': transformed_image, 'target': target}\n\n        return sample","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ImageDataModule(pl.LightningDataModule):\n    def __init__(self,\n                 df: pd.DataFrame,\n                 train_transforms,\n                 valid_transforms,\n                 image_dir: str,\n                 fold_num: int,\n                 configurations: Dict[str, int]):\n        super().__init__()\n        self.df = df\n        self.train_transforms = train_transforms\n        self.valid_transforms = valid_transforms\n        self.image_dir = image_dir\n        self.fold_num = fold_num\n    \n    def setup(self, stage=None) -> None:\n        folds = StratifiedKFold(n_splits=5, shuffle=True)\n        \n        train_indexes, valid_indexes = list(folds.split(self.df, self.df['labels']))[self.fold_num]\n        \n        print(f\"Size of Train Dataset: {len(train_indexes)}\")\n        print(f\"Size of Validation Dataset: {len(valid_indexes)}\")\n        \n        train_df = self.df.iloc[train_indexes]\n        valid_df = self.df.iloc[valid_indexes]\n        \n        self.train_dataset = ImageDataset(image_names=train_df.image.values, \n                                        labels=train_df.iloc[:, 2:].values, \n                                        image_dir=self.image_dir, \n                                        transforms=self.train_transforms,\n                                        )\n\n        self.valid_dataset = ImageDataset(image_names=valid_df.image.values, \n                                        labels=valid_df.iloc[:, 2:].values, \n                                        image_dir=self.image_dir, \n                                        transforms=self.valid_transforms,\n                                        )\n        \n        \n    def train_dataloader(self):        \n        train_loader = DataLoader(\n            self.train_dataset,\n            batch_size=configurations.get(\"BATCH_SIZE\"),\n            num_workers=configurations.get(\"NUM_WORKERS\"),\n            shuffle=True,\n        )\n        return train_loader\n\n    def val_dataloader(self):        \n        valid_loader = DataLoader(\n            self.valid_dataset,\n            batch_size=configurations.get(\"BATCH_SIZE\"),\n            num_workers=configurations.get(\"NUM_WORKERS\"),\n            shuffle=False,\n        )\n        return valid_loader\n\n    def test_dataloader(self):\n        return None","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Image Augmentation with Albumentation\n\n> To create the baseline model, only image resizing and normalizing is considered.","metadata":{}},{"cell_type":"code","source":"train_augs = A.Compose([    \n    A.Resize(height=configurations.get(\"IMAGE_HEIGHT\"), width=configurations.get(\"IMAGE_WIDTH\"), p=1.0),    \n    A.Normalize(),\n    ToTensorV2(),\n])\n\nvalid_augs = A.Compose([\n    A.Resize(height=configurations.get(\"IMAGE_HEIGHT\"), width=configurations.get(\"IMAGE_WIDTH\"), p=1.0),\n    A.Normalize(),\n    ToTensorV2(),\n])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Preparing `LightningModule` ","metadata":{}},{"cell_type":"code","source":"class ClassifierModule(pl.LightningModule):\n    def __init__(self, learning_rate=0.003, num_classes=6):\n        super().__init__()        \n        self.metric = FBeta(num_classes=num_classes, beta=0.5, multilabel=True)\n        self.learning_rate = learning_rate\n        # Try different architectures\n        self.model = models.resnet34(pretrained=True)        \n        self.model.fc = nn.Linear(in_features=self.model.fc.in_features, out_features=num_classes)        \n        \n        \n    def forward(self, x):\n        batch_size, _, _, _ = x.shape\n        x = self.model(x)                \n        x = torch.sigmoid(x)\n        \n        return x.reshape(batch_size, -1)\n    \n    def configure_optimizers(self):\n        optimizer = AdamW(self.model.parameters(), lr=self.learning_rate, weight_decay=0.001)        \n\n        return optimizer            \n    \n    def _get_loss(self, y_hat, y): \n        loss = nn.BCELoss()        \n        return loss(y_hat.to(torch.float32), y.to(torch.float32))\n    \n    def training_step(self, batch, batch_idx):\n        image = batch['image']\n        y = batch['target']\n        y_hat = self(image)                           \n        \n        loss = self._get_loss(y_hat, y)        \n        f1_beta_score = self.metric(y_hat, y)\n        \n        self.log('train_loss', loss, on_step=True, on_epoch=True, prog_bar=True, logger=True)        \n        self.log('f1_train', f1_beta_score, on_step=True, on_epoch=True, prog_bar=True, logger=True)\n                \n        return {\n            'loss': loss,                        \n            'logits': y_hat,\n            'target': y,            \n        }                 \n        \n    def validation_step(self, batch, batch_idx):\n        image = batch['image']\n        y = batch['target']\n        y_hat = self(image)\n        \n        loss = self._get_loss(y_hat, y)\n        f1_beta_score = self.metric(y_hat, y)        \n        \n        self.log('valid_loss', loss, on_step=True, on_epoch=True, prog_bar=True, logger=True)\n        self.log('f1_valid', f1_beta_score, on_step=True, on_epoch=True, prog_bar=True, logger=True)\n\n        return {\n            'loss': loss,                        \n            'logits': y_hat,\n            'target': y,            \n        }                            ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Start Training","metadata":{}},{"cell_type":"code","source":"data_module = ImageDataModule(df=dataset_df_copy,\n                               train_transforms=train_augs,\n                               valid_transforms=valid_augs,\n                               image_dir= os.path.join(ROOT_DIR, TRAIN_IMAGES_FOLDER),\n                               fold_num=0,\n                               configurations=configurations)\n\n\ntrainer = pl.Trainer(\n        deterministic=True,\n        checkpoint_callback=ModelCheckpoint(monitor='train_loss_epoch', save_top_k=1, filename='resnet18-foldnum-0_{epoch}_{valid_loss_epoch:.4f}_{f1_valid_epoch:.4f}', mode='min'),\n        gpus=1 if torch.cuda.is_available() else 0,                \n        max_epochs=configurations.get(\"MAX_EPOCHS\", 1),\n        num_sanity_val_steps=1,        \n        weights_summary='top',        \n)\n\nlightning = ClassifierModule()\n\ntrainer.fit(lightning, data_module)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}