{"cells":[{"metadata":{},"cell_type":"markdown","source":"## Human Protein Atlas - Single Cell Classification\n\n   This is a weakly supervised multi-label classification problem and a code competition. Given images of cells from our microscopes and labels of protein location assigned together for all cells in the image, develop models capable of classifying each individual cell with precise labels"},{"metadata":{},"cell_type":"markdown","source":"## NFNet\n\nSo google has come up with a faster bread of CNN architecture called NFNet(Normalizer free network) which can be trained in larger batch sizes and stronger data augmentations and have set new SOTA validation accuracies on ImageNet. So lets give it a try.\nknow more about NFNet\nhttps://arxiv.org/abs/2102.06171\n![nfnets](https://miro.medium.com/max/910/1*CjpipU_oChc899f_Esjpyg.png)\n\nWe can make use of NFNet for classification"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nimport torchvision\nimport torchvision.transforms as tfms\nimport numpy as np\nimport pandas as pd \nimport matplotlib.pyplot as plt \nimport seaborn as sns\nimport cv2 \nimport torch\nfrom PIL import Image\nfrom collections import defaultdict\nfrom tqdm import tqdm_notebook as tqdm\nimport sys; \nsys.path.insert(0,'../input/timm-nfnet')\nimport timm\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN_DF_PATH = \"../input/hpa-single-cell-image-classification/train.csv\"\nTRAIN_IMG_PATH = \"../input/hpa-single-cell-image-classification/train\"\nTEST_IMG_PATH = \"../input/hpa-single-cell-image-classification/test\"\nSAMPLE_SUB = \"../input/hpa-single-cell-image-classification/sample_submission.csv\"\nCELL_LABEL = {\n0:  \"Nucleoplasm\", \n1:  \"Nuclear membrane\",   \n2:  \"Nucleoli\",   \n3:  \"Nucleoli fibrillar center\" ,  \n4:  \"Nuclear speckles\",\n5:  \"Nuclear bodies\",\n6:  \"Endoplasmic reticulum\",   \n7:  \"Golgi apparatus\",\n8:  \"Intermediate filaments\",\n9:  \"Actin filaments\", \n10: \"Microtubules\",\n11:  \"Mitotic spindle\",\n12:  \"Centrosome\",   \n13:  \"Plasma membrane\",\n14:  \"Mitochondria\",   \n15:  \"Aggresome\",\n16:  \"Cytosol\",   \n17:  \"Vesicles and punctate cytosolic patterns\",   \n18:  \"Negative\"\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(TRAIN_DF_PATH)\ntrain_df['label_count'] = train_df['Label'].apply(lambda x: len(x.split(\"|\")))\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#class count for multi classes\nplt.title(\"Class count\")\nsns.countplot(train_df['label_count'],palette=\"flare\")\nplt.show()\n\n#This shows that most of the samples are single classes compared to multi classes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#now lets compare between single vs multi label distribution\nsingle_class = train_df[train_df['label_count'] == 1]['label_count'].count()\nmulti_class = train_df[train_df['label_count'] > 1]['label_count'].count()\n\nplt.figure(figsize=(10, 8))\nplt.title(\"Single VS Mutli distribution\")\nsns.barplot(x=['Single', 'Multi'], y=[single_class, multi_class],palette='flare')\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#now lets compare between single vs multi label distribution\nlabels = train_df[\"Label\"].apply(lambda x: x.split(\"|\"))\nlabels_count = defaultdict(int)\n\n# Update the counter \nfor label in labels:\n    if len(label) > 1:\n        for l in label:\n            labels_count[CELL_LABEL[int(l)]]+=1\n    else:\n        labels_count[CELL_LABEL[int(label[0])]]+=1\n        \nplt.figure(figsize=(10, 8))\nplt.xticks(rotation=45)\nplt.title(\"Target counts\")\nsns.barplot(list(labels_count.keys()),list(labels_count.values()))\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#images are given in the form of red green blue and yellow\ndef show_image(img_path):\n    \n    sns.reset_orig()\n\n    #get image id\n    im_id = train_df.loc[1, \"ID\"]\n\n    cdict1 = {'red':   ((0.0,  0.0, 0.0),\n                       (1.0,  0.0, 0.0)),\n\n             'green': ((0.0,  0.0, 0.0),\n                       (0.75, 1.0, 1.0),\n                       (1.0,  1.0, 1.0)),\n\n             'blue':  ((0.0,  0.0, 0.0),\n                       (1.0,  0.0, 0.0))}\n\n    cdict2 = {'red':   ((0.0,  0.0, 0.0),\n                       (0.75, 1.0, 1.0),\n                       (1.0,  1.0, 1.0)),\n\n             'green': ((0.0,  0.0, 0.0),\n                       (1.0,  0.0, 0.0)),\n\n             'blue':  ((0.0,  0.0, 0.0),\n                       (1.0,  0.0, 0.0))}\n\n    cdict3 = {'red':   ((0.0,  0.0, 0.0),\n                       (1.0,  0.0, 0.0)),\n\n             'green': ((0.0,  0.0, 0.0),\n                       (1.0,  0.0, 0.0)),\n\n             'blue':  ((0.0,  0.0, 0.0),\n                       (0.75, 1.0, 1.0),\n                       (1.0,  1.0, 1.0))}\n\n    cdict4 = {'red': ((0.0,  0.0, 0.0),\n                       (0.75, 1.0, 1.0),\n                       (1.0,  1.0, 1.0)),\n\n             'green': ((0.0,  0.0, 0.0),\n                       (0.75, 1.0, 1.0),\n                       (1.0,  1.0, 1.0)),\n\n             'blue':  ((0.0,  0.0, 0.0),\n                       (1.0,  0.0, 0.0))}\n\n    plt.register_cmap(name='greens', data=cdict1)\n    plt.register_cmap(name='reds', data=cdict2)\n    plt.register_cmap(name='blues', data=cdict3)\n    plt.register_cmap(name='yellows', data=cdict4)\n\n    #get each image channel as a greyscale image (second argument 0 in imread)\n    green = cv2.imread('../input/hpa-single-cell-image-classification/train/{}_green.png'.format(img_path), 0)\n    red = cv2.imread('../input/hpa-single-cell-image-classification/train/{}_red.png'.format(img_path), 0)\n    blue = cv2.imread('../input/hpa-single-cell-image-classification/train/{}_blue.png'.format(img_path), 0)\n    yellow = cv2.imread('../input/hpa-single-cell-image-classification/train/{}_yellow.png'.format(img_path), 0)\n\n\n    #display each channel separately\n    fig, ax = plt.subplots(nrows = 2, ncols=2, figsize=(15, 15))\n    ax[0, 0].imshow(green, cmap=\"greens\")\n    ax[0, 0].set_title(\"Protein of interest\", fontsize=18)\n    ax[0, 1].imshow(red, cmap=\"reds\")\n    ax[0, 1].set_title(\"Microtubules\", fontsize=18)\n    ax[1, 0].imshow(blue, cmap=\"blues\")\n    ax[1, 0].set_title(\"Nucleus\", fontsize=18)\n    ax[1, 1].imshow(yellow, cmap=\"yellows\")\n    ax[1, 1].set_title(\"Endoplasmic reticulum\", fontsize=18)\n    for i in range(2):\n        for j in range(2):\n            ax[i, j].set_xticklabels([])\n            ax[i, j].set_yticklabels([])\n            ax[i, j].tick_params(left=False, bottom=False)\n    plt.show()\n    \n\nshow_image(train_df.iloc[1,0])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#hyperparameters\nCLASS = 19\nBATCH_SIZE = 32\nEPOCHS = 5\nLR = 1e-4\nRESIZE = 256\nDEVICE = torch.device('cuda') if torch.cuda.is_available() \\\n         else torch.device('cpu')\nPATH = '../input/hpa-single-cell-image-classification/'\nTRAIN_DIR = PATH + 'train/'\nTEST_DIR = PATH + 'test/'\n\n#imagenet transform\nimg_tfms = tfms.Compose(\n    [tfms.ToTensor(),\n     tfms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))])\nDEVICE","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class HPADataset(Dataset):\n    def __init__(self,csv_path,ids,label,resize=None,transforms=None):\n        self.csv_path = csv_path\n        self.ids = ids\n        self.label = label\n        self.resize = resize\n        self.transforms = transforms\n        \n    def __len__(self):\n        return len(self.ids)\n    \n    def __getitem__(self, item):\n        _ids = self.ids[item]\n        image = cv2.imread(os.path.join(self.csv_path,_ids +'_green.png'))\n        if self.resize:\n            image = cv2.resize(image, (self.resize, self.resize))\n            image = image / 255.0\n        \n        #setting the target to one hot encoded form\n        if \"train\" in self.csv_path:\n            y = self.label[item]\n            y = y.split('|')\n            y = list(map(int, y))\n            y = np.eye(CLASS, dtype='float')[y]\n            y = y.sum(axis=0)\n            return self.transforms(image), y\n        elif \"test\" in self.csv_path:\n            return self.transforms(image), _ids","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#model\nclass NFNet(nn.Module):\n    def __init__(self,output_features, model_name = 'nfnet_f1', pertrained=True):\n        super(NFNet, self).__init__()\n        self.model = timm.create_model(model_name, pretrained=pertrained)\n        self.model.head.fc = nn.Sequential(nn.Linear(self.model.head.fc.in_features, 512),\n                                 nn.ReLU(),\n                                 nn.Linear(512, output_features))\n        \n    def forward(self, x):\n        x = self.model(x)\n        return x\n\nclass CNNet(nn.Module):\n    def __init__(self, input_features, output_features):\n        super(CNNet, self).__init__()\n        self.model = torchvision.models.resnet34(pretrained=True)\n        self.model.fc = nn.Sequential(nn.Linear(input_features, 100),\n                                 nn.ReLU(),\n                                 nn.Linear(100, output_features))\n\n    def forward(self, x):\n        out = self.model(x)\n        return out","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = NFNet(CLASS)\nmodel = model.to(DEVICE)\nloss_fn = nn.BCEWithLogitsLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=LR)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Since we are experimenting we will only be taking a small batch of 5000 sample"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(PATH + \"train.csv\")\ntrain_df =train_df.sample(frac=1).reset_index(drop=True)\ntrain_df = train_df.iloc[:5000,:]\nX_train, y_train = train_df.loc[:,'ID'].values,\\\n                    train_df['Label'].values\nX_ds = HPADataset(TRAIN_DIR, X_train, y_train, RESIZE, img_tfms)\ntrain_ds, valid_ds = random_split(X_ds,[4000,1000])  \ntrain_dl = DataLoader(train_ds, batch_size=BATCH_SIZE,shuffle=True)\nvalid_dl = DataLoader(valid_ds, batch_size=BATCH_SIZE,shuffle=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#train loop\nloss_hist = []\nfor epoch in tqdm(range(EPOCHS)):\n    losses = []\n    model = model.train()\n    for batch_idx, (image, label) in enumerate(train_dl):\n        image = image.to(DEVICE)\n        label = label.to(DEVICE)\n        output = model(image.float())\n        loss = loss_fn(output, label)\n        losses.append(loss.item())\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n    loss_hist.append(sum(losses)/len(losses))\n    print(f\"epoch: {epoch} loss:{sum(losses)/len(losses)}\")\n\nplt.figure(figsize=(15, 8))\nplt.title('Train Loss')\nplt.xlabel('epochs')\nplt.ylabel('loss')\nplt.plot(loss_hist)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def check_accuracy(loader, model):\n    correct = 0.\n    total = 0.\n    with torch.no_grad():\n        for images, labels in loader:\n            images = images.to(DEVICE)\n            outputs = model(images.float())\n            outputs = torch.sigmoid(outputs).cpu() \n            predicted = np.round(outputs)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n            break\n    accuracy = 100 * correct / total\n    print(\"Accuracy: {}%\".format(accuracy))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_Test = [name.rstrip('green.png').rstrip('_') for name in (os.listdir(TEST_DIR)) if '_green.png' in name]\n\ntest_ds = HPADataset(TEST_DIR, X_Test, None, RESIZE, img_tfms)\ntest_dl = DataLoader(test_ds, batch_size=1, shuffle=False)\n\nsubmission_lst = []\n\nwith torch.no_grad():\n    model.eval()\n    for image, file in test_dl:     \n        image = image.to(DEVICE)        \n        output = model(image.float())                          \n        prob = torch.softmax(output, dim=1)\n        p, top_class = prob.topk(1, dim=1)\n        sp = ' '.join(str(e) for e in [top_class[0][0].item(), p[0][0].item()])               \n        img = cv2.imread(TEST_DIR + file[0] + '_green.png')\n        \n        if img.shape[0] == 2048:\n            sp = sp + ' eNoLCAgIMAEABJkBdQ=='\n        elif img.shape[0] == 1728:\n            sp = sp + ' eNoLCAjJNgIABNkBkg=='\n        else:\n            sp = sp + ' eNoLCAgIsAQABJ4Beg=='\n        \n        submission_lst.append([file[0], img.shape[1], img.shape[0], sp])\n        \nsub = pd.DataFrame.from_records(submission_lst, columns=['ID', 'ImageWidth', 'ImageHeight', 'PredictionString'])\nsub.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub.to_csv(\"submission.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Thanks to @ateplyuk for the dataset and inference pipelines\n\n**if you found this notebook helpful, please leave an upvote!**"},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}