{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":52254,"databundleVersionId":6863140},{"sourceType":"datasetVersion","sourceId":6211844,"datasetId":3567114,"databundleVersionId":6291180},{"sourceType":"modelInstanceVersion","sourceId":6091,"databundleVersionId":7429345,"modelInstanceId":4623}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<center><img src=\"https://keras.io/img/logo-small.png\" alt=\"Keras logo\" width=\"100\"><br/>\nThis starter notebook is provided by the Keras team.</center>","metadata":{}},{"cell_type":"markdown","source":"# Training Notebook\n\n# RSNA 2023 Abdominal Trauma Detection with [KerasCV](https://github.com/keras-team/keras-cv) and [KerasCore](https://github.com/keras-team/keras-core)\n\nThis notebook walks you through how to train a **Convolutional Neural Network (CNN)** model using Keras (Core and CV) on the RSNA 2023 Abdominal Trauma Detection dataset made available for this competition.\n\nFun fact: This notebook is backend (tensorflow, pytorch, jax) agnostic. Using KerasCV and KerasCore we can choose a backend of our choise! Feel free to read [Keras Core](https://keras.io/keras_core/announcement/) announcement to know more about Keras.\n\nIn this notebook you will learn:\n\n* Loading the data using [`tf.data`](https://www.tensorflow.org/guide/data).\n* Applying augmentations inside the data pipeline.\n* Create the model using KerasCV presets.\n* Train the model.\n* Visualize the training plots.\n\n## Notebooks\n\nFor this competition we have two starter notebook. This notebook (you are reading) trains the model on the dataset, while there lies another notebook that performs inference and submits to the competition.\n\n1. [**Training Kernel**](https://www.kaggle.com/code/aritrag/kerascv-starter-notebook-train)\n2. [**Inference Kernel**](https://www.kaggle.com/code/aritrag/kerascv-starter-notebook-infer)\n\n**Note**: [KerasCV guides](https://keras.io/guides/keras_cv/) is the place to go for a deeper understanding of KerasCV individually.","metadata":{}},{"cell_type":"markdown","source":"# Setup and Imports\n\nWe will need KerasCV for this notebook.\n\nFeel free to use `pip install keras-cv` instead of the installation from github.","metadata":{}},{"cell_type":"markdown","source":"# For Keras V2","metadata":{}},{"cell_type":"code","source":"# ! pip install -q git+https://github.com/keras-team/keras-cv","metadata":{"_kg_hide-output":true,"_kg_hide-input":false,"execution":{"iopub.status.busy":"2024-01-11T18:15:02.24961Z","iopub.execute_input":"2024-01-11T18:15:02.250386Z","iopub.status.idle":"2024-01-11T18:15:02.255263Z","shell.execute_reply.started":"2024-01-11T18:15:02.25035Z","shell.execute_reply":"2024-01-11T18:15:02.254305Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# For Keras V3","metadata":{}},{"cell_type":"code","source":"!pip install -q --upgrade keras-cv tensorflow\n!pip install -q --upgrade keras","metadata":{"execution":{"iopub.status.busy":"2024-01-11T18:15:02.259241Z","iopub.execute_input":"2024-01-11T18:15:02.259793Z","iopub.status.idle":"2024-01-11T18:16:26.459401Z","shell.execute_reply.started":"2024-01-11T18:15:02.259753Z","shell.execute_reply":"2024-01-11T18:16:26.458141Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\n# You can use `tensorflow`, `pytorch`, `jax` here\n# KerasCore makes the notebook backend agnostic :)\nos.environ[\"KERAS_BACKEND\"] = \"jax\"\n\nimport keras_cv\nimport keras # keras_core as keras\nfrom keras import layers\n\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom matplotlib import pyplot as plt\nfrom sklearn.model_selection import train_test_split","metadata":{"execution":{"iopub.status.busy":"2024-01-11T18:16:26.461485Z","iopub.execute_input":"2024-01-11T18:16:26.461832Z","iopub.status.idle":"2024-01-11T18:16:38.139726Z","shell.execute_reply.started":"2024-01-11T18:16:26.461793Z","shell.execute_reply":"2024-01-11T18:16:38.138836Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f\"Keras Version: {keras.__version__}\")","metadata":{"execution":{"iopub.status.busy":"2024-01-11T18:16:38.141017Z","iopub.execute_input":"2024-01-11T18:16:38.141773Z","iopub.status.idle":"2024-01-11T18:16:38.147138Z","shell.execute_reply.started":"2024-01-11T18:16:38.141743Z","shell.execute_reply":"2024-01-11T18:16:38.146256Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Configuration\n\nA particularly good practise is to have a configuration class for your notebooks. This not only keeps your configurations all at a single place but also becomes handy to map the configs to the performance of the model.\n\nPlease play around with the configurations and see how the performance of the model changes.","metadata":{}},{"cell_type":"markdown","source":"## Note on some observations\n\nReference Notebook: https://www.kaggle.com/code/aritrag/eda-train-csv\n\n1. Class Dependencies: Refers to inherent relationships between classes in the analysis.\n2. Complementarity: `bowel_injury` and `bowel_healthy`, as well as `extravasation_injury` and `extravasation_healthy`, are perfectly complementary, with their sum always equal to 1.0.\n3. Simplification: For the model, only `{bowel/extravasation}_injury` will be included, and the corresponding healthy status can be calculated using a sigmoid function.\n4. Softmax: `{kidney/liver/spleen}_{healthy/low/high}` classifications are softmaxed, ensuring their combined probabilities sum up to 1.0 for each organ, simplifying the model while preserving essential information.","metadata":{"_kg_hide-input":true}},{"cell_type":"code","source":"class Config:\n    SEED = 42\n    IMAGE_SIZE = [256, 256]\n    BATCH_SIZE = 64\n    EPOCHS = 10\n    TARGET_COLS  = [\n        \"bowel_injury\", \"extravasation_injury\",\n        \"kidney_healthy\", \"kidney_low\", \"kidney_high\",\n        \"liver_healthy\", \"liver_low\", \"liver_high\",\n        \"spleen_healthy\", \"spleen_low\", \"spleen_high\",\n    ]\n    AUTOTUNE = tf.data.AUTOTUNE\n\nconfig = Config()","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-01-11T18:16:38.149279Z","iopub.execute_input":"2024-01-11T18:16:38.149547Z","iopub.status.idle":"2024-01-11T18:16:38.267451Z","shell.execute_reply.started":"2024-01-11T18:16:38.149524Z","shell.execute_reply":"2024-01-11T18:16:38.266224Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Reproducibility\n\nWe would want this notebook to have reproducible results. Here we set the seed for all the random algorithms so that we can reproduce the experiments each time exactly the same way.","metadata":{"_kg_hide-input":true,"_kg_hide-output":true}},{"cell_type":"code","source":"keras.utils.set_random_seed(seed=config.SEED)","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-01-11T18:16:38.26867Z","iopub.execute_input":"2024-01-11T18:16:38.269455Z","iopub.status.idle":"2024-01-11T18:16:38.279255Z","shell.execute_reply.started":"2024-01-11T18:16:38.26941Z","shell.execute_reply":"2024-01-11T18:16:38.278304Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataset\n\nThe dataset provided in the competition consists of DICOM images. We will not be training on the DICOM images, rather would work on PNG image which are extracted from the DICOM format.\n\n[A helpful resource on the conversion of DICOM to PNG](https://www.kaggle.com/code/radek1/how-to-process-dicom-images-to-pngs)","metadata":{"_kg_hide-input":true}},{"cell_type":"code","source":"BASE_PATH = f\"/kaggle/input/rsna-atd-512x512-png-v2-dataset\"","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-01-11T18:16:38.280588Z","iopub.execute_input":"2024-01-11T18:16:38.280972Z","iopub.status.idle":"2024-01-11T18:16:38.29156Z","shell.execute_reply.started":"2024-01-11T18:16:38.28094Z","shell.execute_reply":"2024-01-11T18:16:38.290662Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Meta Data\n\nThe `train.csv` file contains the following meta information:\n\n- `patient_id`: A unique ID code for each patient.\n- `series_id`: A unique ID code for each scan.\n- `instance_number`: The image number within the scan. The lowest instance number for many series is above zero as the original scans were cropped to the abdomen.\n- `[bowel/extravasation]_[healthy/injury]`: The two injury types with binary targets.\n- `[kidney/liver/spleen]_[healthy/low/high]`: The three injury types with three target levels.\n- `any_injury`: Whether the patient had any injury at all.\n","metadata":{"_kg_hide-input":true}},{"cell_type":"code","source":"# train\ndataframe = pd.read_csv(f\"{BASE_PATH}/train.csv\")\ndataframe[\"image_path\"] = f\"{BASE_PATH}/train_images\"\\\n                    + \"/\" + dataframe.patient_id.astype(str)\\\n                    + \"/\" + dataframe.series_id.astype(str)\\\n                    + \"/\" + dataframe.instance_number.astype(str) +\".png\"\ndataframe = dataframe.drop_duplicates()\n\ndataframe.head(2)","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-01-11T18:16:38.292877Z","iopub.execute_input":"2024-01-11T18:16:38.293256Z","iopub.status.idle":"2024-01-11T18:16:38.487272Z","shell.execute_reply.started":"2024-01-11T18:16:38.293218Z","shell.execute_reply":"2024-01-11T18:16:38.486362Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We split the training dataset into train and validation. This is a common practise in the Machine Learning pipelines. We not only want to train our model, but also want to validate it's training.\n\nA small catch here is that the training and validation data should have an aligned data distribution. Here we handle that by grouping the lables and then splitting the dataset. This ensures an aligned data distribution between the training and the validation splits.","metadata":{"_kg_hide-input":true}},{"cell_type":"code","source":"# Function to handle the split for each group\ndef split_group(group, test_size=0.2):\n    if len(group) == 1:\n        return (group, pd.DataFrame()) if np.random.rand() < test_size else (pd.DataFrame(), group)\n    else:\n        return train_test_split(group, test_size=test_size, random_state=42)\n\n# Initialize the train and validation datasets\ntrain_data = pd.DataFrame()\nval_data = pd.DataFrame()\n\n# Iterate through the groups and split them, handling single-sample groups\nfor _, group in dataframe.groupby(config.TARGET_COLS):\n    train_group, val_group = split_group(group)\n    train_data = pd.concat([train_data, train_group], ignore_index=True)\n    val_data = pd.concat([val_data, val_group], ignore_index=True)","metadata":{"_kg_hide-output":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-01-11T18:16:38.488382Z","iopub.execute_input":"2024-01-11T18:16:38.488649Z","iopub.status.idle":"2024-01-11T18:16:38.572919Z","shell.execute_reply.started":"2024-01-11T18:16:38.488625Z","shell.execute_reply":"2024-01-11T18:16:38.571721Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data.shape, val_data.shape","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-01-11T18:16:38.574345Z","iopub.execute_input":"2024-01-11T18:16:38.574687Z","iopub.status.idle":"2024-01-11T18:16:38.581286Z","shell.execute_reply.started":"2024-01-11T18:16:38.57466Z","shell.execute_reply":"2024-01-11T18:16:38.580228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Pipeline /w tf.data\n\nHere we build the data pipeline using `tf.data`. Using `tf.data` we can map out data to an augmentation pipeline simple by using the ` map` API.\n\nAdding augmentations to the data pipeline is as simple as adding a layer into the list of layers that the `Augmenter` processes.\n\nReference: https://keras.io/api/keras_cv/layers/augmentation/","metadata":{"_kg_hide-input":true}},{"cell_type":"code","source":"def decode_image_and_label(image_path, label):\n    file_bytes = tf.io.read_file(image_path)\n    image = tf.io.decode_png(file_bytes, channels=3, dtype=tf.uint8)\n    image = tf.image.resize(image, config.IMAGE_SIZE, method=\"bilinear\")\n    image = tf.cast(image, tf.float32) / 255.0\n    \n    label = tf.cast(label, tf.float32)\n    #         bowel       fluid       kidney      liver       spleen\n    labels = (label[0:1], label[1:2], label[2:5], label[5:8], label[8:11])\n    \n    return (image, labels)\n\n\ndef apply_augmentation(images, labels):\n    augmenter = keras_cv.layers.Augmenter(\n        [\n            keras_cv.layers.RandomFlip(mode=\"horizontal_and_vertical\"),\n            keras_cv.layers.RandomCutout(height_factor=0.2, width_factor=0.2),\n            \n        ]\n    )\n    return (augmenter(images), labels)\n\n\ndef build_dataset(image_paths, labels):\n    ds = (\n        tf.data.Dataset.from_tensor_slices((image_paths, labels))\n        .map(decode_image_and_label, num_parallel_calls=config.AUTOTUNE)\n        .shuffle(config.BATCH_SIZE * 10)\n        .batch(config.BATCH_SIZE)\n        .map(apply_augmentation, num_parallel_calls=config.AUTOTUNE)\n        .prefetch(config.AUTOTUNE)\n    )\n    return ds","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-01-11T18:16:38.584484Z","iopub.execute_input":"2024-01-11T18:16:38.58485Z","iopub.status.idle":"2024-01-11T18:16:38.59599Z","shell.execute_reply.started":"2024-01-11T18:16:38.584817Z","shell.execute_reply":"2024-01-11T18:16:38.594943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# !**Error**!","metadata":{}},{"cell_type":"code","source":"paths  = train_data.image_path.tolist()\nlabels = train_data[config.TARGET_COLS].values\n\nds = build_dataset(image_paths=paths, labels=labels)\nimages, labels = next(iter(ds))\nimages.shape, [label.shape for label in labels]","metadata":{"execution":{"iopub.status.busy":"2024-01-11T18:16:38.597302Z","iopub.execute_input":"2024-01-11T18:16:38.597682Z","iopub.status.idle":"2024-01-11T18:16:42.811907Z","shell.execute_reply.started":"2024-01-11T18:16:38.597641Z","shell.execute_reply":"2024-01-11T18:16:42.810008Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# No more customizing your plots by hand, KerasCV has your back ;)\nkeras_cv.visualization.plot_image_gallery(\n    images=images,\n    value_range=(0, 1),\n    rows=2,\n    cols=2,\n)","metadata":{"execution":{"iopub.status.busy":"2024-01-11T18:16:42.81301Z","iopub.status.idle":"2024-01-11T18:16:42.813398Z","shell.execute_reply.started":"2024-01-11T18:16:42.813214Z","shell.execute_reply":"2024-01-11T18:16:42.813232Z"},"trusted":true},"execution_count":null,"outputs":[]}]}