{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np  # linear algebra\nimport pandas as pd  # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt\nfrom plotly.subplots import make_subplots\nimport plotly.graph_objs as go\nimport copy\nimport os\nimport torch\nfrom PIL import Image\nfrom PIL import Image, ImageDraw\nfrom torch.utils.data import Dataset\nimport torchvision.transforms as transforms\nfrom torch.utils.data import random_split\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nimport torch.nn as nn\nfrom torchvision import utils\nimport torch.nn.functional as F\n%matplotlib inline","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# library which allows us to view model summary like keras/tf\n!pip install torchsummary","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 1. Data Exploration","metadata":{}},{"cell_type":"code","source":"torch.manual_seed(42)  # fix random seed\n\n\nclass MyDataSet(Dataset):\n\n    def __init__(self, data_dir, transform, data_type=\"train\"):\n        # Get Image File Names\n        cdm_data = os.path.join(data_dir, data_type)  # directory of files\n\n        file_names = os.listdir(cdm_data)  # get list of images in that directory\n        idx_choose = np.random.choice(np.arange(len(file_names)),\n                                      4000,\n                                      replace=False).tolist()\n        file_names_sample = [file_names[x] for x in idx_choose]\n        self.full_filenames = [os.path.join(cdm_data, f) for f in file_names_sample]  # get the full path to images\n\n        # Get Labels\n        labels_data = os.path.join(data_dir, \"train_labels.csv\")\n        labels_df = pd.read_csv(labels_data)\n        labels_df.set_index(\"id\", inplace=True)  # set data frame index to id\n        self.labels = [labels_df.loc[filename[:-4]].values[0] for filename in\n                       file_names_sample]  # obtained labels from df\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.full_filenames)  # size of dataset\n\n    def __getitem__(self, idx):\n        # open image, apply transforms and return with label\n        image = Image.open(self.full_filenames[idx])  # Open Image with PIL\n        image = self.transform(image)  # Apply Specific Transformation to Image\n        return image, self.labels[idx]","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# define transformation that converts a PIL image into PyTorch tensors\nimport torchvision.transforms as transforms\n\ndata_transformer = transforms.Compose([transforms.ToTensor(),\n                                       transforms.Resize((46, 46))])","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define an object of the custom dataset for the train folder.\ndata_dir = '/kaggle/input/histopathologic-cancer-detection/'\nimg_dataset = MyDataSet(data_dir, data_transformer, \"train\")  # Histopathalogic images\n# load an example tensor\nimg, label = img_dataset[10]\nprint(img.shape, torch.min(img), torch.max(img))","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len_img = len(img_dataset)\nlen_train = int(0.8 * len_img)\nlen_val = len_img - len_train\n\n# Split Pytorch tensor\ntrain_ts, val_ts = random_split(img_dataset,\n                                [len_train, len_val])  # random split 80/20\n\nprint(\"train dataset size:\", len(train_ts))\nprint(\"validation dataset size:\", len(val_ts))","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2. Data Augmentation","metadata":{}},{"cell_type":"code","source":"# Define the following transformations for the training dataset\ntr_transf = transforms.Compose([\n    #     transforms.Resize((40,40)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomRotation(45),\n    #     transforms.RandomResizedCrop(50,scale=(0.8,1.0),ratio=(1.0,1.0)),\n    transforms.ToTensor()])","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# For the validation dataset, we don't need any augmentation; simply convert images into tensors\nval_transf = transforms.Compose([\n    transforms.ToTensor()])\n\n# After defining the transformations, overwrite the transform functions of train_ts, val_ts\ntrain_ts.transform = tr_transf\nval_ts.transform = val_transf\n\nprint(train_ts.transform)","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\n# Training DataLoader\ntrain_dl = DataLoader(train_ts,\n                      batch_size=32,\n                      shuffle=True)\n\n# Validation DataLoader\nval_dl = DataLoader(val_ts,\n                    batch_size=32,\n                    shuffle=False)","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 检查下可否正常使用\nfor x, y in train_dl:\n    print(x.shape, y)\n    break","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. Network Architecture","metadata":{}},{"cell_type":"code","source":"def findConv2dOutShape(hin, win, conv, pool=2):\n    # get conv arguments\n    kernel_size = conv.kernel_size\n    stride = conv.stride\n    padding = conv.padding\n    dilation = conv.dilation\n\n    hout = np.floor((hin + 2 * padding[0] - dilation[0] * (kernel_size[0] - 1) - 1) / stride[0] + 1)\n    wout = np.floor((win + 2 * padding[1] - dilation[1] * (kernel_size[1] - 1) - 1) / stride[1] + 1)\n\n    if pool:\n        hout /= pool\n        wout /= pool\n    return int(hout), int(wout)\n\n\nclass Network(nn.Module):\n    def __init__(self, params):\n        super(Network, self).__init__()\n\n        Cin, Hin, Win = params[\"shape_in\"]\n        init_f = params[\"initial_filters\"]\n        num_fc1 = params[\"num_fc1\"]\n        num_classes = params[\"num_classes\"]\n        self.dropout_rate = params[\"dropout_rate\"]\n\n        # Convolution Layers\n        self.conv = nn.Sequential(\n            nn.Conv2d(Cin, init_f, kernel_size=3),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2),\n            nn.Conv2d(init_f, 2 * init_f, kernel_size=3),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2),\n            nn.Conv2d(2 * init_f, 4 * init_f, kernel_size=3),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2),\n            nn.Conv2d(4 * init_f, 8 * init_f, kernel_size=3),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2)\n        )\n\n        # compute the flatten size\n        h, w = findConv2dOutShape(Hin, Win, self.conv[0])\n        h, w = findConv2dOutShape(h, w, self.conv[3])\n        h, w = findConv2dOutShape(h, w, self.conv[6])\n        h, w = findConv2dOutShape(h, w, self.conv[9])\n        self.num_flatten = h * w * 8 * init_f\n\n        # Fully Connected Layers\n        self.fc = nn.Sequential(\n            nn.Linear(self.num_flatten, num_fc1),\n            nn.ReLU(),\n            nn.Dropout(self.dropout_rate),\n            nn.Linear(num_fc1, num_classes)\n        )\n\n    def forward(self,X):\n        X = self.conv(X)\n        X = X.view(-1, self.num_flatten)\n        X = self.fc(X)\n        return F.log_softmax(X,dim=1)\n","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-07-23T12:33:07.987608Z","iopub.execute_input":"2023-07-23T12:33:07.987959Z","iopub.status.idle":"2023-07-23T12:33:08.434189Z","shell.execute_reply.started":"2023-07-23T12:33:07.987930Z","shell.execute_reply":"2023-07-23T12:33:08.432685Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Neural Network Predefined Parameters\nparams_model = {\n    \"shape_in\": (3, 46, 46),\n    \"initial_filters\": 8,\n    \"num_fc1\": 100,\n    \"dropout_rate\": 0.25,\n    \"num_classes\": 2}\n\n# Create instantiation of Network class\ncnn_model = Network(params_model)\n\n# define computation hardware approach (GPU/CPU)\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = cnn_model.to(device)","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchsummary import summary\n\nsummary(cnn_model, input_size=(3, 46, 46), device=device.type)","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. Loss Function and Optimizer","metadata":{}},{"cell_type":"code","source":"loss_func = nn.NLLLoss(reduction=\"sum\")\n\nfrom torch import optim\n\nopt = optim.Adam(cnn_model.parameters(), lr=3e-4)\nlr_scheduler = ReduceLROnPlateau(opt, mode='min', factor=0.5, patience=20, verbose=1)","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 5. Training","metadata":{}},{"cell_type":"markdown","source":"### 5.1 Helper Functions","metadata":{}},{"cell_type":"code","source":"''' Helper Functions'''\n\n\n# Function to get the learning rate\ndef get_lr(opt):\n    for param_group in opt.param_groups:\n        return param_group['lr']\n\n\n# Function to compute the loss value per batch of data\ndef loss_batch(loss_func, output, target, opt=None):\n    loss = loss_func(output, target)  # get loss\n    pred = output.argmax(dim=1, keepdim=True)  # Get Output Class\n    metric_b = pred.eq(target.view_as(pred)).sum().item()  # get performance metric\n\n    if opt is not None:\n        opt.zero_grad()\n        loss.backward()\n        opt.step()\n\n    return loss.item(), metric_b\n\n\n# Compute the loss value & performance metric for the entire dataset (epoch)\ndef loss_epoch(model, loss_func, dataset_dl, opt=None):\n    run_loss = 0.0\n    t_metric = 0.0\n    len_data = len(dataset_dl.dataset)\n\n    # internal loop over dataset\n    for xb, yb in dataset_dl:\n        # move batch to device\n        xb = xb.to(device)\n        yb = yb.to(device)\n        output = model(xb)  # get model output\n        loss_b, metric_b = loss_batch(loss_func, output, yb, opt)  # get loss per batch\n        run_loss += loss_b  # update running loss\n\n        if metric_b is not None:  # update running metric\n            t_metric += metric_b\n\n    loss = run_loss / float(len_data)  # average loss value\n    metric = t_metric / float(len_data)  # average metric value\n\n    return loss, metric","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 5.2 Main Training Function","metadata":{}},{"cell_type":"code","source":"from tqdm.notebook import trange, tqdm\n\n\ndef train_val(model, params, verbose=True):\n    # Get the parameters\n    epochs = params[\"epochs\"]\n    loss_func = params[\"f_loss\"]\n    opt = params[\"optimiser\"]\n    train_dl = params[\"train\"]\n    val_dl = params[\"val\"]\n    lr_scheduler = params[\"lr_change\"]\n    weight_path = params[\"weight_path\"]\n\n    loss_history = {\"train\": [], \"val\": []}  # history of loss values in each epoch\n    metric_history = {\"train\": [], \"val\": []}  # histroy of metric values in each epoch\n    best_model_wts = copy.deepcopy(model.state_dict())  # a deep copy of weights for the best performing model\n    best_loss = float('inf')  # initialize the best loss to a large value\n\n    ''' Train Model n_epochs '''\n\n    for epoch in tqdm(range(epochs)):\n\n        ''' Get the Learning Rate '''\n        current_lr = get_lr(opt)\n        if (verbose):\n            print('Epoch {}/{}, current lr={}'.format(epoch, epochs - 1, current_lr))\n\n        '''\n\n        Train Model Process\n\n        '''\n\n        model.train()\n        train_loss, train_metric = loss_epoch(model, loss_func, train_dl, opt)\n\n        # collect losses\n        loss_history[\"train\"].append(train_loss)\n        metric_history[\"train\"].append(train_metric)\n\n        '''\n\n        Evaluate Model Process\n\n        '''\n\n        model.eval()\n        with torch.no_grad():\n            val_loss, val_metric = loss_epoch(model, loss_func, val_dl)\n\n        # store best model\n        if (val_loss < best_loss):\n            best_loss = val_loss\n            best_model_wts = copy.deepcopy(model.state_dict())\n\n            # store weights into a local file\n            torch.save(model.state_dict(), weight_path)\n            if (verbose):\n                print(\"Copied best model weights!\")\n\n        # collect loss and metric for validation dataset\n        loss_history[\"val\"].append(val_loss)\n        metric_history[\"val\"].append(val_metric)\n\n        # learning rate schedule\n        lr_scheduler.step(val_loss)\n        if current_lr != get_lr(opt):\n            if (verbose):\n                print(\"Loading best model weights!\")\n            model.load_state_dict(best_model_wts)\n\n        if (verbose):\n            print(f\"train loss: {train_loss:.6f}, dev loss: {val_loss:.6f}, accuracy: {100 * val_metric:.2f}\")\n            print(\"-\" * 10)\n\n            # load best model weights\n    model.load_state_dict(best_model_wts)\n\n    return model, loss_history, metric_history","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 5.3 Training Process","metadata":{}},{"cell_type":"code","source":"params_train = {\n    \"train\": train_dl, \"val\": val_dl,\n    \"epochs\": 50,\n    \"optimiser\": optim.Adam(cnn_model.parameters(), lr=3e-4),\n    \"lr_change\": ReduceLROnPlateau(opt,\n                                   mode='min',\n                                   factor=0.5,\n                                   patience=20,\n                                   verbose=0),\n    \"f_loss\": nn.NLLLoss(reduction=\"sum\"),\n    \"weight_path\": \"weights.pt\",\n}\n\n''' Actual Train / Evaluation of CNN Model '''\n# train and validate the model\n\ncnn_model, loss_hist, metric_hist = train_val(cnn_model, params_train)","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Loss & Evaluation Metric Visualisation","metadata":{}},{"cell_type":"code","source":"import seaborn as sns\n\nsns.set(style='whitegrid')\n\nepochs = params_train[\"epochs\"]\n\nfig, ax = plt.subplots(1, 2, figsize=(12, 5))\n\nsns.lineplot(x=[*range(1, epochs + 1)], y=loss_hist[\"train\"], ax=ax[0], label='loss_hist[\"train\"]')\nsns.lineplot(x=[*range(1, epochs + 1)], y=loss_hist[\"val\"], ax=ax[0], label='loss_hist[\"val\"]')\nsns.lineplot(x=[*range(1, epochs + 1)], y=metric_hist[\"train\"], ax=ax[1], label='metric_hist[\"train\"]')\nsns.lineplot(x=[*range(1, epochs + 1)], y=metric_hist[\"val\"], ax=ax[1], label='metric_hist[\"val\"]')\nplt.title('Convergence History')","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 7. Inference","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class pytorchdata_test(Dataset):\n\n    def __init__(self, data_dir, transform,data_type=\"train\"):\n\n        path2data = os.path.join(data_dir,data_type)\n        filenames = os.listdir(path2data)\n        self.full_filenames = [os.path.join(path2data, f) for f in filenames]\n\n        # labels are in a csv file named train_labels.csv\n        csv_filename=\"sample_submission.csv\"\n        path2csvLabels=os.path.join(data_dir,csv_filename)\n        labels_df=pd.read_csv(path2csvLabels)\n\n        # set data frame index to id\n        labels_df.set_index(\"id\", inplace=True)\n\n        # obtain labels from data frame\n        self.labels = [labels_df.loc[filename[:-4]].values[0] for filename in filenames]\n        self.transform = transform\n\n    def __len__(self):\n        # return size of dataset\n        return len(self.full_filenames)\n\n    def __getitem__(self, idx):\n        # open image, apply transforms and return with label\n        image = Image.open(self.full_filenames[idx]) # PIL image\n        image = self.transform(image)\n        return image, self.labels[idx]","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# load any model weights for the model\ncnn_model.load_state_dict(torch.load('weights.pt'))","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_dir = '/kaggle/input/histopathologic-cancer-detection/'\n\ndata_transformer = transforms.Compose([transforms.ToTensor(),\n                                       transforms.Resize((46,46))])\n\nimg_dataset_test = pytorchdata_test(data_dir,data_transformer,data_type=\"test\")\nprint(len(img_dataset_test), 'samples found')","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def inference(model,dataset,device,num_classes=2):\n\n    len_data=len(dataset)\n    y_out=torch.zeros(len_data,num_classes) # initialize output tensor on CPU\n    y_gt=np.zeros((len_data),dtype=\"uint8\") # initialize ground truth on CPU\n    model=model.to(device) # move model to device\n\n    with torch.no_grad():\n        for i in tqdm(range(len_data)):\n            x,y=dataset[i]\n            y_gt[i]=y\n            y_out[i]=model(x.unsqueeze(0).to(device))\n\n    return y_out.numpy(),y_gt","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_test_out,_ = inference(cnn_model,img_dataset_test, device)","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# class predictions 0,1\ny_test_pred=np.argmax(y_test_out,axis=1)\nprint(y_test_pred.shape)\nprint(y_test_pred[0:5])","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# probabilities of predicted selection\n# return F.log_softmax(x, dim=1) ie.\npreds = np.exp(y_test_out[:, 1])\nprint(preds.shape)\nprint(preds[0:5])","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false}},"execution_count":null,"outputs":[]}]}