{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"!unzip -qq /kaggle/input/pandatiles/train\n!pip install /kaggle/input/pretrainedmodelswhl/pretrainedmodels-0.7.4-py3-none-any.whl\n!pip install iterative-stratification","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\n\nimport numpy as np\nimport pandas as pd\n\nimport openslide\nimport torch\nimport cv2\nfrom tqdm.auto import tqdm\nimport albumentations\nimport pretrainedmodels\n\nfrom iterstrat.ml_stratifiers import MultilabelStratifiedKFold\nfrom matplotlib import pyplot as plt\n\nimport torch.nn as nn\nimport torch.utils.data as data_utils\nfrom torch.nn import functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom fastai.layers import LabelSmoothingCrossEntropy\n\nfrom sklearn.metrics import cohen_kappa_score","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# Some constants\nBASE_DIR = '/kaggle/input/prostate-cancer-grade-assessment'\nDATA_DIR = '/kaggle/working'\nDEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\nBATCH_SIZE = 16\nEPOCHS = 20\nLEARNING_RATE = 0.01\nSEED = 28\nFOLDS = 5\nN_IMAGES = 16","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(os.path.join(BASE_DIR, 'train.csv'))\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Split dataset into 5 folds"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.loc[:, 'kfold'] = -1\n\nX = train_df[['image_id', 'data_provider']].values\ny = train_df[['isup_grade', 'gleason_score']].values\n\nmskf = MultilabelStratifiedKFold(n_splits=FOLDS, shuffle=True, random_state=SEED)\n\nfor fold, (train_idx, val_idx) in enumerate(mskf.split(X, y)):\n    train_df.loc[val_idx, 'kfold'] = fold\n\nprint(train_df.kfold.value_counts())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['gleason_score'].unique()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['gleason_score'] = train_df['gleason_score'].str.replace('negative', '0+0')\ntrain_df['gleason_score'].unique()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['gleason_score'] = train_df['gleason_score'].astype('category')\ntrain_df.dtypes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"mappings = dict(enumerate(train_df['gleason_score'].cat.categories))\nmappings","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['gleason_score'] = train_df['gleason_score'].cat.codes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.dtypes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f'Number of classes: {len(train_df[\"gleason_score\"].unique())}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class PandaDataset(Dataset):\n    \"\"\"Custom dataset for PANDA\"\"\"\n    \n    def __init__(self, df, folds, mean=(1.0-0.90949707, 1.0-0.8188697, 1.0-0.87795304), std=(0.36357649, 0.49984502, 0.40477625)):\n        self.df = df\n        self.df = self.df[self.df.kfold.isin(folds)].reset_index(drop=True)\n        \n        # In case of validation dataset, don't apply transformations\n        if len(folds) == 1:\n            self.aug = albumentations.Compose([\n                albumentations.Normalize(mean, std, always_apply=True)\n            ])\n        else:\n            self.aug = albumentations.Compose([\n                albumentations.Normalize(mean, std, always_apply=True)\n            ])\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, index):\n        image_id = self.df.loc[index]['image_id']\n        \n        # Read all 16 images and create a large image\n        arr = [[], [], [], []]\n        row = 0\n        for i in range(N_IMAGES):\n            image = cv2.imread(os.path.join(DATA_DIR, f'{image_id}.tiff_{i}.png'))\n            if i % 4 == 0:\n                row += 1\n            arr[row-1].append(image)\n\n        for i in range(len(arr)):\n            arr[i] = np.hstack(arr[i])\n\n        full_image = np.vstack(arr)\n        full_image = self.aug(image=full_image)['image']\n        \n        # Convert from NHWC to NCHW as pytorch expects images in NCHW format\n        full_image = np.transpose(full_image, (2, 0, 1))\n        \n        # For now, just return image and ISUP grades\n        return full_image, self.df.loc[index]['gleason_score']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# temp_train_df = train_df.iloc[0:10]\n# temp_train_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_dataset = PandaDataset(train_df, folds=[1, 2, 3, 4])\ntrain_loader = data_utils.DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\n\nval_dataset = PandaDataset(train_df, folds=[0])\nval_loader = data_utils.DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class SEResNext50(nn.Module):\n    \"\"\"\n    Define SEResNext50 model with 10 output classes based on gleason scores\n    \"\"\"\n    def __init__(self, pretrained):\n        super(SEResNext50, self).__init__()\n        if pretrained is True:\n            self.model = pretrainedmodels.__dict__[\"se_resnext50_32x4d\"](pretrained=\"imagenet\")\n        else:\n            self.model = pretrainedmodels.__dict__[\"se_resnext50_32x4d\"](pretrained=None)\n        \n        self.l0 = nn.Linear(2048, 10)\n\n    def forward(self, x):\n        bs, _, _, _ = x.shape\n        x = self.model.features(x)\n        x = F.adaptive_avg_pool2d(x, 1).reshape(bs, -1)\n        l0 = self.l0(x)\n        return l0","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = SEResNext50(pretrained=True)\nmodel.to(DEVICE)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)\nlr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=3, verbose=False, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0.00001, eps=1e-08)\ncriterion = LabelSmoothingCrossEntropy(0.1)  # Using label smoothing loss instead of normal cross entropy loss","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"val_acc_list, qwks = [], []\n\nfor epoch in range(EPOCHS):\n    model.train()\n    for i, (x_train, y_train) in tqdm(enumerate(train_loader), total=int(len(train_dataset)/train_loader.batch_size)):\n        x_train = x_train.to(DEVICE, dtype=torch.float32)/255\n        y_train = y_train.to(DEVICE, dtype=torch.long)\n        \n        \n        # Forward pass\n        preds = model(x_train)\n        loss = criterion(preds, y_train)\n        \n        # Backpropagate\n        optimizer.zero_grad()  # Reason: https://stackoverflow.com/questions/48001598/why-do-we-need-to-call-zero-grad-in-pytorch\n        loss.backward()\n        optimizer.step()\n    \n    lr_scheduler.step(loss.item())\n\n    # Calculate validation accuracy after each epoch\n    # Predict on validation set\n    \n    with torch.no_grad():\n        model.eval()\n\n        correct = 0\n        val_size = len(val_loader)\n        all_targets, all_preds = [], []\n        for x_val, y_val in tqdm(val_loader, total=int(len(val_dataset)/val_loader.batch_size)):\n\n            x_val = x_val.to(DEVICE, dtype=torch.float32)/255\n            y_val = y_val.to(DEVICE, dtype=torch.long)\n\n            val_preds = model(x_val)\n            _, preds = torch.max(val_preds.data, axis=1)\n            correct += (preds == y_val).sum().item()\n            \n            all_targets.append(y_val.cpu().numpy())\n            all_preds.append(preds.cpu().numpy())\n\n        val_acc = correct / len(val_dataset)\n        val_acc_list.append(val_acc)\n        \n        all_preds = np.concatenate(all_preds, axis=0)\n        all_targets = np.concatenate(all_targets, axis=0)\n        qwk = cohen_kappa_score(all_preds, all_targets, weights='quadratic')\n        qwks.append(qwk)\n\n    print('Epoch [{}/{}], Loss: {:.4f}, QWK: {} Validation accuracy: {:.2f}%'\n          .format(epoch + 1, EPOCHS, loss.item(), qwk, val_acc * 100))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.style.use('ggplot')\nplt.figure()\nplt.plot(np.arange(0, len(val_acc_list)), val_acc_list, label='Validation Accuracy')\n\nplt.title('Accuracy')\nplt.xlabel('# of epochs')\nplt.ylabel('Accuracy')\nplt.legend(loc='lower right')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(qwks)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Remove all the images from working directory to prevent them from kernel's output\n!rm -rf *","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"torch.save(model.state_dict(), 'seresnext_ls_fold0.pth')","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}