{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.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":65035,"databundleVersionId":7153963,"sourceType":"competition"}],"dockerImageVersionId":30699,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Install required libraries to execute this notebook","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"code","source":"!pip install natsort timm==0.6.12 pretrainedmodels==0.7.4 efficientnet_pytorch==0.7.1 albumentations==1.3.1\n","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:33:55.309233Z","iopub.execute_input":"2024-04-24T16:33:55.309702Z","iopub.status.idle":"2024-04-24T16:34:07.886540Z","shell.execute_reply.started":"2024-04-24T16:33:55.309671Z","shell.execute_reply":"2024-04-24T16:34:07.885495Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!git clone https://github.com/MichaelIbrahim-GaTech/Deepdensity","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:34:07.889024Z","iopub.execute_input":"2024-04-24T16:34:07.889881Z","iopub.status.idle":"2024-04-24T16:34:08.867692Z","shell.execute_reply.started":"2024-04-24T16:34:07.889841Z","shell.execute_reply":"2024-04-24T16:34:08.866488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport os\nfrom natsort import natsorted\nimport glob\nimport numpy as np\nfrom PIL import Image\nimport cv2\nimport torch\nfrom torch.utils.data import Dataset\nfrom torchvision import transforms\nimport albumentations as A\nimport torch\nimport torch.optim as optim\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\nimport Deepdensity.scr.segmentation_models_multi_tasking as smp\nimport matplotlib.pyplot as plt\nfrom catboost import CatBoostRegressor\n","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:34:08.869489Z","iopub.execute_input":"2024-04-24T16:34:08.870438Z","iopub.status.idle":"2024-04-24T16:34:08.913391Z","shell.execute_reply.started":"2024-04-24T16:34:08.870394Z","shell.execute_reply":"2024-04-24T16:34:08.912569Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Reading the training and testing dataframes","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/breast-density-prediction/train.csv')\nsubmission = pd.read_csv('/kaggle/input/breast-density-prediction/sample_submission.csv')\ntrain_df.sort_values(by='Density', inplace=True)\nall_files = train_df['Filename']\ntrain_df.set_index('Filename', inplace=True)\ntrain_df['pred1'] = 0\ntrain_df['pred2'] = 0\nsubmission['pred1'] = 0\nsubmission['pred2'] = 0","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:34:08.915649Z","iopub.execute_input":"2024-04-24T16:34:08.915942Z","iopub.status.idle":"2024-04-24T16:34:08.933946Z","shell.execute_reply.started":"2024-04-24T16:34:08.915916Z","shell.execute_reply":"2024-04-24T16:34:08.933134Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Creating dataset loader function","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:32:27.490619Z","iopub.execute_input":"2024-04-24T16:32:27.490926Z","iopub.status.idle":"2024-04-24T16:32:27.497400Z","shell.execute_reply.started":"2024-04-24T16:32:27.490901Z","shell.execute_reply":"2024-04-24T16:32:27.495427Z"}}},{"cell_type":"code","source":"class MammoDataset(Dataset):\n    def __init__(self, path, files, augmentations=False):\n        self.augmentations = augmentations\n\n        self.images = natsorted([path+'images/'+f for f in files])\n        self.masks = natsorted([path+'breast_masks/'+f for f in files])\n        self.contours = natsorted([path+'dense_masks/'+f for f in files])\n        \n    def __len__(self):\n        return len(self.images)\n    \n    def __getitem__(self, index):\n\n        self.to_tensor = transforms.Compose([transforms.ToPILImage(),\n                                             transforms.Resize((256, 256)),\n                                             transforms.ToTensor(),])\n\n        self.aug_pipeline = A.Compose([A.ShiftScaleRotate(p = 0.5),\n                                       A.HorizontalFlip(0.5),\n                                       A.VerticalFlip(0.5),\n                                       A.RandomBrightnessContrast(0.5),\n                                       A.GaussNoise(p = 0.5),\n                                       A.GaussianBlur(p=0.5),\n                                       A.ElasticTransform(),],p=0.8)\n\n\n        self.image = cv2.imread(self.images[index], 1)\n        self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)\n\n        self.mask = cv2.imread(self.masks[index], 0)\n        self.contour = cv2.imread(self.contours[index], 0)\n\n        if self.augmentations:\n            torch.manual_seed(1990)\n            masks = [self.mask, self.contour]\n            #if self.augmentation_type == 'pixel':\n            #    self.sample = self.pixel_augmentations(image = self.image, masks = masks)\n            #elif self.augmentation_type == 'spatial':\n            #    self.sample = self.spatial_augmentations(image=self.image, masks=masks)\n            #elif self.augmentation_type == 'pixel_spatial':\n            #    self.sample = self.pixel_spatial_augmentations(image=self.image, masks=masks)\n            #elif self.augmentation_type == 'aug_pipeline':\n            self.sample = self.aug_pipeline(image=self.image, masks=masks)\n\n            return self.to_tensor(self.sample['image']), self.to_tensor(self.sample['masks'][0]), self.to_tensor(self.sample['masks'][1])\n        else:\n            return self.to_tensor(self.image), self.to_tensor(self.mask), self.to_tensor(self.contour)","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:34:08.935230Z","iopub.execute_input":"2024-04-24T16:34:08.935548Z","iopub.status.idle":"2024-04-24T16:34:08.949472Z","shell.execute_reply.started":"2024-04-24T16:34:08.935520Z","shell.execute_reply":"2024-04-24T16:34:08.948548Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Convert the image to a pytorch tensor\ndef image_tensor(img):\n    \"\"\"\n    Converts a PIL Image or NumPy array to a PyTorch tensor.\n    Args:\n    - img: PIL.Image or np.ndarray, the image to be converted to a PyTorch tensor.\n    Returns:\n    - image: torch.Tensor, the converted PyTorch tensor.\n    Raises:\n    - TypeError: if the input image is not a PIL.Image or np.ndarray.\n    \"\"\"\n    \n    if type(img) not in [np.ndarray, Image.Image]:\n        raise TypeError(\"Input must be np.ndarray or PIL.Image\")\n\n    # Define a PyTorch tensor transformer pipeline\n    torch_tensor = transforms.Compose(\n        [transforms.Resize((256, 256)), transforms.ToTensor()]\n    )\n\n    if type(img) == Image.Image:\n        # Convert PIL image to PyTorch tensor\n        image = torch_tensor(img)\n        image = image.unsqueeze(0)\n        print(\"tensor\", type(image))\n        return image\n    elif type(img) == np.ndarray:\n        # Convert NumPy array to RGB PIL image and then to PyTorch tensor\n        pil_image = Image.fromarray(img).convert(\"RGB\")\n        image = torch_tensor(pil_image)\n        image = image.unsqueeze(0)\n        print(\"tensor\", type(image))\n        return image\n    else:\n        raise TypeError(\"Input must be np.ndarray or PIL.Image\")\n        \n# learning curves visualization        \ndef plot_learning_curves(train_accuracy, valid_accuracy, train_loss, valid_loss, num_epochs):\n    \"\"\"\n    Plots the learning curves for training and validation accuracy and loss.\n    \n    Parameters:\n    - train_accuracy: List of training accuracy values for each epoch.\n    - valid_accuracy: List of validation accuracy values for each epoch.\n    - train_loss: List of training loss values for each epoch.\n    - valid_loss: List of validation loss values for each epoch.\n    - epochs: List of epoch numbers.\n    \"\"\"\n    fig, axes = plt.subplots(1, 2, figsize=(12, 4))  # 1 row, 2 columns for subplots\n    \n    epochs = range(1, num_epochs+1)\n    # Plot accuracy on the first subplot\n    axes[0].plot(epochs, train_accuracy, label='Train Accuracy')\n    axes[0].plot(epochs, valid_accuracy, label='Validation Accuracy')\n    axes[0].set_xlabel('Epochs')\n    axes[0].set_ylabel('Accuracy')\n    axes[0].set_title('Accuracy Curve')\n    axes[0].legend()\n\n    # Plot loss on the second subplot\n    axes[1].plot(epochs, train_loss, label='Train Loss')\n    axes[1].plot(epochs, valid_loss, label='Validation Loss')\n    axes[1].set_xlabel('Epochs')\n    axes[1].set_ylabel('Loss')\n    axes[1].set_title('Loss Curve')\n    axes[1].legend()\n\n    # Adjust spacing between subplots\n    plt.tight_layout()\n\n    # Display the subplots\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:34:08.950907Z","iopub.execute_input":"2024-04-24T16:34:08.951299Z","iopub.status.idle":"2024-04-24T16:34:08.966809Z","shell.execute_reply.started":"2024-04-24T16:34:08.951263Z","shell.execute_reply":"2024-04-24T16:34:08.965895Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"5-fold training of the data","metadata":{}},{"cell_type":"code","source":"num_epochs = 20\nfolds = 5\nimport random\nfor fold in range(folds): # \n    valid_files = all_files[fold::folds]\n    train_files = [f for f in all_files if f not in valid_files]\n    random.shuffle(train_files)\n    # loading training dataset\n    train_dataset = MammoDataset(path='/kaggle/input/breast-density-prediction/train/train/', files=train_files, augmentations=None)\n    print(len(train_dataset))\n    train_dataloader = DataLoader(train_dataset, shuffle=True, batch_size = 8, num_workers=0)\n\n    # loading valid dataset\n    valid_dataset = MammoDataset(path='/kaggle/input/breast-density-prediction/train/train/', files=valid_files, augmentations=None)\n    print(len(valid_dataset))\n    valid_dataloader = DataLoader(valid_dataset, shuffle=True, batch_size= 8, num_workers=0)\n    # device to work (CPU/GPU)\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    #DEVICE\n    # create segmentation model with pretrained encoder, here we are using Unet architecrure with Resnet50 as encoder with ImageNet pretrained weights\n    model = getattr(smp, 'Unet')(\n        encoder_name= 'resnet50',\n        encoder_weights = 'imagenet', \n        classes =1,\n        activation = 'sigmoid')\n\n    model = model.to(DEVICE)\n    model = nn.DataParallel(model)\n    # We modified the FocalTverskyLoss function to adopt the multi-task learning approach proposed in our paper\n    loss = getattr(smp.utils.losses, 'FocalTverskyLoss')()\n\n    # Adam optimizer with reduce learning rate schedular\n    optimizer = getattr(torch.optim, 'Adam')([dict(params=model.parameters(), lr=0.0001),])\n    lr_schedular = optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.25, patience=2)\n\n    # Evaluation metrics\n    metrics = [\n        smp.utils.metrics.Precision(),\n        smp.utils.metrics.Recall(),\n        smp.utils.metrics.Accuracy(),\n        smp.utils.metrics.Fscore(),\n        smp.utils.metrics.IoU(threshold=0.5),]\n    \n    # it is a simple loop of iterating over dataloader`s samples\n    train_epoch = smp.utils.train.TrainEpoch(\n        model, \n        loss=loss, \n        metrics=metrics, \n        optimizer=optimizer,\n        lr_schedular=lr_schedular,\n        device=DEVICE,\n        verbose=True,\n    )\n\n    valid_epoch = smp.utils.train.ValidEpoch(\n        model, \n        loss=loss, \n        metrics=metrics, \n        device=DEVICE,\n        verbose=True,\n    )\n    \n    os.makedirs(\"/kaggle/working/results/logs\", exist_ok=True) # saving the logs \n    os.makedirs(\"/kaggle/working/results/models\", exist_ok=True) # asving the trained model\n\n    train_accuracy = []\n    valid_accuracy = []\n    train_loss = []\n    valid_loss = []\n\n\n    with open(f\"/kaggle/working/results/logs/unet_resnet50_logs_{fold}.txt\", 'a+') as logs_file:\n        # train model for 40 epochs\n    \n        max_score = 0\n        for i in range(0, num_epochs):# 30 epochs\n            print('\\nEpoch: {}'.format(i))\n            train_logs = train_epoch.run(train_dataloader)\n            valid_logs = valid_epoch.run(valid_dataloader)\n\n\n            train_accuracy.append(train_logs['accuracy'])\n            valid_accuracy.append(valid_logs['accuracy'])\n            train_loss.append(train_logs['focal_tversky_loss_weighted'])\n            valid_loss.append(valid_logs['focal_tversky_loss_weighted'])\n\n            print('{} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {} \\t {}'.format(i, train_logs['focal_tversky_loss_breast'],\n                                                                                                                    train_logs['focal_tversky_loss_dense'],\n                                                                                                                    train_logs['focal_tversky_loss_weighted'],\n                                                                                                                    train_logs['precision'],\n                                                                                                                    train_logs['recall'],\n                                                                                                                    train_logs['accuracy'],\n                                                                                                                    train_logs['fscore'],\n                                                                                                                    train_logs['iou_score'],\n                                                                                                                    valid_logs['focal_tversky_loss_breast'],\n                                                                                                                    valid_logs['focal_tversky_loss_dense'],\n                                                                                                                    valid_logs['focal_tversky_loss_weighted'],\n                                                                                                                    valid_logs['precision'],\n                                                                                                                    valid_logs['recall'],\n                                                                                                                    valid_logs['accuracy'],\n                                                                                                                    valid_logs['fscore'],\n                                                                                                                    valid_logs['iou_score']), file=logs_file)\n\n        \n            # do something (save model, change lr, etc.)\n            if max_score < valid_logs['iou_score']:\n                max_score = valid_logs['iou_score']\n                torch.save(model, f\"/kaggle/working/results/models/unet_resnet50_model_{fold}.pth\")\n                print('Model saved!')\n    \n    model_path=f\"/kaggle/working/results/models/unet_resnet50_model_{fold}.pth\"\n    # Load the model\n    model = torch.load(model_path)\n    model.eval()\n    \n    #for index, row in train_df.iterrows():\n    for index in valid_files:\n        image_path = '/kaggle/input/breast-density-prediction/train/train/images/' + index\n        image = Image.open(image_path).convert('RGB')\n        img = image_tensor(image)\n    \n       # Make predictions using the model\n        with torch.no_grad():\n            pred1, pred2 = model.module.predict(img.cuda())\n        pred1 = pred1[0].cpu().numpy().transpose(1, 2, 0)\n        pred1 = pred1[:, :, 0]\n        pred2 = pred2[0].cpu().numpy().transpose(1, 2, 0)\n        pred2 = pred2[:, :, 0]\n        train_df.at[index,'pred1'] = train_df.at[index,'pred1'] + pred1.sum()\n        train_df.at[index,'pred2'] = train_df.at[index,'pred2'] + pred2.sum()\n        \n    for index, row in submission.iterrows():\n        image_path = '/kaggle/input/breast-density-prediction/test/test/images/' + row['Filename']\n        image = Image.open(image_path).convert('RGB')\n        img = image_tensor(image)\n    \n        # Make predictions using the model\n        with torch.no_grad():\n            pred1, pred2 = model.module.predict(img.cuda())\n        pred1 = pred1[0].cpu().numpy().transpose(1, 2, 0)\n        pred1 = pred1[:, :, 0]\n        pred2 = pred2[0].cpu().numpy().transpose(1, 2, 0)\n        pred2 = pred2[:, :, 0]\n        \n        submission.at[index,'pred1'] = submission.at[index,'pred1'] + pred1.sum()/folds\n        submission.at[index,'pred2'] = submission.at[index,'pred2'] + pred2.sum()/folds","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:34:08.968042Z","iopub.execute_input":"2024-04-24T16:34:08.968353Z","iopub.status.idle":"2024-04-24T16:39:55.319050Z","shell.execute_reply.started":"2024-04-24T16:34:08.968327Z","shell.execute_reply":"2024-04-24T16:39:55.317509Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_learning_curves(train_accuracy, valid_accuracy, train_loss, valid_loss, num_epochs)\n","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:39:55.320231Z","iopub.status.idle":"2024-04-24T16:39:55.320617Z","shell.execute_reply.started":"2024-04-24T16:39:55.320439Z","shell.execute_reply":"2024-04-24T16:39:55.320455Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now after extracting the features from the image segmentation phase, we will use these values to predict the breast density","metadata":{}},{"cell_type":"code","source":"train_df['pred3'] = train_df['pred2']/train_df['pred1']\nsubmission['pred3'] = submission['pred2']/submission['pred1']\n","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:39:55.322571Z","iopub.status.idle":"2024-04-24T16:39:55.322946Z","shell.execute_reply.started":"2024-04-24T16:39:55.322762Z","shell.execute_reply":"2024-04-24T16:39:55.322778Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The first model is a linear regression model","metadata":{}},{"cell_type":"code","source":"import numpy as np\nfrom sklearn.linear_model import LinearRegression\nX = train_df[['pred2','pred1','pred3']].to_numpy()\nY = train_df['Density'].to_numpy()\nreg = LinearRegression().fit(X, Y)\na = reg.intercept_\nb = reg.coef_[0]\nc = reg.coef_[1]\nd = reg.coef_[2]\nprint(a,b,c,d)\ntrain_df['Yhat'] = a + b*train_df['pred2'] + c*train_df['pred1'] + d*train_df['pred3']\n#(a+b*train_df['pred2'])/(1+c*train_df['pred1'])\ntrain_df\ne1 = np.sum(np.abs(train_df['Yhat'].clip(1,90)-train_df['Density']))\nsubmission['pred3'] = submission['pred2']/submission['pred1']\nsubmission['Yhat'] = a + b*submission['pred2'] + c*submission['pred1'] + d*submission['pred3']","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The second model is a random forest","metadata":{}},{"cell_type":"code","source":"from sklearn.ensemble import RandomForestRegressor\nregr = RandomForestRegressor(max_depth=2, random_state=0)\nregr.fit(train_df[['pred1','pred2','pred3']], train_df['Density'])\ntrain_df['Yhat2'] = regr.predict(train_df[['pred1','pred2','pred3']])\ne2 = np.sum(np.abs(train_df['Yhat2']-train_df['Density']))\ntrain_df['Yhat3'] = 0.5*(train_df['Yhat']+train_df['Yhat2'])\ne3 = np.sum(np.abs(train_df['Yhat3'].clip(1,90)-train_df['Density']))\nprint(e1,e2,e3)\nsubmission['Yhat2'] = regr.predict(submission[['pred1','pred2','pred3']])\nsubmission['Yhat3'] = 0.5*(submission['Yhat']+submission['Yhat2'])","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The regression model is a catboost regression model","metadata":{}},{"cell_type":"code","source":"cols = ['pred1','pred2','pred3']\nX_all = train_df[cols]\ny_all_reg = train_df['Density']\nX_solution = submission[cols]\n\nsubmission['Density'] = 0\nfolds = 5\nimport random\nfor fold in range(folds): # \n    test_index = [i for i in range(train_df.shape[0]) if i%folds==fold]\n    train_index = [i for i in range(train_df.shape[0]) if i%folds!=fold]\n    reg = CatBoostRegressor(random_seed=42)\n    reg.fit(X_all.iloc[train_index], y_all_reg[train_index], eval_set=(X_all.iloc[test_index], y_all_reg[test_index]), verbose=1000)\n    \n    submission['Density'] += reg.predict(X_solution)/5\n    ","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:39:55.326944Z","iopub.status.idle":"2024-04-24T16:39:55.327315Z","shell.execute_reply.started":"2024-04-24T16:39:55.327120Z","shell.execute_reply":"2024-04-24T16:39:55.327135Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission['Density'] = np.round(0.5*(submission['Yhat3']+submission['Density']))\nsubmission[['Filename','Density']].to_csv('submission.csv',index=False)","metadata":{"execution":{"iopub.status.busy":"2024-04-24T16:39:55.328792Z","iopub.status.idle":"2024-04-24T16:39:55.329145Z","shell.execute_reply.started":"2024-04-24T16:39:55.328976Z","shell.execute_reply":"2024-04-24T16:39:55.328991Z"},"trusted":true},"execution_count":null,"outputs":[]}]}