{"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":"%matplotlib inline\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Plant2021 - PyTorch - Submission","metadata":{}},{"cell_type":"markdown","source":"# Overview\n\n* Plant Pathology 2021 Competition\n* Use pretrained PyTorch ResNet model\n* Multi-label classification\n\n\nThe trained model was designed in the Norbook *Plant2021 - PyTorch - ResNet*. ","metadata":{}},{"cell_type":"markdown","source":"## Imports","metadata":{}},{"cell_type":"code","source":"from typing import List, Dict\n\nimport random\nimport os\n\nimport numpy as np\nimport pandas as pd\nimport PIL\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport torchvision\nimport torch.onnx\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\n\nfrom torchvision import transforms as T\nfrom torchvision.transforms import functional as F\n\nimport skimage.io as io\nimport skimage.feature\nfrom skimage import color\nfrom skimage import segmentation\n\nfrom tqdm.notebook import tqdm","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Configuration","metadata":{}},{"cell_type":"code","source":"import torch\nprint(torch.__version__)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\nplt.rc('font', size=15)\nplt.rc('axes', titlesize=18)  \nplt.rc('xtick', labelsize=10)  \nplt.rc('ytick', labelsize=10)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Config: \n    \"\"\"\n    \"\"\"\n    DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n    INPUT_PATH = '../input/plant-pathology-2021-fgvc8'\n    OUTPUT_PATH = './'\n    BATCH_SIZE = 64\n    RANDOM_STATE = 2021\n    SAMPLE_FRAC = 0.01\n    IMG_SIZE = 224\n    TRAIN_DATA_FILE = os.path.join(INPUT_PATH, 'train.csv')\n    SAMPLE_SUBMISSION_FILE = os.path.join(INPUT_PATH, 'sample_submission.csv')\n    SUBMISSION_FILE = os.path.join(OUTPUT_PATH, 'submission.csv')\n    MODEL_FILE = f'../input/plant2021-pytorch-resnet/plant2021_{DEVICE}.pth'\n    CLASSES = [\n        'rust', \n        'complex', \n        'healthy', \n        'powdery_mildew', \n        'scab', \n        'frog_eye_leaf_spot'\n    ]\n    N_CLASSES = len(CLASSES)\n    CLASS_THRESHOLD = 0.3\n    \n    folders = dict({\n        'data': INPUT_PATH,\n        'train':  os.path.join(INPUT_PATH, 'train_images'),\n        'test': os.path.join(INPUT_PATH, 'test_images')\n    })\n    \n    @staticmethod\n    def set_seed():\n        torch.manual_seed(Config.RANDOM_STATE)\n        random.seed(Config.RANDOM_STATE)\n        np.random.seed(Config.RANDOM_STATE)\n        \nConfig.set_seed()        ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f'Using {Config.DEVICE} device.')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def to_numpy(tensor):\n    \"\"\"Auxiliary function to convert tensors into numpy arrays\n    \"\"\"\n    return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load images labels","metadata":{}},{"cell_type":"code","source":"def read_image_labels():\n    \"\"\"\n    \"\"\"\n    df = pd.read_csv(Config.TRAIN_DATA_FILE).set_index('image')\n    return df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_labels = read_image_labels().sample(\n    frac=Config.SAMPLE_FRAC, \n    random_state=Config.RANDOM_STATE\n)\n\nimg_labels.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image_ids = pd.read_csv(Config.SAMPLE_SUBMISSION_FILE).set_index('image')\nimage_ids","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Label distribution","metadata":{}},{"cell_type":"code","source":"def get_image_infos(img_labels):\n    \"\"\"\n    \"\"\"\n    df = img_labels.reset_index().groupby(by='labels').count().reset_index()\n    df.columns = ['disease', 'count']\n    \n    df['%'] = np.round((df['count'] / img_labels.shape[0]), 2) * 100\n    df = df.set_index('disease').sort_values(by='count', ascending=False)\n\n    return df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"get_image_infos(img_labels)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_labels.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## One hot encoding","metadata":{}},{"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    \n    for label in unique_labels:\n        single_labels += label.split()\n        \n    single_labels = set(single_labels)\n    return list(single_labels)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_one_hot_encoded_labels(dataset_df) -> pd.DataFrame:\n    \"\"\"\n    \"\"\"\n    df = dataset_df.copy()\n    \n    unique_labels = df.labels.unique()\n    column_names = get_single_labels(unique_labels)\n    \n    df[column_names] = 0        \n    \n    # one-hot-encoding\n    for label in unique_labels:                \n        label_indices = df[df['labels'] == label].index\n        splited_labels = label.split()\n        df.loc[label_indices, splited_labels] = 1\n    \n    return df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"one_hot_encoded_labels = get_one_hot_encoded_labels(img_labels)\none_hot_encoded_labels.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Visualization of images","metadata":{}},{"cell_type":"code","source":"def get_image(image_id, kind='train'):\n    \"\"\"Loads an image from file\n    \"\"\"\n    fname = os.path.join(Config.folders[kind], image_id)\n    return PIL.Image.open(fname)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def visualize_images(image_ids, labels, nrows=1, ncols=4, kind='train', image_transform=None):\n    \"\"\"\n    \"\"\"\n    fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20, 8))\n    for image_id, label, ax in zip(image_ids, labels, axes.flatten()):\n        \n        fname = os.path.join(Config.folders[kind], image_id)\n        image = np.array(PIL.Image.open(fname))\n        \n        if image_transform:\n            image = transform = A.Compose(\n                [t for t in image_transform.transforms if not isinstance(t, (\n                    A.Normalize, \n                    ToTensorV2\n                ))])(image=image)['image']\n        \n        io.imshow(image, ax=ax)\n        \n        ax.set_title(f\"Class: {label}\", fontsize=12)\n        ax.get_xaxis().set_visible(False)\n        ax.get_yaxis().set_visible(False)\n        \n        del image\n        \n    plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"visualize_images(img_labels.index, img_labels.labels, nrows=2, ncols=4)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Augmentation pipeline","metadata":{}},{"cell_type":"code","source":"image_transfom = A.Compose([\n    A.Resize(\n        height=Config.IMG_SIZE,\n        width=Config.IMG_SIZE,\n    ),\n    A.Normalize(\n        mean=(0.485, 0.456, 0.406), \n        std=(0.229, 0.224, 0.225)\n    ),\n    ToTensorV2(),\n])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images = img_labels.sample(n=5)\n\nvisualize_images(\n    images.index, \n    images.labels, \n    nrows=1,\n    ncols=5,\n    image_transform=image_transfom\n)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Database","metadata":{}},{"cell_type":"code","source":"from scipy.stats import bernoulli\nfrom torch.utils.data import Dataset\nfrom torch.utils.data import DataLoader\n\nclass PlantDataset(Dataset):\n    \"\"\"\n    \"\"\"\n    def __init__(self, \n                 image_ids, \n                 targets,\n                 transform=None, \n                 target_transform=None, \n                 kind='train'):\n        self.image_ids = image_ids\n        self.targets = targets\n        self.transform = transform\n        self.target_transform = target_transform\n        self.kind = kind\n    \n    def __len__(self):\n        return len(self.image_ids)\n    \n    def __getitem__(self, idx):\n        # load and transform image\n        img = np.array(get_image(self.image_ids.iloc[idx], kind=self.kind))\n        \n        if self.transform:\n            img = self.transform(image=img)['image']\n        \n        # get image target \n        target = self.targets[idx]\n        if self.target_transform:\n            target = self.target_transform(target)\n        \n        return img, target","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_val = pd.Series(img_labels.index)\ny_val = np.array(one_hot_encoded_labels[Config.CLASSES])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"val_set = PlantDataset(X_val, y_val, transform=image_transfom, kind='train')\nval_loader = DataLoader(val_set, batch_size=Config.BATCH_SIZE, shuffle=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Create model and load weights","metadata":{}},{"cell_type":"code","source":"def load_weights(model, load_path=Config.MODEL_FILE):\n    model.load_state_dict(torch.load(load_path))\n    model.eval()\n\ndef create_model(pretrained=False):\n    model = torchvision.models.resnet50(pretrained=pretrained).to(Config.DEVICE)\n    model.fc = torch.nn.Sequential(\n        torch.nn.Linear(\n            in_features=model.fc.in_features,\n            out_features=Config.N_CLASSES\n        ),\n        torch.nn.Sigmoid()\n    ).to(Config.DEVICE)\n    \n    return model","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = create_model(pretrained=False).to(Config.DEVICE);\nload_weights(model)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Confusion matrix","metadata":{}},{"cell_type":"code","source":"def predict(model, loader):\n    y_true = np.empty(shape=(0, 6), dtype=np.int)\n    y_pred_proba = np.empty(shape=(0, 6), dtype=np.int)\n\n    stream = tqdm(loader)\n    for batch, (X, y) in enumerate(stream, start=1):\n        X = X.to(Config.DEVICE)\n        y = to_numpy(y.to(Config.DEVICE))\n        pred = to_numpy(model(X))\n\n        y_true = np.vstack((y_true, y))\n        y_pred_proba = np.vstack((y_pred_proba, pred))\n        \n    return y_true, y_pred_proba","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_true, y_pred_proba = predict(model, val_loader)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import multilabel_confusion_matrix\n\ndef plot_confusion_matrix(\n    y_test, \n    y_pred_proba, \n    threshold=Config.CLASS_THRESHOLD, \n    label_names=Config.CLASSES\n)-> None:\n    \"\"\"\n    \"\"\"\n    y_pred = np.where(y_pred_proba > threshold, 1, 0)\n    c_matrices = multilabel_confusion_matrix(y_test, y_pred)\n    \n    cmap = plt.get_cmap('Blues')\n    fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(15, 8))\n\n    for cm, label, ax in zip(c_matrices, label_names, axes.flatten()):\n        sns.heatmap(cm, annot=True, fmt='g', ax=ax, cmap=cmap);\n\n        ax.set_xlabel('Predicted labels');\n        ax.set_ylabel('True labels'); \n        ax.set_title(f'{label}');\n\n    plt.tight_layout()    \n    plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_confusion_matrix(y_true, y_pred_proba)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Submission","metadata":{}},{"cell_type":"code","source":"def save_submission(model):\n    \"\"\"\n    \"\"\"\n    image_ids = pd.read_csv(Config.SAMPLE_SUBMISSION_FILE)\n    \n    dataset = PlantDataset(\n        image_ids['image'], \n        image_ids['labels'], \n        transform=image_transfom, \n        kind='test'\n    )\n    \n    loader = DataLoader(dataset)\n\n    for idx, (X, _) in enumerate(loader):\n        X = X.float().to(Config.DEVICE)\n        y_pred = to_numpy(torch.argmax(model(X), dim=1))\n\n        pred_labels = ' '.join([Config.CLASSES[i] for i in y_pred]).strip()\n        image_ids.iloc[idx]['labels'] = pred_labels\n    \n    # save data frame as csv\n    image_ids.set_index('image', inplace=True)\n    image_ids.to_csv(Config.SUBMISSION_FILE)\n    \n    return image_ids","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"save_submission(model)   ","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}