{"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":"# 🦠 Sartorius - Resnet34 Classifier\n\n![](https://storage.googleapis.com/kaggle-competitions/kaggle/30201/logos/header.png)\n\n__Here we train a ResNet34 as a classifier for `cell_type` reaching a 100% accuracy with `6` epochs.__\n\n__We use the `train_semi_supervised` data.__\n\n\nThis classifier can be plugged together with an instance segmentation model.\n* It can determine the number of individuals to predict for a given image based on its `cell_type`\n* It can determine minimum and maximum sizes for the masks depending on the image's `cell_type`\n\n\n## Usages\nWe use this model together with a instance segmentation model in the following notebook, getting an increase of `0.005`, which is very modest but _something_:\n\n* [🦠 Sartorius - Torch - Classifier + Mask R-CNN [0.275]](https://www.kaggle.com/julian3833/sartorius-torch-classifier-mask-r-cnn)\n\n## Dataset \nA dataset is provided with the weights of the model as well:\n* [sartorius-resnet-34-classifier-finetuned](https://www.kaggle.com/julian3833/sartorius-resnet-34-classifier-finetuned)\n\n\n## Please _DO_ upvote!","metadata":{}},{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport warnings\nimport random\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nfrom torch import nn\nfrom torch import optim\nfrom torch.nn import CrossEntropyLoss\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision.models import resnet34\n\nfrom albumentations import Normalize, Resize, Compose\nfrom albumentations.pytorch import ToTensorV2\n\nwarnings.filterwarnings(\"ignore\")\n\ndef fix_all_seeds(seed):\n    np.random.seed(seed)\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nfix_all_seeds(2021)","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:07:26.956536Z","iopub.execute_input":"2021-10-18T22:07:26.95723Z","iopub.status.idle":"2021-10-18T22:07:28.116518Z","shell.execute_reply.started":"2021-10-18T22:07:26.957135Z","shell.execute_reply":"2021-10-18T22:07:28.115764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# The ResNet34 model\n\nWe override the fully-connected head with a 3-label one.","metadata":{}},{"cell_type":"code","source":"def get_resnet():\n    # Download pretrained weights\n    m = resnet34(True)\n    # Replace classification head with a 3-classes one\n    m.fc = nn.Linear(512, 3)\n    return m","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:07:28.117716Z","iopub.execute_input":"2021-10-18T22:07:28.118053Z","iopub.status.idle":"2021-10-18T22:07:28.122688Z","shell.execute_reply.started":"2021-10-18T22:07:28.118016Z","shell.execute_reply":"2021-10-18T22:07:28.121874Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"SAMPLE_SUBMISSION  = '../input/sartorius-cell-instance-segmentation/sample_submission.csv'\nTRAIN_CSV = \"../input/sartorius-cell-instance-segmentation/train.csv\"\nTRAIN_PATH = \"../input/sartorius-cell-instance-segmentation/train\"\nTEST_PATH = \"../input/sartorius-cell-instance-segmentation/test\"\n\nRESNET_MEAN = (0.485, 0.456, 0.406)\nRESNET_STD = (0.229, 0.224, 0.225)\n\nIMAGE_RESIZE = (224, 224)\n\nLEARNING_RATE = 5e-4\nEPOCHS = 8","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:07:28.123885Z","iopub.execute_input":"2021-10-18T22:07:28.124534Z","iopub.status.idle":"2021-10-18T22:07:28.133275Z","shell.execute_reply.started":"2021-10-18T22:07:28.124496Z","shell.execute_reply":"2021-10-18T22:07:28.132533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Build the classification dataframe\ndf_train = pd.read_csv(TRAIN_CSV)\ndf_class = df_train.groupby(\"id\")[['cell_type']].first().reset_index()\ndf_class.head()","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:07:28.135699Z","iopub.execute_input":"2021-10-18T22:07:28.136205Z","iopub.status.idle":"2021-10-18T22:07:28.43011Z","shell.execute_reply.started":"2021-10-18T22:07:28.136169Z","shell.execute_reply":"2021-10-18T22:07:28.429437Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_class['cell_type'].value_counts(normalize=True).round(2)","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:07:28.46032Z","iopub.execute_input":"2021-10-18T22:07:28.460705Z","iopub.status.idle":"2021-10-18T22:07:28.470184Z","shell.execute_reply.started":"2021-10-18T22:07:28.460668Z","shell.execute_reply":"2021-10-18T22:07:28.469364Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(df_class)","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:07:28.471287Z","iopub.execute_input":"2021-10-18T22:07:28.471952Z","iopub.status.idle":"2021-10-18T22:07:28.478396Z","shell.execute_reply.started":"2021-10-18T22:07:28.471903Z","shell.execute_reply":"2021-10-18T22:07:28.477528Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Split it into train and validation\ndf_class_train, df_class_val = train_test_split(df_class, test_size=0.20)\nlen(df_class_train), len(df_class_val)","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:15:03.261384Z","iopub.execute_input":"2021-10-18T22:15:03.262116Z","iopub.status.idle":"2021-10-18T22:15:03.2682Z","shell.execute_reply.started":"2021-10-18T22:15:03.262079Z","shell.execute_reply":"2021-10-18T22:15:03.267445Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_class_train['cell_type'].value_counts(normalize=True).round(2)","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:15:05.797868Z","iopub.execute_input":"2021-10-18T22:15:05.798626Z","iopub.status.idle":"2021-10-18T22:15:05.807513Z","shell.execute_reply.started":"2021-10-18T22:15:05.798589Z","shell.execute_reply":"2021-10-18T22:15:05.806684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Pytorch Dataset for `train_semi_supervised` Data","metadata":{}},{"cell_type":"code","source":"EXTRA_DATA_PATH = \"../input/sartorius-cell-instance-segmentation/train_semi_supervised\"\n\nclass CellClassificationDatasetExtraData(Dataset):\n    def __init__(self):\n        self.base_path = EXTRA_DATA_PATH\n        self.transforms = Compose([Resize(IMAGE_RESIZE[0], IMAGE_RESIZE[1]), \n                                   Normalize(mean=RESNET_MEAN, std=RESNET_STD, p=1), \n                                   ToTensorV2()])\n        self.files = os.listdir(EXTRA_DATA_PATH)\n        self.labels = ['shsy5y', 'astro', 'cort']\n\n\n    def __getitem__(self, idx):\n        file = self.files[idx]\n        image_path = os.path.join(self.base_path, file)\n        image = self.transforms(image=cv2.imread(image_path))['image']\n        label = file.split(\"[\")[0]\n        if label == 'astros':\n            label = 'astro'\n        return {'image': image, 'label': self.labels.index(label)}\n\n    def __len__(self):\n        return len(self.files)","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:18:50.229504Z","iopub.execute_input":"2021-10-18T22:18:50.229765Z","iopub.status.idle":"2021-10-18T22:18:50.239454Z","shell.execute_reply.started":"2021-10-18T22:18:50.229736Z","shell.execute_reply":"2021-10-18T22:18:50.238618Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Pytorch Dataset for train data","metadata":{}},{"cell_type":"code","source":"class CellClassificationDataset(Dataset):\n    def __init__(self, df):\n        self.df = df\n        self.base_path = TRAIN_PATH\n        self.transforms = Compose([Resize(IMAGE_RESIZE[0], IMAGE_RESIZE[1]), \n                                   Normalize(mean=RESNET_MEAN, std=RESNET_STD, p=1), \n                                   ToTensorV2()])\n        self.image_ids = df.id.unique().tolist()\n        self.labels = ['shsy5y', 'astro', 'cort']\n\n    def get_label_for_img(self, image_id):\n        label = self.df.loc[self.df['id'] == image_id, 'cell_type'].iloc[0]\n        label_id = self.labels.index(label)\n        return label_id\n        \n    def __getitem__(self, idx):\n        image_id = self.image_ids[idx]\n        image_path = os.path.join(self.base_path, image_id + \".png\")\n        image = self.transforms(image=cv2.imread(image_path))['image']\n        label = self.get_label_for_img(image_id)\n        return {'image': image, 'label': label}\n\n    def __len__(self):\n        return len(self.image_ids)","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:18:53.196223Z","iopub.execute_input":"2021-10-18T22:18:53.196895Z","iopub.status.idle":"2021-10-18T22:18:53.205051Z","shell.execute_reply.started":"2021-10-18T22:18:53.196855Z","shell.execute_reply":"2021-10-18T22:18:53.204347Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train loop","metadata":{}},{"cell_type":"code","source":"ds_train = CellClassificationDataset(df_class_train)\ndl_train = DataLoader(ds_train, batch_size=64, num_workers=4, pin_memory=True, shuffle=False)\n\nds_train_extra = CellClassificationDatasetExtraData()\ndl_train_extra = DataLoader(ds_train_extra, batch_size=65, num_workers=4, pin_memory=True, shuffle=False)\n\nds_val = CellClassificationDataset(df_class_val)\ndl_val = DataLoader(ds_val, batch_size=8, num_workers=64, pin_memory=True, shuffle=False)\n\nmodel = get_resnet()\n\nmodel.cuda()\n\nn_samples_val = len(ds_val)\nn_batches_val = len(ds_val)\nn_batches_train = len(dl_train)\nn_batches_train_extra = len(dl_train_extra)\ncriterion = CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"Starting epoch: {epoch} / {EPOCHS}\")\n    \n    train_loss = 0.0\n    train_extra_loss = 0.0\n    optimizer.zero_grad()\n    model.train()\n    \n    # Train on extra data\n    for batch_idx, batch in enumerate(dl_train_extra):\n        \n        # Predict\n        images, labels = batch['image'], batch['label']\n        images, labels = images.cuda(),  labels.cuda()\n        \n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        \n        # Back prop\n        loss.backward()\n        optimizer.step()\n        optimizer.zero_grad()\n        train_extra_loss += loss.item()\n    \n    # Train on train data\n    for batch_idx, batch in enumerate(dl_train):\n        \n        # Predict\n        images, labels = batch['image'], batch['label']\n        images, labels = images.cuda(),  labels.cuda()\n        \n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        \n        # Back prop\n        loss.backward()\n        optimizer.step()\n        optimizer.zero_grad()\n        train_loss += loss.item()\n    \n    # Validate\n    model.eval()\n    loss = 0\n    correct = 0\n    \n    with torch.no_grad():\n        for batch_idx, batch in enumerate(dl_val, 1):\n            images, labels = batch['image'], batch['label']\n            images, labels = images.cuda(),  labels.cuda()\n            preds = model(images)\n            final_pred = preds.argmax(dim=1)\n            correct += (final_pred == labels).sum().item()\n            loss += criterion(preds, labels)\n\n    train_loss = train_loss / n_batches_train\n    train_extra_loss = train_extra_loss / n_batches_train_extra\n    loss = loss / n_batches_val\n    acc = correct / n_samples_val\n    print(f\"Epoch: {epoch} - Train Extra Loss {train_extra_loss:.4f}. Train Loss {train_loss:.4f}. Val. Loss: {loss:.4f} Accuracy: {acc*100:.2f}%\")","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:20:54.862614Z","iopub.execute_input":"2021-10-18T22:20:54.863253Z","iopub.status.idle":"2021-10-18T22:22:01.972912Z","shell.execute_reply.started":"2021-10-18T22:20:54.863217Z","shell.execute_reply":"2021-10-18T22:22:01.971935Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"torch.save(model, 'resnet34-finetuned.bin')","metadata":{"execution":{"iopub.status.busy":"2021-10-18T22:07:52.273098Z","iopub.execute_input":"2021-10-18T22:07:52.273326Z","iopub.status.idle":"2021-10-18T22:07:52.496242Z","shell.execute_reply.started":"2021-10-18T22:07:52.273299Z","shell.execute_reply":"2021-10-18T22:07:52.495354Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Remember to upvote if you found this notebook useful or interesting!","metadata":{}}]}