{"cells":[{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://healthitanalytics.com/images/site/article_headers/_normal/ThinkstockPhotos-495951912.jpg\">","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"- Aim\n  -  Create an algorithm to identify metastatic cancer in small image patches taken from larger digital pathology scans.\n  \n- Techniques\n  - Check the distribution of labels \n  - Take a random look at a certain set of images to check for any noticable difference\n  - Check variation of the color channels in a cancerous and non cancerous image\n  - Apply image augmentations based on the features noticed above\n  - Apply transfer learning on the training set using VGG19 architecture.\n  - Apply model on the test set. ","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Libraries","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"!pip install git+https://github.com/mjkvaak/ImageDataAugmentor\n\nimport numpy as np \n\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport cv2\n\nimport keras\nfrom keras import losses\nfrom keras.layers.normalization import BatchNormalization\nfrom keras.layers.core import Activation,Flatten, Dropout, Dense\nfrom keras.layers.convolutional import MaxPooling2D\nfrom keras import Model\nfrom keras.layers import GlobalAveragePooling2D\nfrom keras.callbacks import EarlyStopping,ModelCheckpoint\nfrom keras.optimizers import Nadam,Adam\n\nfrom ImageDataAugmentor.image_data_augmentor import *\nimport albumentations\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_curve, auc, roc_auc_score\n\nimport glob\n\n\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# References\n- https://www.kaggle.com/sdelecourt/cnn-with-keras\n- https://www.alibabacloud.com/blog/part-3-image-classification-using-features-extracted-by-transfer-learning-in-keras_595291\n- https://www.kaggle.com/frlemarchand/transfer-learning-for-cancer-detection-keras","execution_count":null},{"metadata":{"_uuid":"510e75653d766d32142ae0cc3324863e8d8834da"},"cell_type":"markdown","source":"# Extract tabular data","execution_count":null},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"df = pd.read_csv('../input/histopathologic-cancer-detection/train_labels.csv')\nprint(df.head(10))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"column_names = list(df.columns)\nprint(len(df))\n\nprint(len(df[column_names[0]].unique()))\nprint(df[column_names[1]].unique())\n# No duplciate rows","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df['id']=df['id'].apply(lambda x: x+'.tif')\nprint(df['id'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Check distribution of labels","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"hist = df['label'].hist(bins=5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- Almost fair distribution of labels","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Look at random set of images for any noticable difference","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axs = plt.subplots(4,4,figsize=(10, 10), dpi=150)\n\n\nimages = []\nfor i in range(4):\n    for j in range(4):\n        \n        No = np.random.randint(0,2000)\n                \n        image = cv2.imread('../input/histopathologic-cancer-detection/train/'+ df.iloc[No]['id'])\n        images.append(axs[i, j].imshow(image))\n        \n        if df.iloc[No]['label'] == 1:\n            axs[i,j].set_title('Cancerous')\n        else:\n            axs[i,j].set_title('Non Cancerous')\n            \n        axs[i,j].set_xticks([])\n        axs[i,j].set_yticks([])\n        \n\n    \nplt.show()\n# This will be helpful for image augmentations, like we can do do channel shuffling in order for the model to extract features well or\n# contrast enhancement to make features distinguishable.\ndel images","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- From the above it is difficult to ascertain the features that distinguish between the cancerous and non cancerous cells\n- There are images of cancerous and non cancerous cells with similar colors.\n- Also, there are images of cancerous and non cancerous with high and low number of circular nodes.\n- Let's look at the frequency count of the color channels of a random image from each category.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Check frequency count of the color channels","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"malignant_data = df[(df.label==1)]\nmalignant_image = malignant_data.iloc[1000]['id']\n\nimg = cv2.imread('../input/histopathologic-cancer-detection/train/'+malignant_image)\nplt.imshow(img)\nplt.title(\"Cancerous Image\")\nplt.show()\nplt.hist(img[:, :,  0].ravel(), bins = 256, color = 'red')\nplt.hist(img[:, :, 1].ravel(), bins = 256, color = 'Green')\nplt.hist(img[:, :, 2].ravel(), bins = 256, color = 'Blue')\nplt.xlabel('Intensity Value')\nplt.ylabel('Count')\nplt.legend(['Red_Channel', 'Green_Channel', 'Blue_Channel'])\nplt.title(\"Cancerous Frequency plot\")\nplt.show()\n\nbenign_data = df[(df.label==0)]\nbenign_image = benign_data.iloc[1]['id']\n\nimg = cv2.imread('../input/histopathologic-cancer-detection/train/'+benign_image)\nplt.imshow(img)\nplt.title(\"Non Cancerous Image\")\nplt.show()\nplt.hist(img[:, :, 0].ravel(), bins = 256, color = 'red')\nplt.hist(img[:, :, 1].ravel(), bins = 256, color = 'Green')\nplt.hist(img[:, :, 2].ravel(), bins = 256, color = 'Blue')\nplt.xlabel('Intensity Value')\nplt.ylabel('Count')\nplt.legend(['Red_Channel', 'Green_Channel', 'Blue_Channel'])\nplt.title(\"Non Cancerous Frequency plot\")\nplt.show()\n\ndel img,benign_data,malignant_data","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- Frequency in a cancerous image more spread out as compared to a non cancerous image\n- However, this could vary from image to image","execution_count":null},{"metadata":{"_uuid":"90efa6916403cb5a7446639bc576e461e460bdbc"},"cell_type":"markdown","source":"# Image Augmentation","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"-  Apart from flipping and rotating the images, we can apply other augmentations\n-  We can reduce contrast of the image to bring down the effect of the color channels.\n-  And apply CLAHE (Contrast Limited Adaptive histogram equalization) to improve the contrast in the image to pick up the effect of the features and the color channel","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"AUGMENTATIONS = albumentations.Compose([\n    \n    albumentations.Flip(p=0.5),\n    albumentations.Rotate(p=0.5),    \n    albumentations.CLAHE(p=0.3), \n    albumentations.RandomContrast(p=0.3) # (Default varies from -0.2 to 0.2)\n    \n]) \n\ntrain_datagen = ImageDataAugmentor(\n        rescale=1./255,\n        augment = AUGMENTATIONS\n      )","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Splitting into training, validation and testing sets","execution_count":null},{"metadata":{"trusted":true,"_uuid":"6caeaa84fb97c9160c98ca960d083b7eafca5f16"},"cell_type":"code","source":"df['label'] = df['label'].astype('str')\n\ntrain, test = train_test_split(df, test_size=0.2,random_state=42)\ntrain, valid = train_test_split(train, test_size=0.1,random_state=42)\n\ntrain_path = '../input/histopathologic-cancer-detection/train/'\nvalid_path = '../input/histopathologic-cancer-detection/train/'\n\ntrain_generator = train_datagen.flow_from_dataframe(\n                dataframe=train,\n                directory=train_path,\n                x_col = 'id',\n                y_col = 'label',\n                shuffle=True,\n                subset='training',\n                target_size=(94, 94),\n                batch_size=64,\n                class_mode=\"binary\"\n                )\n\nvalid_datagen = ImageDataAugmentor(\n        rescale=1./255, )\n\nvalidation_generator = valid_datagen.flow_from_dataframe(\n                dataframe=valid,\n                directory=valid_path,\n                x_col = 'id',\n                y_col = 'label',\n                subset=None, \n                target_size=(94, 94),\n                batch_size=64,\n                shuffle=True,\n                class_mode=\"binary\"\n                )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.imshow(train_generator[0][0][1])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.imshow(train_generator[0][0][2])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6c52cbdd1a74085b164c6f328d1eefc35287968d"},"cell_type":"markdown","source":"# Model Structure adapted from VGG19","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"image_shape = (94,94, 3)\n\n# Define base_model\nTLModel = keras.applications.VGG19(weights='imagenet',\n                  include_top=False,\n                  input_shape=(image_shape))\n\n# Make the botton 8 layers trainable\nfor layer in TLModel.layers[:-8]:\n    layer.trainable = False\n\n\nx = TLModel.output\nx = GlobalAveragePooling2D()(x)\n\n# Helps to learn new features\nx = Dense(1000,activation='relu')(x)\nx = Dense(500,activation='relu')(x)\nx = Dense(500,activation='relu')(x)\nx = BatchNormalization()(x)\n\noutput = Dense(1, activation='sigmoid')(x)\n\nmodel = Model(inputs=TLModel.input, outputs=output)\n    \nmodel.summary()\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"opt = Nadam()\nmodel.compile(optimizer= opt, loss=losses.binary_crossentropy, metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Implementing Results on Model","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"earlyStop = EarlyStopping(monitor='val_accuracy', mode='max',patience= 4)\nCheckpoint = ModelCheckpoint(filepath='best_model.h5', monitor='val_accuracy', save_best_only=True,mode='max',verbose=1)\n\nStepSizeTrain= int(train_generator.n/train_generator.batch_size)\nStepSizeValid= int(validation_generator.n/validation_generator.batch_size)\n\nhist = model.fit_generator(\n                train_generator,\n                steps_per_epoch=StepSizeTrain,\n                validation_steps=StepSizeValid,\n                epochs=25,\n                validation_data=validation_generator\n                ,callbacks=[earlyStop,Checkpoint],verbose=1)\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"0c15a464419e0cb98f94aeea03aa3433435ad370"},"cell_type":"markdown","source":"# Plot training - validation accuracy and loss curves to check for over and underfitting","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Summarize Accuracy\nplt.plot(hist.history['accuracy'])\nplt.plot(hist.history['val_accuracy'])\nplt.title('model accuracy')\nplt.ylabel('accuracy')\nplt.xlabel('epoch')\nplt.legend(['train', 'test'], loc='upper left')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Summarize Loss \nplt.plot(hist.history[\"loss\"])\nplt.plot(hist.history['val_loss'])\nplt.title(\"model loss\")\nplt.ylabel(\"loss\")\nplt.xlabel(\"Epoch\")\nplt.legend([\"Loss\",\"Validation Loss\"])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- Testing accuracy higher than the training accuracy. This means model is not overfitting.\n- Also a higher accuracy suggest model is not underfitting. ","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Testing on testing set which was seperated from training set","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"test_datagen = ImageDataAugmentor(rescale=1./255)\n\ntest_path = '../input/histopathologic-cancer-detection/train/'\ntest_gen = test_datagen.flow_from_dataframe(\n                dataframe=test,\n                directory=test_path,\n                x_col = 'id',\n                y_col = 'label',      \n                target_size=(94, 94),\n                batch_size=1,\n                shuffle=False,\n               class_mode=\"binary\"\n                ) \n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.load_weights(\"best_model.h5\")\n\n# make predictions\npredictions = model.predict_generator(test_gen, steps=len(test_gen), verbose=1)\nFalse_Positive_rate, True_Positive_rate, Thresholds = roc_curve(test_gen.classes, predictions)\nAUC = auc(False_Positive_rate, True_Positive_rate)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(20,20))\nplt.plot([0, 1], [0, 1], 'k--')\nplt.plot(False_Positive_rate, True_Positive_rate, label='area = {:.3f}'.format(AUC))\nplt.xlabel('False positive rate')\nplt.ylabel('True positive rate')\nplt.title('ROC curve')\nplt.legend(loc='best')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"del test_gen, train_generator, validation_generator","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Applying on testing set","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"test  = pd.DataFrame()\ntest_fileNames = [file for file in glob.glob(\"../input/histopathologic-cancer-detection/test/*.tif\")]\n\ntest_fileNames.sort()\n\nFileNames = []\nfor name in test_fileNames:\n    Name = name.replace(\"../input/histopathologic-cancer-detection/test/\", \"\")\n    FileNames.append(Name)\n    \ntest['id'] = FileNames","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_datagen = ImageDataAugmentor(rescale=1./255)\n\ntest_path = '../input/histopathologic-cancer-detection/test/'\ntest_gen = test_datagen.flow_from_dataframe(\n                dataframe=test,\n                directory=test_path,\n                x_col = 'id',\n                y_col = None,\n                class_mode=None,\n                target_size=(94, 94),\n                batch_size=1,\n                shuffle=False,   \n                ) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = model.predict(test_gen, steps=len(test_gen), verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"output = pd.DataFrame()\n\noutput['id'] = test_gen.filenames\noutput['id'] = output['id'].str.replace('.tif','')\noutput['label'] = predictions\n\n\noutput.to_csv(\"submission.csv\",index=False)\nprint(output)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- Would appreciate feedback","execution_count":null}],"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}