{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":11848,"databundleVersionId":862157,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<a href=\"https://www.kaggle.com/code/dazhengzhu/histopathologic-cancer-detection?scriptVersionId=193995947\" target=\"_blank\"><img align=\"left\" alt=\"Kaggle\" title=\"Open in Kaggle\" src=\"https://kaggle.com/static/images/open-in-kaggle.svg\"></a>","metadata":{}},{"cell_type":"markdown","source":"# Histopathologic Cancer Detection","metadata":{}},{"cell_type":"markdown","source":"## Introduction\n","metadata":{}},{"cell_type":"markdown","source":"Early and accurate cancer detection is critical for improving patient outcomes. Traditional methods relying on human expertise are time-consuming and prone to errors. The increasing volume of histopathological images further exacerbates this challenge. \n\nThis project aims to develop a deep learning-based model capable of accurately classifying histopathological images as cancerous or non-cancerous. By leveraging the Histopathologic Cancer Detection dataset, we will explore the potential of deep learning techniques to assist doctors in their diagnostic process. Successful implementation of this model could significantly enhance cancer diagnosis efficiency and contribute to advancements in medical image analysis.","metadata":{}},{"cell_type":"markdown","source":"### Setup\n\nImport Tensorflow and other necessary libraries","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n%matplotlib inline\n\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport numpy as np\nimport PIL\nimport tensorflow as tf\n\nfrom tensorflow import keras\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n\nprint(tf.__version__)\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-08-27T01:23:05.204718Z","iopub.execute_input":"2024-08-27T01:23:05.205675Z","iopub.status.idle":"2024-08-27T01:23:19.416743Z","shell.execute_reply.started":"2024-08-27T01:23:05.205634Z","shell.execute_reply":"2024-08-27T01:23:19.415652Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Data","metadata":{}},{"cell_type":"markdown","source":"PatchCamelyon (PCam) packs the clinically-relevant task of metastasis detection into a straight-forward binary image classification task, akin to CIFAR-10 and MNIST. \n\nThe data for this project is a slightly modified version of the PCam benchmark dataset (the original PCam dataset contains duplicate images due to its probabilistic sampling, however, the version presented on Kaggle does not contain duplicates).","metadata":{}},{"cell_type":"markdown","source":"## Exploratory Data Analysis","metadata":{}},{"cell_type":"code","source":"import pathlib\n\ntrain_path = '/kaggle/input/histopathologic-cancer-detection/train'\ntest_path = '/kaggle/input/histopathologic-cancer-detection/test'\ntrain_dir = pathlib.Path(train_path).with_suffix('')\ntest_dir = pathlib.Path(test_path).with_suffix('')\n\ntrain_imgs = list(train_dir.glob('*.tif'))\ntest_imgs = list(test_dir.glob('*.tif'))\n\nprint(len(train_imgs))\nprint(len(test_imgs))","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:23:24.936370Z","iopub.execute_input":"2024-08-27T01:23:24.937021Z","iopub.status.idle":"2024-08-27T01:23:30.296704Z","shell.execute_reply.started":"2024-08-27T01:23:24.936980Z","shell.execute_reply":"2024-08-27T01:23:30.295680Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The dataset uses total 220025 training images, and 57458 images for testing, its too large, we'll select a subset of images for training and validation later.","metadata":{}},{"cell_type":"markdown","source":"#### Preview Images\n\nNow take a look at a few pictures to get a better sense of what the dataset look like.","metadata":{}},{"cell_type":"code","source":"# Get training and testing files\ntrain_files = os.listdir(train_dir)\ntest_files = os.listdir(test_dir)\n\nprint('Training image files: ')\nprint(train_files[:10])\nprint('Testing image files: ')\nprint(test_files[:10])","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:17:27.458464Z","iopub.execute_input":"2024-08-27T02:17:27.458901Z","iopub.status.idle":"2024-08-27T02:17:27.665630Z","shell.execute_reply.started":"2024-08-27T02:17:27.458859Z","shell.execute_reply":"2024-08-27T02:17:27.664684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create a 4x4 plot\nnrows = 4\nncols = 4\n\n# Index for iterating over images\npic_index = 0\n\n# Set up matplotlib fig, and size it to fit 4x4 pics\nfig = plt.gcf()\nfig.set_size_inches(ncols * 4, nrows * 4)\n\npic_index += 8\nnext_train_pix = [os.path.join(train_dir, file)\n                for file in train_files[pic_index-8:pic_index]]\nnext_test_pix = [os.path.join(test_dir, file)\n                for file in test_files[pic_index-8:pic_index]]\n\nfor i, img_path in enumerate(next_train_pix+next_test_pix):\n  # Set up subplot; subplot indices start at 1\n  sp = plt.subplot(nrows, ncols, i + 1)\n  sp.axis('Off') # Don't show axes (or gridlines)\n\n  img = mpimg.imread(img_path)\n  plt.imshow(img)\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:17:29.934037Z","iopub.execute_input":"2024-08-27T02:17:29.934412Z","iopub.status.idle":"2024-08-27T02:17:31.376471Z","shell.execute_reply.started":"2024-08-27T02:17:29.934375Z","shell.execute_reply":"2024-08-27T02:17:31.375051Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Data Preprocessing\n\nThere are total 220025 images in the train folder, it's a large dataset, we'll select a subset of them for testing and validation.\n\nThe image file type is `.tif`, which is not an supported file type with the `keras.utils.image_dataset_from_directory`, fortunately, the `ImageDataGenerator` could help, to use `ImageDataGenerator`, we need to use the label as the name create a new folder named with the label, and then copy the image files to its corresponding label folder.","metadata":{}},{"cell_type":"markdown","source":"### Select Train & Validation Samples\n\nTo maintain data balance, we'll randomly sample 40000 images from the dataset. Of these, 20000 will have a label of 1, while the remaining 20000 will have a label of 0.","metadata":{}},{"cell_type":"code","source":"# load labels dataframe\ndf = pd.read_csv('../input/histopathologic-cancer-detection/train_labels.csv')\n\n# Filter samples with the label\npos_df = df[df['label'] == 1]\nneg_df = df[df['label'] == 0]\n\ntrain_pos_df = pos_df.sample(n=20000, random_state=42)\ntrain_neg_df = neg_df.sample(n=20000, random_state=42)\ntrain_df = pd.concat([train_pos_df, train_neg_df])\n# Shuffle the dataframe\ntrain_df = train_df.sample(frac=1, random_state=42).reset_index(drop=True)\n\n# Add file type\ntrain_df.id = train_df.id + '.tif'\ntrain_df.label = train_df.label.astype(str)\n\ntrain_df.head()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:23:38.841206Z","iopub.execute_input":"2024-08-27T01:23:38.841589Z","iopub.status.idle":"2024-08-27T01:23:39.219694Z","shell.execute_reply.started":"2024-08-27T01:23:38.841554Z","shell.execute_reply":"2024-08-27T01:23:39.218570Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Setup Data Generators\n\nLet's setup training and validation data generators. The generators will yield batches of 32 images of size 96x96 and their labels.\n\nWe'll use the `keras.preprocessing.image.ImageDataGenerator` class to create generators and using the rescale parameter to normalizing the pixel values to be in the [0,1] range (from original [0, 255] range).","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport tifffile as tiff\n\n# Define batch size and image size\nBATCH_SIZE = 32\nIMG_SIZE = (96, 96)\n\nTRAINING_SUBSET = \"training\"\nVALIDATION_SUBSET = \"validation\"\n\n# Create ImageDataGenerator with TIFF preprocessing\ndef tiff_to_rgb(img):\n    # Assuming TIFF images have 3 channels (RGB)\n    if img.shape[-1] != 3:\n        img = tf.image.grayscale_to_rgb(img)\n    return img\n\ntrain_datagen = ImageDataGenerator(validation_split=0.2, preprocessing_function=tiff_to_rgb)\nval_datagen = ImageDataGenerator(validation_split=0.2, preprocessing_function=tiff_to_rgb)\n\n# Create Image Generator in batches of 32\ndef create_generator(datagen: ImageDataGenerator, subset: str):\n    return datagen.flow_from_dataframe(\n        directory = train_dir,\n        dataframe = train_df,\n        x_col = 'id',\n        y_col = 'label',\n        subset = subset,\n        seed = 123,\n        shuffle=True,\n        target_size = IMG_SIZE,\n        batch_size = BATCH_SIZE,\n        class_mode = 'binary')\n\ntrain_generator = create_generator(train_datagen, TRAINING_SUBSET)\nval_generator = create_generator(val_datagen, VALIDATION_SUBSET)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:23:43.791691Z","iopub.execute_input":"2024-08-27T01:23:43.792077Z","iopub.status.idle":"2024-08-27T01:25:47.572641Z","shell.execute_reply.started":"2024-08-27T01:23:43.792042Z","shell.execute_reply":"2024-08-27T01:25:47.571840Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"markdown","source":"### Build a Baseline Model\n\nThe images that will go into our convnet are 96x96 color images.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import layers\nfrom tensorflow.keras import Model\n\n# Our input feature map is 96x96x3: 96x96 stands for image height x width pixels, \n# and 3 for the three color channels: R, G, and B\nimg_input = layers.Input(shape=IMG_SIZE + (3,))\n\n# Standardize RGB channel values to be in the [0, 1]\nx = layers.Rescaling(1./255)(img_input)\n\n# First convolution extracts 16 filters that are 3x3\n# Convolution is followed by max-pooling layer with a 2x2 window\nx = layers.Conv2D(16, 3, padding='same', activation='relu')(x)\nx = layers.MaxPooling2D(2)(x)\n\n# Second convolution extracts 32 filters that are 3x3\n# Convolution is followed by max-pooling layer with a 2x2 window\nx = layers.Conv2D(32, 3, padding='same', activation='relu')(x)\nx = layers.MaxPooling2D(2)(x)\n\n# Third convolution extracts 64 filters that are 3x3\n# Convolution is followed by max-pooling layer with a 2x2 window\nx = layers.Convolution2D(64, 3, padding='same', activation='relu')(x)\nx = layers.MaxPooling2D(2)(x)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:28:14.636264Z","iopub.execute_input":"2024-08-27T01:28:14.637131Z","iopub.status.idle":"2024-08-27T01:28:15.513682Z","shell.execute_reply.started":"2024-08-27T01:28:14.637090Z","shell.execute_reply":"2024-08-27T01:28:15.512861Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"On top of it are two fully-connected layers. Because we are facing binary classification problem, we will end our network with a sigmoid activation function, so that the output of our network will be a single scalar 0 or 1, indicates the probability the image label is 0 or 1.","metadata":{}},{"cell_type":"code","source":"# Flatten feature map to a 1-dim tensor\nx = layers.Flatten()(x)\n\n# Create a fully connected layer with ReLU activation and 128 hidden units\nx = layers.Dense(128, activation='relu')(x)\n\n# Create output layer with a single node and sigmoid activation\noutput = layers.Dense(1, activation='sigmoid')(x)\n\n# Create model:\n# input = input feature map\n# output = input feature map + stacked convolution/maxpooling layers + fully\n# connected layer + sigmoid output layer\nmodel = Model(img_input, output)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:28:21.822343Z","iopub.execute_input":"2024-08-27T01:28:21.822968Z","iopub.status.idle":"2024-08-27T01:28:21.852783Z","shell.execute_reply.started":"2024-08-27T01:28:21.822931Z","shell.execute_reply":"2024-08-27T01:28:21.851825Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's summarize the model:","metadata":{}},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:28:24.969334Z","iopub.execute_input":"2024-08-27T01:28:24.970187Z","iopub.status.idle":"2024-08-27T01:28:24.996762Z","shell.execute_reply.started":"2024-08-27T01:28:24.970146Z","shell.execute_reply":"2024-08-27T01:28:24.995891Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The Output shape column shows how the size of our feature map evolves in each successive layer. We can observe that the convolution layers reduce the size of the feature maps by a bit due to padding, and each pooling layer halves the feature map.","metadata":{}},{"cell_type":"markdown","source":"Next, let's configure the sepecifications for model training. Since we're facing a binary classification problem, we'll select the `BinaryCrossentropy` loss and use the `RMSprop` optimizer, also monitoring the classification accuracy during the training.","metadata":{}},{"cell_type":"code","source":"learning_rate=0.001\ndef compile_model():\n    model.compile(loss=keras.losses.BinaryCrossentropy(),\n             optimizer=keras.optimizers.RMSprop(learning_rate=learning_rate),\n             metrics=['accuracy'])\ncompile_model()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:28:28.793828Z","iopub.execute_input":"2024-08-27T01:28:28.794205Z","iopub.status.idle":"2024-08-27T01:28:28.808791Z","shell.execute_reply.started":"2024-08-27T01:28:28.794169Z","shell.execute_reply":"2024-08-27T01:28:28.808031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training\n\nLet's train our model for 20 epochs, using 50 batches of training data and 12 batches of validation data per epoch. The `history` object used to capture information during the training.","metadata":{}},{"cell_type":"code","source":"EPOCHS=10\ndef fit_model():\n    return model.fit(\n        train_generator,\n        epochs=EPOCHS,\n        validation_data=val_generator,\n        verbose=2\n    )\n    \nhistory = fit_model()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:28:35.886012Z","iopub.execute_input":"2024-08-27T01:28:35.886942Z","iopub.status.idle":"2024-08-27T01:40:27.965602Z","shell.execute_reply.started":"2024-08-27T01:28:35.886899Z","shell.execute_reply":"2024-08-27T01:40:27.964740Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Visualizing Intermediate Representations\n\nTo get a feel for what kind of features our convnet has learned, one fun thing to do is to visualize how an input gets transformed as it goes through the convnet.\n\nLet's pick a random training or testing image from the training set, and then generate a figure where each row is the output of a layer, and each image in the row is a specific filter in that output feature map. Rerun this cell to generate intermediate representations for a variety of training images.","metadata":{}},{"cell_type":"code","source":"import random\n# Let's define a new Model that will take an image as input, and will output\n# intermediate representations for all layers in the previous model after\n# the first.\nsuccessive_outputs = [layer.output for layer in model.layers[1:]]\nvisualization_model = Model(img_input, successive_outputs)\n\n# Let's prepare a random input image from the training set.\nimg_files = [os.path.join(train_dir, f) for f in train_imgs]\nimg_path = random.choice(img_files)\n\nimg = keras.utils.load_img(img_path, target_size=IMG_SIZE)  # this is a PIL image\nx = keras.utils.img_to_array(img)  # Numpy array with shape (96, 96, 3)\nx = x.reshape((1,) + x.shape)  # Numpy array with shape (1, 96, 96, 3)\n\n# Rescale by 1/255\nx /= 255\n\n# Let's run our image through our network, thus obtaining all\n# intermediate representations for this image.\nsuccessive_feature_maps = visualization_model.predict(x)\n\n# These are the names of the layers, so can have them as part of our plot\nlayer_names = [layer.name for layer in model.layers[1:]]\n\n# Now let's display our representations\nfor layer_name, feature_map in zip(layer_names, successive_feature_maps):\n  if len(feature_map.shape) == 4:\n    # Just do this for the conv / maxpool layers, not the fully-connected layers\n    n_features = feature_map.shape[-1]  # number of features in feature map\n    # The feature map has shape (1, size, size, n_features)\n    size = feature_map.shape[1]\n    # We will tile our images in this matrix\n    display_grid = np.zeros((size, size * n_features))\n    for i in range(n_features):\n      # Postprocess the feature to make it visually palatable\n      x = feature_map[0, :, :, i]\n      x -= x.mean()\n      x /= x.std()\n      x *= 64\n      x += 128\n      x = np.clip(x, 0, 255).astype('uint8')\n      # We'll tile each filter into this big horizontal grid\n      display_grid[:, i * size : (i + 1) * size] = x\n    # Display the grid\n    scale = 20. / n_features\n    plt.figure(figsize=(scale * n_features, scale))\n    plt.title(layer_name)\n    plt.grid(False)\n    plt.imshow(display_grid, aspect='auto', cmap='viridis')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Results And Analysis","metadata":{}},{"cell_type":"markdown","source":"### Evaluate Accuracy and Loss for the Baseline Model\n\nLet's plot the training and validation accuracy and loss during training:","metadata":{}},{"cell_type":"code","source":"# Retrieve a list of accuracy results on training and validation data\n# sets for each training epoch\n\ndef preview_learning_curve():\n    acc = history.history['accuracy']\n    val_acc = history.history['val_accuracy']\n\n    loss = history.history['loss']\n    val_loss = history.history['val_loss']\n\n    plt.figure(figsize=(8, 8))\n    plt.subplot(2, 1, 1)\n    plt.plot(acc, label='Training Accuracy')\n    plt.plot(val_acc, label='Validation Accuracy')\n    plt.legend(loc='lower right')\n    plt.ylabel('Accuracy')\n    plt.ylim([min(plt.ylim()),1])\n    plt.title('Training and Validation Accuracy')\n\n    plt.subplot(2, 1, 2)\n    plt.plot(loss, label='Training Loss')\n    plt.plot(val_loss, label='Validation Loss')\n    plt.legend(loc='upper right')\n    plt.ylabel('Cross Entropy')\n    plt.ylim([0,1.0])\n    plt.title('Training and Validation Loss')\n    plt.xlabel('epoch')","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:40:41.915912Z","iopub.execute_input":"2024-08-27T01:40:41.918689Z","iopub.status.idle":"2024-08-27T01:40:41.928953Z","shell.execute_reply.started":"2024-08-27T01:40:41.918631Z","shell.execute_reply":"2024-08-27T01:40:41.927876Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Show the baseline model learning curve:","metadata":{}},{"cell_type":"code","source":"preview_learning_curve()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:40:46.179313Z","iopub.execute_input":"2024-08-27T01:40:46.179700Z","iopub.status.idle":"2024-08-27T01:40:46.787694Z","shell.execute_reply.started":"2024-08-27T01:40:46.179658Z","shell.execute_reply":"2024-08-27T01:40:46.786619Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Below are the observation from the figure:\n\n- Performance: Both training and validation accuracies are generally above 80%, which indicates decent performance, however, there's room for improve validation accuracy.\n- Convergence: The accuracies seems to stabilize somewhat towards the end, but there's still significant fluctuation.\n- Overfitting: There's a noticeable gap between training and validation accuracy, with training accuracy often higher. This suggests some degree of overfitting.\n- Consistency: The validation accuracy shows more variability than the training accuracy, which is common but ideally should be reduced.","metadata":{}},{"cell_type":"markdown","source":"### Addressing overfitting in Baseline Model\n\nWe could employ several strategies to address overfitting:\n    \n**Data Augmentation**\n\nIn order to make the most of our few training examples, we will \"augment\" them via a number of random transformations, so that at training time, **our model will never see the exact same picture twice**. \n\nLet's create a new training data generator and apply several random transformations:","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import layers\ndata_augmentation = keras.Sequential(\n    [\n        layers.RandomFlip('horizontal'),\n        layers.RandomRotation(0.2),\n        layers.RandomZoom(0.1),\n        layers.RandomContrast(0.2),\n    ]\n)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:40:53.586762Z","iopub.execute_input":"2024-08-27T01:40:53.587520Z","iopub.status.idle":"2024-08-27T01:40:53.605293Z","shell.execute_reply.started":"2024-08-27T01:40:53.587482Z","shell.execute_reply":"2024-08-27T01:40:53.604501Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Dropout**\n\nAnother popular strategy is adding **dropout** layer to reduce overfitting:","metadata":{}},{"cell_type":"code","source":"inputs = layers.Input(shape=IMG_SIZE +(3,))\nx = data_augmentation(inputs)\nx = layers.Rescaling(1./255)(x)\nx = layers.Conv2D(16, 3, padding='same', activation='relu')(x)\nx = layers.MaxPooling2D()(x)\nx = layers.Conv2D(32, 3, padding='same', activation='relu')(x)\nx = layers.MaxPooling2D()(x)\nx = layers.Conv2D(64, 3, padding='same', activation='relu')(x)\nx = layers.MaxPooling2D()(x)\n# Add dropout layer\nx = layers.Dropout(0.2)(x)\nx = layers.Flatten()(x)\nx = layers.Dense(128, activation='relu')(x)\noutputs = layers.Dense(1, activation='sigmoid')(x)\n\nmodel = Model(inputs, outputs)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:40:56.738878Z","iopub.execute_input":"2024-08-27T01:40:56.739666Z","iopub.status.idle":"2024-08-27T01:40:56.804557Z","shell.execute_reply.started":"2024-08-27T01:40:56.739623Z","shell.execute_reply":"2024-08-27T01:40:56.803585Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"After employ **data augmentation** and **dropout**, show the model summary:","metadata":{}},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:41:02.475271Z","iopub.execute_input":"2024-08-27T01:41:02.475652Z","iopub.status.idle":"2024-08-27T01:41:02.502911Z","shell.execute_reply.started":"2024-08-27T01:41:02.475616Z","shell.execute_reply":"2024-08-27T01:41:02.501800Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's train our model and preview the results again:","metadata":{}},{"cell_type":"code","source":"# Compile model -> fit model -> preview learning curve\ncompile_model()\nhistory = fit_model()\npreview_learning_curve()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:41:12.474359Z","iopub.execute_input":"2024-08-27T01:41:12.475387Z","iopub.status.idle":"2024-08-27T01:50:48.112255Z","shell.execute_reply.started":"2024-08-27T01:41:12.475345Z","shell.execute_reply":"2024-08-27T01:50:48.111301Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see below improvements:\n- Both training and validation accuracies are mostly above 80%.\n- Both training and validation accuracies show consistency than before.\n- The gap in accuracy between training and validation becomes smaller, this also indicates overfitting reduced.\n\nOk, this is the baseline model we built empirically, its accuracy looks good but not great, let’s get a taste of what the state-of-the-art (SOTA) Convolutional Neural Network (ConvNet) are like.","metadata":{}},{"cell_type":"markdown","source":"### Try EfficientNetV2 Model\n\nEfficientNet is a family of convolutional neural network (CNN) architectures designed to achieve state-of-the-art accuracy while being computationally efficient. Unlike previous methods that arbitrarily scaled network depth, width, or resolution, EfficientNet introduces a compound scaling method that uniformly scales all three dimensions.\n\nEfficientNetV2 is an improved version of the EfficientNet architecture, designed to be even faster to train and more parameter-efficient while maintaining or even surpassing the accuracy of its predecessor.\n\n","metadata":{}},{"cell_type":"markdown","source":"#### Create EfficientNetB0 model\n\nLet's instantiate a EfficientNetB0 model pre-loaded with weights trained on `ImageNet` first. By specifying the `include_top=False` argument, we load a network that doesn't include the classification layers at the top, which is ideal for feature extraction.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import EfficientNetB0\n\nbase_model = EfficientNetB0(\n    weights='imagenet', \n    include_top=False,\n    input_shape=IMG_SIZE + (3,))","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:51:12.480625Z","iopub.execute_input":"2024-08-27T01:51:12.484618Z","iopub.status.idle":"2024-08-27T01:51:14.043064Z","shell.execute_reply.started":"2024-08-27T01:51:12.484576Z","shell.execute_reply":"2024-08-27T01:51:14.042246Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Build `base_model` and feature extractor layers using the Keras Functional API.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import layers,models\nfrom tensorflow.keras.applications.efficientnet_v2 import preprocess_input\n\n# Create the full model\ninputs = layers.Input(shape=IMG_SIZE + (3,))\nx = preprocess_input(inputs)\nx = data_augmentation(x)\nx = base_model(x, training=False)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dropout(0.5)(x)\nx = layers.Dense(128, activation='relu')(x)\nx = layers.Dropout(0.5)(x)\noutputs = layers.Dense(1, activation='sigmoid')(x)\n\nmodel = models.Model(inputs, outputs)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:51:15.864080Z","iopub.execute_input":"2024-08-27T01:51:15.864457Z","iopub.status.idle":"2024-08-27T01:51:15.903573Z","shell.execute_reply.started":"2024-08-27T01:51:15.864422Z","shell.execute_reply":"2024-08-27T01:51:15.902744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's take a look at the efficientnet model architecture:","metadata":{}},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:51:18.904153Z","iopub.execute_input":"2024-08-27T01:51:18.904541Z","iopub.status.idle":"2024-08-27T01:51:18.937833Z","shell.execute_reply.started":"2024-08-27T01:51:18.904506Z","shell.execute_reply":"2024-08-27T01:51:18.936833Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learning_rate = 0.0001\nmodel.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),\n              loss=tf.keras.losses.BinaryCrossentropy(),\n              metrics=['accuracy'])\n\ninitial_epochs = 10\nhistory = model.fit(train_generator,\n                    epochs=initial_epochs,\n                    validation_data=val_generator,\n                    verbose=2\n)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T01:51:22.839395Z","iopub.execute_input":"2024-08-27T01:51:22.840103Z","iopub.status.idle":"2024-08-27T02:03:23.533946Z","shell.execute_reply.started":"2024-08-27T01:51:22.840064Z","shell.execute_reply":"2024-08-27T02:03:23.532886Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's preview the learning curve:","metadata":{}},{"cell_type":"code","source":"preview_learning_curve()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:03:29.381609Z","iopub.execute_input":"2024-08-27T02:03:29.382377Z","iopub.status.idle":"2024-08-27T02:03:29.886615Z","shell.execute_reply.started":"2024-08-27T02:03:29.382338Z","shell.execute_reply":"2024-08-27T02:03:29.885665Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Much better! The EfficientNetB0 model give significant higher training & validation accuracies and smaller loss values. We will use this model to make prediction.","metadata":{}},{"cell_type":"markdown","source":"### Submission\n\nLet's use the `final_model` to make prediction:","metadata":{}},{"cell_type":"code","source":"test_path = '/kaggle/input/histopathologic-cancer-detection/test'\n\ndf_test = pd.read_csv('/kaggle/input/histopathologic-cancer-detection/sample_submission.csv')\ndf_test['filename'] = df_test.id + '.tif'\n\ntest_datagen = ImageDataGenerator(preprocessing_function=tiff_to_rgb)\ntest_generator = test_datagen.flow_from_dataframe(\n    dataframe = df_test,\n    directory = test_path,\n    x_col = 'filename',\n    batch_size = BATCH_SIZE,\n    shuffle = False,\n    class_mode = None,\n    target_size = IMG_SIZE,\n)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:03:42.076844Z","iopub.execute_input":"2024-08-27T02:03:42.077716Z","iopub.status.idle":"2024-08-27T02:05:34.849723Z","shell.execute_reply.started":"2024-08-27T02:03:42.077675Z","shell.execute_reply":"2024-08-27T02:05:34.848858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions = model.predict(test_generator, verbose=1)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:05:39.571019Z","iopub.execute_input":"2024-08-27T02:05:39.571643Z","iopub.status.idle":"2024-08-27T02:10:47.274643Z","shell.execute_reply.started":"2024-08-27T02:05:39.571604Z","shell.execute_reply":"2024-08-27T02:10:47.273787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Convert the predictions to the labels:","metadata":{}},{"cell_type":"code","source":"label_pred = np.where(predictions > 0.5, 1, 0)\nlabel_pred","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:16:12.852971Z","iopub.execute_input":"2024-08-27T02:16:12.853762Z","iopub.status.idle":"2024-08-27T02:16:12.860935Z","shell.execute_reply.started":"2024-08-27T02:16:12.853722Z","shell.execute_reply":"2024-08-27T02:16:12.860035Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Apply predictions to submission dataframe:","metadata":{}},{"cell_type":"code","source":"submission = pd.read_csv('../input/histopathologic-cancer-detection/sample_submission.csv')\nsubmission.label = label_pred\nsubmission.head()","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:16:17.016548Z","iopub.execute_input":"2024-08-27T02:16:17.017390Z","iopub.status.idle":"2024-08-27T02:16:17.082014Z","shell.execute_reply.started":"2024-08-27T02:16:17.017350Z","shell.execute_reply":"2024-08-27T02:16:17.081011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Write dataframe to submission csv file:","metadata":{}},{"cell_type":"code","source":"submission.to_csv('submission.csv', header = True, index = False)","metadata":{"execution":{"iopub.status.busy":"2024-08-27T02:16:20.952280Z","iopub.execute_input":"2024-08-27T02:16:20.952951Z","iopub.status.idle":"2024-08-27T02:16:21.104721Z","shell.execute_reply.started":"2024-08-27T02:16:20.952909Z","shell.execute_reply":"2024-08-27T02:16:21.103831Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Conclusion","metadata":{}},{"cell_type":"markdown","source":"This project aimed to explore the ConvNet models for Histopathologic Cancer Detection. The EfficientNetB0 model achieved 85% average accuracy on the validation dataset, surpassing the baseline model.\n\nWe first build basemodel and achieve decent accuracies for training and validation data set, however, the learning curve shows some degree of overfitting, then we employed reduce overfitting techniques such as enable data augmentation and add dropout layer, these methods show good results on reducing overfitting. \n\nIn order to further improve the accuracy, we select a SOTA EfficientNet model, and apply transfer learning from a pre-trained network, add a new classifier on top of the pretrained model and retrain the model, the result shows significant higher accuracy, while the model demonstrated promising results, further improvements could be achieved by exploring fine-tuning.\n\nAlthough the model shows good accuracy and consistency, there is still fluctuation in accuracy. There may be some noise in the dataset. We can further adopt anomaly detection methods to clean up the anomalies and reduce the fluctuation in accuracy.","metadata":{}},{"cell_type":"markdown","source":"## Reference","metadata":{}},{"cell_type":"markdown","source":"- [Keras Functional API](https://www.tensorflow.org/guide/keras/functional)\n- [Tensorflow: Classification](https://www.tensorflow.org/tutorials/images/classification)\n- [Tensorflow: Data Augmentation](https://www.tensorflow.org/tutorials/images/data_augmentation)\n- [Tensorflow: Transfer learning and fine-tuning](https://www.tensorflow.org/tutorials/images/transfer_learning)\n- [EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks](https://arxiv.org/abs/1905.11946) (ICML 2019)\n- [Meta Pseudo Labels](https://paperswithcode.com/paper/meta-pseudo-labels) (CVPR 2021)","metadata":{}}]}