{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":39272,"databundleVersionId":4629629,"sourceType":"competition"},{"sourceId":4619402,"sourceType":"datasetVersion","datasetId":2688773},{"sourceId":4619805,"sourceType":"datasetVersion","datasetId":2688675}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport time\nimport random \nimport pathlib     \nimport itertools \nfrom glob import *  \nfrom tqdm import *\n\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nsns.set_style('darkgrid')\n\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nfrom skimage.color import *          \nfrom skimage.morphology import *     \nfrom skimage.transform import *       \nfrom sklearn.model_selection import * \nfrom skimage.io import *         \nfrom sklearn.metrics import *         \nfrom sklearn.utils import *         \nfrom sklearn.preprocessing import *\n\nimport tensorflow as tf                  \nfrom tensorflow import keras              \nfrom tensorflow.keras import *               \nfrom tensorflow.keras import backend as K    \nfrom tensorflow.keras.models import *     \nfrom tensorflow.keras.preprocessing.image import * \nfrom tensorflow.keras.optimizers import *    \nfrom tensorflow.keras.callbacks import *   \nfrom tensorflow.keras.layers import *      \nfrom tensorflow.keras.applications import *  \nfrom tensorflow.keras.utils import * ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:08:51.727858Z","iopub.execute_input":"2025-10-27T13:08:51.728113Z","iopub.status.idle":"2025-10-27T13:09:08.376254Z","shell.execute_reply.started":"2025-10-27T13:08:51.728087Z","shell.execute_reply":"2025-10-27T13:09:08.375673Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.377744Z","iopub.execute_input":"2025-10-27T13:09:08.378163Z","iopub.status.idle":"2025-10-27T13:09:08.38206Z","shell.execute_reply.started":"2025-10-27T13:09:08.378144Z","shell.execute_reply":"2025-10-27T13:09:08.381297Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm -rf /kaggle/working/*","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.38283Z","iopub.execute_input":"2025-10-27T13:09:08.383075Z","iopub.status.idle":"2025-10-27T13:09:08.529187Z","shell.execute_reply.started":"2025-10-27T13:09:08.383053Z","shell.execute_reply":"2025-10-27T13:09:08.528388Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Read the CSV File\n\nNext, we get the paths to access the image data and csv data. The csv data include various information about patients, such as biopsy, malignant cancer, and invasive cancer.","metadata":{}},{"cell_type":"code","source":"RSNA_512_path = '/kaggle/input/rsna-breast-cancer-512-pngs'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.530015Z","iopub.execute_input":"2025-10-27T13:09:08.530218Z","iopub.status.idle":"2025-10-27T13:09:08.53421Z","shell.execute_reply.started":"2025-10-27T13:09:08.5302Z","shell.execute_reply":"2025-10-27T13:09:08.533428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train = pd.read_csv('/kaggle/input/rsna-breast-cancer-detection/train.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.534853Z","iopub.execute_input":"2025-10-27T13:09:08.53505Z","iopub.status.idle":"2025-10-27T13:09:08.821581Z","shell.execute_reply.started":"2025-10-27T13:09:08.535035Z","shell.execute_reply":"2025-10-27T13:09:08.820961Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Từ bệnh nhân đã sinh thiết nhưng không bị ung thư ác tính – phù hợp cho trường hợp nghi ngờ có ung thư**","metadata":{}},{"cell_type":"code","source":"#Lọc dữ liệu\nDF_train = df_train[df_train['biopsy'] == 1].reset_index(drop = True)\nprint(len(DF_train))\nDF_train.sample(20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.822354Z","iopub.execute_input":"2025-10-27T13:09:08.822577Z","iopub.status.idle":"2025-10-27T13:09:08.862945Z","shell.execute_reply.started":"2025-10-27T13:09:08.822559Z","shell.execute_reply":"2025-10-27T13:09:08.862323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Cân bằng dữ liệu\nDF_train = DF_train.groupby(['cancer']).apply(lambda x: x.sample(1500, replace = True)).reset_index(drop = True)\nprint('New Data Size:', DF_train.shape[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.864957Z","iopub.execute_input":"2025-10-27T13:09:08.865172Z","iopub.status.idle":"2025-10-27T13:09:08.880426Z","shell.execute_reply.started":"2025-10-27T13:09:08.865149Z","shell.execute_reply":"2025-10-27T13:09:08.879812Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DF_train['cancer'].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.881156Z","iopub.execute_input":"2025-10-27T13:09:08.881388Z","iopub.status.idle":"2025-10-27T13:09:08.892637Z","shell.execute_reply.started":"2025-10-27T13:09:08.881372Z","shell.execute_reply":"2025-10-27T13:09:08.892074Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Create the Path to Each Image","metadata":{}},{"cell_type":"code","source":"# Create the path to each image.\nfor i in range(len(DF_train)):\n    DF_train.loc[i, 'path'] = os.path.join(RSNA_512_path + '/' + str(DF_train.loc[i, 'patient_id']) + '_' + str(DF_train.loc[i, 'image_id']) + '.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:08.893262Z","iopub.execute_input":"2025-10-27T13:09:08.893433Z","iopub.status.idle":"2025-10-27T13:09:09.383406Z","shell.execute_reply.started":"2025-10-27T13:09:08.89342Z","shell.execute_reply":"2025-10-27T13:09:09.382844Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Chia Training và Validation\n\nThis time we divide the data into training and validation data, because test data originally exist.","metadata":{}},{"cell_type":"code","source":"train_datagen = ImageDataGenerator(\n    rescale = 1./255.,\n    rotation_range=10,       # xoay nhẹ\n    width_shift_range=0.05,  # dịch ngang\n    height_shift_range=0.05, # dịch dọc\n    zoom_range=0.1,          # phóng to/thu nhỏ nhẹ\n    horizontal_flip=True,    # lật ảnh (nếu hợp lý)\n    fill_mode='nearest')\n\nval_datagen = ImageDataGenerator(rescale = 1./255.,)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:09.384119Z","iopub.execute_input":"2025-10-27T13:09:09.38437Z","iopub.status.idle":"2025-10-27T13:09:09.388494Z","shell.execute_reply.started":"2025-10-27T13:09:09.384345Z","shell.execute_reply":"2025-10-27T13:09:09.38788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, val_df = train_test_split(DF_train, \n                                 test_size = 0.1, \n                                 stratify = DF_train[['cancer']])\n#Tạo df chứa bình thường\ntrain_df_normal = train_df[train_df['cancer'] == 0].reset_index(drop = True)\nval_df_normal = val_df[val_df['cancer'] == 0].reset_index(drop = True)\n#Tạo df chứa ut\ntrain_df_cancer = train_df[train_df['cancer'] == 1].reset_index(drop = True)\nval_df_cancer = val_df[val_df['cancer'] == 1].reset_index(drop = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:09.389204Z","iopub.execute_input":"2025-10-27T13:09:09.389426Z","iopub.status.idle":"2025-10-27T13:09:09.423387Z","shell.execute_reply.started":"2025-10-27T13:09:09.389405Z","shell.execute_reply":"2025-10-27T13:09:09.422644Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Create Image data Generators\n\nThe dataset has already been divided into train and validation datasets, and each dataset includes normal and cancer image files. Thus, image data generators were easily created. ","metadata":{}},{"cell_type":"code","source":"import shutil\n# Khai báo destination directory.\ndestination_dir = '/kaggle/working/train'\ndestination_dir_sub = '/kaggle/working/train/normal'\n\n# Tạo nếu destination directory chưa tồn tại.\nif not os.path.exists(destination_dir):\n    os.makedirs(destination_dir)\n\nif not os.path.exists(destination_dir_sub):\n    os.makedirs(destination_dir_sub)   \n    \n# Copy ảnh vào destination directory.\nfor path in train_df_normal['path']:\n    shutil.copy2(path, destination_dir_sub)\n\n\ndestination_dir = '/kaggle/working/train'\ndestination_dir_sub = '/kaggle/working/train/cancer'\n\n# Create the destination directory if it doesn't exist.\nif not os.path.exists(destination_dir):\n    os.makedirs(destination_dir)\n\nif not os.path.exists(destination_dir_sub):\n    os.makedirs(destination_dir_sub)   \n    \n# Copy the images to the destination directory.\nfor path in train_df_cancer['path']:\n    shutil.copy2(path, destination_dir_sub)\n\n\n# Define the destination directory.\ndestination_dir = '/kaggle/working/val'\ndestination_dir_sub = '/kaggle/working/val/normal'\n\n# Create the destination directory if it doesn't exist.\nif not os.path.exists(destination_dir):\n    os.makedirs(destination_dir)\n\nif not os.path.exists(destination_dir_sub):\n    os.makedirs(destination_dir_sub)   \n    \n# Copy the images to the destination directory.\nfor path in val_df_normal['path']:\n    shutil.copy2(path, destination_dir_sub)\n\n\n# Define the destination directory.\ndestination_dir = '/kaggle/working/val'\ndestination_dir_sub = '/kaggle/working/val/cancer'\n\n# Create the destination directory if it doesn't exist.\nif not os.path.exists(destination_dir):\n    os.makedirs(destination_dir)\n\nif not os.path.exists(destination_dir_sub):\n    os.makedirs(destination_dir_sub)   \n    \n# Copy the images to the destination directory.\nfor path in val_df_cancer['path']:\n    shutil.copy2(path, destination_dir_sub)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:09:09.424101Z","iopub.execute_input":"2025-10-27T13:09:09.424331Z","iopub.status.idle":"2025-10-27T13:10:34.756373Z","shell.execute_reply.started":"2025-10-27T13:09:09.424315Z","shell.execute_reply":"2025-10-27T13:10:34.755799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_path = '/kaggle/working/train'\nval_path = '/kaggle/working/val'\n\ntrain_generator = train_datagen.flow_from_directory(\n    train_path,\n    target_size = (256, 256),\n    batch_size = 16,\n    class_mode = 'binary',\n    shuffle = False\n)\nvalidation_generator = val_datagen.flow_from_directory(\n    val_path,\n    target_size = (256, 256),\n    batch_size = 16,\n    class_mode = 'binary',\n    shuffle = False\n)\n\ntrain_directory_iterator = train_generator\nvalidation_directory_iterator = validation_generator\n\ndef segmentation_generator(directory_iterator):\n    while True:\n        images, labels = next(directory_iterator)\n        masks = np.ones((labels.shape[0], IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.float32)\n        masks *= labels[:, None, None, None]\n        yield images, masks\n\ntrain_steps_per_epoch = len(train_directory_iterator)\nval_steps_per_epoch = len(validation_directory_iterator)\n\ntrain_generator = segmentation_generator(train_directory_iterator)\nvalidation_generator = segmentation_generator(validation_directory_iterator)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.757161Z","iopub.execute_input":"2025-10-27T13:10:34.757371Z","iopub.status.idle":"2025-10-27T13:10:34.790078Z","shell.execute_reply.started":"2025-10-27T13:10:34.757355Z","shell.execute_reply":"2025-10-27T13:10:34.789375Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Define the Model (Transfer Learning)","metadata":{}},{"cell_type":"code","source":"def mean_iou(y_true, y_pred):\n    y_true = tf.cast(y_true, tf.float32)\n    y_pred = tf.clip_by_value(y_pred, 0, 1)\n    y_pred = tf.cast(y_pred >= 0.5, tf.float32)\n\n    intersection = tf.reduce_sum(y_true * y_pred, axis=[1, 2, 3])\n    union = tf.reduce_sum(y_true + y_pred, axis=[1, 2, 3]) - intersection\n\n    iou = tf.math.divide_no_nan(intersection, union)\n    return tf.reduce_mean(iou)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.790856Z","iopub.execute_input":"2025-10-27T13:10:34.791071Z","iopub.status.idle":"2025-10-27T13:10:34.795528Z","shell.execute_reply.started":"2025-10-27T13:10:34.791047Z","shell.execute_reply":"2025-10-27T13:10:34.794783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dice_loss(y_true, y_pred):\n    smooth = 1e-6\n    y_true_f = tf.keras.backend.flatten(y_true)\n    y_pred_f = tf.keras.backend.flatten(y_pred)\n    intersection = tf.reduce_sum(y_true_f * y_pred_f)\n    return 1 - ((2. * intersection + smooth) / (tf.reduce_sum(y_true_f) + tf.reduce_sum(y_pred_f) + smooth))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.796196Z","iopub.execute_input":"2025-10-27T13:10:34.796483Z","iopub.status.idle":"2025-10-27T13:10:34.809046Z","shell.execute_reply.started":"2025-10-27T13:10:34.796466Z","shell.execute_reply":"2025-10-27T13:10:34.808338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.losses import BinaryFocalCrossentropy\nfocal_loss = BinaryFocalCrossentropy(gamma=2.0)\n\ndef dice_focal_loss(y_true, y_pred):\n    # Dice Loss\n    smooth = 1e-6\n    y_true_f = tf.cast(y_true, tf.float32)\n    y_pred_f = tf.cast(y_pred, tf.float32)\n    intersection = tf.reduce_sum(y_true_f * y_pred_f)\n    dice = (2. * intersection + smooth) / (tf.reduce_sum(y_true_f) + tf.reduce_sum(y_pred_f) + smooth)\n    dice_loss = 1 - dice\n    \n    # Focal Loss\n    focal = focal_loss(y_true, y_pred)\n    \n    # Tổng hợp 2 loss (có thể điều chỉnh trọng số)\n    return 0.5 * dice_loss + 0.5 * focal\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.809867Z","iopub.execute_input":"2025-10-27T13:10:34.810102Z","iopub.status.idle":"2025-10-27T13:10:34.823557Z","shell.execute_reply.started":"2025-10-27T13:10:34.81008Z","shell.execute_reply":"2025-10-27T13:10:34.82301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dice_coef(y_true, y_pred, smooth=1):\n    y_true_f = tf.reshape(y_true, [-1])\n    y_pred_f = tf.reshape(y_pred, [-1])\n    intersection = tf.reduce_sum(y_true_f * y_pred_f)\n    return (2. * intersection + smooth) / (tf.reduce_sum(y_true_f) + tf.reduce_sum(y_pred_f) + smooth)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.824192Z","iopub.execute_input":"2025-10-27T13:10:34.824413Z","iopub.status.idle":"2025-10-27T13:10:34.839433Z","shell.execute_reply.started":"2025-10-27T13:10:34.824393Z","shell.execute_reply":"2025-10-27T13:10:34.838891Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Build U-Net model\nIMG_HEIGHT = 256\nIMG_WIDTH = 256\nIMG_CHANNELS = 3\n\ndef unet(input_shape=(IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS)):\n    inputs = Input(shape=input_shape)\n    s = Lambda(lambda x: x / 255.0)(inputs)\n\n    c1 = Conv2D(16, (3, 3), kernel_initializer='he_normal', padding='same')(s)\n    c1 = BatchNormalization()(c1)\n    c1 = Activation('elu')(c1)\n    c1 = Dropout(0.05)(c1)\n    c1 = Conv2D(16, (3, 3), kernel_initializer='he_normal', padding='same')(c1)\n    c1 = BatchNormalization()(c1)\n    c1 = Activation('elu')(c1)\n    p1 = MaxPooling2D((2, 2))(c1)\n\n    c2 = Conv2D(32, (3, 3), kernel_initializer='he_normal', padding='same')(p1)\n    c2 = BatchNormalization()(c2)\n    c2 = Activation('elu')(c2)\n    c2 = Dropout(0.05)(c2)\n    c2 = Conv2D(32, (3, 3), kernel_initializer='he_normal', padding='same')(c2)\n    c2 = BatchNormalization()(c2)\n    c2 = Activation('elu')(c2)\n    p2 = MaxPooling2D((2, 2))(c2)\n\n    c3 = Conv2D(64, (3, 3), kernel_initializer='he_normal', padding='same')(p2)\n    c3 = BatchNormalization()(c3)\n    c3 = Activation('elu')(c3)\n    c3 = Dropout(0.05)(c3)\n    c3 = Conv2D(64, (3, 3), kernel_initializer='he_normal', padding='same')(c3)\n    c3 = BatchNormalization()(c3)\n    c3 = Activation('elu')(c3)\n    p3 = MaxPooling2D((2, 2))(c3)\n\n    c4 = Conv2D(128, (3, 3), kernel_initializer='he_normal', padding='same')(p3)\n    c4 = BatchNormalization()(c4)\n    c4 = Activation('elu')(c4)\n    c4 = Dropout(0.05)(c4)\n    c4 = Conv2D(128, (3, 3), kernel_initializer='he_normal', padding='same')(c4)\n    c4 = BatchNormalization()(c4)\n    c4 = Activation('elu')(c4)\n    p4 = MaxPooling2D((2, 2))(c4)\n\n    c5 = Conv2D(256, (3, 3), kernel_initializer='he_normal', padding='same')(p4)\n    c5 = BatchNormalization()(c5)\n    c5 = Activation('elu')(c5)\n    c5 = Dropout(0.05)(c5)\n    c5 = Conv2D(256, (3, 3), kernel_initializer='he_normal', padding='same')(c5)\n    c5 = BatchNormalization()(c5)\n    c5 = Activation('elu')(c5)\n\n    # --- Decoder ---\n    u6 = Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(c5)\n    u6 = Concatenate()([u6, c4])\n    c6 = Conv2D(128, (3, 3), kernel_initializer='he_normal', padding='same')(u6)\n    c6 = BatchNormalization()(c6)\n    c6 = Activation('elu')(c6)\n    c6 = Dropout(0.05)(c6)\n    c6 = Conv2D(128, (3, 3), kernel_initializer='he_normal', padding='same')(c6)\n    c6 = BatchNormalization()(c6)\n    c6 = Activation('elu')(c6)\n\n    u7 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(c6)\n    u7 = Concatenate()([u7, c3])\n    c7 = Conv2D(64, (3, 3), kernel_initializer='he_normal', padding='same')(u7)\n    c7 = BatchNormalization()(c7)\n    c7 = Activation('elu')(c7)\n    c7 = Dropout(0.05)(c7)\n    c7 = Conv2D(64, (3, 3), kernel_initializer='he_normal', padding='same')(c7)\n    c7 = BatchNormalization()(c7)\n    c7 = Activation('elu')(c7)\n\n    u8 = Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(c7)\n    u8 = Concatenate()([u8, c2])\n    c8 = Conv2D(32, (3, 3), kernel_initializer='he_normal', padding='same')(u8)\n    c8 = BatchNormalization()(c8)\n    c8 = Activation('elu')(c8)\n    c8 = Dropout(0.05)(c8)\n    c8 = Conv2D(32, (3, 3), kernel_initializer='he_normal', padding='same')(c8)\n    c8 = BatchNormalization()(c8)\n    c8 = Activation('elu')(c8)\n\n    u9 = Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same')(c8)\n    u9 = Concatenate()([u9, c1])\n    c9 = Conv2D(16, (3, 3), kernel_initializer='he_normal', padding='same')(u9)\n    c9 = BatchNormalization()(c9)\n    c9 = Activation('elu')(c9)\n    c9 = Dropout(0.05)(c9)\n    c9 = Conv2D(16, (3, 3), kernel_initializer='he_normal', padding='same')(c9)\n    c9 = BatchNormalization()(c9)\n    c9 = Activation('elu')(c9)\n\n    outputs = Conv2D(1, (1, 1), activation='sigmoid')(c9)\n\n    model = Model(inputs=inputs, outputs=outputs)\n    model.compile(\n        optimizer=Adam(learning_rate=1e-4),\n        loss='binary_crossentropy',\n        metrics=[\n            'accuracy',\n            metrics.AUC(name='auc'),\n            metrics.Recall(name='recall'),\n            metrics.Precision(name='precision'),\n            mean_iou,\n            dice_coef\n        ]\n    )\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.840183Z","iopub.execute_input":"2025-10-27T13:10:34.84042Z","iopub.status.idle":"2025-10-27T13:10:34.856415Z","shell.execute_reply.started":"2025-10-27T13:10:34.840401Z","shell.execute_reply":"2025-10-27T13:10:34.855721Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_HEIGHT = 256\nIMG_WIDTH  = 256\nIMG_CHANNELS = 3\nnum_labels = 1  #Binary\ninput_shape = (IMG_HEIGHT,IMG_WIDTH,IMG_CHANNELS)\nbatch_size = 16","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.857281Z","iopub.execute_input":"2025-10-27T13:10:34.857464Z","iopub.status.idle":"2025-10-27T13:10:34.871822Z","shell.execute_reply.started":"2025-10-27T13:10:34.857444Z","shell.execute_reply":"2025-10-27T13:10:34.871239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def initial_block(inputs, num_filters):\n#     conv = tf.keras.layers.Conv2D(num_filters, 3, padding=\"same\")(inputs)\n#     conv = tf.keras.layers.BatchNormalization()(conv)\n#     conv = tf.keras.layers.Activation(\"relu\")(conv)\n#     return conv\n\n# def encoder_block(inputs, num_filters):\n#     shortcut = inputs\n#     conv = tf.keras.layers.Conv2D(num_filters, 3, padding=\"same\")(inputs)\n#     conv = tf.keras.layers.BatchNormalization()(conv)\n#     conv = tf.keras.layers.Activation(\"relu\")(conv)\n#     conv = tf.keras.layers.Conv2D(num_filters, 3, padding=\"same\")(conv)\n#     conv = tf.keras.layers.BatchNormalization()(conv)\n\n#     shortcut = tf.keras.layers.Conv2D(num_filters, 1, padding=\"same\")(shortcut)\n#     shortcut = tf.keras.layers.BatchNormalization()(shortcut)\n\n#     conv = tf.keras.layers.Add()([conv, shortcut])\n#     conv = tf.keras.layers.Activation(\"relu\")(conv)\n\n#     return conv\n\n# def downsampling_block(inputs, num_filters):\n#     shortcut = inputs\n#     conv = tf.keras.layers.Conv2D(num_filters, 3, padding=\"same\", strides=2)(inputs)\n#     conv = tf.keras.layers.BatchNormalization()(conv)\n#     conv = tf.keras.layers.Activation(\"relu\")(conv)\n#     conv = tf.keras.layers.Conv2D(num_filters, 3, padding=\"same\")(conv)\n#     conv = tf.keras.layers.BatchNormalization()(conv)\n\n#     shortcut = tf.keras.layers.Conv2D(num_filters, 1, padding=\"same\", strides=2)(shortcut)\n#     shortcut = tf.keras.layers.BatchNormalization()(shortcut)\n\n#     conv = tf.keras.layers.Add()([conv, shortcut])\n#     conv = tf.keras.layers.Activation(\"relu\")(conv)\n\n#     return conv\n\n# def decoder_block(inputs, skip, num_filters):\n#     up_conv = tf.keras.layers.Conv2DTranspose(num_filters, (2,2), strides=2, padding=\"same\")(inputs)\n#     conv = tf.keras.layers.Concatenate()([up_conv, skip])\n#     conv = encoder_block(conv, num_filters)\n#     return conv\n\n# def LinkNet(input_shape, num_classes=1):\n#     inputs = tf.keras.layers.Input(input_shape)\n\n#     initial = initial_block(inputs, 64)\n\n#     # Encoder\n#     down1 = downsampling_block(initial, 64)\n#     down2 = downsampling_block(down1, 128)\n#     down3 = downsampling_block(down2, 256)\n#     down4 = downsampling_block(down3, 512)\n\n#     # Decoder\n#     up4 = decoder_block(down4, down3, 256)\n#     up3 = decoder_block(up4, down2, 128)\n#     up2 = decoder_block(up3, down1, 64)\n#     up1 = decoder_block(up2, initial, 64)\n\n#     outputs = tf.keras.layers.Conv2D(num_classes, 1, activation=\"sigmoid\")(up1)\n\n#     model = tf.keras.models.Model(inputs, outputs, name=\"LinkNet\")\n#     return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.872439Z","iopub.execute_input":"2025-10-27T13:10:34.872638Z","iopub.status.idle":"2025-10-27T13:10:34.885411Z","shell.execute_reply.started":"2025-10-27T13:10:34.872601Z","shell.execute_reply":"2025-10-27T13:10:34.884829Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Attention Block ","metadata":{}},{"cell_type":"code","source":"def conv_block(x, filter_size, size, dropout, batch_norm=False):\n    \n    conv = layers.Conv2D(size, (filter_size, filter_size), padding=\"same\")(x)\n    if batch_norm is True:\n        conv = layers.BatchNormalization(axis=3)(conv)\n    conv = layers.Activation(\"relu\")(conv)\n\n    conv = layers.Conv2D(size, (filter_size, filter_size), padding=\"same\")(conv)\n    if batch_norm is True:\n        conv = layers.BatchNormalization(axis=3)(conv)\n    conv = layers.Activation(\"relu\")(conv)\n    \n    if dropout > 0:\n        conv = layers.Dropout(dropout)(conv)\n\n    return conv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.88611Z","iopub.execute_input":"2025-10-27T13:10:34.886394Z","iopub.status.idle":"2025-10-27T13:10:34.90345Z","shell.execute_reply.started":"2025-10-27T13:10:34.88637Z","shell.execute_reply":"2025-10-27T13:10:34.902791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def gating_signal(input, out_size, batch_norm=False):\n    \"\"\"\n    resize the down layer feature map into the same dimension as the up layer feature map\n    using 1x1 conv\n    :return: the gating feature map with the same dimension of the up layer feature map\n    \"\"\"\n    x = layers.Conv2D(out_size, (1, 1), padding='same')(input)\n    if batch_norm:\n        x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.907382Z","iopub.execute_input":"2025-10-27T13:10:34.90764Z","iopub.status.idle":"2025-10-27T13:10:34.91879Z","shell.execute_reply.started":"2025-10-27T13:10:34.9076Z","shell.execute_reply":"2025-10-27T13:10:34.918063Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def repeat_elem(tensor, rep):\n    # lambda function to repeat Repeats the elements of a tensor along an axis\n    #by a factor of rep.\n    # If tensor has shape (None, 256,256,3), lambda will return a tensor of shape \n    #(None, 256,256,6), if specified axis=3 and rep=2.\n\n     return layers.Lambda(lambda x, repnum: K.repeat_elements(x, repnum, axis=3),\n                          arguments={'repnum': rep})(tensor)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.919452Z","iopub.execute_input":"2025-10-27T13:10:34.919681Z","iopub.status.idle":"2025-10-27T13:10:34.931965Z","shell.execute_reply.started":"2025-10-27T13:10:34.919663Z","shell.execute_reply":"2025-10-27T13:10:34.931434Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def attention_block(x, gating, inter_shape):\n    shape_x = K.int_shape(x)\n    shape_g = K.int_shape(gating)\n\n# Getting the x signal to the same shape as the gating signal\n    theta_x = layers.Conv2D(inter_shape, (2, 2), strides=(2, 2), padding='same')(x)  # 16\n    shape_theta_x = K.int_shape(theta_x)\n\n# Getting the gating signal to the same number of filters as the inter_shape\n    phi_g = layers.Conv2D(inter_shape, (1, 1), padding='same')(gating)\n    upsample_g = layers.Conv2DTranspose(inter_shape, (3, 3),\n                                 strides=(shape_theta_x[1] // shape_g[1], shape_theta_x[2] // shape_g[2]),\n                                 padding='same')(phi_g)  # 16\n\n    concat_xg = layers.add([upsample_g, theta_x])\n    act_xg = layers.Activation('relu')(concat_xg)\n    psi = layers.Conv2D(1, (1, 1), padding='same')(act_xg)\n    sigmoid_xg = layers.Activation('sigmoid')(psi)\n    shape_sigmoid = K.int_shape(sigmoid_xg)\n    upsample_psi = layers.UpSampling2D(size=(shape_x[1] // shape_sigmoid[1], shape_x[2] // shape_sigmoid[2]))(sigmoid_xg)  # 32\n\n    upsample_psi = repeat_elem(upsample_psi, shape_x[3])\n\n    y = layers.multiply([upsample_psi, x])\n\n    result = layers.Conv2D(shape_x[3], (1, 1), padding='same')(y)\n    result_bn = layers.BatchNormalization()(result)\n    return result_bn","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.932551Z","iopub.execute_input":"2025-10-27T13:10:34.932738Z","iopub.status.idle":"2025-10-27T13:10:34.949229Z","shell.execute_reply.started":"2025-10-27T13:10:34.932724Z","shell.execute_reply":"2025-10-27T13:10:34.948654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def Attention_UNet(input_shape, NUM_CLASSES=1, dropout_rate=0.0, batch_norm=True):\n    '''\n    Attention UNet, \n    \n    '''\n    # network structure\n    FILTER_NUM = 64 # number of basic filters for the first layer\n    FILTER_SIZE = 3 # size of the convolutional filter\n    UP_SAMP_SIZE = 2 # size of upsampling filters\n    \n    inputs = layers.Input(input_shape, dtype=tf.float32)\n\n    # Downsampling layers\n    # DownRes 1, convolution + pooling\n    conv_128 = conv_block(inputs, FILTER_SIZE, FILTER_NUM, dropout_rate, batch_norm)\n    pool_64 = layers.MaxPooling2D(pool_size=(2,2))(conv_128)\n    # DownRes 2\n    conv_64 = conv_block(pool_64, FILTER_SIZE, 2*FILTER_NUM, dropout_rate, batch_norm)\n    pool_32 = layers.MaxPooling2D(pool_size=(2,2))(conv_64)\n    # DownRes 3\n    conv_32 = conv_block(pool_32, FILTER_SIZE, 4*FILTER_NUM, dropout_rate, batch_norm)\n    pool_16 = layers.MaxPooling2D(pool_size=(2,2))(conv_32)\n    # DownRes 4\n    conv_16 = conv_block(pool_16, FILTER_SIZE, 8*FILTER_NUM, dropout_rate, batch_norm)\n    pool_8 = layers.MaxPooling2D(pool_size=(2,2))(conv_16)\n    # DownRes 5, convolution only\n    conv_8 = conv_block(pool_8, FILTER_SIZE, 16*FILTER_NUM, dropout_rate, batch_norm)\n\n    # Upsampling layers\n    # UpRes 6, attention gated concatenation + upsampling + double residual convolution\n    gating_16 = gating_signal(conv_8, 8*FILTER_NUM, batch_norm)\n    att_16 = attention_block(conv_16, gating_16, 8*FILTER_NUM)\n    up_16 = layers.UpSampling2D(size=(UP_SAMP_SIZE, UP_SAMP_SIZE), data_format=\"channels_last\")(conv_8)\n    up_16 = layers.concatenate([up_16, att_16], axis=3)\n    up_conv_16 = conv_block(up_16, FILTER_SIZE, 8*FILTER_NUM, dropout_rate, batch_norm)\n    # UpRes 7\n    gating_32 = gating_signal(up_conv_16, 4*FILTER_NUM, batch_norm)\n    att_32 = attention_block(conv_32, gating_32, 4*FILTER_NUM)\n    up_32 = layers.UpSampling2D(size=(UP_SAMP_SIZE, UP_SAMP_SIZE), data_format=\"channels_last\")(up_conv_16)\n    up_32 = layers.concatenate([up_32, att_32], axis=3)\n    up_conv_32 = conv_block(up_32, FILTER_SIZE, 4*FILTER_NUM, dropout_rate, batch_norm)\n    # UpRes 8\n    gating_64 = gating_signal(up_conv_32, 2*FILTER_NUM, batch_norm)\n    att_64 = attention_block(conv_64, gating_64, 2*FILTER_NUM)\n    up_64 = layers.UpSampling2D(size=(UP_SAMP_SIZE, UP_SAMP_SIZE), data_format=\"channels_last\")(up_conv_32)\n    up_64 = layers.concatenate([up_64, att_64], axis=3)\n    up_conv_64 = conv_block(up_64, FILTER_SIZE, 2*FILTER_NUM, dropout_rate, batch_norm)\n    # UpRes 9\n    gating_128 = gating_signal(up_conv_64, FILTER_NUM, batch_norm)\n    att_128 = attention_block(conv_128, gating_128, FILTER_NUM)\n    up_128 = layers.UpSampling2D(size=(UP_SAMP_SIZE, UP_SAMP_SIZE), data_format=\"channels_last\")(up_conv_64)\n    up_128 = layers.concatenate([up_128, att_128], axis=3)\n    up_conv_128 = conv_block(up_128, FILTER_SIZE, FILTER_NUM, dropout_rate, batch_norm)\n\n    # 1*1 convolutional layers\n    conv_final = layers.Conv2D(NUM_CLASSES, kernel_size=(1,1))(up_conv_128)\n    conv_final = layers.BatchNormalization(axis=3)(conv_final)\n    conv_final = layers.Activation('sigmoid')(conv_final)  #Change to softmax for multichannel\n\n    # Model integration\n    model = models.Model(inputs, conv_final, name=\"Attention_UNet\")\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.949857Z","iopub.execute_input":"2025-10-27T13:10:34.950184Z","iopub.status.idle":"2025-10-27T13:10:34.967885Z","shell.execute_reply.started":"2025-10-27T13:10:34.95016Z","shell.execute_reply":"2025-10-27T13:10:34.967253Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = Attention_UNet(input_shape)\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-4),\n    loss='binary_crossentropy',\n    metrics=[\n        'accuracy',\n        metrics.AUC(name='auc'),\n        metrics.Recall(name='recall'),\n        metrics.Precision(name='precision'),\n        mean_iou,\n        dice_coef\n    ]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:34.968474Z","iopub.execute_input":"2025-10-27T13:10:34.968644Z","iopub.status.idle":"2025-10-27T13:10:37.68489Z","shell.execute_reply.started":"2025-10-27T13:10:34.968632Z","shell.execute_reply":"2025-10-27T13:10:37.68406Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create U-Net base model with correct parameters\n#model = unet(input_shape=(IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS))\n\n#for layer in model.layers:\n#    layer.trainable = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:37.685716Z","iopub.execute_input":"2025-10-27T13:10:37.685932Z","iopub.status.idle":"2025-10-27T13:10:37.689146Z","shell.execute_reply.started":"2025-10-27T13:10:37.685916Z","shell.execute_reply":"2025-10-27T13:10:37.688414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# model = LinkNet((256,256,3))\n# model.compile(\n#     optimizer=Adam(learning_rate=1e-4),\n#     loss='binary_crossentropy',\n#     metrics=[\n#         'accuracy',\n#         metrics.AUC(name='auc'),\n#         metrics.Recall(name='recall'),\n#         metrics.Precision(name='precision'),\n#         mean_iou,\n#         dice_coef\n#     ]\n# )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:37.690113Z","iopub.execute_input":"2025-10-27T13:10:37.690428Z","iopub.status.idle":"2025-10-27T13:10:37.70293Z","shell.execute_reply.started":"2025-10-27T13:10:37.690412Z","shell.execute_reply":"2025-10-27T13:10:37.702349Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:37.703475Z","iopub.execute_input":"2025-10-27T13:10:37.703709Z","iopub.status.idle":"2025-10-27T13:10:37.826166Z","shell.execute_reply.started":"2025-10-27T13:10:37.703695Z","shell.execute_reply":"2025-10-27T13:10:37.825443Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"| Metric | Clinical meaning | Important |\n|:--------|:-----:|---------:|\n| Recall (Sensitivity)   |Detect all cancers (minimize missed diagnoses)\t| 4/5 |\n| AUC\tOverall    | separation ability\t| 4/5 |\n| Precision  | Avoid false alarms\t| 3/5 |\n| F1-score  | Balance between recall & precision\t| 3/5 |\n| Accuracy  | % of correct predictions\t (can be misleading if data is imbalanced)| 1/5 |\n| Loss (val_loss) |  Optimization stability | 2/5  |        ","metadata":{}},{"cell_type":"code","source":"callbacks = [\n    ModelCheckpoint(\n        'best_model.keras',\n        monitor='val_mean_iou',\n        mode='max',\n        save_best_only=True,\n        verbose=1\n    ),\n\n    EarlyStopping(\n        monitor='val_mean_iou',\n        mode='max',\n        patience=10,\n        verbose=1,\n        restore_best_weights=True\n    ),\n\n    ReduceLROnPlateau(\n        monitor='val_loss',\n        mode='max',\n        factor=0.5,\n        patience=5,\n        min_lr=1e-7,\n        verbose=1\n    )\n]\n\nhistory = model.fit(\n    x=train_generator,\n    validation_data=validation_generator,\n    steps_per_epoch=train_steps_per_epoch,\n    validation_steps=val_steps_per_epoch,\n    epochs=50,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T13:10:37.826893Z","iopub.execute_input":"2025-10-27T13:10:37.827099Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Metrics","metadata":{}},{"cell_type":"code","source":"auc = history.history['auc']\nval_auc = history.history['val_auc']\n\nrecall = history.history['recall']\nval_recall = history.history['val_recall']\n\nmean_iou = history.history['mean_iou']\nval_mean_iou = history.history['val_mean_iou']\n\nloss = history.history['loss']\nval_loss = history.history['val_loss']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T14:11:53.601133Z","iopub.execute_input":"2025-10-27T14:11:53.601404Z","iopub.status.idle":"2025-10-27T14:11:53.605731Z","shell.execute_reply.started":"2025-10-27T14:11:53.601387Z","shell.execute_reply":"2025-10-27T14:11:53.605161Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize = (15,10))\n\nplt.subplot(2, 4, 1)\nplt.plot(auc, label = \"Training AUC\")\nplt.plot(val_auc, label = \"Validation AUC\")\nplt.ylim(0, 1)\nplt.legend(['Train', 'Validation'], loc = 'upper left')\nplt.title(\"Training vs Validation AUC\")\nplt.xlabel('epoch')\nplt.ylabel('AUC')\n\nplt.subplot(2, 4, 2)\nplt.plot(recall, label = \"Training Recall\")\nplt.plot(val_recall, label = \"Validation Recall\")\nplt.ylim(0, 1)\nplt.legend(['Train', 'Validation'], loc = 'upper left')\nplt.title(\"Training vs Validation Recall\")\nplt.xlabel('epoch')\nplt.ylabel('AUC')\n\nplt.subplot(2, 4, 3)\nplt.plot(mean_iou, label = \"Training Mean IOU\")\nplt.plot(val_mean_iou, label = \"Validation Mean IOU\")\nplt.ylim(0, 1)\nplt.legend(['Train', 'Validation'], loc = 'upper left')\nplt.title(\"Training vs Validation Mean IOU\")\nplt.xlabel('epoch')\nplt.ylabel('Mean IOU')\n\nplt.subplot(2, 4, 4)\nplt.plot(loss, label = \"Training Loss\")\nplt.plot(val_loss, label = \"Validation Loss\")\nplt.legend(['Train', 'Validation'], loc = 'upper left')\nplt.title(\"Training vs Validation Loss\")\nplt.xlabel('epoch')\nplt.ylabel('loss')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T14:11:57.369086Z","iopub.execute_input":"2025-10-27T14:11:57.369823Z","iopub.status.idle":"2025-10-27T14:11:58.236953Z","shell.execute_reply.started":"2025-10-27T14:11:57.369799Z","shell.execute_reply":"2025-10-27T14:11:58.23624Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predictions","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.models import load_model\nmodel = load_model('/kaggle/working/best_model.keras', safe_mode=False, custom_objects={'mean_iou': mean_iou, 'dice_coef': dice_coef})","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred = model.predict(validation_generator, steps=val_steps_per_epoch, verbose=1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(pred.shape)\npred","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Pred min:\", pred.min())\nprint(\"Pred max:\", pred.max())\nprint(\"Pred mean:\", pred.mean())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"binary_pred = (pred >= 0.5).astype(np.uint8)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(binary_pred)\nprint(binary_pred.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pixel_fraction = binary_pred.mean(axis=(1, 2, 3))\ny_pred = (pixel_fraction > 0.01).astype(np.uint8)\n\nprint(y_pred.shape)\nprint(np.unique(y_pred, return_counts=True))\nprint(y_pred)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.Series(y_pred).value_counts()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true = validation_directory_iterator.classes","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(y_true)\nprint(y_true.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.Series(y_true).value_counts()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Confusion Matrix\n\nConfusion matrix was created with the predicted and observed values. The matrix indicated almost correct predictions by the trained model except that there were two cases observed as false positive. False positive means that a case is actually negative but predicted as positive.","metadata":{}},{"cell_type":"code","source":"cm = confusion_matrix(y_true, y_pred)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define the class names.\nclass_names = ['Cancer', 'Normal']\n\n# Create the heatmap with class names as tick labels.\nax = sns.heatmap(cm, annot = True, fmt = '.0f', cmap = \"Blues\", annot_kws = {\"size\": 16},\\\n           xticklabels = class_names, yticklabels = class_names)\n\n# Set the axis labels.\nax.set_xlabel(\"Prediction\")\nax.set_ylabel(\"Truth\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Save the Model","metadata":{}},{"cell_type":"code","source":"model.save('mammography_pred_model_finetuning.h5')","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}