{"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":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport os\nbase_dir = '../input/histopathologic-cancer-detection/'\nprint(os.listdir(base_dir))\n\n# Matplotlib for visualization\nimport matplotlib.pyplot as plt\nplt.style.use(\"ggplot\")\n\n# OpenCV Image Library\nimport cv2\n\n# Import PyTorch\nimport torchvision.transforms as transforms\nfrom torch.utils.data.sampler import SubsetRandomSampler\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import TensorDataset, DataLoader, Dataset\nimport torchvision\nimport torch.optim as optim\n\n# Import useful sklearn functions\nimport sklearn\nfrom sklearn.metrics import roc_auc_score, accuracy_score\nfrom PIL import Image","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Loading Data and EDA\nHaving a look at the data, just like any other image classification problem we have a csv file with image ids and labels. The directories train, test contain the actual images.","metadata":{}},{"cell_type":"code","source":"full_train_df = pd.read_csv(\"../input/histopathologic-cancer-detection/train_labels.csv\")\nfull_train_df.head()","metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Train Size: {}\".format(len(os.listdir('../input/histopathologic-cancer-detection/train/'))))\nprint(\"Test Size: {}\".format(len(os.listdir('../input/histopathologic-cancer-detection/test/'))))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualizing Images\nClassifying metastases is probably not an easy task for a trained pathologist and extremely difficult for an untrained eye when we take a look at the image.","metadata":{}},{"cell_type":"code","source":"fig = plt.figure(figsize=(30, 6))\n# display 20 images\ntrain_imgs = os.listdir(base_dir+\"train\")\nfor idx, img in enumerate(np.random.choice(train_imgs, 20)):\n    ax = fig.add_subplot(2, 20//2, idx+1, xticks=[], yticks=[])\n    im = Image.open(base_dir+\"train/\" + img)\n    print(im.size)\n    plt.imshow(im)\n    lab = full_train_df.loc[full_train_df['id'] == img.split('.')[0], 'label'].values[0]\n    ax.set_title('Label: %s'%lab)","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Sampling\nSince the train dataset contains 220.025 images we can sample out a shuffled part of that, in this case 160000 samples and train on them to make predictions later. ","metadata":{}},{"cell_type":"code","source":"# Number of samples in each class\nSAMPLE_SIZE = 80000\n\n# Data paths\ntrain_path = '../input/histopathologic-cancer-detection/train/'\ntest_path = '../input/histopathologic-cancer-detection/test/'\n\n# Use 80000 positive and negative examples\ndf_negatives = full_train_df[full_train_df['label'] == 0].sample(SAMPLE_SIZE, random_state=42)\ndf_positives = full_train_df[full_train_df['label'] == 1].sample(SAMPLE_SIZE, random_state=42)\n\n# Concatenate the two dfs and shuffle them up\ntrain_df = sklearn.utils.shuffle(pd.concat([df_positives, df_negatives], axis=0).reset_index(drop=True))\n\ntrain_df.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data Pre-processing for our PyTorch\nFirst we turn our data into PyTorch dataset then the data is sampled into train and validation sets. Data Augmentations are added for train data to improve performance.","metadata":{}},{"cell_type":"code","source":"# Our own custom class for datasets\nclass CreateDataset(Dataset):\n    def __init__(self, df_data, data_dir = './', transform=None):\n        super().__init__()\n        self.df = df_data.values\n        self.data_dir = data_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, index):\n        img_name,label = self.df[index]\n        img_path = os.path.join(self.data_dir, img_name+'.tif')\n        image = cv2.imread(img_path)\n        if self.transform is not None:\n            image = self.transform(image)\n        return image, label","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transforms_train = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.RandomHorizontalFlip(p=0.4),\n    transforms.RandomVerticalFlip(p=0.4),\n    transforms.RandomRotation(20),\n    transforms.ToTensor(),\n    # We the get the following mean and std for the channels of all the images\n    #transforms.Normalize((0.70244707, 0.54624322, 0.69645334), (0.23889325, 0.28209431, 0.21625058))\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\n\ntrain_data = CreateDataset(df_data=train_df, data_dir=train_path, transform=transforms_train)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Set Batch Size\nbatch_size = 128\n\n# Percentage of training set to use as validation\nvalid_size = 0.1\n\n# obtain training indices that will be used for validation\nnum_train = len(train_data)\nindices = list(range(num_train))\n# np.random.shuffle(indices)\nsplit = int(np.floor(valid_size * num_train))\ntrain_idx, valid_idx = indices[split:], indices[:split]\n\n# Create Samplers\ntrain_sampler = SubsetRandomSampler(train_idx)\nvalid_sampler = SubsetRandomSampler(valid_idx)\n\n# prepare data loaders (combine dataset and sampler)\ntrain_loader = DataLoader(train_data, batch_size=batch_size, sampler=train_sampler)\nvalid_loader = DataLoader(train_data, batch_size=batch_size, sampler=valid_sampler)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transforms_test = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.ToTensor(),\n    #transforms.Normalize((0.70244707, 0.54624322, 0.69645334), (0.23889325, 0.28209431, 0.21625058))\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\n\n# creating test data\nsample_sub = pd.read_csv(\"../input/histopathologic-cancer-detection/sample_submission.csv\")\ntest_data = CreateDataset(df_data=sample_sub, data_dir=test_path, transform=transforms_test)\n\n# prepare the test loader\ntest_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Defining Model Architecture\nI'm using a Deep Convolutional Neural Network for this task building which is fairly straight-forward in PyTorch if you understand how it works. This is one of many architectures I tried that gave better results.","metadata":{}},{"cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self):\n        super(CNN,self).__init__()\n        # Convolutional and Pooling Layers\n        self.conv1=nn.Sequential(\n                nn.Conv2d(in_channels=3,out_channels=32,kernel_size=3,stride=1,padding=0),\n                nn.BatchNorm2d(32),\n                nn.ReLU(inplace=True),\n                nn.MaxPool2d(2,2))\n        self.conv2=nn.Sequential(\n                nn.Conv2d(in_channels=32,out_channels=64,kernel_size=2,stride=1,padding=1),\n                nn.BatchNorm2d(64),\n                nn.ReLU(inplace=True),\n                nn.MaxPool2d(2,2))\n        self.conv3=nn.Sequential(\n                nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,stride=1,padding=1),\n                nn.BatchNorm2d(128),\n                nn.ReLU(inplace=True),\n                nn.MaxPool2d(2,2))\n        self.conv4=nn.Sequential(\n                nn.Conv2d(in_channels=128,out_channels=256,kernel_size=3,stride=1,padding=1),\n                nn.BatchNorm2d(256),\n                nn.ReLU(inplace=True),\n                nn.MaxPool2d(2,2))\n        self.conv5=nn.Sequential(\n                nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1),\n                nn.BatchNorm2d(512),\n                nn.ReLU(inplace=True),\n                nn.MaxPool2d(2,2))\n        \n        self.dropout2d = nn.Dropout2d()\n        \n        \n        self.fc=nn.Sequential(\n                nn.Linear(512*3*3,1024),\n                nn.ReLU(inplace=True),\n                nn.Dropout(0.4),\n                nn.Linear(1024,512),\n                nn.Dropout(0.4),\n                nn.Linear(512, 1),\n                nn.Sigmoid())\n        \n    def forward(self,x):\n        \"\"\"Method for Forward Prop\"\"\"\n        x=self.conv1(x)\n        x=self.conv2(x)\n        x=self.conv3(x)\n        x=self.conv4(x)\n        x=self.conv5(x)\n        #print(x.shape) <-- Life saving debugging step :D\n        x=x.view(x.shape[0],-1)\n        x=self.fc(x)\n        return x","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# check if CUDA is available\ntrain_on_gpu = torch.cuda.is_available()\n\nif not train_on_gpu:\n    print('CUDA is not available.  Training on CPU ...')\nelse:\n    print('CUDA is available!  Training on GPU ...')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training and Validation","metadata":{}},{"cell_type":"code","source":"# create a complete CNN\nmodel = CNN()\nprint(model)\n\n# Move model to GPU if available\nif train_on_gpu: model.cuda()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Trainable Parameters\npytorch_total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\nprint(\"Number of trainable parameters: \\n{}\".format(pytorch_total_params))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# specify loss function (categorical cross-entropy loss)\ncriterion = nn.BCELoss()\n\n# specify optimizer\noptimizer = optim.Adam(model.parameters(), lr=0.00015)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load Best parameters learned from training into our model to make predictions later\nmodel.load_state_dict(torch.load('../input/histopathologic-cancer-detection-using-cnns/best_model.pt'))","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### validating results with the saved model weights.","metadata":{}},{"cell_type":"code","source":"# number of epochs to train the model\n# change this to 20 for better results\nn_epochs = 2 \n\nvalid_loss_min = np.Inf\n\n# keeping track of losses as it happen\ntrain_losses = []\nvalid_losses = []\nval_auc = []\ntest_accuracies = []\nvalid_accuracies = []\nauc_epoch = []\n\nfor epoch in range(1, n_epochs+1):\n\n    break\n    # keep track of training and validation loss\n    train_loss = 0.0\n    valid_loss = 0.0\n    \n    ######################    \n    # validate the model #\n    ######################\n    model.eval()\n    for data, target in valid_loader:\n        # move tensors to GPU if CUDA is available\n        if train_on_gpu:\n            data, target = data.cuda(), target.cuda().float()\n        # forward pass: compute predicted outputs by passing inputs to the model\n        target = target.view(-1, 1)\n        #print(data.shape,' is shape of data')\n        output = model(data)\n        # calculate the batch loss\n        loss = criterion(output, target)\n        # update average validation loss \n        valid_loss += loss.item()*data.size(0)\n        #output = output.topk()\n        y_actual = target.data.cpu().numpy()\n        y_pred = output[:,-1].detach().cpu().numpy()\n        #print('shape of data', data.shape)\n        #print('shape of target',target.shape)\n        #print(target)\n        #print(y_actual)\n        #print(y_pred)\n        val_auc.append(roc_auc_score(y_actual, y_pred))        \n    \n    # calculate average losses\n    valid_loss = valid_loss/len(valid_loader.sampler)\n    valid_auc = np.mean(val_auc)\n    auc_epoch.append(np.mean(val_auc))\n    valid_losses.append(valid_loss)\n        \n    # print training/validation statistics \n    print('Epoch: {} | Validation Loss: {:.6f} | Validation AUC: {:.4f}'.format(\n        epoch, valid_loss, valid_auc))\n    \n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predictions on Test set","metadata":{}},{"cell_type":"code","source":"# # Turn off gradients\n# model.eval()\n\n# preds = []\n# for batch_i, (data, target) in enumerate(valid_loader):\n#     data, target = data.cuda(), target.cuda()\n#     output = model(data)\n#     if(batch_i==0):\n#         print(data.shape, target.shape)\n#     pr = output.detach().cpu().numpy()\n#     for i in pr:\n#         preds.append(i)\n\n# # Create Submission file        \n# # sample_sub['label'] = preds","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"match = 0\ntotal = 0\nfor batch_i, (data,target) in enumerate(valid_loader):\n    data,target = data.cuda(), target.cuda()\n    output = model(data)\n    output = output.detach().cpu().numpy()\n    target = target.reshape(-1,1).cpu().numpy()\n    output = (output > 0.5).astype(np.int_)\n    print(output.shape,target.shape)\n    #print(output)\n    #print(target)\n    matching = accuracy_score(target, output, normalize=False)\n    match += matching\n    total += target.shape[0]\nprint(match*100/total)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualizing Preditions:","metadata":{}},{"cell_type":"code","source":"def imshow(img):\n    '''Helper function to un-normalize and display an image'''\n    # unnormalize\n    img = img / 2 + 0.5\n    # convert from Tensor image and display\n    plt.imshow(np.transpose(img, (1, 2, 0)))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# obtain one batch of training images\ndataiter = iter(test_loader)\nimages, labels = dataiter.next()\nimages = images.numpy() # convert images to numpy for display\n\n# plot the images in the batch, along with the corresponding labels\nfig = plt.figure(figsize=(25, 4))\n# display 20 images\nfor idx in np.arange(20):\n    ax = fig.add_subplot(2, 20/2, idx+1, xticks=[], yticks=[])\n    imshow(images[idx])\n    prob = \"Cancer\" if(sample_sub.label[idx] >= 0.5) else \"Normal\" \n    ax.set_title('{}'.format(prob))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"How cool is that? Now this model can be used to predict Cancer, maybe even in real-world, the AUC score I was able to achieve with this model on test set is ~0.95 which shows the model is doing way better than just guessing, it might be very much reliable if a few tweaks are to be made to take it even closer to 1.   ","metadata":{}},{"cell_type":"markdown","source":"# Now we import the other dataset\nbreast histopathology images","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nfrom glob import glob\nimport random\nimport matplotlib.pylab as plt\n\nfrom sklearn.model_selection import train_test_split\nimport tensorflow as tf\nimport keras\nfrom keras.utils.np_utils import to_categorical\nfrom keras.models import Sequential\nfrom keras.optimizers import SGD, RMSprop, Adam, Adagrad, Adadelta\nfrom keras.layers import Dense, Dropout, Activation, Flatten, BatchNormalization, Conv2D, MaxPool2D, MaxPooling2D\n%matplotlib inline","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from glob import glob\nimport random","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"imagePatches = glob('../input/breast-histopathology-images/IDC_regular_ps50_idx5/**/*.png', recursive=True)\nfor filename in imagePatches[0:10]:\n    print(filename)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class0 = [] # 0 = no cancer\nclass1 = [] # 1 = cancer\n\nfor filename in imagePatches:\n    if filename.endswith(\"class0.png\"):\n         class0.append(filename)\n    else:\n        class1.append(filename)\nprint('length of class 0', len(class0))\nprint('length of class 1',len(class1))\nsampled_class0 = random.sample(class0, 86)\nsampled_class1 = random.sample(class1, 85)\n\nprint(len(sampled_class0))\n\n\nfrom matplotlib.image import imread\nimport numpy as np\nimport cv2\n\ndef get_image_arrays(data, label):\n    img_arrays = []\n    for i in data:\n      if i.endswith('.png'):\n        img = cv2.imread(i ,cv2.IMREAD_COLOR)\n        img_sized = cv2.resize(img, (96, 96), interpolation=cv2.INTER_LINEAR)\n        img_arrays.append([img_sized, label])\n    return np.array(img_arrays)\n\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class0_array = get_image_arrays(sampled_class0, 0)\nclass1_array = get_image_arrays(sampled_class1, 1)\n\nX = []\ny = []\n\nfor features,label in class0_array:\n    X.append(features)\n    y.append(label)\nfor features,label in class1_array:\n    X.append(features)\n    y.append(label)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X = np.array(X).reshape(-1, 96, 96, 3)\nprint('shape of x',X.shape)\ny = np.array(y)\nprint('shape of y',y.shape)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom keras.utils.np_utils import to_categorical\n\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)\n# y_train = to_categorical(y_train)\nX_train = X_train.reshape(128,3,96,96)\n# y_test = to_categorical(y_test)\ny_train = y_train.reshape(128,1)\nprint(X_train.shape, X_test.shape, y_train.shape, y_test.shape)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# number of epochs to train the model\n# change this to 20 for better results\nn_epochs = 2 \n\nvalid_loss_min = np.Inf\n\nvalid_losses = []\nval_auc = []\nvalid_accuracies = []\nauc_epoch = []\n\nfor epoch in range(1, n_epochs+1):\n\n    # keep track of training and validation loss\n    train_loss = 0.0\n    valid_loss = 0.0\n    \n    ######################    \n    # validate the model #\n    ######################\n    model.eval()\n    for idxx in range(0,1):\n        data =  torch.tensor(data)\n        target = torch.tensor(target)\n        # move tensors to GPU if CUDA is available\n        # if train_on_gpu:\n        data, target = data.cuda(), target.cuda().float()\n        # forward pass: compute predicted outputs by passing inputs to the model\n        # target = target.view(-1, 1)\n        output = model(data)\n        # calculate the batch loss\n        loss = criterion(output, target)\n        # update average validation loss \n        valid_loss += loss.item()*data.size(0)\n        #output = output.topk()\n        y_actual = target.data.cpu().numpy()\n        y_pred = output[:,-1].detach().cpu().numpy()\n        print('shape of data', data.shape)\n        print('shape of target',target.shape)\n        #print(y_actual)\n        #print(y_pred)\n        val_auc.append(roc_auc_score(y_actual, y_pred))        \n    \n    # calculate average losses\n    # valid_loss = valid_loss/len(valid_loader.sampler)\n    valid_auc = np.mean(val_auc)\n    auc_epoch.append(np.mean(val_auc))\n    valid_losses.append(valid_loss)\n        \n    # print training/validation statistics \n    print('Epoch: {} | Validation Loss: {:.6f} | Validation AUC: {:.4f}'.format(\n        epoch, valid_loss, valid_auc))\n    \n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}