{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":61446,"databundleVersionId":6962461,"sourceType":"competition"},{"sourceId":150248402,"sourceType":"kernelVersion"}],"dockerImageVersionId":30635,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"\n<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n\n<html>\n<head>\n</head>\n<body>\n    <h1>If you find my Kaggle notebook helpful, please consider giving it an upvote! 👍</h1>\n    \n<strong>This is a super clean code and the Purpose of this notebook is to help beginners and new competitors on Kaggle to give an idea of how a training notebook looks like for segmentation tasks, this notebook is just a baseline but can be modified into a solid submission\n    HOW TO DO INFERENCE AND SUBMISSION???  CONSIDER GOING TO THIS https://www.kaggle.com/code/vinayaktiwari28/unet-submission-inference </strong>\n</body>\n</html>","metadata":{}},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n    \n# For using pretrained models","metadata":{}},{"cell_type":"code","source":"#for using pretrained models\n!python -m pip install --no-index --find-links=/kaggle/input/pip-download-for-segmentation-models-pytorch segmentation-models-pytorch\n!pip install -q segmentation_models_pytorch","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-01-25T04:26:03.875024Z","iopub.execute_input":"2024-01-25T04:26:03.875489Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n    \n# Import libraries","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nfrom tqdm import tqdm\nimport pandas as pd\nimport numpy as np\nfrom glob import glob\nimport gc\nimport time\nfrom collections import defaultdict\nimport  matplotlib.pyplot as plt\nfrom matplotlib.patches import Rectangle\nimport copy\nimport cv2\n\n#pytorch imports\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim import lr_scheduler\nfrom torch.cuda import amp\nimport torch.optim as optim\nimport albumentations as A\nimport segmentation_models_pytorch as smp\n\nfrom colorama import Fore, Back, Style\nc_  = Fore.GREEN\nsr_ = Style.RESET_ALL\n\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.model_selection import train_test_split\nimport tifffile as tiff\n\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n    \n# Config Class and its constructor\n    \n<strong>Do play with these parameters to tune your model, make sure to track the progress<strong>","metadata":{}},{"cell_type":"code","source":"class Config:\n    def __init__(self):\n        self.seed = 42\n        self.debug = False\n        self.exp_name = 'baseline'\n        self.comment = 'unet-efficientnet_b1-512x512'\n        self.output_dir = './'\n        self.model_name = 'Unet'\n        self.backbone = ['efficientnet-b1','se_resnext50_32x4d']\n        self.train_bs = 16\n        self.valid_bs = 32\n        self.img_size = [768,512]\n        self.epochs = 30\n        self.n_accumulate = max(1, 64 // self.train_bs)\n        self.lr = 2e-3\n#         self.lr = 6e-5\n        self.scheduler = 'CosineAnnealingLR'\n        self.min_lr = 1e-6\n        self.T_max = int(2279 / (self.train_bs * self.n_accumulate) * self.epochs) + 50\n        self.T_0 = 25\n        self.warmup_epochs = 0\n        self.wd = 1e-6\n        self.n_fold = 5\n        self.num_classes = 1\n        self.input_channels = 3\n        self.device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n#         self.gt_df = \"/kaggle/input/sennet-hoa-gt-data/gt.csv\"\n        self.data_root = \"/kaggle/input/blood-vessel-segmentation\"\n        self.train_groups = [\"kidney_1_dense\"]\n        self.valid_groups = [\"kidney_3_dense\"]\n        self.loss_func = \"DiceLoss\"\n\n        self.data_transforms = {\n            \"train\": A.Compose([\n                A.Resize(*self.img_size,interpolation=cv2.INTER_CUBIC),\n                A.HorizontalFlip(p=0.5),\n                A.VerticalFlip(p=0.5),\n                A.Rotate(limit=45, p=0.5),\n                A.RandomScale(scale_limit=(0.8,1.25),interpolation=cv2.INTER_CUBIC,p=0.5),\n                A.RandomCrop(*self.img_size,p=1),], p=1.0),   #using *self operator for iterative unpacking od li\n            \"valid\": A.Compose([\n                A.Resize(*self.img_size, interpolation=cv2.INTER_NEAREST), ], p=1.0)\n        }\n        self.optimizers = 'adam'\n        \nconfig = Config()\n        \ndef set_seed(seed):\n    '''Sets the seed of the entire notebook so results are the same every time we run.\n    This is for REPRODUCIBILITY.'''\n    np.random.seed(seed)\n    random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    # When running on the CuDNN backend, two further options must be set\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n    # Set a fixed value for the hash seed\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    \n\nset_seed(config.seed)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n    \n# DATA PATHS AND SAMPLE IMAGE AND LABEL","metadata":{}},{"cell_type":"code","source":"train_images_path = os.path.join(config.data_root,'train', config.train_groups[0], 'images')\ntrain_labels_path = os.path.join(config.data_root,'train', config.train_groups[0], 'labels')\n\n# valid_images_path = os.path.join(config.data_root,'test', config.valid_groups[0], 'images')\n# valid_labels_path = os.path.join(config.data_root,'test', config.valid_groups[0], 'labels')\n\nprint(train_images_path)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image_files = sorted([os.path.join(train_images_path, f) for f in os.listdir(train_images_path) if f.endswith('.tif')])\nlabel_files = sorted([os.path.join(train_labels_path, f) for f in os.listdir(train_labels_path) if f.endswith('.tif')])\n\ndef show_images(images,titles= None, cmap='gray'):\n    n = len(images)\n    fig, axes = plt.subplots(1, n, figsize=(20, 10))\n    if not isinstance(axes, np.ndarray):\n        axes = [axes]\n    for idx, ax in enumerate(axes):\n        ax.imshow(images[idx], cmap=cmap)\n        if titles:\n            ax.set_title(titles[idx])\n        ax.axis('off')\n    plt.tight_layout()\n    plt.show()\n\nfirst_image = tiff.imread(image_files[981])\nfirst_label = tiff.imread(label_files[981])\n\nshow_images([first_image, first_label], titles=['Train Image', 'Train Label'])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n    \n# Image Processing block","metadata":{}},{"cell_type":"code","source":"\ndef load_img(path):\n    img = cv2.imread(path, cv2.IMREAD_UNCHANGED)\n    img = np.tile(img[...,None], [1, 1, 3]) #  Converts a grayscale image to an \n    #RGB image by replicating the single-channel image three times along the third axis.\n    img = img.astype('float32') # original is uint16\n    mx = np.max(img)\n    if mx:\n        img/=mx # Normalizes the image by dividing each pixel value by the maximum value, scaling it to the range [0, 1].\n    return img\n\ndef load_msk(path):\n    msk = cv2.imread(path, cv2.IMREAD_UNCHANGED)\n    msk = msk.astype('float32')\n    msk/=255.0\n    return msk\n\n\n\nclass DatasetBuilder(Dataset):\n    def __init__(self,images,masks,input_size=(256,256),transforms=None):\n        self.images = images\n        self.masks = masks\n        self.input_size = input_size\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        image = self.images[idx]\n        image = load_img(image)\n        \n   \n        mask = self.masks[idx]\n        mask= load_msk(mask)   \n        \n        if self.transforms:\n            data = self.transforms(image=image, mask=mask)\n            image  = data['image']\n            mask  = data['mask']\n            image = np.transpose(image, (2, 0, 1))  #Transposes the image array to have the channel dimension as the first dimension. This is a common format for PyTorch.\n        return torch.tensor(image), torch.tensor(mask)\n        \n       ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n    \n# TRAIN DATA SPLIT FOR VALIDATION\n    \n<strong>I HAVE UTILISED 80 : 20 SPLIT FOR VALIDATION, I AM KEEPING IT OPEN FOR YOU GUYS TO ADD CROSS VALIDATION AND OTHER VALIDATION TECHNIQUES IN THIS BLOCK<strong>","metadata":{}},{"cell_type":"code","source":"train_image_files, val_image_files, train_mask_files, val_mask_files = train_test_split(image_files, label_files, test_size=0.2, random_state=config.seed)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataset = DatasetBuilder(train_image_files, train_mask_files, transforms=config.data_transforms['train'])\nvalid_dataset = DatasetBuilder(val_image_files, val_mask_files, transforms=config.data_transforms['valid'])\n\ntrain_loader = DataLoader(train_dataset, batch_size=config.train_bs, num_workers=0, shuffle=True, pin_memory=True, drop_last=False)\nvalid_loader = DataLoader(valid_dataset, batch_size=config.valid_bs, num_workers=0, shuffle=False, pin_memory=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for batch_idx, (batch_images, batch_masks) in enumerate(train_loader):\n    print(\"Batch\", batch_idx + 1)\n    print(\"Image batch shape:\", batch_images.shape)\n    print(\"Mask batch shape:\", batch_masks.shape)\n    \n    for image, mask, image_path, mask_path in zip(batch_images, batch_masks, train_image_files, train_mask_files):\n       \n        image = image.permute((1, 2, 0)).numpy()*255.0\n        image = image.astype('uint8')\n        mask = (mask*255).numpy().astype('uint8')\n        \n        image_filename = os.path.basename(image_path)\n        mask_filename = os.path.basename(mask_path)\n        \n        plt.figure(figsize=(15, 10))\n        \n        plt.subplot(2, 4, 1)\n        plt.imshow(image, cmap='gray')\n        plt.title(f\"Original Image - {image_filename}\")\n        \n        plt.subplot(2, 4, 2)\n        plt.imshow(mask, cmap='gray')\n        plt.title(f\"Mask Image - {mask_filename}\")\n        \n        plt.tight_layout()\n        plt.show()\n    break","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# for batch_idx, (batch_images, batch_masks) in enumerate(val_dataloader):\n#     print(\"Batch\", batch_idx + 1)\n#     print(\"Image batch shape:\", batch_images.shape)\n#     print(\"Mask batch shape:\", batch_masks.shape)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n\n# <strong>Building Model</strong>\n\n<strong>BUILDING MODEL USING PYTORCH'S SEGMENTATION MODEL LIBRARY, I AM USING PRETRAINED RESNEXT50 HERE, IT CAN BE REPLACED BY ANY OTHER MODEL LIKE EFFICIENTNETB1, B2, ETC</strong>\n","metadata":{}},{"cell_type":"code","source":"def model_builder(backbone,num_classes,device):\n    model = smp.Unet(encoder_name=backbone,\n                     encoder_weights = \"imagenet\",\n                     in_channels = 3,\n                     classes =num_classes,\n                     activation = None,\n                    )\n    model.to(device)\n    return model","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import ssl\nssl._create_default_https_context = ssl._create_unverified_context\n#above is needed to bypass the SSL certificate verification\n\nmodel = model_builder(config.backbone[1], config.num_classes, config.device)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n\n# <strong>Loss Function</strong>\n\n<strong>Dice Loss and BCE Loss are losses that can be used to track the performance of model during training , in image segmentation tasks, another option can be using focal loss.\n    Currenty using diceLoss</strong>","metadata":{}},{"cell_type":"code","source":"DiceLoss = smp.losses.DiceLoss(mode='binary')\nBCELoss = smp.losses.SoftBCEWithLogitsLoss()\ndef criterion(y_pred, y_true):\n    if config.loss_func == \"DiceLoss\":\n        return DiceLoss(y_pred, y_true)\n    elif config.loss_func == \"BCELoss\":\n        y_true = y_true.unsqueeze(1)\n        return BCELoss(y_pred, y_true)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Eval metrics","metadata":{}},{"cell_type":"code","source":"def dice_coef(y_true, y_pred, thr=0.5, dim=(2,3), epsilon=0.001):\n    y_true = y_true.unsqueeze(1).to(torch.float32)\n    y_pred = (y_pred>thr).to(torch.float32)\n    inter = (y_true*y_pred).sum(dim=dim)\n    den = y_true.sum(dim=dim) + y_pred.sum(dim=dim)\n    dice = ((2*inter+epsilon)/(den+epsilon)).mean(dim=(1,0))\n    return dice\n\ndef iou_coef(y_true, y_pred, thr=0.5, dim=(2,3), epsilon=0.001):\n    y_true = y_true.unsqueeze(1).to(torch.float32)\n    y_pred = (y_pred>thr).to(torch.float32)\n    inter = (y_true*y_pred).sum(dim=dim)\n    union = (y_true + y_pred - y_true*y_pred).sum(dim=dim)\n    iou = ((inter+epsilon)/(union+epsilon)).mean(dim=(1,0))\n    return iou","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n\n","metadata":{}},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n\n# <strong>Learning Rate Schduler and Optimizer</strong>\n\n<strong>Common learning rate schedulers include:\n\nStepLR: Reduces the learning rate by a factor after a fixed number of epochs.\n\nMultiStepLR: Similar to StepLR but allows the learning rate to be reduced at specific epochs.\n\nExponentialLR: Multiplies the learning rate by a constant factor at each epoch.\n\nReduceLROnPlateau: Adjusts the learning rate based on a validation metric (e.g., reducing the learning rate if the validation loss plateaus).\n\nCyclicLR: Alternates between lower and upper learning rate values in a cycle.\n\nCosineAnnealingLR: Gradually reduces the learning rate in a cosine-shaped manner.\n\nOptimizer:\nThe optimizer is an algorithm that adjusts the model's parameters during training to minimize the loss function. It plays a crucial role in determining how quickly the model learns, converges, and generalizes.\n\nCommon optimizers include:\n\nStochastic Gradient Descent (SGD): Updates the model's parameters in the opposite direction of the gradient of the loss function with respect to the parameters.\n\nAdam: Combines ideas from RMSprop and Momentum. It adapts the learning rates for each parameter individually.\n\nAdagrad: Adapts the learning rates of all model parameters based on historical gradients.\n\nRMSprop: Similar to Adagrad but uses a moving average of squared gradients to adapt the learning rates.\n\nAdadelta: An extension of Adagrad that seeks to reduce its aggressive, monotonically decreasing learning rates.\n\nNadam: Nesterov Adam optimizer, a variant of Adam incorporating Nesterov momentum</strong>","metadata":{}},{"cell_type":"code","source":"def scheduler(optimizer):\n    if config.scheduler == 'CosineAnnealingLR':\n        scheduler = lr_scheduler.CosineAnnealingLR(optimizer,T_max=config.T_max, \n                                                   eta_min=config.min_lr)\n    elif config.scheduler == 'CosineAnnealingWarmRestarts':\n        scheduler = lr_scheduler.CosineAnnealingWarmRestarts(optimizer,T_0=config.T_0, \n                                                             eta_min=config.min_lr)\n    elif config.scheduler == 'ReduceLROnPlateau':\n        scheduler = lr_scheduler.ReduceLROnPlateau(optimizer,\n                                                   mode='min',\n                                                   factor=0.1,\n                                                   patience=7,\n                                                   threshold=0.0001,\n                                                   min_lr=config.min_lr,)\n    elif config.scheduer == 'ExponentialLR':\n        scheduler = lr_scheduler.ExponentialLR(optimizer, gamma=0.85)\n    elif config.scheduler == None:\n        return None\n    return scheduler\n\n\ndef select_optimizer():\n    if config.optimizers == 'adam':\n        optimizer = optim.Adam(model.parameters(), lr=config.lr, weight_decay=config.wd)\n        \n    elif config.optimizers == 'nadam':\n        optimizer = optim.NAdam(model.parameters(), lr=config.lr, weight_decay=config.wd)\n      \n    elif config.optimizers == 'adamW':\n        optimizer = optim.AdamW(model.parameters(), lr=config.lr, weight_decay=config.wd)\n            \n    elif config.optimizers == 'sgd':\n        optimizer = optim.SGD(model.parameters(), lr=config.lr, weight_decay=config.wd)\n        \n    elif config.scheduler ==None:\n        return None\n    \n    return optimizer\n\n\nselected_optimizer = select_optimizer()\n\nselected_scheduler = scheduler(selected_optimizer)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"_optimizer = optim.Adam(model.parameters(), lr=config.lr, weight_decay=config.wd)\n_scheduler = scheduler(_optimizer)\nlr_list = []\nfor e in range(config.epochs):\n    for step in range(len(train_loader)):\n        lr_list.append(_optimizer.param_groups[0]['lr'])\n        if (step + 1) % config.n_accumulate == 0:\n            _optimizer.step()\n            _scheduler.step()\nplt.plot(np.array(range(len(lr_list))), np.array(lr_list))\nplt.show()\ndel _optimizer, _scheduler","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"background-color: #e0f8e6; padding: 20px; border-radius: 50px; border: 5px solid #ffa76e;\">\n    \n# Model Training","metadata":{}},{"cell_type":"code","source":"def single_epoch_train(model,optimizer,scheduler,dataloader,device,epoch):\n    model.train()\n    scaler =amp.GradScaler()\n    dataset_size =0\n    running_loss = 0.0\n    pbar = tqdm(enumerate(dataloader), total=len(dataloader), desc='Train ')\n    for step, (images, masks) in pbar:         \n        images = images.to(device, dtype=torch.float)\n        masks  = masks.to(device, dtype=torch.float)\n        \n        batch_size = images.size(0)\n        \n        with amp.autocast(enabled=True):\n            y_pred = model(images)\n            loss   = criterion(y_pred, masks)\n            loss   = loss / config.n_accumulate\n            \n        scaler.scale(loss).backward()\n    \n        if (step + 1) % config.n_accumulate == 0:\n            scaler.step(optimizer)\n            scaler.update()\n\n            # zero the parameter gradients\n            optimizer.zero_grad()\n\n            if scheduler is not None:\n                scheduler.step()\n                \n        running_loss += (loss.item() * batch_size)\n        dataset_size += batch_size\n        \n        epoch_loss = running_loss / dataset_size\n        \n        mem = torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0\n        current_lr = optimizer.param_groups[0]['lr']\n        pbar.set_postfix( epoch=f'{epoch}',\n                          train_loss=f'{epoch_loss:0.4f}',\n                          lr=f'{current_lr:0.5f}',\n                          gpu_mem=f'{mem:0.2f} GB')\n    torch.cuda.empty_cache()\n    gc.collect()\n    return epoch_loss","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"@torch.no_grad()\ndef single_epoch_valid(model, optimizer, dataloader, device, epoch):\n    model.eval()\n    \n    dataset_size = 0\n    running_loss = 0.0\n    \n    val_scores = []\n    \n    pbar = tqdm(enumerate(dataloader), total=len(dataloader), desc='Valid ')\n    for step, (images, masks) in pbar:        \n        images  = images.to(device, dtype=torch.float)\n        masks   = masks.to(device, dtype=torch.float)\n        \n        batch_size = images.size(0)\n        \n        y_pred  = model(images)\n        loss    = criterion(y_pred, masks)\n        \n        running_loss += (loss.item() * batch_size)\n        dataset_size += batch_size\n        \n        epoch_loss = running_loss / dataset_size\n        \n        y_pred = nn.Sigmoid()(y_pred)\n        val_dice = dice_coef(masks, y_pred).cpu().detach().numpy()\n        val_jaccard = iou_coef(masks, y_pred).cpu().detach().numpy()\n        val_scores.append([val_dice, val_jaccard])\n        \n        mem = torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0\n        current_lr = optimizer.param_groups[0]['lr']\n        pbar.set_postfix(valid_loss=f'{epoch_loss:0.4f}',\n                        lr=f'{current_lr:0.5f}',\n                        gpu_memory=f'{mem:0.2f} GB')\n    val_scores  = np.mean(val_scores, axis=0)\n    torch.cuda.empty_cache()\n    gc.collect()\n    return epoch_loss, val_scores","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def training(model, optimizer, scheduler, device, num_epochs):    \n    if torch.cuda.is_available():\n        print(\"cuda: {}\\n\".format(torch.cuda.get_device_name()))\n    \n    start = time.time()\n    best_model_wts = copy.deepcopy(model.state_dict())\n    best_loss      = np.inf\n    best_epoch     = -1\n    history = defaultdict(list)\n    \n    for epoch in range(1, num_epochs + 1): \n        gc.collect()\n        print(f'Epoch {epoch}/{num_epochs}', end='')\n        train_loss = single_epoch_train(model, optimizer, scheduler, \n                                           dataloader=train_loader, \n                                           device=config.device, epoch=epoch)\n        \n        val_loss, val_scores = single_epoch_valid(model, optimizer, valid_loader, \n                                                 device=config.device, \n                                                 epoch=epoch)\n        val_dice, val_jaccard = val_scores\n        history['Train Loss'].append(train_loss)\n        history['Valid Loss'].append(val_loss)\n        history['Valid Dice'].append(val_dice)\n        history['Valid Jaccard'].append(val_jaccard)        \n        print(f'Valid Dice: {val_dice:0.4f} | Valid Jaccard: {val_jaccard:0.4f}')\n        print(f'Valid Loss: {val_loss}')\n        \n        # deep copy the model\n        if val_loss <= best_loss:\n            print(f\"{c_}Valid loss Improved ({best_loss} ---> {val_loss})\")\n            best_dice    = val_dice\n            best_jaccard = val_jaccard\n            best_loss = val_loss\n            best_epoch   = epoch\n            best_model_wts = copy.deepcopy(model.state_dict())\n            PATH = \"best_epoch.bin\"\n            torch.save(model.state_dict(), PATH)\n            print(f\"Model Saved{sr_}\")\n            \n        last_model_wts = copy.deepcopy(model.state_dict())\n        PATH = \"last_epoch.bin\"\n        torch.save(model.state_dict(), PATH)\n            \n        print(); print()\n    \n    end = time.time()\n    time_elapsed = end - start\n    print('Training complete in {:.0f}h {:.0f}m {:.0f}s'.format(\n        time_elapsed // 3600, (time_elapsed % 3600) // 60, (time_elapsed % 3600) % 60))\n    print(\"Best Loss: {:.4f}\".format(best_loss))\n    \n    # load best model weights\n    model.load_state_dict(best_model_wts)\n    return model, history","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model, history = training(model, selected_optimizer, selected_scheduler,\n                                device=config.device,\n                                num_epochs=config.epochs)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}