{"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":"# Multi-Class Image Classification using PyTorch for Beginners\n\nI have fine tuned pre-trained VGG16 model, you can create your own model or use any pre-built pytorch model. More details [here](https://pytorch.org/docs/stable/torchvision/models.html).","metadata":{}},{"cell_type":"code","source":"!pip install -qU python-gdcm pydicom pylibjpeg","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport cv2\nimport time\nimport numpy as np\nimport pydicom\nimport gdcm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom torch.utils.data import DataLoader, Dataset\nfrom torch.utils.data import RandomSampler\n\nimport torchvision.transforms as T\nimport torchvision.models as models\nfrom torchvision.utils import make_grid\nfrom torchvision.datasets import ImageFolder\n\nfrom matplotlib import pyplot as plt","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport csv\n\ntrain_imgs = []\ntest_imgs = []\nval_imgs = []\n\ntrain_labels = []\ntest_labels = []\nval_labels = []\n\ndf = pd.read_csv('/kaggle/input/new-csv/new_test.csv')\nfor i, row in df.iterrows():\n    test_imgs.append(f\"{row['patient_id']}/{row['image_id']}.dcm\")\n    test_labels.append(row['cancer'])\nprint('test_done')\n\ndf = pd.read_csv('/kaggle/input/new-csv/new_train.csv')\nfor i, row in df.iterrows():\n    train_imgs.append(f\"{row['patient_id']}/{row['image_id']}.dcm\")\n    train_labels.append(row['cancer'])\nprint('train_done')\n\ndf = pd.read_csv('/kaggle/input/new-csv/new_validation.csv')\nfor i, row in df.iterrows():\n    val_imgs.append(f\"{row['patient_id']}/{row['image_id']}.dcm\")\n    val_labels.append(row['cancer'])\nprint('val_done')","metadata":{"execution":{"iopub.status.busy":"2023-02-13T06:32:36.153643Z","iopub.execute_input":"2023-02-13T06:32:36.153976Z","iopub.status.idle":"2023-02-13T06:32:42.661615Z","shell.execute_reply.started":"2023-02-13T06:32:36.153938Z","shell.execute_reply":"2023-02-13T06:32:42.660613Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Exploring Dataset\n\nclasses = [0,1]\nprint(\"Total Classes: \",len(classes))\n\n#Counting total train, valid & test images\n\ntrain_count = len(train_imgs)\nvalid_count = len(val_imgs)\ntest_count = len(test_imgs)\n\nprint(\"Total train images: \",train_count)\nprint(\"Total valid images: \",valid_count)\nprint(\"Total test images: \",test_count)\n    ","metadata":{"execution":{"iopub.status.busy":"2023-02-13T06:32:42.663004Z","iopub.execute_input":"2023-02-13T06:32:42.663373Z","iopub.status.idle":"2023-02-13T06:32:42.672964Z","shell.execute_reply.started":"2023-02-13T06:32:42.663335Z","shell.execute_reply":"2023-02-13T06:32:42.671959Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Creating a list of all images : DIR_TRAIN/class_folder/img.jpg - FOR METHOD 2 of data loading\n#   A dict for mapping class labels to index\n\nclass_to_int = {classes[i] : i for i in range(len(classes))}\n\nprint(class_to_int)\n    ","metadata":{"execution":{"iopub.status.busy":"2023-02-13T06:32:42.676586Z","iopub.execute_input":"2023-02-13T06:32:42.677124Z","iopub.status.idle":"2023-02-13T06:32:42.683169Z","shell.execute_reply.started":"2023-02-13T06:32:42.676902Z","shell.execute_reply":"2023-02-13T06:32:42.682355Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Loading Classification Dataset - FOR METHOD 2: For multi-class data, by inheriting Dataset class\n\ndef get_transform():\n    return T.Compose([T.ToTensor()])\n\nclass BirdDataset(Dataset):\n    \n    def __init__(self, imgs_list, labels, transforms = None):\n        \n        super().__init__()\n        self.imgs_list = imgs_list\n        self.class_to_int = class_to_int\n        self.transforms = transforms\n        self.labels = labels\n        \n        \n    def __getitem__(self, index):\n    \n        image_path = self.imgs_list[index]\n        \n        #Reading image\n        #image = cv2.imread(image_path, cv2.IMREAD_COLOR)\n        dicom = pydicom.dcmread('../input/rsna-breast-cancer-detection/train_images/'+image_path)\n        image = dicom.pixel_array\n        \n        image = (image - image.min()) / (image.max() - image.min())\n\n        if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n            image = 1 - image\n        \n        image = cv2.resize(image, (512, 512))\n        image = np.array(image, dtype=np.uint8)\n        print(image.shape)\n        image = cv2.cvtColor(image,cv2.COLOR_GRAY2RGB)\n        \n        #Retriving class label\n        label = self.labels[index]\n        \n        #Applying transforms on image\n        if self.transforms:\n            image = self.transforms(image)\n        return image, label\n        \n        \n        \n    def __len__(self):\n        return len(self.imgs_list)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Loading Classification Dataset\n\n\"\"\"\n# Method 1: For multi-class data directly from folders using ImageFolder\ntrain_dataset = ImageFolder(root = DIR_TRAIN, transform = T.ToTensor())\nvalid_dataset = ImageFolder(root = DIR_VALID, transform = T.ToTensor())\ntest_dataset = ImageFolder(root = DIR_TEST, transform = T.ToTensor())\n\"\"\"\n\n# Method 2: Using Dataset Class\ntrain_dataset = BirdDataset(train_imgs, train_labels, get_transform())\nvalid_dataset = BirdDataset(val_imgs, val_labels, get_transform())\ntest_dataset = BirdDataset(test_imgs, test_labels, get_transform())\n\n#Data Loader  -  using Sampler (YT Video)\ntrain_random_sampler = RandomSampler(train_dataset)\nvalid_random_sampler = RandomSampler(valid_dataset)\ntest_random_sampler = RandomSampler(test_dataset)\n\n#Shuffle Argument is mutually exclusive with Sampler!\ntrain_data_loader = DataLoader(\n    dataset = train_dataset,\n    batch_size = 16,\n    sampler = train_random_sampler,\n    num_workers = 4,\n)\n\nvalid_data_loader = DataLoader(\n    dataset = valid_dataset,\n    batch_size = 16,\n    sampler = valid_random_sampler,\n    num_workers = 4,\n)\n\ntest_data_loader = DataLoader(\n    dataset = test_dataset,\n    batch_size = 16,\n    sampler = test_random_sampler,\n    num_workers = 4,\n)","metadata":{"execution":{"iopub.status.busy":"2023-02-13T06:32:42.699215Z","iopub.execute_input":"2023-02-13T06:32:42.699799Z","iopub.status.idle":"2023-02-13T06:32:42.712024Z","shell.execute_reply.started":"2023-02-13T06:32:42.699761Z","shell.execute_reply":"2023-02-13T06:32:42.711386Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Visualize one training batch\nfor images, labels in train_data_loader:\n    fig, ax = plt.subplots(figsize = (10, 10))\n    ax.set_xticks([])\n    ax.set_yticks([])\n    ax.imshow(make_grid(images, 4).permute(1,2,0))\n    break","metadata":{"execution":{"iopub.status.busy":"2023-02-13T06:32:42.713492Z","iopub.execute_input":"2023-02-13T06:32:42.713901Z","iopub.status.idle":"2023-02-13T06:33:39.865565Z","shell.execute_reply.started":"2023-02-13T06:32:42.713865Z","shell.execute_reply":"2023-02-13T06:33:39.864459Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Define model\nmodel = models.vgg16(pretrained = True)\n\n### Modifying last few layers and no of classes\n# NOTE: cross_entropy loss takes unnormalized op (logits), then function itself applies softmax and calculates loss, so no need to include softmax here\nmodel.classifier = nn.Sequential(\n    nn.Linear(25088, 4096, bias = True),\n    nn.ReLU(inplace = True),\n    nn.Dropout(0.4),\n    nn.Linear(4096, 2048, bias = True),\n    nn.ReLU(inplace = True),\n    nn.Dropout(0.4),\n    nn.Linear(2048, 2)\n)\n","metadata":{"execution":{"iopub.status.busy":"2023-02-13T06:33:39.867406Z","iopub.execute_input":"2023-02-13T06:33:39.867745Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Get device\n\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\ntorch.cuda.empty_cache()\n\nmodel.to(device)\n\n### Training Details\n\noptimizer = torch.optim.Adam(model.parameters(), lr = 0.0001)\nlr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size = 5, gamma = 0.75)\ncriterion = nn.CrossEntropyLoss()\n\ntrain_loss = []\ntrain_accuracy = []\n\nval_loss = []\nval_accuracy = []\n\nepochs = 20\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def calc_accuracy(true,pred):\n    pred = F.softmax(pred, dim = 1)\n    true = torch.zeros(pred.shape[0], pred.shape[1]).scatter_(1, true.unsqueeze(1), 1.)\n    acc = (true.argmax(-1) == pred.argmax(-1)).float().detach().numpy()\n    acc = float((100 * acc.sum()) / len(acc))\n    return round(acc, 4)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Training Code\n\nfor epoch in range(epochs):\n    \n    start = time.time()\n    \n    #Epoch Loss & Accuracy\n    train_epoch_loss = []\n    train_epoch_accuracy = []\n    _iter = 1\n    \n    #Val Loss & Accuracy\n    val_epoch_loss = []\n    val_epoch_accuracy = []\n    \n    # Training\n    for images, labels in train_data_loader:\n        \n        images = images.to(device)\n        labels = labels.to(device)\n        \n        #Reset Grads\n        optimizer.zero_grad()\n        \n        #Forward ->\n        preds = model(images)\n        \n        #Calculate Accuracy\n        acc = calc_accuracy(labels.cpu(), preds.cpu())\n        \n        #Calculate Loss & Backward, Update Weights (Step)\n        loss = criterion(preds, labels)\n        loss.backward()\n        optimizer.step()\n        \n        #Append loss & acc\n        loss_value = loss.item()\n        train_epoch_loss.append(loss_value)\n        train_epoch_accuracy.append(acc)\n        \n        if _iter % 500 == 0:\n            print(\"> Iteration {} < \".format(_iter))\n            print(\"Iter Loss = {}\".format(round(loss_value, 4)))\n            print(\"Iter Accuracy = {} % \\n\".format(acc))\n        \n        _iter += 1\n    \n    #Validation\n    for images, labels in valid_data_loader:\n        \n        images = images.to(device)\n        labels = labels.to(device)\n        \n        #Forward ->\n        preds = model(images)\n        \n        #Calculate Accuracy\n        acc = calc_accuracy(labels.cpu(), preds.cpu())\n        \n        #Calculate Loss\n        loss = criterion(preds, labels)\n        \n        #Append loss & acc\n        loss_value = loss.item()\n        val_epoch_loss.append(loss_value)\n        val_epoch_accuracy.append(acc)\n    \n    \n    train_epoch_loss = np.mean(train_epoch_loss)\n    train_epoch_accuracy = np.mean(train_epoch_accuracy)\n    \n    val_epoch_loss = np.mean(val_epoch_loss)\n    val_epoch_accuracy = np.mean(val_epoch_accuracy)\n    \n    end = time.time()\n    \n    train_loss.append(train_epoch_loss)\n    train_accuracy.append(train_epoch_accuracy)\n    \n    val_loss.append(val_epoch_loss)\n    val_accuracy.append(val_epoch_accuracy)\n    \n    #Print Epoch Statistics\n    print(\"** Epoch {} ** - Epoch Time {}\".format(epoch, int(end-start)))\n    print(\"Train Loss = {}\".format(round(train_epoch_loss, 4)))\n    print(\"Train Accuracy = {} % \\n\".format(train_epoch_accuracy))\n    print(\"Val Loss = {}\".format(round(val_epoch_loss, 4)))\n    print(\"Val Accuracy = {} % \\n\".format(val_epoch_accuracy))\n    \n    ","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}