610 lines (609 with data), 20.1 kB
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## EfficientNet B4 model\n",
"\n",
"\n",
"**Due to GPU quota is only 30 hours/per week on Kaggle, each training need 15+ hours, so the notebook cann't commiting(otherwise will exceeding the quota), only download the csv files to submit**\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Install EfficentNet \n",
"!pip install efficientnet\n",
"!pip install iterative-stratification"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n",
"import efficientnet.keras as efn \n",
"from iterstrat.ml_stratifiers import MultilabelStratifiedShuffleSplit"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import pydicom\n",
"import os\n",
"import collections\n",
"import sys\n",
"import glob\n",
"import random\n",
"import cv2\n",
"import tensorflow as tf\n",
"import multiprocessing\n",
"\n",
"from math import ceil, floor\n",
"from copy import deepcopy\n",
"from tqdm import tqdm\n",
"from imgaug import augmenters as iaa\n",
"\n",
"import keras\n",
"import keras.backend as K\n",
"from keras.callbacks import Callback, ModelCheckpoint\n",
"from keras.layers import Dense, Flatten, Dropout\n",
"from keras.models import Model, load_model\n",
"from keras.utils import Sequence\n",
"from keras.losses import binary_crossentropy\n",
"from keras.optimizers import Adam"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Model Parameters Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"_cell_guid": "79c7e3d0-c299-4dcb-8224-4455121ee9b0",
"_uuid": "d629ff2d2480ee46fbb7e2d37f6b5fab8052498a"
},
"outputs": [],
"source": [
"# Setting the parameters:\n",
"seed = 42\n",
"np.random.seed(seed)\n",
"tf.random.set_seed(seed)\n",
"\n",
"\n",
"input_image_width = 256\n",
"input_image_height = 256\n",
"\n",
"input_image_shape = (input_image_height,input_image_width,3)\n",
"\n",
"test_size = 0.01\n",
"batch_size = 16\n",
"train_batch_size = 16\n",
"valid_batch_size = 32\n",
"\n",
"# Setting the Path \n",
"path = '../input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/'\n",
"train_img_path = path + 'stage_2_train/'\n",
"test_img_path = path + 'stage_2_test/'\n",
"\n",
"# Dataset Filenames\n",
"train_dataset_fns = path + 'stage_2_train.csv'\n",
"test_dataset_fns = path + 'stage_2_sample_submission.csv'\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"dup_image_list = [56346, 56347, 56348, 56349,\n",
" 56350, 56351, 1171830, 1171831,\n",
" 1171832, 1171833, 1171834, 1171835,\n",
" 3705312, 3705313, 3705314, 3705315,\n",
" 3705316, 3705317, 3842478, 3842479,\n",
" 3842480, 3842481, 3842482, 3842483 ]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### load the dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def train_dataset_loader(filename):\n",
" df = pd.read_csv(filename)\n",
" df[\"Image\"] = df[\"ID\"].str.slice(stop=12)\n",
" df[\"Diagnosis\"] = df[\"ID\"].str.slice(start=13)\n",
" df = df.drop(index = dup_image_list)\n",
" df = df.reset_index(drop = True) \n",
" df = df.loc[:, [\"Label\", \"Diagnosis\", \"Image\"]]\n",
" df = df.set_index(['Image', 'Diagnosis']).unstack(level=-1)\n",
" return df\n",
"\n",
"\n",
"def test_dataset_loader(filename):\n",
" df = pd.read_csv(filename)\n",
" df[\"Image\"] = df[\"ID\"].str.slice(stop=12)\n",
" df[\"Diagnosis\"] = df[\"ID\"].str.slice(start=13)\n",
" df = df.loc[:, [\"Label\", \"Diagnosis\", \"Image\"]]\n",
" df = df.set_index(['Image', 'Diagnosis']).unstack(level=-1)\n",
" return df\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_df = train_dataset_loader(train_dataset_fns)\n",
"test_df = test_dataset_loader(test_dataset_fns)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_df.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"test_df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Data EDA and Cleaning"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def correct_dcm(dcm):\n",
" x = dcm.pixel_array + 1000\n",
" px_mode = 4096\n",
" x[x>=px_mode] = x[x>=px_mode] - px_mode\n",
" dcm.PixelData = x.tobytes()\n",
" dcm.RescaleIntercept = -1000\n",
"\n",
"def window_image(dcm, window_center, window_width): \n",
" if (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -100):\n",
" correct_dcm(dcm)\n",
" img = dcm.pixel_array * dcm.RescaleSlope + dcm.RescaleIntercept\n",
" \n",
" # Resize\n",
" img = cv2.resize(img, SHAPE[:2], interpolation = cv2.INTER_LINEAR)\n",
" \n",
" img_min = window_center - window_width // 2\n",
" img_max = window_center + window_width // 2\n",
" img = np.clip(img, img_min, img_max)\n",
" return img\n",
"\n",
"def bsb_window(dcm):\n",
" brain_img = window_image(dcm, 40, 80)\n",
" subdural_img = window_image(dcm, 80, 200)\n",
" soft_img = window_image(dcm, 40, 380)\n",
" \n",
" brain_img = (brain_img - 0) / 80\n",
" subdural_img = (subdural_img - (-20)) / 200\n",
" soft_img = (soft_img - (-150)) / 380\n",
" bsb_img = np.array([brain_img, subdural_img, soft_img]).transpose(1,2,0)\n",
" return bsb_img\n",
"\n",
"def _read(path, SHAPE):\n",
" dcm = pydicom.dcmread(path)\n",
" try:\n",
" img = bsb_window(dcm)\n",
" except:\n",
" img = np.zeros(SHAPE)\n",
" return img"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def window_with_correction(dcm, window_center, window_width):\n",
" if (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -100):\n",
" correct_dcm(dcm)\n",
" img = dcm.pixel_array * dcm.RescaleSlope + dcm.RescaleIntercept\n",
" img_min = window_center - window_width // 2\n",
" img_max = window_center + window_width // 2\n",
" img = np.clip(img, img_min, img_max)\n",
" return img\n",
"\n",
"def window_without_correction(dcm, window_center, window_width):\n",
" img = dcm.pixel_array * dcm.RescaleSlope + dcm.RescaleIntercept\n",
" img_min = window_center - window_width // 2\n",
" img_max = window_center + window_width // 2\n",
" img = np.clip(img, img_min, img_max)\n",
" return img\n",
"\n",
"def window_testing(img, window):\n",
" brain_img = window(img, 40, 80)\n",
" subdural_img = window(img, 80, 200)\n",
" soft_img = window(img, 40, 380)\n",
" \n",
" brain_img = (brain_img - 0) / 80\n",
" subdural_img = (subdural_img - (-20)) / 200\n",
" soft_img = (soft_img - (-150)) / 380\n",
" bsb_img = np.array([brain_img, subdural_img, soft_img]).transpose(1,2,0)\n",
"\n",
" return bsb_img\n",
"\n",
"\n",
"# example of a \"bad data point\" (i.e. (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -100) == True)\n",
"import matplotlib.pyplot as plt\n",
"dicom = pydicom.dcmread(train_img_path + train_df.index[101] + \".dcm\")\n",
"\n",
"fig, ax = plt.subplots(1, 2)\n",
"\n",
"ax[0].imshow(window_testing(dicom, window_without_correction), cmap=plt.cm.bone);\n",
"ax[0].set_title(\"original\")\n",
"ax[1].imshow(window_testing(dicom, window_with_correction), cmap=plt.cm.bone);\n",
"ax[1].set_title(\"corrected\");"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Random image augmentation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Image Augmentation\n",
"sometimes = lambda aug: iaa.Sometimes(0.25, aug)\n",
" \n",
"augmentation = iaa.Sequential([ iaa.Fliplr(0.25),\n",
" iaa.Flipud(0.10),\n",
" iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5),\n",
" iaa.Sometimes(0.5,iaa.GaussianBlur(sigma=(0, 0.5))),# Strengthen or weaken the contrast in each image.\n",
" iaa.ContrastNormalization((0.75, 1.5)),\n",
" sometimes(iaa.Crop(px=(0, 25), keep_size = True, sample_independently = False)) \n",
" ], random_order = True) \n",
" \n",
"\n",
"\n",
"# Generators\n",
"class DataGenerator_Train(keras.utils.Sequence):\n",
" def __init__(self, dataset, labels, batch_size = batch_size, image_shape = input_image_shape, image_path = train_img_path, augment = False, *args, **kwargs):\n",
" self.dataset = dataset\n",
" self.ids = dataset.index\n",
" self.labels = labels\n",
" self.batch_size = batch_size\n",
" self.image_shape = image_shape\n",
" self.image_path = train_img_path\n",
" self.augment = augment\n",
" self.on_epoch_end()\n",
"\n",
" def __len__(self):\n",
" return int(ceil(len(self.ids) / self.batch_size))\n",
"\n",
" def __getitem__(self, index):\n",
" indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]\n",
" X, Y = self.__data_generation(indices)\n",
" return X, Y\n",
"\n",
" def augmentor(self, image):\n",
" augment_img = augmentation \n",
" image_aug = augment_img.augment_image(image)\n",
" return image_aug\n",
"\n",
" def on_epoch_end(self):\n",
" self.indices = np.arange(len(self.ids))\n",
" np.random.shuffle(self.indices)\n",
"\n",
" def __data_generation(self, indices):\n",
" X = np.empty((self.batch_size, *self.image_shape))\n",
" Y = np.empty((self.batch_size, 6), dtype=np.float32)\n",
" \n",
" for i, index in enumerate(indices):\n",
" ID = self.ids[index]\n",
" image = _read(self.image_path+ID+\".dcm\", self.image_shape)\n",
" if self.augment:\n",
" X[i,] = self.augmentor(image)\n",
" else:\n",
" X[i,] = image\n",
" Y[i,] = self.labels.iloc[index].values \n",
" return X, Y\n",
" \n",
"class DataGenerator_Test(keras.utils.Sequence):\n",
" def __init__(self, dataset, labels, batch_size = batch_size, image_shape = input_image_shape, image_path = test_img_path, *args, **kwargs):\n",
" self.dataset = dataset\n",
" self.ids = dataset.index\n",
" self.labels = labels\n",
" self.batch_size = batch_size\n",
" self.image_shape = image_shape\n",
" self.image_path = image_path\n",
" self.on_epoch_end()\n",
"\n",
" def __len__(self):\n",
" return int(ceil(len(self.ids) / self.batch_size))\n",
"\n",
" def __getitem__(self, index):\n",
" indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]\n",
" X = self.__data_generation(indices)\n",
" return X\n",
"\n",
" def on_epoch_end(self):\n",
" self.indices = np.arange(len(self.ids))\n",
" \n",
" def __data_generation(self, indices):\n",
" X = np.empty((self.batch_size, *self.image_shape))\n",
" \n",
" for i, index in enumerate(indices):\n",
" ID = self.ids[index]\n",
" image = _read(self.image_path+ID+\".dcm\", self.image_shape)\n",
" X[i,] = image \n",
" return X"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Import the training and test datasets."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- oversample the minority class 'epidural' "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Oversampling\n",
"epidural_df = train_df[train_df.Label['epidural'] == 1]\n",
"train_oversample_df = pd.concat([train_df, epidural_df])\n",
"train_df = train_oversample_df\n",
"\n",
"# Summary\n",
"print('Train Shape: {}'.format(train_df.shape))\n",
"print('Test Shape: {}'.format(test_df.shape))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### EfficientNet model"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def predictions(test_df, model): \n",
" test_preds = model.predict_generator(DataGenerator_Test(test_df, None, 5, input_image_shape, test_img_path), verbose = 1)\n",
" return test_preds[:test_df.iloc[range(test_df.shape[0])].shape[0]]\n",
"\n",
"def ModelCheckpointFull(model_name):\n",
" return ModelCheckpoint(model_name, \n",
" monitor = 'val_loss', \n",
" verbose = 1, \n",
" save_best_only = False, \n",
" save_weights_only = True, \n",
" mode = 'min', \n",
" period = 1)\n",
"\n",
"# Create Model\n",
"def create_model():\n",
" K.clear_session()\n",
" \n",
" base_model = efn.EfficientNetB4(weights = 'imagenet', include_top = False, pooling = 'avg', input_shape = input_image_shape)\n",
" x = base_model.output\n",
" x = Dropout(0.2)(x)\n",
" y_pred = Dense(6, activation = 'sigmoid')(x)\n",
"\n",
" return Model(inputs = base_model.input, outputs = y_pred)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Multi-Labels Train/Valid Dataset Split"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Submission Placeholder\n",
"submission_predictions = []\n",
"\n",
"\n",
"Multi_Stratified_split = MultilabelStratifiedShuffleSplit(n_splits = 10, test_size = test_size, random_state = seed)\n",
"X = train_df.index\n",
"Y = train_df.Label.values\n",
"\n",
"# Get train and test index\n",
"Multi_Stratified_splits = next(Multi_Stratified_split.split(X, Y))\n",
"train_idx = Multi_Stratified_splits[0]\n",
"valid_idx = Multi_Stratified_splits[1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Loop through Folds of Multi Label Stratified Split\n",
"\n",
"for epoch in range(0, 4):\n",
" print('=========== EPOCH {}'.format(epoch))\n",
"\n",
" # Shuffle Train data\n",
" np.random.shuffle(train_idx)\n",
" print(train_idx[:5]) \n",
" print(valid_idx[:5])\n",
"\n",
" # Create Data Generators for Train and Valid\n",
" data_generator_train = DataGenerator_Train(train_df.iloc[train_idx], \n",
" train_df.iloc[train_idx], \n",
" train_batch_size, \n",
" input_image_shape,\n",
" augment = True)\n",
" data_generator_val = DataGenerator_Train(train_df.iloc[valid_idx], \n",
" train_df.iloc[valid_idx], \n",
" valid_batch_size, \n",
" input_image_shape,\n",
" augment = False)\n",
"\n",
" # Create Model\n",
" model = create_model()\n",
" \n",
" # Full Training Model\n",
" for base_layer in model.layers[:-1]:\n",
" base_layer.trainable = True\n",
" steps = int(len(data_generator_train) / 6)\n",
" LR = 0.0001\n",
"\n",
" if epoch != 0:\n",
" # Load Model Weights\n",
" model.load_weights('model.h5') \n",
"\n",
" model.compile(optimizer = Adam(learning_rate = LR), \n",
" loss = 'binary_crossentropy',\n",
" metrics = ['acc', tf.keras.metrics.AUC()])\n",
" \n",
" # Train Model\n",
" model.fit_generator(generator = data_generator_train,\n",
" validation_data = data_generator_val,\n",
" steps_per_epoch = steps,\n",
" epochs = 1,\n",
" callbacks = [ModelCheckpointFull('model.h5')],\n",
" verbose = 1)\n",
" \n",
" # Starting with the 6th epoch we create predictions for the test set on each epoch\n",
" if epoch >= 1:\n",
" preds = predictions(test_df, model)\n",
" submission_predictions.append(preds)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ensemble and average all submission_predictions."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"test_df.iloc[:, :] = np.average(submission_predictions, axis = 0, weights = [2**i for i in range(len(submission_predictions))])\n",
"test_df = test_df.stack().reset_index()\n",
"test_df.insert(loc = 0, column = 'ID', value = test_df['Image'].astype(str) + \"_\" + test_df['Diagnosis'])\n",
"test_df = test_df.drop([\"Image\", \"Diagnosis\"], axis=1)\n",
"test_df.to_csv('submission.csv', index = False)\n",
"print(test_df.head(12))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from IPython.display import FileLink, FileLinks\n",
"FileLink('submission.csv')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
}