{"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,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":30786,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Cancer Detection Using CNN #","metadata":{}},{"cell_type":"markdown","source":"Import Necessary libraries:","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport PIL\nimport tensorflow as tf\nimport os\nimport pandas as pd\nimport seaborn as sns\nfrom pathlib import Path\nimport tifffile as tiff\n\n\n\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, Input, BatchNormalization\nfrom PIL import Image\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\nfrom tensorflow.keras.optimizers import Adam\n\n\nfrom sklearn.model_selection import train_test_split\nfrom keras_tuner import HyperModel\nfrom keras_tuner import RandomSearch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:26.737646Z","iopub.execute_input":"2025-07-08T15:46:26.738269Z","iopub.status.idle":"2025-07-08T15:46:26.744137Z","shell.execute_reply.started":"2025-07-08T15:46:26.738229Z","shell.execute_reply":"2025-07-08T15:46:26.743255Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load Data and Exploratory Analysis ##\n\nOur data set consists of 220,025 images of metastatic tissue in histopathologic scans of lymph node sections. In an accompanying .csv file, each image is identified with an \"id\" and labeled as either containing tumor tissue (1) or not containing tumor tissue (0). We will see that the dataset is complete (no null values) and no repeat values. We will also look at the distribution of tumor and non-tumor images in the training data.\n    Our challenge is to train a model that can predict whether there is tumor tissue in each of the \"test\" images.","metadata":{"execution":{"iopub.status.busy":"2024-11-14T22:00:48.265083Z","iopub.execute_input":"2024-11-14T22:00:48.266078Z","iopub.status.idle":"2024-11-14T22:00:48.295056Z","shell.execute_reply.started":"2024-11-14T22:00:48.266030Z","shell.execute_reply":"2024-11-14T22:00:48.293665Z"}}},{"cell_type":"code","source":"df_labels = pd.read_csv('/kaggle/input/histopathologic-cancer-detection/train_labels.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:31.099341Z","iopub.execute_input":"2025-07-08T15:46:31.099647Z","iopub.status.idle":"2025-07-08T15:46:31.530613Z","shell.execute_reply.started":"2025-07-08T15:46:31.099623Z","shell.execute_reply":"2025-07-08T15:46:31.529862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_labels.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:31.743451Z","iopub.execute_input":"2025-07-08T15:46:31.744003Z","iopub.status.idle":"2025-07-08T15:46:31.775677Z","shell.execute_reply.started":"2025-07-08T15:46:31.743966Z","shell.execute_reply":"2025-07-08T15:46:31.774788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(df_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:33.710752Z","iopub.execute_input":"2025-07-08T15:46:33.711102Z","iopub.status.idle":"2025-07-08T15:46:33.716549Z","shell.execute_reply.started":"2025-07-08T15:46:33.711074Z","shell.execute_reply":"2025-07-08T15:46:33.715664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_labels.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:34.570055Z","iopub.execute_input":"2025-07-08T15:46:34.570905Z","iopub.status.idle":"2025-07-08T15:46:34.600848Z","shell.execute_reply.started":"2025-07-08T15:46:34.570872Z","shell.execute_reply":"2025-07-08T15:46:34.600117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sns.countplot(x='label', data=df_labels)\nplt.title('Class Distribution')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:36.077580Z","iopub.execute_input":"2025-07-08T15:46:36.077918Z","iopub.status.idle":"2025-07-08T15:46:36.391196Z","shell.execute_reply.started":"2025-07-08T15:46:36.077889Z","shell.execute_reply":"2025-07-08T15:46:36.390468Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We can see that there are slightly more non-cancerous images than cancerous images. ","metadata":{"execution":{"iopub.status.busy":"2024-11-23T17:58:34.326155Z","iopub.execute_input":"2024-11-23T17:58:34.326681Z","iopub.status.idle":"2024-11-23T17:58:34.334798Z","shell.execute_reply.started":"2024-11-23T17:58:34.326634Z","shell.execute_reply":"2024-11-23T17:58:34.333346Z"}}},{"cell_type":"code","source":"base_path = Path(\"/kaggle/input/histopathologic-cancer-detection/train\")\nfirst_file = next(base_path.iterdir(), None)\nimg = tiff.imread(first_file)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:39.160991Z","iopub.execute_input":"2025-07-08T15:46:39.161335Z","iopub.status.idle":"2025-07-08T15:46:48.143078Z","shell.execute_reply.started":"2025-07-08T15:46:39.161302Z","shell.execute_reply":"2025-07-08T15:46:48.142363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:48.144285Z","iopub.execute_input":"2025-07-08T15:46:48.144507Z","iopub.status.idle":"2025-07-08T15:46:48.150151Z","shell.execute_reply.started":"2025-07-08T15:46:48.144484Z","shell.execute_reply":"2025-07-08T15:46:48.149273Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Each image file is 96 by 96 pixels with a RGB tuple associated with each pixel. The RGB values are on a scale of 1 to 255, so they will need standardization. ","metadata":{}},{"cell_type":"markdown","source":"### Visualize Image Files ###","metadata":{}},{"cell_type":"code","source":"for i in range(0, 4):\n    label = str(df_labels['label'][i])\n    id = df_labels['id'][i]\n    path = base_path / id \n    img = tiff.imread(path.with_suffix(\".tif\"))\n    plt.imshow(img)\n    plt.title('Label: ' + label)\n    plt.show()\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T15:46:48.151469Z","iopub.execute_input":"2025-07-08T15:46:48.151683Z","iopub.status.idle":"2025-07-08T15:46:48.884127Z","shell.execute_reply.started":"2025-07-08T15:46:48.151662Z","shell.execute_reply":"2025-07-08T15:46:48.883261Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Prepare Dataset ###","metadata":{}},{"cell_type":"markdown","source":"We add a \"filename\" column in the \"df_labels\" dataframe so the image files can be accessed by the ImageDataGenerator. When we run the data through the ImageDataGenerator it does a few different things: 1. It efficiently streams our image data into the model without using a large amount of memory. 2. It preprocesses the data, standardizing the RGB values to a decimal value. and 3. Integrates well with the Keras model methods that we will be using.","metadata":{}},{"cell_type":"code","source":"df_labels['filename'] = df_labels['id'] + '.tif'\ndf_labels['filename'] = df_labels['filename'].astype(str)\ndf_labels['label'] = df_labels['label'].astype(str)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:37.453892Z","iopub.execute_input":"2025-07-08T16:15:37.454732Z","iopub.status.idle":"2025-07-08T16:15:37.500396Z","shell.execute_reply.started":"2025-07-08T16:15:37.454698Z","shell.execute_reply":"2025-07-08T16:15:37.499662Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_labels.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:38.434724Z","iopub.execute_input":"2025-07-08T16:15:38.435506Z","iopub.status.idle":"2025-07-08T16:15:38.444286Z","shell.execute_reply.started":"2025-07-08T16:15:38.435473Z","shell.execute_reply":"2025-07-08T16:15:38.443340Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# We take a small, stratified subset in order to debug (etc) code\ndf_subset, _ = train_test_split(\n    df_labels,\n    train_size=5000,\n    stratify=df_labels['label'],\n    random_state=42\n)\n\n# Split into train and validation sets (e.g., 80/20)\ndf_train_basic, df_val_basic = train_test_split(\n    df_subset,\n    test_size=0.2,\n    stratify=df_subset['label'],\n    random_state=42\n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:40.150003Z","iopub.execute_input":"2025-07-08T16:15:40.151121Z","iopub.status.idle":"2025-07-08T16:15:40.434226Z","shell.execute_reply.started":"2025-07-08T16:15:40.151085Z","shell.execute_reply":"2025-07-08T16:15:40.433272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path_train = \"/kaggle/input/histopathologic-cancer-detection/train/\"\npath_test = \"/kaggle/input/histopathologic-cancer-detection/test/\"\ntrain_datagen = ImageDataGenerator(rescale=1 / 255.0)\nval_datagen = ImageDataGenerator(rescale=1 / 255.0)\n\ntest_datagen = ImageDataGenerator(rescale=1 / 255.0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:42.309854Z","iopub.execute_input":"2025-07-08T16:15:42.310465Z","iopub.status.idle":"2025-07-08T16:15:42.314831Z","shell.execute_reply.started":"2025-07-08T16:15:42.310426Z","shell.execute_reply":"2025-07-08T16:15:42.314018Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 32\ntrain_generator_basic = train_datagen.flow_from_dataframe(\n    dataframe= df_train_basic,\n    directory=path_train,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=(96, 96),\n    batch_size=batch_size,\n    class_mode=\"binary\",\n    shuffle=True,\n    seed=42\n)\nvalid_generator_basic = val_datagen.flow_from_dataframe(\n    dataframe=df_val_basic,\n    directory=path_train,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=(96, 96),\n    batch_size=batch_size,\n    class_mode=\"binary\",\n    shuffle=False,\n    seed=42\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:43.211737Z","iopub.execute_input":"2025-07-08T16:15:43.212584Z","iopub.status.idle":"2025-07-08T16:15:49.529344Z","shell.execute_reply.started":"2025-07-08T16:15:43.212550Z","shell.execute_reply":"2025-07-08T16:15:49.528399Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Architecture #","metadata":{}},{"cell_type":"markdown","source":"We will be using a convulational neural network. First we will train a basic model, then move on to a more complex architecture to extract more features and ideally produce stronger results. ","metadata":{}},{"cell_type":"code","source":"def prepare_basic_model():\n    model = Sequential([\n    Input(shape=(96, 96, 3)),\n    Conv2D(32, (3, 3), activation='relu'),\n    MaxPooling2D((2, 2)),\n    Flatten(),\n    Dense(16, activation='relu'),\n    Dense(1, activation='sigmoid')])\n    model.compile(\n        optimizer='adam',\n        loss=\"binary_crossentropy\",\n        metrics=[tf.keras.metrics.AUC(name='auc')])\n    return model\nmodel = prepare_basic_model()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:54.851607Z","iopub.execute_input":"2025-07-08T16:15:54.852047Z","iopub.status.idle":"2025-07-08T16:15:54.885245Z","shell.execute_reply.started":"2025-07-08T16:15:54.852016Z","shell.execute_reply":"2025-07-08T16:15:54.884605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:56.786680Z","iopub.execute_input":"2025-07-08T16:15:56.787403Z","iopub.status.idle":"2025-07-08T16:15:56.804178Z","shell.execute_reply.started":"2025-07-08T16:15:56.787368Z","shell.execute_reply":"2025-07-08T16:15:56.803157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stop = EarlyStopping(monitor='val_auc', mode = 'max', patience=5, restore_best_weights=True, verbose = 1)\nlr_schedule = ReduceLROnPlateau(monitor='val_auc', mode='max', factor=0.5, patience=2, min_lr=1e-6, verbose=1)\n\ncallbacks = [early_stop, lr_schedule]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:58.421538Z","iopub.execute_input":"2025-07-08T16:15:58.422152Z","iopub.status.idle":"2025-07-08T16:15:58.426512Z","shell.execute_reply.started":"2025-07-08T16:15:58.422117Z","shell.execute_reply":"2025-07-08T16:15:58.425630Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(valid_generator_basic)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:15:59.311454Z","iopub.execute_input":"2025-07-08T16:15:59.312294Z","iopub.status.idle":"2025-07-08T16:15:59.317394Z","shell.execute_reply.started":"2025-07-08T16:15:59.312250Z","shell.execute_reply":"2025-07-08T16:15:59.316434Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"steps_per_epoch = len(train_generator_basic)\nvalidation_steps = len(valid_generator_basic)\n\nhistory = model.fit(train_generator_basic,\n                    validation_data = valid_generator_basic,\n                    #steps_per_epoch = steps_per_epoch,\n                    #validation_steps = validation_steps,\n                    callbacks=callbacks, \n                    epochs=15)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:16:03.636802Z","iopub.execute_input":"2025-07-08T16:16:03.637177Z","iopub.status.idle":"2025-07-08T16:17:27.960164Z","shell.execute_reply.started":"2025-07-08T16:16:03.637147Z","shell.execute_reply":"2025-07-08T16:17:27.959476Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"score = model.evaluate(\n    valid_generator_basic,  \n    steps=len(valid_generator_basic),  \n    verbose=1 \n)\n\nprint('Test loss:', score[0])\nprint('Test auc:', score[1])\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:20:58.598801Z","iopub.execute_input":"2025-07-08T16:20:58.599402Z","iopub.status.idle":"2025-07-08T16:21:00.900502Z","shell.execute_reply.started":"2025-07-08T16:20:58.599370Z","shell.execute_reply":"2025-07-08T16:21:00.899653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(history.history['auc'], label='Training AUC')\nplt.plot(history.history['val_auc'], label='Validation AUC')\nplt.title('AUC Over Epochs')\nplt.xlabel('Epoch')\nplt.ylabel('AUC')\nplt.legend()\nplt.grid(True)\nplt.show()\n\nplt.plot(history.history['loss'], label='Training Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.title('Loss Over Epochs')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:27:07.712577Z","iopub.execute_input":"2025-07-08T16:27:07.713450Z","iopub.status.idle":"2025-07-08T16:27:08.090725Z","shell.execute_reply.started":"2025-07-08T16:27:07.713413Z","shell.execute_reply":"2025-07-08T16:27:08.089978Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Results:","metadata":{}},{"cell_type":"markdown","source":"We can see that the simple model did quite well. The very high training auc and lack of decrease in validation loss might be indicative of overfitting. This is probably because I trained it on only a small portion of the data. I did this to make sure the pipeline and the code was working properly. Once I increased the size of the data subset, the model actually performed much worse, getting repeatedly stalled on a fairly high validation loss. I will not show all the results of the training with more data and instead move to more advanced architectures that handle higher volumes of training data much better.","metadata":{}},{"cell_type":"markdown","source":"## Advanced Model:","metadata":{}},{"cell_type":"markdown","source":"I will look at two different more advanced architectures before tuning and applying the better model. First, I take a large subset of the image data to train and validate then models. I have decided to use two architectures: a simplified VGG (Visual Geometry Group) Networks architecture (Oxford, 2014) and a simplified version of the AlexNet Architecture (Krizhevsky et al, 2012). ","metadata":{}},{"cell_type":"code","source":"df_subset, _ = train_test_split(\n    df_labels,\n    train_size=20000,\n    stratify=df_labels['label'],\n    random_state=42\n)\n\ndf_train, df_val = train_test_split(\n    df_subset,\n    test_size=0.2,\n    stratify=df_subset['label'],\n    random_state=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:27:46.135350Z","iopub.execute_input":"2025-07-08T16:27:46.136064Z","iopub.status.idle":"2025-07-08T16:27:46.447553Z","shell.execute_reply.started":"2025-07-08T16:27:46.136030Z","shell.execute_reply":"2025-07-08T16:27:46.446842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 32\ntrain_generator = train_datagen.flow_from_dataframe(\n    dataframe= df_train,\n    directory=path_train,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=(96, 96),\n    batch_size=batch_size,\n    class_mode=\"binary\",\n    shuffle=True,\n    seed=42\n)\nvalid_generator = val_datagen.flow_from_dataframe(\n    dataframe=df_val,\n    directory=path_train,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=(96, 96),\n    batch_size=batch_size,\n    class_mode=\"binary\",\n    shuffle=False,\n    seed=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:28:14.870036Z","iopub.execute_input":"2025-07-08T16:28:14.870923Z","iopub.status.idle":"2025-07-08T16:29:23.105184Z","shell.execute_reply.started":"2025-07-08T16:28:14.870886Z","shell.execute_reply":"2025-07-08T16:29:23.104532Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"def mini_vgg(input_shape=(96, 96, 3)):\n    model = Sequential()\n\n    model.add(Conv2D(32, (3,3), activation='relu', padding='same', input_shape=input_shape))\n    model.add(Conv2D(32, (3,3), activation='relu', padding='same'))\n    model.add(MaxPooling2D(pool_size=(2,2)))\n\n    model.add(Conv2D(64, (3,3), activation='relu', padding='same'))\n    model.add(Conv2D(64, (3,3), activation='relu', padding='same'))\n    model.add(MaxPooling2D(pool_size=(2,2)))\n\n    model.add(Flatten())\n    model.add(Dense(128, activation='relu'))\n    model.add(Dropout(0.5))\n    model.add(Dense(1, activation='sigmoid'))\n\n    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=[tf.keras.metrics.AUC(name='auc')])\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:29:30.382465Z","iopub.execute_input":"2025-07-08T16:29:30.382831Z","iopub.status.idle":"2025-07-08T16:29:30.390136Z","shell.execute_reply.started":"2025-07-08T16:29:30.382782Z","shell.execute_reply":"2025-07-08T16:29:30.389182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"vgg_model = mini_vgg()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:31:18.808839Z","iopub.execute_input":"2025-07-08T16:31:18.809166Z","iopub.status.idle":"2025-07-08T16:31:19.177765Z","shell.execute_reply.started":"2025-07-08T16:31:18.809138Z","shell.execute_reply":"2025-07-08T16:31:19.177124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"vgg_history = vgg_model.fit(train_generator,\n                    validation_data = valid_generator,\n                    callbacks=callbacks, \n                    epochs=15)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:31:23.712966Z","iopub.execute_input":"2025-07-08T16:31:23.713292Z","iopub.status.idle":"2025-07-08T16:39:28.090546Z","shell.execute_reply.started":"2025-07-08T16:31:23.713266Z","shell.execute_reply":"2025-07-08T16:39:28.089837Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"score = vgg_model.evaluate(\n    valid_generator,  \n    steps=len(valid_generator),  \n    verbose=1 \n)\n\nprint('Test loss:', score[0])\nprint('Test auc:', score[1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:40:18.717747Z","iopub.execute_input":"2025-07-08T16:40:18.718478Z","iopub.status.idle":"2025-07-08T16:40:26.623298Z","shell.execute_reply.started":"2025-07-08T16:40:18.718444Z","shell.execute_reply":"2025-07-08T16:40:26.622427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_alexnet(input_shape=(96, 96, 3), dropout_rate=0.5, learning_rate=1e-3):\n    model = Sequential([\n        Input(shape=input_shape),\n\n        Conv2D(32, (11, 11), strides=(4, 4), activation='relu', padding='same'),\n        MaxPooling2D(pool_size=(2, 2)),\n        BatchNormalization(),\n\n        Conv2D(64, (5, 5), activation='relu', padding='same'),\n        MaxPooling2D(pool_size=(2, 2)),\n        BatchNormalization(),\n\n        Conv2D(192, (3, 3), activation='relu', padding='same'),\n        Conv2D(192, (3, 3), activation='relu', padding='same'),\n        Conv2D(128, (3, 3), activation='relu', padding='same'),\n        MaxPooling2D(pool_size=(2, 2)),\n\n        Flatten(),\n        Dense(1024, activation='relu'),\n        Dropout(dropout_rate),\n\n        Dense(512, activation='relu'),\n        Dropout(dropout_rate),\n\n        Dense(1, activation='sigmoid')  # Binary classification\n    ])\n\n    model.compile(\n        optimizer=Adam(learning_rate=learning_rate),\n        loss='binary_crossentropy',\n        metrics=[tf.keras.metrics.AUC(name='auc')]\n    )\n\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:47:54.593779Z","iopub.execute_input":"2025-07-08T16:47:54.594442Z","iopub.status.idle":"2025-07-08T16:47:54.601305Z","shell.execute_reply.started":"2025-07-08T16:47:54.594408Z","shell.execute_reply":"2025-07-08T16:47:54.600462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"alexnet_model = build_alexnet()\nalexnet_model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:47:57.063063Z","iopub.execute_input":"2025-07-08T16:47:57.063712Z","iopub.status.idle":"2025-07-08T16:47:57.163411Z","shell.execute_reply.started":"2025-07-08T16:47:57.063681Z","shell.execute_reply":"2025-07-08T16:47:57.162627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"alexnet_history = alexnet_model.fit(train_generator,\n                    validation_data = valid_generator,\n                    callbacks=callbacks, \n                    epochs=15)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T16:58:26.770882Z","iopub.execute_input":"2025-07-08T16:58:26.771264Z","iopub.status.idle":"2025-07-08T17:01:57.802527Z","shell.execute_reply.started":"2025-07-08T16:58:26.771233Z","shell.execute_reply":"2025-07-08T17:01:57.801588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"score = alexnet_model.evaluate(\n    valid_generator,  \n    steps=len(valid_generator),  \n    verbose=1 \n)\n\nprint('Test loss:', score[0])\nprint('Test auc:', score[1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T17:02:54.264712Z","iopub.execute_input":"2025-07-08T17:02:54.265403Z","iopub.status.idle":"2025-07-08T17:03:02.129048Z","shell.execute_reply.started":"2025-07-08T17:02:54.265368Z","shell.execute_reply":"2025-07-08T17:03:02.128061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(alexnet_history.history['auc'], label='Training AUC')\nplt.plot(alexnet_history.history['val_auc'], label='Validation AUC')\nplt.title('AUC Over Epochs')\nplt.xlabel('Epoch')\nplt.ylabel('AUC')\nplt.legend()\nplt.grid(True)\nplt.show()\n\nplt.plot(alexnet_history.history['loss'], label='Training Loss')\nplt.plot(alexnet_history.history['val_loss'], label='Validation Loss')\nplt.title('Loss Over Epochs')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T17:03:13.439594Z","iopub.execute_input":"2025-07-08T17:03:13.439953Z","iopub.status.idle":"2025-07-08T17:03:13.996219Z","shell.execute_reply.started":"2025-07-08T17:03:13.439920Z","shell.execute_reply":"2025-07-08T17:03:13.995336Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Based on the higher validation auc, we are going to focus on the VGG architecture for tuning.","metadata":{}},{"cell_type":"markdown","source":"# Hyperparamter Tuning","metadata":{}},{"cell_type":"markdown","source":"I already had early stopping and learning rate scheduler enabled in the fitting of the model, but I still have to do more hyperparameter tuning to improve the performance of the model. I will use the hyperband tuner to find the best hyperparameters.","metadata":{}},{"cell_type":"code","source":"pip install keras-tuner --upgrade\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T17:03:39.889473Z","iopub.execute_input":"2025-07-08T17:03:39.889804Z","iopub.status.idle":"2025-07-08T17:03:52.378005Z","shell.execute_reply.started":"2025-07-08T17:03:39.889775Z","shell.execute_reply":"2025-07-08T17:03:52.376906Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport keras_tuner as kt\n\ndef build_mini_vgg(hp):\n    model = Sequential()\n    \n    # First Conv block\n    model.add(Conv2D(\n        filters=hp.Choice('conv1_filters', [32, 64]),\n        kernel_size=(3, 3),\n        activation='relu',\n        padding='same',\n        input_shape=(96, 96, 3)))\n    \n    model.add(Conv2D(\n        filters=hp.Choice('conv2_filters', [32, 64]),\n        kernel_size=(3, 3),\n        activation='relu',\n        padding='same'))\n    \n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    \n    # Second Conv block\n    model.add(Conv2D(\n        filters=hp.Choice('conv3_filters', [64, 128]),\n        kernel_size=(3, 3),\n        activation='relu',\n        padding='same'))\n    \n    model.add(Conv2D(\n        filters=hp.Choice('conv4_filters', [64, 128]),\n        kernel_size=(3, 3),\n        activation='relu',\n        padding='same'))\n    \n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    \n    # Dense block\n    model.add(Flatten())\n    model.add(Dense(\n        units=hp.Choice('dense_units', [64, 128, 256]),\n        activation='relu'))\n    model.add(Dropout(\n        rate=hp.Float('dropout_rate', 0.3, 0.6, step=0.1)))\n    \n    model.add(Dense(1, activation='sigmoid'))\n    \n    # Compile\n    learning_rate = hp.Float('learning_rate', 1e-4, 1e-2, sampling='log')\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),\n        loss='binary_crossentropy',\n        metrics=[tf.keras.metrics.AUC(name='auc')]\n    )\n    \n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T17:04:54.963429Z","iopub.execute_input":"2025-07-08T17:04:54.964648Z","iopub.status.idle":"2025-07-08T17:04:54.980100Z","shell.execute_reply.started":"2025-07-08T17:04:54.964596Z","shell.execute_reply":"2025-07-08T17:04:54.979320Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import (\n    Input, Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization\n)\nfrom tensorflow.keras.optimizers import Adam\n\ndef build_hp_model(hp):\n    model = Sequential()\n    model.add(Input(shape=(96, 96, 3)))\n\n    model.add(Conv2D(\n        filters=hp.Choice('conv1_filters', [32, 64, 96]),\n        kernel_size=(11, 11),\n        strides=(4, 4),\n        activation='relu',\n        padding='same'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    model.add(BatchNormalization())\n\n    model.add(Conv2D(\n        filters=hp.Choice('conv2_filters', [128, 192]),\n        kernel_size=(5, 5),\n        activation='relu',\n        padding='same'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    model.add(BatchNormalization())\n\n    model.add(Conv2D(\n        filters=hp.Choice('conv3_filters', [192, 256]),\n        kernel_size=(3, 3),\n        activation='relu',\n        padding='same'))\n    model.add(Conv2D(192, (3, 3), activation='relu', padding='same'))\n    model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n\n    model.add(Flatten())\n\n    model.add(Dense(\n        units=hp.Choice('dense1_units', [512, 1024]),\n        activation='relu'))\n    model.add(Dropout(hp.Float('dropout1', 0.3, 0.6, step=0.1)))\n\n    # Dense Layer 2\n    model.add(Dense(\n        units=hp.Choice('dense2_units', [256, 512]),\n        activation='relu'))\n    model.add(Dropout(hp.Float('dropout2', 0.3, 0.6, step=0.1)))\n\n    # Output\n    model.add(Dense(1, activation='sigmoid'))\n\n    # Compile\n    model.compile(\n        optimizer=Adam(learning_rate=hp.Choice('learning_rate', [1e-3, 5e-4, 1e-4])),\n        loss='binary_crossentropy',\n        metrics=[tf.keras.metrics.AUC(name='auc')]\n    )\n\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T04:02:47.780082Z","iopub.execute_input":"2025-07-08T04:02:47.780677Z","iopub.status.idle":"2025-07-08T04:02:47.790749Z","shell.execute_reply.started":"2025-07-08T04:02:47.780644Z","shell.execute_reply":"2025-07-08T04:02:47.789818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from keras_tuner import Hyperband\n\ntuner = Hyperband(\n    build_mini_vgg,\n    objective='val_auc',\n    max_epochs=15,              \n    factor=3,                   \n    directory='vgg_hyperband_2',\n    project_name='cancer_detection'\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T17:05:51.516709Z","iopub.execute_input":"2025-07-08T17:05:51.517509Z","iopub.status.idle":"2025-07-08T17:05:51.612547Z","shell.execute_reply.started":"2025-07-08T17:05:51.517474Z","shell.execute_reply":"2025-07-08T17:05:51.611649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stop = keras.callbacks.EarlyStopping(\n    monitor='val_auc',\n    patience=3,\n    restore_best_weights=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T17:05:55.303677Z","iopub.execute_input":"2025-07-08T17:05:55.304301Z","iopub.status.idle":"2025-07-08T17:05:55.308354Z","shell.execute_reply.started":"2025-07-08T17:05:55.304265Z","shell.execute_reply":"2025-07-08T17:05:55.307391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tuner.search(\n    train_generator,\n    validation_data=valid_generator,\n    epochs=15,\n    callbacks=[early_stop]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T17:07:08.658177Z","iopub.execute_input":"2025-07-08T17:07:08.658871Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_model = tuner.get_best_models(num_models=1)[0]\nbest_hp = tuner.get_best_hyperparameters(1)[0]\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T04:58:02.781445Z","iopub.execute_input":"2025-07-08T04:58:02.782309Z","iopub.status.idle":"2025-07-08T04:58:04.432467Z","shell.execute_reply.started":"2025-07-08T04:58:02.782252Z","shell.execute_reply":"2025-07-08T04:58:04.431424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T04:58:06.905363Z","iopub.execute_input":"2025-07-08T04:58:06.905739Z","iopub.status.idle":"2025-07-08T04:58:06.933866Z","shell.execute_reply.started":"2025-07-08T04:58:06.905705Z","shell.execute_reply":"2025-07-08T04:58:06.933183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train_final, df_val_final = train_test_split(\n    df_labels,\n    test_size=0.2,\n    stratify=df_labels['label'],\n    random_state=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T04:58:14.343684Z","iopub.execute_input":"2025-07-08T04:58:14.344406Z","iopub.status.idle":"2025-07-08T04:58:14.610398Z","shell.execute_reply.started":"2025-07-08T04:58:14.344372Z","shell.execute_reply":"2025-07-08T04:58:14.609634Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 32\ntrain_generator_final = train_datagen.flow_from_dataframe(\n    dataframe= df_train_final,\n    directory=path_train,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=(96, 96),\n    batch_size=batch_size,\n    class_mode=\"binary\",\n    shuffle=True,\n    seed=42\n)\nvalid_generator_final = val_datagen.flow_from_dataframe(\n    dataframe=df_val_final,\n    directory=path_train,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=(96, 96),\n    batch_size=batch_size,\n    class_mode=\"binary\",\n    shuffle=False,\n    seed=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T04:58:22.586873Z","iopub.execute_input":"2025-07-08T04:58:22.587237Z","iopub.status.idle":"2025-07-08T05:06:28.810165Z","shell.execute_reply.started":"2025-07-08T04:58:22.587205Z","shell.execute_reply":"2025-07-08T05:06:28.809189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stop_final = keras.callbacks.EarlyStopping(\n    monitor='val_auc',\n    patience=5,\n    restore_best_weights=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T05:07:21.136524Z","iopub.execute_input":"2025-07-08T05:07:21.136905Z","iopub.status.idle":"2025-07-08T05:07:21.141463Z","shell.execute_reply.started":"2025-07-08T05:07:21.136869Z","shell.execute_reply":"2025-07-08T05:07:21.140437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]\nmodel_final = build_hp_model(best_hps)  \n\nfrom tensorflow.keras.callbacks import ModelCheckpoint\n\ncheckpoint_cb = ModelCheckpoint(\n    filepath='best_model.keras',     \n    save_best_only=True,\n    monitor='val_auc',               \n    mode='max'\n)\n\n\nhistory_final = model_final.fit(train_generator_final, \n                    validation_data=valid_generator_final,\n                    epochs=20,\n                    callbacks=early_stop_final)  ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T05:07:31.727850Z","iopub.execute_input":"2025-07-08T05:07:31.728478Z","iopub.status.idle":"2025-07-08T06:30:20.254073Z","shell.execute_reply.started":"2025-07-08T05:07:31.728441Z","shell.execute_reply":"2025-07-08T06:30:20.253310Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(history_final.history['auc'], label='Training Auc')\nplt.plot(history_final.history['val_auc'], label='Validation Auc')\nplt.title('Auc Over Epochs')\nplt.xlabel('Epoch')\nplt.ylabel('Auc')\nplt.legend()\nplt.grid(True)\nplt.show()\n\nplt.plot(history_final.history['loss'], label='Training Loss')\nplt.plot(history_final.history['val_loss'], label='Validation Loss')\nplt.title('Loss Over Epochs')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T13:58:14.747229Z","iopub.status.idle":"2025-07-08T13:58:14.747754Z","shell.execute_reply.started":"2025-07-08T13:58:14.747467Z","shell.execute_reply":"2025-07-08T13:58:14.747492Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Test For Kaggle","metadata":{}},{"cell_type":"code","source":"test_dir = '/kaggle/input/histopathologic-cancer-detection/test'\ntest_filenames = os.listdir(test_dir)\n\ntest_df = pd.DataFrame({'id': [f.split('.')[0] for f in test_filenames]})\ntest_df['filename'] = test_df['id'] + '.tif'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_generator = test_datagen.flow_from_dataframe(\n    dataframe=test_df,\n    directory=test_dir,\n    x_col=\"filename\",\n    y_col = None, \n    target_size=(96, 96),\n    batch_size=batch_size,\n    color_mode = \"rgb\",\n    class_mode= None,\n    shuffle=False,\n    seed=42\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\npreds = model_final.predict(test_generator, verbose=1)\n\nbinary_preds = (preds > 0.5).astype(int).flatten()\n\nsubmission = pd.DataFrame({\n    'id': test_df['id'],\n    'label': binary_preds\n})\n\nsubmission.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}}]}