{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.10","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":13451,"datasetId":654585,"databundleVersionId":1188070}],"dockerImageVersionId":30140,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"**(Version 6)**\n- Training `DenseNet201`\n\n**(Version 7)**\n- Try for 60 epochs\n\n**(Version 11)**\n- Try `ResNext101`\n- Change Bone window to soft\n- Add RadomContrast (Wasn't a good idea, maybe resulted in adding noise to the windowing operation)\n- Two useful notebooks were: [Pytorch ResNext101](https://www.kaggle.com/braquino/pytorch-resnext-32x8d-centercrop), [Keras ResNext101](https://www.kaggle.com/afsan123/keras-resnext50-holdout-split)\n- ++ Other changes which can be explored in version comparing\n- Removing the additional layers\n\n**(Version 12)**\n- Bringing back the additional layers\n\n**(Version 14)**\n- Return to bone windowing (adding soft windowing seems to have some issues)\n\n**(Version 15)**\n- Use soft window\n- Use Densenet201 and make all layers trainable\n- Remove some layers from the augmentation, they may have resulted in noise\n- Modify the last layers of the model (Removed Dense(100) and Dropout)\n- n_samples is 15000\n\n**(Version 16)**\n- Increase the Dropout to 0.5 in the last layer\n- n_samples is 20000\n\n**(Version 17)**\n- Increase the Dropout to 0.5 in the last layer\n- n_samples is 20000\n\n**(Version 18)**\n- Increase the Dropout to 0.8 in the last layer\n- Make the whole model trainable, except the last layer (This worked better surprisingly!) (LR = 0.000125)\n\n\n**(Version 19)**\n- Try `InceptionResnetV2`\n\n**(Version 20)**\n- Freeze the first 15 layers of the model and make the rest trainable\n\n**(Version 23)**\n- The whole model is trainable\n- Return to bone windowing\n- Return to the very first data augmentation layers\n- Try `DenseNet201` again\n\n--------------------------------------------------------------------------------------------------------------\n**(Version 24)**\n- Try `DenseNet121` again\n\n--------------------------------------------------------------------------------------------------------------\n- Try `DenseNet169` again\n--------------------------------------------------------------------------------------------------------------\n- `MobileNetV2` sample size 107933\n\n- MobileNetV1","metadata":{}},{"cell_type":"code","source":"# !pip install image-classifiers\n# # !pip install iterative-stratification","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:13.756273Z","iopub.execute_input":"2026-03-09T16:58:13.756582Z","iopub.status.idle":"2026-03-09T16:58:13.794571Z","shell.execute_reply.started":"2026-03-09T16:58:13.756496Z","shell.execute_reply":"2026-03-09T16:58:13.794069Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport pandas as pd\nimport numpy as np\nimport pydicom\nimport math\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport os\nimport seaborn as sns\nfrom sklearn.metrics import multilabel_confusion_matrix\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' #disable \nimport matplotlib\nmatplotlib.rc('xtick', labelsize=15) \nmatplotlib.rc('ytick', labelsize=15) \nsns.set_style(\"darkgrid\")\nsns.set_context(\"notebook\", font_scale=1.5, rc={\"lines.linewidth\": 4})","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2026-03-09T16:58:13.795704Z","iopub.execute_input":"2026-03-09T16:58:13.796016Z","iopub.status.idle":"2026-03-09T16:58:22.194225Z","shell.execute_reply.started":"2026-03-09T16:58:13.795991Z","shell.execute_reply":"2026-03-09T16:58:22.193609Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"https://www.kaggle.com/afsan123/keras-resnext50-holdout-split#kln-220","metadata":{}},{"cell_type":"code","source":"# from classification_models.tfkeras import Classifiers","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:22.195095Z","iopub.execute_input":"2026-03-09T16:58:22.195269Z","iopub.status.idle":"2026-03-09T16:58:22.198639Z","shell.execute_reply.started":"2026-03-09T16:58:22.195248Z","shell.execute_reply":"2026-03-09T16:58:22.197985Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SEED = 42\nSUBCLASSES = ['epidural', 'intraparenchymal', 'intraventricular', 'subarachnoid', 'subdural']\nSAMPLE_SUBCLASS = 107933\nHU_MIN = 0\nHU_MAX = 100\nIMAGE_SIZE = (224,224)\nBATCH_SIZE = 32\nNUM_CLASSES = 5\nEPOCHS =10 \nMETRICS = [tf.keras.metrics.BinaryAccuracy(), \n           tf.keras.metrics.Precision(),\n           tf.keras.metrics.Recall(),\n           tf.keras.metrics.AUC(),\n           tf.keras.metrics.SpecificityAtSensitivity(0.5),\n           tf.keras.metrics.SensitivityAtSpecificity(0.5)\n          ]","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:22.199644Z","iopub.execute_input":"2026-03-09T16:58:22.199806Z","iopub.status.idle":"2026-03-09T16:58:27.194919Z","shell.execute_reply.started":"2026-03-09T16:58:22.199787Z","shell.execute_reply":"2026-03-09T16:58:27.194366Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_learning_curves(history, metrics_to_plot = ['loss','binary_accuracy', 'precision', 'recall', 'auc']):\n  ncols = 2\n  nrows = math.ceil(len(metrics_to_plot) / 2)\n  if len(metrics_to_plot) <= 2:\n        fig, axes = plt.subplots(nrows,ncols, figsize=(20,10))\n        for i in range(2):\n            axes[i].plot(history.history[metrics_to_plot[i]], label=metrics_to_plot[i] +' (training data)')\n            axes[i].plot(history.history['val_'+metrics_to_plot[i]], label=metrics_to_plot[i] + ' (val data)')\n            axes[i].set_ylabel('Value', fontsize = 20)\n            axes[i].set_xlabel('No. epoch', fontsize = 20)\n            axes[i].legend(prop={'size': 20})\n            axes[i].set_title(metrics_to_plot[i], size = 22)\n  else:        \n      fig, axes = plt.subplots(nrows,ncols, figsize=(15,20))\n\n      for i in range(ncols):\n        for j in range(nrows):\n          metric_idx = j * ncols + i\n          if metric_idx >= len(metrics_to_plot):\n                break\n          axes[j,i].plot(history.history[metrics_to_plot[metric_idx]], label=metrics_to_plot[metric_idx] +' (training data)')\n          axes[j,i].plot(history.history['val_'+metrics_to_plot[metric_idx]], label=metrics_to_plot[metric_idx] + ' (val data)')\n          axes[j,i].set_ylabel('Value', fontsize = 20)\n          axes[j,i].set_xlabel('No. epoch', fontsize = 20)\n          axes[j,i].legend(prop={'size': 20})\n          axes[j,i].set_title(metrics_to_plot[metric_idx], size = 22)\n  plt.tight_layout()\n     ","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:27.196661Z","iopub.execute_input":"2026-03-09T16:58:27.19687Z","iopub.status.idle":"2026-03-09T16:58:27.205865Z","shell.execute_reply.started":"2026-03-09T16:58:27.196846Z","shell.execute_reply":"2026-03-09T16:58:27.205147Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_PATH = '../input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/stage_2_train/'\ntrain_df = pd.read_csv('../input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/stage_2_train.csv')\ntrain_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:27.206832Z","iopub.execute_input":"2026-03-09T16:58:27.207085Z","iopub.status.idle":"2026-03-09T16:58:30.379765Z","shell.execute_reply.started":"2026-03-09T16:58:27.207052Z","shell.execute_reply":"2026-03-09T16:58:30.37908Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label = train_df.Label\ntrain_df = train_df.ID.str.rsplit('_', n=1, expand = True)\ntrain_df['label'] = label\ntrain_df.rename({0:'id', 1: 'subtype'}, axis =1, inplace=True)\ntrain_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:30.380651Z","iopub.execute_input":"2026-03-09T16:58:30.380829Z","iopub.status.idle":"2026-03-09T16:58:38.947531Z","shell.execute_reply.started":"2026-03-09T16:58:30.380808Z","shell.execute_reply":"2026-03-09T16:58:38.946833Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.pivot_table(train_df, index='id', columns='subtype', values = 'label')\ntrain_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:38.948631Z","iopub.execute_input":"2026-03-09T16:58:38.9492Z","iopub.status.idle":"2026-03-09T16:58:45.947826Z","shell.execute_reply.started":"2026-03-09T16:58:38.949163Z","shell.execute_reply":"2026-03-09T16:58:45.947241Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.index = train_df.index.astype(str) + '.dcm'\ntrain_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:45.949175Z","iopub.execute_input":"2026-03-09T16:58:45.949673Z","iopub.status.idle":"2026-03-09T16:58:46.530653Z","shell.execute_reply.started":"2026-03-09T16:58:45.949638Z","shell.execute_reply":"2026-03-09T16:58:46.529828Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"abnormal_df = train_df[train_df['any'] == 1]\nabnormal_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:46.531605Z","iopub.execute_input":"2026-03-09T16:58:46.531794Z","iopub.status.idle":"2026-03-09T16:58:46.553871Z","shell.execute_reply.started":"2026-03-09T16:58:46.531769Z","shell.execute_reply":"2026-03-09T16:58:46.553181Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"abnormal_df.shape","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:46.554938Z","iopub.execute_input":"2026-03-09T16:58:46.555182Z","iopub.status.idle":"2026-03-09T16:58:46.559906Z","shell.execute_reply.started":"2026-03-09T16:58:46.555157Z","shell.execute_reply":"2026-03-09T16:58:46.559236Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sums = abnormal_df.drop(columns=['any']).sum()\nsns.barplot(data=sums, x = sums.index, y = sums.values)\nplt.xticks(ticks = range(5), labels=sums.sort_values().index, rotation=90)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:46.560856Z","iopub.execute_input":"2026-03-09T16:58:46.561241Z","iopub.status.idle":"2026-03-09T16:58:46.811581Z","shell.execute_reply.started":"2026-03-09T16:58:46.561175Z","shell.execute_reply":"2026-03-09T16:58:46.810932Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample = abnormal_df.sample(SAMPLE_SUBCLASS, replace = False, random_state = SEED)\nsample.head()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:46.812417Z","iopub.execute_input":"2026-03-09T16:58:46.812574Z","iopub.status.idle":"2026-03-09T16:58:46.841068Z","shell.execute_reply.started":"2026-03-09T16:58:46.812555Z","shell.execute_reply":"2026-03-09T16:58:46.840338Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample.drop(columns = ['any'], inplace=True)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:46.843562Z","iopub.execute_input":"2026-03-09T16:58:46.843755Z","iopub.status.idle":"2026-03-09T16:58:46.848678Z","shell.execute_reply.started":"2026-03-09T16:58:46.843734Z","shell.execute_reply":"2026-03-09T16:58:46.847994Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sums = sample.sum()\nsns.barplot(data=sums, x = sums.index, y = sums.values)\nplt.xticks(ticks = range(5), labels=sums.sort_values().index, rotation=90)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:46.849599Z","iopub.execute_input":"2026-03-09T16:58:46.849808Z","iopub.status.idle":"2026-03-09T16:58:47.052068Z","shell.execute_reply.started":"2026-03-09T16:58:46.849785Z","shell.execute_reply":"2026-03-09T16:58:47.051472Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train, X_test, y_train, y_test = train_test_split(sample.index, sample, test_size = 0.3, random_state = SEED)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.053073Z","iopub.execute_input":"2026-03-09T16:58:47.053303Z","iopub.status.idle":"2026-03-09T16:58:47.082049Z","shell.execute_reply.started":"2026-03-09T16:58:47.053273Z","shell.execute_reply":"2026-03-09T16:58:47.081527Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train, X_val, y_train, y_val = train_test_split(y_train.index, y_train, test_size = 0.3, random_state = SEED)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.082961Z","iopub.execute_input":"2026-03-09T16:58:47.083206Z","iopub.status.idle":"2026-03-09T16:58:47.104342Z","shell.execute_reply.started":"2026-03-09T16:58:47.083171Z","shell.execute_reply":"2026-03-09T16:58:47.103835Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = y_train\nval_df = y_val\ntest_df = y_test","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.105254Z","iopub.execute_input":"2026-03-09T16:58:47.105499Z","iopub.status.idle":"2026-03-09T16:58:47.10924Z","shell.execute_reply.started":"2026-03-09T16:58:47.105468Z","shell.execute_reply":"2026-03-09T16:58:47.108583Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = (len(sample) / (len(SUBCLASSES) * sample.sum())).values\nclass_weights","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.110228Z","iopub.execute_input":"2026-03-09T16:58:47.110474Z","iopub.status.idle":"2026-03-09T16:58:47.121146Z","shell.execute_reply.started":"2026-03-09T16:58:47.110443Z","shell.execute_reply":"2026-03-09T16:58:47.120465Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Custom loss\n#### Multilabel Loss","metadata":{}},{"cell_type":"code","source":"def np_multilabel_loss(class_weights=None):\n    def single_class_crossentropy(y_true, y_pred):\n        y_true = tf.cast(y_true, tf.float32)\n        y_pred = tf.cast(y_pred, tf.float32)\n        \n        y_pred = tf.where(y_pred > 1-(1e-07), 1-1e-07, y_pred)\n        y_pred = tf.where(y_pred < 1e-07, 1e-07, y_pred)\n        single_class_cross_entropies = - tf.reduce_mean(y_true * tf.math.log(y_pred) + (1-y_true) * tf.math.log(1-y_pred), axis=0)\n\n        if class_weights is None:\n            loss = tf.reduce_mean(single_class_cross_entropies)\n        else:\n            loss = tf.reduce_sum(class_weights*single_class_cross_entropies)\n        return loss\n    return single_class_crossentropy","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.122213Z","iopub.execute_input":"2026-03-09T16:58:47.122389Z","iopub.status.idle":"2026-03-09T16:58:47.12989Z","shell.execute_reply.started":"2026-03-09T16:58:47.122368Z","shell.execute_reply":"2026-03-09T16:58:47.129255Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"METRICS = METRICS + [np_multilabel_loss()]\nMETRICS_NAMES = []\nfor metric in METRICS:\n    if hasattr(metric, 'name'):\n        METRICS_NAMES.append(metric.name)\n    else:\n        METRICS_NAMES.append(metric.__name__)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.130819Z","iopub.execute_input":"2026-03-09T16:58:47.131022Z","iopub.status.idle":"2026-03-09T16:58:47.138827Z","shell.execute_reply.started":"2026-03-09T16:58:47.131Z","shell.execute_reply":"2026-03-09T16:58:47.138241Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","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    \ndef get_first_of_dicom_field_as_int(x):\n    if type(x) == pydicom.multival.MultiValue:\n        return int(x[0])\n    return int(x)\n    \ndef get_windowing(data):\n    dicom_fields = [data[('0028','1050')].value, # window center\n                    data[('0028','1051')].value, # window width\n                    data[('0028','1052')].value, # intercept\n                    data[('0028','1053')].value, # slope\n                   ]\n    return [get_first_of_dicom_field_as_int(x) for x in dicom_fields]\n    \n\ndef get_min_max_of_window_value(window_center, window_width):\n    mini = window_center - (window_width // 2)\n    maxi = window_center + (window_width // 2) \n    return mini, maxi\n\ndef window_image(img, window_center, window_width):\n    try:\n        _,_, intercept, slope = get_windowing(img)\n        img = img.pixel_array * slope + intercept\n        img_min, img_max = get_min_max_of_window_value(window_center, window_width)\n        img[img < img_min] = img_min\n        img[img > img_max] = img_max\n#         img = (img - np.min(img)) / (np.max(img) - np.min(img)) #normalize\n    except:\n        img = img_min * np.ones(IMAGE_SIZE)\n        \n    return img\n\ndef normalize(channel, wc_ww: tuple, norm_type = 'none'):\n    if norm_type.lower() == 'none':\n        return channel\n    if norm_type.lower() == 'min_max':\n        mini, maxi = get_min_max_of_window_value(wc_ww[0], wc_ww[1])\n        resulted_channel = (channel - mini) / (maxi - mini)\n        return resulted_channel\n    \n\ndef bsb_window(img):\n    bsb_config = {'brain': (40,80),\n             'subdural': (80,200),\n             'soft': (50, 350)}\n    brain_img = window_image(img, *bsb_config['brain'])\n    subdural_img = window_image(img,*bsb_config['subdural'])\n    soft_img = window_image(img, *bsb_config['soft'])\n    \n    brain_img = normalize(brain_img, bsb_config['brain'], 'min_max')\n    subdural_img = normalize(subdural_img, bsb_config['subdural'], 'min_max')\n    soft_img = normalize(soft_img, bsb_config['soft'], 'min_max')\n    \n#         print(np.min(soft_img))\n#     brain_img = (brain_img - 0) / 80\n#     subdural_img = (subdural_img - (-20)) / 200\n#     soft_img = (soft_img - (-150)) / 380 # (-150 = 40 - 380 / 2)\n#         print(np.min(soft_img))\n    bsb_img = np.zeros((brain_img.shape[0], brain_img.shape[1],3))\n    bsb_img[:, :, 0] = brain_img\n    bsb_img[:, :, 1] = subdural_img\n    bsb_img[:, :, 2] = soft_img\n    \n    if (np.any(np.isnan(bsb_img))):\n        bsb_img = np.ones((*IMAGE_SIZE,3))\n        \n    return bsb_img\n\n    \nclass ImageGenerator(tf.keras.utils.Sequence):\n    def __init__(self, dataframe,batch_size,shuffle,num_classes = NUM_CLASSES):\n        self.dataframe = dataframe\n        self.num_classes = num_classes\n        self.batch_size = batch_size\n        self.shuffle = shuffle\n        \n    def __len__(self):\n        return math.ceil(len(self.dataframe) / self.batch_size)\n    \n    def __getitem__(self, index):\n        batch_df = self.dataframe.iloc[index * self.batch_size: (index+1) * self.batch_size]\n        paths = TRAIN_PATH + batch_df.index.astype(str)\n        X = np.empty((len(batch_df), *IMAGE_SIZE, 3))\n        y = np.empty((len(batch_df), self.num_classes))\n        for i, path in enumerate(paths):\n            dcm = pydicom.dcmread(path)\n            # correct dcm\n            if (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -100):\n                correct_dcm(dcm)\n#             rescaled_img = rescale_pixelarray(dcm)\n#             windowed_img = set_manual_window(rescaled_img, HU_MIN, HU_MAX)\n#             img = tf.convert_to_tensor(windowed_img, dtype=tf.float32)\n            img = bsb_window(dcm)\n            img = tf.convert_to_tensor(img, dtype=tf.float64)\n#             assert tf.reduce_min(img) >= -1 and tf.reduce_max(img) <= 1, 'Check these values img in (-1,1)'\n            X[i] = tf.image.resize(img, IMAGE_SIZE)\n            y[i] = batch_df.iloc[i].values\n#             assert tf.reduce_min(y[i]) >= 0 and tf.reduce_max(y[i]) <= 1, 'Check target values in (0,1)'\n            \n        return X, y\n    def on_epoch_end(self):\n        if self.shuffle:\n            self.dataframe = self.dataframe.sample(len(self.dataframe), replace = False, random_state = SEED)\n        self.current_epoch += 1","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.139859Z","iopub.execute_input":"2026-03-09T16:58:47.14013Z","iopub.status.idle":"2026-03-09T16:58:47.158281Z","shell.execute_reply.started":"2026-03-09T16:58:47.140099Z","shell.execute_reply":"2026-03-09T16:58:47.157773Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_generator_train = ImageGenerator(train_df, BATCH_SIZE, shuffle=True)\nimg_generator_val = ImageGenerator(val_df, BATCH_SIZE, shuffle = True)\nimg_generator_test = ImageGenerator(test_df, BATCH_SIZE, shuffle = False)\ntrain_data = tf.data.Dataset.from_generator(lambda: map(tuple, img_generator_train), \n                                            output_types=(tf.float64, tf.uint8),\n                                            output_shapes = (\n                                                    tf.TensorShape((None, *IMAGE_SIZE,3)),\n                                                    tf.TensorShape((None, NUM_CLASSES))\n                                            ))\nval_data = tf.data.Dataset.from_generator(lambda: map(tuple, img_generator_val), \n                                          output_types=(tf.float64, tf.uint8),\n                                          output_shapes = (\n                                                    tf.TensorShape((None, *IMAGE_SIZE,3)),\n                                                    tf.TensorShape((None, NUM_CLASSES))\n                                            ))\ntest_data = tf.data.Dataset.from_generator(lambda: map(tuple, img_generator_test), \n                                          output_types=(tf.float64, tf.uint8),\n                                          output_shapes = (\n                                                    tf.TensorShape((None, *IMAGE_SIZE,3)),\n                                                    tf.TensorShape((None, NUM_CLASSES))\n                                            ))\n\n","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.159055Z","iopub.execute_input":"2026-03-09T16:58:47.159263Z","iopub.status.idle":"2026-03-09T16:58:47.245134Z","shell.execute_reply.started":"2026-03-09T16:58:47.159225Z","shell.execute_reply":"2026-03-09T16:58:47.244444Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_augmentation = tf.keras.Sequential([\n   tf.keras.layers.experimental.preprocessing.RandomFlip(\"horizontal\"),\n   tf.keras.layers.experimental.preprocessing.RandomRotation(0.2),\n   tf.keras.layers.experimental.preprocessing.RandomZoom(0.2),\n   tf.keras.layers.experimental.preprocessing.RandomHeight(0.2),\n   tf.keras.layers.experimental.preprocessing.RandomWidth(0.2),  \n])\n\n# ResNext101, preprocess_input = Classifiers.get('resnext101')\n# base_model = ResNext101(IMAGE_SIZE + (3,), weights = 'imagenet', include_top = False)\nbase_model = tf.keras.applications.MobileNet(include_top = False)\nfor layer in base_model.layers:\n    layer.trainable = True\n    \n\ninputs = tf.keras.layers.Input(shape = IMAGE_SIZE + (3,), name = \"input_layer\")\n# x = preprocess_input(inputs)\nx = data_augmentation(inputs)\n\nx = base_model(x)\n\nx = tf.keras.layers.GlobalAveragePooling2D()(x)\nx = tf.keras.layers.Dropout(0.8)(x)\n# x = tf.keras.layers.Dense(100)(x)\n# x = tf.keras.layers.Dropout(0.5)(x)\n\noutputs = tf.keras.layers.Dense(NUM_CLASSES, activation='sigmoid')(x)\nmodel = tf.keras.Model(inputs, outputs)\n\nmodel.compile(loss=np_multilabel_loss(class_weights),\n             optimizer = tf.keras.optimizers.Adam(learning_rate=0.000125),\n             metrics = METRICS)\nprint(model.summary())\nhistory = model.fit(train_data, \n                   epochs = EPOCHS,\n                   validation_data = val_data,\n                   callbacks = [tf.keras.callbacks.ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True, mode='min', save_freq='epoch'),\n                               tf.keras.callbacks.EarlyStopping(restore_best_weights=True, patience=5),\n                               tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss',\n                                           factor=0.5,\n                                           patience=2,\n                                           min_lr=1e-8,\n                                           mode=\"min\")\n                               ]\n)\n\neval_res = model.evaluate(val_data)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T16:58:47.246202Z","iopub.execute_input":"2026-03-09T16:58:47.246461Z","iopub.status.idle":"2026-03-09T22:08:10.984651Z","shell.execute_reply.started":"2026-03-09T16:58:47.246426Z","shell.execute_reply":"2026-03-09T22:08:10.984065Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:08:10.9871Z","iopub.execute_input":"2026-03-09T22:08:10.987725Z","iopub.status.idle":"2026-03-09T22:08:10.997991Z","shell.execute_reply.started":"2026-03-09T22:08:10.987691Z","shell.execute_reply":"2026-03-09T22:08:10.997432Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.evaluate(test_data)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:08:10.998746Z","iopub.execute_input":"2026-03-09T22:08:10.998904Z","iopub.status.idle":"2026-03-09T22:21:28.403864Z","shell.execute_reply.started":"2026-03-09T22:08:10.998884Z","shell.execute_reply":"2026-03-09T22:21:28.403143Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_prob = model.predict(test_data)\npred_prob","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:21:28.405155Z","iopub.execute_input":"2026-03-09T22:21:28.405792Z","iopub.status.idle":"2026-03-09T22:30:50.746999Z","shell.execute_reply.started":"2026-03-09T22:21:28.405748Z","shell.execute_reply":"2026-03-09T22:30:50.746451Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_prob = pd.DataFrame(pred_prob, columns = SUBCLASSES, index = test_df.index)\npred_prob","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.748131Z","iopub.execute_input":"2026-03-09T22:30:50.748397Z","iopub.status.idle":"2026-03-09T22:30:50.812423Z","shell.execute_reply.started":"2026-03-09T22:30:50.748355Z","shell.execute_reply":"2026-03-09T22:30:50.811728Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_values = pd.DataFrame((pred_prob > 0.5).astype(int), columns = SUBCLASSES, index = test_df.index)\npred_values","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.813223Z","iopub.execute_input":"2026-03-09T22:30:50.813407Z","iopub.status.idle":"2026-03-09T22:30:50.831446Z","shell.execute_reply.started":"2026-03-09T22:30:50.813379Z","shell.execute_reply":"2026-03-09T22:30:50.830826Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_values.sum(axis = 1).value_counts()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.83217Z","iopub.execute_input":"2026-03-09T22:30:50.832321Z","iopub.status.idle":"2026-03-09T22:30:50.856818Z","shell.execute_reply.started":"2026-03-09T22:30:50.832302Z","shell.execute_reply":"2026-03-09T22:30:50.855986Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.857707Z","iopub.execute_input":"2026-03-09T22:30:50.857942Z","iopub.status.idle":"2026-03-09T22:30:50.865477Z","shell.execute_reply.started":"2026-03-09T22:30:50.85791Z","shell.execute_reply":"2026-03-09T22:30:50.864762Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_values","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.866415Z","iopub.execute_input":"2026-03-09T22:30:50.86659Z","iopub.status.idle":"2026-03-09T22:30:50.879666Z","shell.execute_reply.started":"2026-03-09T22:30:50.866566Z","shell.execute_reply":"2026-03-09T22:30:50.879051Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_values.sum(axis = 1) == 2","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.880558Z","iopub.execute_input":"2026-03-09T22:30:50.880782Z","iopub.status.idle":"2026-03-09T22:30:50.899569Z","shell.execute_reply.started":"2026-03-09T22:30:50.880751Z","shell.execute_reply":"2026-03-09T22:30:50.898932Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_class_names(row):\n    trues = row == 1\n    return np.array(trues[trues].index)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.90041Z","iopub.execute_input":"2026-03-09T22:30:50.900893Z","iopub.status.idle":"2026-03-09T22:30:50.907745Z","shell.execute_reply.started":"2026-03-09T22:30:50.900858Z","shell.execute_reply":"2026-03-09T22:30:50.90716Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_pred = pred_values.apply(get_class_names, axis = 1)\ny_pred","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:50.908636Z","iopub.execute_input":"2026-03-09T22:30:50.908816Z","iopub.status.idle":"2026-03-09T22:30:55.233349Z","shell.execute_reply.started":"2026-03-09T22:30:50.908795Z","shell.execute_reply":"2026-03-09T22:30:55.232625Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true = test_df.apply(get_class_names, axis = 1)\ny_true","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:55.23422Z","iopub.execute_input":"2026-03-09T22:30:55.234414Z","iopub.status.idle":"2026-03-09T22:30:59.599983Z","shell.execute_reply.started":"2026-03-09T22:30:55.23439Z","shell.execute_reply":"2026-03-09T22:30:59.59941Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_values.to_csv('pred_values.csv')\npred_prob.to_csv('pred_prob.csv')\ntest_df.to_csv('test_df.csv')","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:59.601258Z","iopub.execute_input":"2026-03-09T22:30:59.601521Z","iopub.status.idle":"2026-03-09T22:30:59.864147Z","shell.execute_reply.started":"2026-03-09T22:30:59.601488Z","shell.execute_reply":"2026-03-09T22:30:59.863559Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(history.history[\"lr\"], 'o-')\nplt.title('Learning Rate')\nplt.xlabel('Epochs')\nplt.ylabel('LR')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:30:59.86508Z","iopub.execute_input":"2026-03-09T22:30:59.865287Z","iopub.status.idle":"2026-03-09T22:31:00.152241Z","shell.execute_reply.started":"2026-03-09T22:30:59.865263Z","shell.execute_reply":"2026-03-09T22:31:00.151558Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_confusion_matrix(cm, class_name,\n                          normalize=False,\n                          title='Confusion matrix',\n                          cmap=plt.cm.Blues):8\n\n    # Plot confusion matrix in a beautiful manner\n    fig = plt.figure(figsize=(12, 10))\n    ax= plt.subplot()\n    sns.heatmap(cm, annot=True, ax = ax, fmt = 'g', cmap = cmap); #annot=True to annotate cells\n    # labels, title and ticks\n    ax.set_xlabel('Predicted', fontsize=20)\n    ax.xaxis.set_label_position('bottom')\n    plt.xticks(rotation=0)\n    ax.xaxis.tick_bottom()\n\n    ax.set_ylabel('True', fontsize=20)\n    plt.yticks(rotation=0)\n\n    plt.title(class_name, fontsize=20)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:31:00.153712Z","iopub.execute_input":"2026-03-09T22:31:00.154407Z","iopub.status.idle":"2026-03-09T22:31:00.162233Z","shell.execute_reply.started":"2026-03-09T22:31:00.154311Z","shell.execute_reply":"2026-03-09T22:31:00.16109Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for subclass_name, matrix in zip(SUBCLASSES, multilabel_confusion_matrix(test_df, pred_values)):\n#     print(matrix)\n    plot_confusion_matrix(matrix, subclass_name)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:31:00.162912Z","iopub.status.idle":"2026-03-09T22:31:00.163277Z","shell.execute_reply.started":"2026-03-09T22:31:00.163086Z","shell.execute_reply":"2026-03-09T22:31:00.16311Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(classification_report(test_df, pred_values, target_names = SUBCLASSES))","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:31:00.164478Z","iopub.status.idle":"2026-03-09T22:31:00.16477Z","shell.execute_reply.started":"2026-03-09T22:31:00.164624Z","shell.execute_reply":"2026-03-09T22:31:00.164644Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_learning_curves(history, metrics_to_plot=['loss'] + METRICS_NAMES)","metadata":{"execution":{"iopub.status.busy":"2026-03-09T22:31:00.166098Z","iopub.status.idle":"2026-03-09T22:31:00.166583Z","shell.execute_reply.started":"2026-03-09T22:31:00.166358Z","shell.execute_reply":"2026-03-09T22:31:00.166381Z"},"trusted":true},"outputs":[],"execution_count":null}]}