{"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":"gpu","dataSources":[{"sourceId":66696,"databundleVersionId":7411250,"sourceType":"competition"}],"dockerImageVersionId":30665,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Please comment and upvote if you like this work!\n\n## Take a look at the other notebooks in my profile:\n## [CV notebooks](https://www.kaggle.com/killa92/code)","metadata":{}},{"cell_type":"markdown","source":"### Custom Datasets and DataLoaders","metadata":{}},{"cell_type":"code","source":"import os, torch, shutil, numpy as np\nfrom glob import glob; from PIL import Image\nfrom torch.utils.data import random_split, Dataset, DataLoader\nfrom torchvision import transforms as T\ntorch.manual_seed(2024)\n\nclass CustomDataset(Dataset):\n    \n    def __init__(self, root, data_type = \"train\", transformations = None):\n        \n        self.transformations, self.data_type = transformations, data_type\n        self.im_paths = sorted(glob(f\"{root}/{data_type}/*/*.png\")) if data_type == \"train\" else sorted(glob(f\"{root}/{data_type}/*.png\"))\n        \n        if self.data_type == \"train\":\n            self.cls_names, self.cls_counts, count, data_count = {}, {}, 0, 0\n            for idx, im_path in enumerate(self.im_paths):\n                class_name = self.get_class(im_path)\n                if class_name not in self.cls_names: self.cls_names[class_name] = count; self.cls_counts[class_name] = 1; count += 1\n                else: self.cls_counts[class_name] += 1\n        \n    def get_class(self, path): return os.path.dirname(path).split(\"/\")[-1]\n    \n    def __len__(self): return len(self.im_paths)\n\n    def __getitem__(self, idx):\n        \n        im_path = self.im_paths[idx]\n        im = Image.open(im_path).convert(\"RGB\")\n        if self.data_type == \"train\": gt = self.cls_names[self.get_class(im_path)]\n        if self.transformations is not None: im = self.transformations(im)\n        \n        return (im, gt) if self.data_type == \"train\" else (im, im_path)\n    \ndef get_dls(root, transformations, bs, split = [0.9, 0.05, 0.05], ns = 4):\n    \n    ds = CustomDataset(root = root, data_type = \"train\", transformations = transformations)\n    \n    total_len = len(ds)\n    tr_len = int(total_len * split[0])\n    vl_len = int(total_len * split[1])\n    ts_len = total_len - (tr_len + vl_len)\n    \n    tr_ds, vl_ds, ts_ds = random_split(dataset = ds, lengths = [tr_len, vl_len, ts_len])\n    \n    tr_dl, val_dl, ts_dl = DataLoader(tr_ds, batch_size = bs, shuffle = True, num_workers = ns), DataLoader(vl_ds, batch_size = bs, shuffle = False, num_workers = ns), DataLoader(ts_ds, batch_size = 1, shuffle = False, num_workers = ns)\n    \n    return tr_dl, val_dl, ts_dl, ds.cls_names\n\nroot = \"/kaggle/input/dhu-ailab-image-classification\"\n# mean, std, im_size = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225], 224\nmean, std, im_size = [0.5, 0.5, 0.5], [0.5, 0.5, 0.5], 224\ntfs = T.Compose([T.Resize((im_size, im_size)), T.ToTensor(), T.Normalize(mean = mean, std = std)])\ntr_dl, val_dl, ts_dl, classes = get_dls(root = root, transformations = tfs, bs = 32)\n\nprint(len(tr_dl)); print(len(val_dl)); print(len(ts_dl)); print(classes)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-03-12T07:06:19.030399Z","iopub.execute_input":"2024-03-12T07:06:19.030825Z","iopub.status.idle":"2024-03-12T07:06:19.099196Z","shell.execute_reply.started":"2024-03-12T07:06:19.030792Z","shell.execute_reply":"2024-03-12T07:06:19.098097Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Data Visualization","metadata":{}},{"cell_type":"code","source":"import random\nfrom matplotlib import pyplot as plt\n\ndef tensor_2_im(t, t_type = \"rgb\"):\n    \n    gray_tfs = T.Compose([T.Normalize(mean = [ 0.], std = [1/0.5]), T.Normalize(mean = [-0.5], std = [1])])\n#     rgb_tfs = T.Compose([T.Normalize(mean = [ 0., 0., 0. ], std = [ 1/0.229, 1/0.224, 1/0.225 ]), T.Normalize(mean = [ -0.485, -0.456, -0.406 ], std = [ 1., 1., 1. ])])\n    rgb_tfs = T.Compose([T.Normalize(mean = [ 0., 0., 0. ], std = [ 1/0.5, 1/0.5, 1/0.5 ]), T.Normalize(mean = [ -0.5, -0.5, -0.5 ], std = [ 1., 1., 1. ])])\n    \n    invTrans = gray_tfs if t_type == \"gray\" else rgb_tfs \n    \n    return (invTrans(t) * 255).detach().squeeze().cpu().permute(1,2,0).numpy().astype(np.uint8) if t_type == \"gray\" else (invTrans(t) * 255).detach().cpu().permute(1,2,0).numpy().astype(np.uint8)\n\ndef visualize(data, n_ims, rows, cmap = None, cls_names = None):\n    \n    assert cmap in [\"rgb\", \"gray\"], \"Rasmni oq-qora yoki rangli ekanini aniqlashtirib bering!\"\n    if cmap == \"rgb\": cmap = \"viridis\"\n    \n    plt.figure(figsize = (20, 10))\n    indekslar = [random.randint(0, len(data) - 1) for _ in range(n_ims)]\n    for idx, indeks in enumerate(indekslar):\n        \n        im, gt = data[indeks]\n        # Start plot\n        plt.subplot(rows, n_ims // rows, idx + 1)\n        if cmap: plt.imshow(tensor_2_im(im, cmap), cmap=cmap)\n        else: plt.imshow(tensor_2_im(im))\n        plt.axis('off')\n        if cls_names is not None: plt.title(f\"GT -> {cls_names[int(gt)]}\")\n        else: plt.title(f\"GT -> {gt}\")\n            \nvisualize(tr_dl.dataset, 20, 4, \"rgb\", list(classes.keys()))","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:06:19.101245Z","iopub.execute_input":"2024-03-12T07:06:19.101629Z","iopub.status.idle":"2024-03-12T07:06:21.877777Z","shell.execute_reply.started":"2024-03-12T07:06:19.101599Z","shell.execute_reply":"2024-03-12T07:06:21.876663Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"visualize(val_dl.dataset, 20, 4, \"rgb\", list(classes.keys()))","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:06:21.879038Z","iopub.execute_input":"2024-03-12T07:06:21.879354Z","iopub.status.idle":"2024-03-12T07:06:24.569020Z","shell.execute_reply.started":"2024-03-12T07:06:21.879327Z","shell.execute_reply":"2024-03-12T07:06:24.567825Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"visualize(ts_dl.dataset, 20, 4, \"rgb\", list(classes.keys()))","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:06:24.572136Z","iopub.execute_input":"2024-03-12T07:06:24.572539Z","iopub.status.idle":"2024-03-12T07:06:27.357401Z","shell.execute_reply.started":"2024-03-12T07:06:24.572490Z","shell.execute_reply":"2024-03-12T07:06:27.356296Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Data Analysis","metadata":{}},{"cell_type":"code","source":"def data_analysis(root, transformations):\n    \n    ds = CustomDataset(root = root, transformations = transformations)\n    cls_counts, width, text_width = ds.cls_counts,  0.7, 0.05\n    text_height = 2\n    cls_names = list(cls_counts.keys()); counts = list(cls_counts.values())\n    \n    fig, ax = plt.subplots(figsize = (20, 10))\n    indices = np.arange(len(counts))\n\n    ax.bar(indices, counts, width, color = \"firebrick\")\n    ax.set_xlabel(\"Class Names\", color = \"red\")\n    ax.set_xticklabels(cls_names, rotation = 60)\n    ax.set(xticks = indices, xticklabels = cls_names)\n    ax.set_ylabel(\"Data Counts\", color = \"red\")\n    ax.set_title(f\"Dataset Class Imbalance Analysis\")\n\n    for i, v in enumerate(counts): ax.text(i - text_width, v + text_height, str(v), color = \"royalblue\")\n    \ndata_analysis(root = root, transformations = tfs)","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:06:27.358909Z","iopub.execute_input":"2024-03-12T07:06:27.359256Z","iopub.status.idle":"2024-03-12T07:06:27.969458Z","shell.execute_reply.started":"2024-03-12T07:06:27.359226Z","shell.execute_reply":"2024-03-12T07:06:27.968337Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### AI Model Train and Validation","metadata":{}},{"cell_type":"code","source":"import timm, torchmetrics\nfrom tqdm import tqdm\nm = timm.create_model(\"rexnet_200\", pretrained = True, num_classes = len(classes))  \n\ndef train_setup(m): return m.to(\"cuda\").eval(), 20, \"cuda\", torch.nn.CrossEntropyLoss(), torch.optim.Adam(params = m.parameters(), lr = 1e-4)\ndef to_device(batch, device): return batch[0].to(device), batch[1].to(device)\ndef get_metrics(model, ims, gts, loss_fn, epoch_loss, epoch_acc, epoch_f1): preds = model(ims); loss = loss_fn(preds, gts); return loss, epoch_loss + (loss.item()), epoch_acc + (torch.argmax(preds, dim = 1) == gts).sum().item(), epoch_f1 + f1_score(preds, gts)\n\nm, epochs, device, loss_fn, optimizer = train_setup(m)\n\nf1_score = torchmetrics.F1Score(task = \"multiclass\", num_classes = len(classes)).to(device)\nsave_prefix, save_dir = \"dubai\", \"saved_models\"\nprint(\"Start training...\")\nbest_acc, best_loss, threshold, not_improved, patience = 0, float(\"inf\"), 0.01, 0, 10\ntr_losses, val_losses, tr_accs, val_accs, tr_f1s, val_f1s = [], [], [], [], [], []\n\nbest_loss = float(torch.inf)\n    \nfor epoch in range(epochs):\n\n    epoch_loss, epoch_acc, epoch_f1 = 0, 0, 0\n    for idx, batch in tqdm(enumerate(tr_dl)):\n\n        ims, gts = to_device(batch, device)\n\n        loss, epoch_loss, epoch_acc, epoch_f1 = get_metrics(m, ims, gts, loss_fn, epoch_loss, epoch_acc, epoch_f1)\n        optimizer.zero_grad(); loss.backward(); optimizer.step()\n\n    tr_loss_to_track = epoch_loss / len(tr_dl)\n    tr_acc_to_track  = epoch_acc  / len(tr_dl.dataset)\n    tr_f1_to_track   = epoch_f1   / len(tr_dl)\n    tr_losses.append(tr_loss_to_track); tr_accs.append(tr_acc_to_track); tr_f1s.append(tr_f1_to_track)\n\n    print(f\"{epoch + 1}-epoch train process is completed!\")\n    print(f\"{epoch + 1}-epoch train loss          -> {tr_loss_to_track:.3f}\")\n    print(f\"{epoch + 1}-epoch train accuracy      -> {tr_acc_to_track:.3f}\")\n    print(f\"{epoch + 1}-epoch train f1-score      -> {tr_f1_to_track:.3f}\")\n\n    m.eval()\n    with torch.no_grad():\n        val_epoch_loss, val_epoch_acc, val_epoch_f1 = 0, 0, 0\n        for idx, batch in enumerate(val_dl):\n            ims, gts = to_device(batch, device)\n            loss, val_epoch_loss, val_epoch_acc, val_epoch_f1 = get_metrics(m, ims, gts, loss_fn, val_epoch_loss, val_epoch_acc, val_epoch_f1)\n\n        val_loss_to_track = val_epoch_loss / len(val_dl)\n        val_acc_to_track  = val_epoch_acc  / len(val_dl.dataset)\n        val_f1_to_track   = val_epoch_f1   / len(val_dl)\n        val_losses.append(val_loss_to_track); val_accs.append(val_acc_to_track); val_f1s.append(val_f1_to_track)\n\n        print(f\"{epoch + 1}-epoch validation process is completed!\")\n        print(f\"{epoch + 1}-epoch validation loss     -> {val_loss_to_track:.3f}\")\n        print(f\"{epoch + 1}-epoch validation accuracy -> {val_acc_to_track:.3f}\")\n        print(f\"{epoch + 1}-epoch validation f1-score -> {val_f1_to_track:.3f}\")\n\n        if val_loss_to_track < (best_loss + threshold):\n            os.makedirs(save_dir, exist_ok = True)\n            best_loss = val_loss_to_track\n            torch.save(m.state_dict(), f\"{save_dir}/{save_prefix}_best_model.pth\")\n            \n        else:\n            not_improved += 1\n            print(f\"Loss value did not decrease for {not_improved} epochs\")\n            if not_improved == patience:\n                print(f\"Stop training since loss value did not decrease for {patience} epochs.\")\n                break","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:06:27.971130Z","iopub.execute_input":"2024-03-12T07:06:27.971556Z","iopub.status.idle":"2024-03-12T07:16:47.357381Z","shell.execute_reply.started":"2024-03-12T07:06:27.971520Z","shell.execute_reply":"2024-03-12T07:16:47.356016Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Learning Curves","metadata":{}},{"cell_type":"code","source":"class PlotLearningCurves:\n    \n    def __init__(self, tr_losses, val_losses, tr_accs, val_accs, tr_f1s, val_f1s):\n        \n        self.tr_losses, self.val_losses, self.tr_accs, self.val_accs, self.tr_f1s, self.val_f1s = tr_losses, val_losses, tr_accs, val_accs, tr_f1s, val_f1s\n        \n    def plot(self, array_1, array_2, label_1, label_2, color_1, color_2):\n        \n        plt.plot(array_1, label = label_1, c = color_1); plt.plot(array_2, label = label_2, c = color_2)\n        \n    def create_figure(self): plt.figure(figsize = (10, 5))\n    \n    def decorate(self, ylabel, xlabel = \"Epochs\"): \n        \n        plt.xlabel(xlabel); plt.ylabel(ylabel)\n        plt.xticks(ticks = np.arange(len(self.tr_accs)), labels = [i for i in range(1, len(self.tr_accs) + 1)])\n        plt.legend(); plt.show()      \n        \n    def visualize(self):\n        \n        # Figure 1\n        self.create_figure()\n        self.plot(array_1 = self.tr_losses, array_2 = self.val_losses, label_1 = \"Train Loss\", label_2 = \"Validation Loss\", color_1 = \"red\", color_2 = \"blue\"); self.decorate(ylabel = \"Loss Values\")\n        \n        # Figure 2\n        self.create_figure()\n        self.plot(array_1 = self.tr_accs, array_2 = self.val_accs, label_1 = \"Train Accuracy\", label_2 = \"Validation Accuracy\", color_1 = \"orangered\", color_2 = \"darkgreen\")\n        self.decorate(ylabel = \"Accuracy Scores\")\n        \n        # Figure 3\n        self.create_figure()\n        self.plot(array_1 = [tr_f1.cpu() for tr_f1 in self.tr_f1s], array_2 = [vl_f1.cpu() for vl_f1 in self.val_f1s], label_1 = \"Train F1 Score\", label_2 = \"Validation F1 Score\", color_1 = \"blueviolet\", color_2 = \"crimson\"); self.decorate(ylabel = \"F1 Scores\")\n        \nPlotLearningCurves(tr_losses, val_losses, tr_accs, val_accs, tr_f1s, val_f1s).visualize()","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:16:47.359483Z","iopub.execute_input":"2024-03-12T07:16:47.359859Z","iopub.status.idle":"2024-03-12T07:16:48.473239Z","shell.execute_reply.started":"2024-03-12T07:16:47.359826Z","shell.execute_reply":"2024-03-12T07:16:48.472017Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Inference and AI Model Performance Analysis with GradCAM","metadata":{}},{"cell_type":"code","source":"import cv2\nclass SaveFeatures():\n    \n    \"\"\" Extract pretrained activations\"\"\"\n    features = None\n    def __init__(self, m):\n        self.hook = m.register_forward_hook(self.hook_fn)\n    def hook_fn(self, module, input, output):\n        self.features = ((output.cpu()).data).numpy()\n    def remove(self): self.hook.remove()\n\ndef getCAM(conv_fs, linear_weights, class_idx):\n    \n    bs, chs, h, w = conv_fs.shape\n    cam = linear_weights[class_idx].dot(conv_fs[0,:, :, ].reshape((chs, h * w)))\n    cam = cam.reshape(h, w)\n    \n    return (cam - np.min(cam)) / np.max(cam)\n\ndef inference(model, device, test_dl, num_ims, row, final_conv, fc_params, cls_names = None):\n    \n    weight, acc = np.squeeze(fc_params[0].cpu().data.numpy()), 0\n    activated_features = SaveFeatures(final_conv)\n    preds, images, lbls = [], [], []\n    for idx, batch in tqdm(enumerate(test_dl)):\n        im, gt = to_device(batch, device)\n        pred_class = torch.argmax(model(im), dim = 1)\n        acc += (pred_class == gt).sum().item()\n        images.append(im)\n        preds.append(pred_class.item())\n        lbls.append(gt.item())\n    \n    print(f\"Accuracy of the model on the test data -> {(acc / len(test_dl.dataset)):.3f}\")\n    \n    plt.figure(figsize = (20, 10))\n    indekslar = [random.randint(0, len(images) - 1) for _ in range(num_ims)]\n    \n    for idx, indeks in enumerate(indekslar):\n        \n        im = images[indeks].squeeze()\n        pred_idx = preds[indeks]\n        heatmap = getCAM(activated_features.features, weight, pred_idx)\n        \n        # Start plot\n        plt.subplot(row, num_ims // row, idx + 1)\n        plt.imshow(tensor_2_im(im), cmap = \"gray\"); plt.axis(\"off\")\n        plt.imshow(cv2.resize(heatmap, (im_size, im_size), interpolation=cv2.INTER_LINEAR), alpha=0.4, cmap='jet'); plt.axis(\"off\")\n        \n        if cls_names is not None: plt.title(f\"GT -> {cls_names[int(lbls[indeks])]} ; PRED -> {cls_names[int(preds[indeks])]}\", color=(\"green\" if {cls_names[int(lbls[indeks])]} == {cls_names[int(preds[indeks])]} else \"red\"))\n        else: plt.title(f\"GT -> {gt} ; PRED -> {pred}\")\n\nm.load_state_dict(torch.load(f\"{save_dir}/{save_prefix}_best_model.pth\"))\nm.eval()\nfinal_conv, fc_params = m.features[-1], list(m.head.fc.parameters())\ninference(model = m.to(device), device = device, test_dl = ts_dl, num_ims = 20, row = 4, cls_names = list(classes.keys()), final_conv = final_conv, fc_params = fc_params)","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:16:48.475181Z","iopub.execute_input":"2024-03-12T07:16:48.475605Z","iopub.status.idle":"2024-03-12T07:16:57.205571Z","shell.execute_reply.started":"2024-03-12T07:16:48.475567Z","shell.execute_reply":"2024-03-12T07:16:57.204281Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Submission","metadata":{}},{"cell_type":"code","source":"submission_ds = CustomDataset(root = root, data_type = \"test\", transformations = tfs)\nsubmission_dl = DataLoader(dataset = submission_ds, batch_size = 1, shuffle = False, num_workers = 8)","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:16:57.207357Z","iopub.execute_input":"2024-03-12T07:16:57.207767Z","iopub.status.idle":"2024-03-12T07:16:57.215951Z","shell.execute_reply.started":"2024-03-12T07:16:57.207728Z","shell.execute_reply":"2024-03-12T07:16:57.214967Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tqdm import tqdm\nimport pandas as pd\n\ntest_predictions, fnames = [], []\n\nfor idx, data in enumerate(submission_dl):\n    im, im_path = data\n    with torch.no_grad():\n        pred_logits = m(im.to(device))\n        pred = torch.argmax(pred_logits, dim = 1)\n        test_predictions.append(list(classes.keys())[pred.cpu().item()])\n        fnames.append(os.path.basename(im_path[0]))","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:16:57.219492Z","iopub.execute_input":"2024-03-12T07:16:57.219844Z","iopub.status.idle":"2024-03-12T07:17:00.694299Z","shell.execute_reply.started":"2024-03-12T07:16:57.219818Z","shell.execute_reply":"2024-03-12T07:17:00.692990Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred_df = pd.DataFrame({\"file\": fnames, \"species\": test_predictions})\npred_df.to_csv(\"my_submission.csv\", index = False)","metadata":{"execution":{"iopub.status.busy":"2024-03-12T07:17:00.696024Z","iopub.execute_input":"2024-03-12T07:17:00.696374Z","iopub.status.idle":"2024-03-12T07:17:00.706186Z","shell.execute_reply.started":"2024-03-12T07:17:00.696342Z","shell.execute_reply":"2024-03-12T07:17:00.705196Z"},"trusted":true},"execution_count":null,"outputs":[]}]}