{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport time\nimport math\n\nimport torch\nimport torch.nn as nn\nimport torchvision\nimport torchvision.transforms as transforms\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.nn.functional as F\n\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score, accuracy_score\nfrom PIL import Image\n\nnp.random.seed(42)\ntorch.manual_seed(42)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TARGET_COLUMNS = ['ETT - Abnormal', 'ETT - Borderline', 'ETT - Normal',\n                 'NGT - Abnormal', 'NGT - Borderline', 'NGT - Incompletely Imaged', 'NGT - Normal', \n                 'CVC - Abnormal', 'CVC - Borderline', 'CVC - Normal',\n                 'Swan Ganz Catheter Present']\n\nDEBUG = False\n\nif DEBUG is False:\n    BATCH_SIZE = 32\n    EPOCHS = 10\n    AVERAGING_SIZE = 100\nelse:\n    BATCH_SIZE = 4\n    EPOCHS = 2\n    AVERAGING_SIZE = 20\n\nROOT_DIR = '/kaggle/input/ranzcr-clip-catheter-line-classification/train'\nOUTPUT_DIR = './'\nMODEL_NAME = 'cnn1'\nIMG_SIZE = 256\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ndevice","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Load and configure data "},{"metadata":{},"cell_type":"markdown","source":"### Load DataFrame with labels of images"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load DF with labels \ntrain_set_df = pd.read_csv('/kaggle/input/ranzcr-clip-catheter-line-classification/train.csv')\n\nif DEBUG is True:\n    train_set_df = train_set_df.sample(200)\nelse:\n    train_set_df = train_set_df\n\n\ntrain_set_df.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Create custom PyTorch dataset\n\nWe do this instead of using ImageFolder as we don't want to reorganize the input folder as it is given from Kaggle already loaded without any nesting, and we have more than one class for each image so we need custom dataset"},{"metadata":{"trusted":true},"cell_type":"code","source":"class RanzcrClipDataset(torch.utils.data.Dataset):\n    \"\"\"Face Landmarks dataset.\"\"\"\n\n    def __init__(self, labels_df, transform=None):\n        \"\"\"\n        Args:\n            labels_df (string): DataFrame with mapping of images to target\n            root_dir (string): Directory with all the images.\n            transform (callable, optional): Optional transform to be applied\n                on a sample.\n        \"\"\"\n        self.labels = labels_df[TARGET_COLUMNS].values\n        self.file_paths = [os.path.join(ROOT_DIR, f\"{uid}.jpg\") for uid in labels_df[\"StudyInstanceUID\"].values]\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.labels)\n\n    def __getitem__(self, idx):\n\n        # Read image as PIL\n        sample = Image.open(self.file_paths[idx]).convert('RGB')\n        \n        # Get label vector for this UID\n        # Vector of length 11 where there is 1 for each class the image is in, 0 otherwise\n        label = torch.tensor(self.labels[idx], dtype=torch.float)\n\n        # Run all given transformations on image\n        if self.transform:\n            sample = self.transform(sample)\n\n        return (sample, label)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Transforms"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_transforms = transforms.Compose([transforms.Resize((IMG_SIZE, IMG_SIZE)),\n                                       transforms.ToTensor(),\n                                       transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])\n\nval_transforms = transforms.Compose([transforms.Resize((IMG_SIZE, IMG_SIZE)),\n                                     transforms.ToTensor(),\n                                     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Split test dataset into train and test"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Train is 75%, Test 25%\ntrain_split_df, val_split_df = train_test_split(train_set_df, test_size=0.25, random_state=42)\n\ntrain_set = RanzcrClipDataset(labels_df=train_split_df, transform=train_transforms)\nval_set = RanzcrClipDataset(labels_df=val_split_df, transform=val_transforms)\n\nprint(f'Train size: {len(train_set)}, Validation size: {len(val_set)}')\n\ntrain_loader = torch.utils.data.DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=4, pin_memory=True, drop_last=True)\nval_loader = torch.utils.data.DataLoader(val_set, batch_size=BATCH_SIZE * 2, shuffle=False, num_workers=4, pin_memory=True, drop_last=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Visualize some images"},{"metadata":{"trusted":true},"cell_type":"code","source":"def imshow(inp, title=None):\n    \"\"\"Imshow for Tensor.\"\"\"\n    inp = inp.numpy().transpose((1, 2, 0))\n    mean = np.array([0.485, 0.456, 0.406])\n    std = np.array([0.229, 0.224, 0.225])\n    inp = std * inp + mean\n    inp = np.clip(inp, 0, 1)\n    plt.imshow(inp)\n    if title is not None:\n        plt.title(title)\n    plt.pause(0.001)  # pause a bit so that plots are updated\n\n\n# Get a batch of training data\ninputs, classes = next(iter(train_loader))\n\n# Make a grid from batch\nout = torchvision.utils.make_grid(inputs)\n\nimshow(out, title=classes)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Training Code"},{"metadata":{"trusted":true},"cell_type":"code","source":"def valid(net, criterion, val_loader):\n    \n    y_true = []\n    y_pred = []\n    y_prob = []\n    \n    # switch to evaluation mode\n    model.eval()\n    \n    start_time = time.time()\n    \n    with torch.no_grad():\n        loss = 0.0\n        for i, (inputs, labels) in enumerate(val_loader, 0):\n\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n      \n            outputs = net(inputs)\n\n            loss += criterion(outputs, labels).item()\n            \n            softmax = nn.Softmax(dim=1)\n\n            probs = softmax(outputs)\n            \n            for i in range(len(outputs)):\n                y_true.append(labels[i].cpu().detach().numpy())\n                y_pred.append(np.round(probs[i].cpu().detach().numpy()))\n                y_prob.append(probs[i].cpu().detach().numpy())\n        \n        y_true = np.vstack(y_true)\n        y_pred = np.vstack(y_pred)\n        y_prob = np.vstack(y_prob)\n        \n        del inputs\n        del labels\n        torch.cuda.empty_cache()\n        \n        end_time = time.time()\n        print(f'EVAL: Elapsed {(end_time - start_time):.4f} Loss: {(loss / len(val_loader)):.4f}') \n        \n        return y_true, y_pred, y_prob, (loss / len(val_loader))\n\n\ndef train(net, train_loader, criterion, optimizer, epoch):\n    \n    train_loss = []\n    \n    trainloader_size = len(train_loader)\n    \n    epoch_loss = 0.0    # Cummulative loss for epoch\n    running_loss = 0.0  # Loss per averaging size\n    \n    # switch to train mode\n    model.train()\n    start = end = time.time()\n    \n    for i, (inputs, labels) in enumerate(train_loader, 0):\n\n        inputs = inputs.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = net(inputs)\n\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n        epoch_loss += loss.item()\n\n        del inputs\n        del labels\n        torch.cuda.empty_cache()\n\n        # measure elapsed time\n        end = time.time()\n        \n        if i % AVERAGING_SIZE == 0 or i == (trainloader_size-1):\n            print('Epoch: [{0}][{1}/{2}] '\n                  'Loss: {loss:.4f} '\n                  'Time: {elapsed_time:.4f}'\n                  .format(epoch+1, i, trainloader_size,\n                          loss=(running_loss / AVERAGING_SIZE),\n                          elapsed_time=(end - start)))\n            train_loss.append(running_loss / AVERAGING_SIZE)\n            running_loss = 0.0\n\n    return train_loss, (epoch_loss / len(train_loader))\n\n\ndef run_training(net, train_loader=train_loader, val_loader=val_loader):\n    \n    train_loss_per_epoch = []\n    val_loss = []\n    roc_per_epoch = []\n    \n    net.to(device)\n    criterion = nn.BCEWithLogitsLoss()\n    optimizer = torch.optim.Adam(net.parameters(), lr=1e-4, weight_decay=1e-6, amsgrad=False)\n    \n    for epoch in range(EPOCHS):\n        start_time = time.time()\n\n        train_loss, avg_epoch_loss = train(net, train_loader, criterion, optimizer, epoch)\n        train_loss_per_epoch.append(avg_epoch_loss)\n        \n        y_true, y_pred, y_prob, avg_val_loss = valid(net, criterion, val_loader)\n        val_loss.append(avg_val_loss)\n        \n        elapsed = time.time() - start_time\n        \n        print(f'Epoch {epoch+1} - avg_epoch_loss: {avg_epoch_loss:.4f}  avg_val_loss: {avg_val_loss:.4f}  time: {elapsed:.0f}s')\n        \n        try:\n            roc = calc_metrics(y_true, y_pred, y_prob)\n        except:\n            roc = 0\n            print(\"Error calculating metrics\")\n        \n        roc_per_epoch.append(roc)\n        \n        torch.save({'model': net.state_dict(), \n                    'epoch': epoch,\n                    'optimizer_state_dict': optimizer.state_dict(),\n                    'loss': avg_epoch_loss,\n                    'train_loss_per_epoch': train_loss_per_epoch,\n                    'val_loss': val_loss,\n                    'roc_per_epoch': roc_per_epoch},\n                    f'{OUTPUT_DIR}{MODEL_NAME}_epoch_{epoch+1}_loss_{avg_val_loss:.4f}_roc_{roc:.4f}.pth')\n\n    \n    return train_loss_per_epoch, val_loss, roc_per_epoch\n\n\ndef calc_metrics(y_true, y_pred, y_prob):\n\n    # Calculate accuracy for each label\n    acc = [accuracy_score(y_true[:, i], y_pred[:, i]) for i in range(len(TARGET_COLUMNS))]\n    print(\"ACC: \", np.around(acc, decimals=3))\n    print(\"AVG ACC: \", np.around(np.mean(acc),  decimals=3))\n\n    try:\n        # Calculate ROC\n        roc = [roc_auc_score(y_true[:, i], y_prob[:, i]) for i in range(len(TARGET_COLUMNS))]\n        avg_roc = np.around(np.mean(roc),  decimals=3)\n        print(\"ROC: \", np.around(roc, decimals=3))\n        print(\"AVG ROC: \", avg_roc)\n    except:\n        print(\"Error calculating roc\")\n        avg_roc = 0\n    \n    return avg_roc","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Create Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"class CNN1(nn.Module):\n\n    def __init__(self):\n        super(CNN1, self).__init__()\n        self.conv1 = nn.Conv2d(3, 10, 3)        \n        self.conv2 = nn.Conv2d(10, 5, 3)        \n        self.conv3 = nn.Conv2d(5, 8, 3)\n        self.fc1 = nn.Linear(8 * 61 * 61, 1024)\n        self.fc2 = nn.Linear(1024, 128)\n        self.fc3 = nn.Linear(128, 11)\n\n    def forward(self, x):\n        x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))\n        x = F.relu(self.conv2(x))\n        x = F.max_pool2d(F.relu(self.conv3(x)), (2,2))\n        x = x.view(-1, self.num_flat_features(x))\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n        return x\n    \n    def num_flat_features(self, x):\n        size = x.size()[1:]  # all dimensions except the batch dimension\n        num_features = 1\n        for s in size:\n            num_features *= s\n        return num_features","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train Model!"},{"metadata":{"trusted":true},"cell_type":"code","source":"model = CNN1()\ntrain_loss, val_loss, roc_per_epoch = run_training(net=model, train_loader=train_loader, val_loader=val_loader)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.plot(train_loss, 'go-', label='train')\nplt.plot(val_loss, 'ro-', label='validation')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.plot(roc_per_epoch, 'go-', label='train')\nplt.xlabel('Epoch')\nplt.ylabel('Average ROC')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"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}