{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":4918.797895,"end_time":"2025-06-15T20:16:14.533373","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-06-15T18:54:15.735478","version":"2.6.0"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"321a77af071a4416b82aaf24eb688f05":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_f85511d0b182478a9b7e856a8217906a","placeholder":"​","style":"IPY_MODEL_a3870b4367cd4260ada400386a9ad072","tabbable":null,"tooltip":null,"value":" 355M/355M [00:01&lt;00:00, 248MB/s]"}},"3b9684f8e6f245b99e4b346ea215cd7c":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_8315ef9077034ffb8ffb69b9e9b1689a","placeholder":"​","style":"IPY_MODEL_a269751e79274c19adad395775deb529","tabbable":null,"tooltip":null,"value":"model.safetensors: 100%"}},"4341e46847b548bf89d0e3ae59830308":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"559808ad311c4cc0867798ca5d8f3de2":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"8315ef9077034ffb8ffb69b9e9b1689a":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"9699bf0e2b434f8cb5d028fefd9f33d3":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"success","description":"","description_allow_html":false,"layout":"IPY_MODEL_4341e46847b548bf89d0e3ae59830308","max":354909632,"min":0,"orientation":"horizontal","style":"IPY_MODEL_559808ad311c4cc0867798ca5d8f3de2","tabbable":null,"tooltip":null,"value":354909632}},"a044903dae244964ab53b4d517aac9bd":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"a269751e79274c19adad395775deb529":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"a3870b4367cd4260ada400386a9ad072":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"bbb1f7a0c6634826995d5e2e52cef1e9":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_3b9684f8e6f245b99e4b346ea215cd7c","IPY_MODEL_9699bf0e2b434f8cb5d028fefd9f33d3","IPY_MODEL_321a77af071a4416b82aaf24eb688f05"],"layout":"IPY_MODEL_a044903dae244964ab53b4d517aac9bd","tabbable":null,"tooltip":null}},"f85511d0b182478a9b7e856a8217906a":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}}},"version_major":2,"version_minor":0}}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Importing Required Libraries","metadata":{"papermill":{"duration":0.005621,"end_time":"2025-06-15T18:54:19.273281","exception":false,"start_time":"2025-06-15T18:54:19.267660","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nimport copy\nimport timm\nimport random\nimport time\nimport torch\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.optim.optimizer\nimport concurrent.futures\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport hashlib\nfrom PIL import Image\nfrom collections import OrderedDict\nfrom torch.utils.data import Dataset, DataLoader, Subset, random_split\nfrom torch.cuda import amp\nfrom torchvision import transforms as T\nfrom torchvision.io import read_image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.metrics import accuracy_score, confusion_matrix, f1_score, classification_report\nfrom tqdm import tqdm\n\nprint(torch.__version__)","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2025-07-03T03:45:16.149613Z","iopub.execute_input":"2025-07-03T03:45:16.150227Z","iopub.status.idle":"2025-07-03T03:45:16.156384Z","shell.execute_reply.started":"2025-07-03T03:45:16.150202Z","shell.execute_reply":"2025-07-03T03:45:16.155486Z"},"papermill":{"duration":18.978597,"end_time":"2025-06-15T18:54:38.256987","exception":false,"start_time":"2025-06-15T18:54:19.278390","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Seeds for Reproducibility","metadata":{"papermill":{"duration":0.004818,"end_time":"2025-06-15T18:54:38.267326","exception":false,"start_time":"2025-06-15T18:54:38.262508","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def seed_everything(seed):\n    \"\"\"\n    Sets seeds for reproducibility in training.\n\n    Args:\n        seed (int): Seed value to ensure determinism.\n    \"\"\"\n    random.seed(seed)\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)  # Seed for hash-based operations\n    np.random.seed(seed)  # Seed for NumPy\n    torch.manual_seed(seed)  # Seed for PyTorch (CPU)\n    torch.cuda.manual_seed(seed)  # Seed for PyTorch (GPU)\n    torch.backends.cudnn.deterministic = True  # Make CuDNN deterministic\n    torch.backends.cudnn.benchmark = False  # Enable benchmark mode for CuDNN\n\nseed_everything(42)","metadata":{"execution":{"iopub.status.busy":"2025-07-03T03:45:18.476930Z","iopub.execute_input":"2025-07-03T03:45:18.477173Z","iopub.status.idle":"2025-07-03T03:45:18.487815Z","shell.execute_reply.started":"2025-07-03T03:45:18.477157Z","shell.execute_reply":"2025-07-03T03:45:18.487087Z"},"papermill":{"duration":0.011316,"end_time":"2025-06-15T18:54:38.283516","exception":false,"start_time":"2025-06-15T18:54:38.272200","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Cleaning","metadata":{}},{"cell_type":"code","source":"# Path to the image directory and load the CSV file\nimage_dir = '../input/aptos2019-blindness-detection/train_images'\ndata = pd.read_csv('../input/aptos2019-blindness-detection/train.csv')\n\n# Add .png extension to the ID column to get the actual filenames\ndata['file_name'] = data['id_code'] + '.png'\n\n# 1. Check for missing files\nmissing_files = []\nfor fname in data['file_name']:\n    if not os.path.exists(os.path.join(image_dir, fname)):\n        missing_files.append(fname)\n\nif missing_files:\n    print(f\"{len(missing_files)} file hilang dan akan dihapus dari data:\")\n    data = data[~data['file_name'].isin(missing_files)]\nelse:\n    print(\"✅ Semua file gambar ada.\")\n\n# 2. Remove/flag corrupted images & 3. Validate resolution + resize to 384x384\nvalid_images = []\ninvalid_images = []\nresized_dir = './resized_cleaned_images_384'\nos.makedirs(resized_dir, exist_ok=True)\n\nfor idx, row in tqdm(data.iterrows(), total=len(data)):\n    path = os.path.join(image_dir, row['file_name'])\n    try:\n        img = Image.open(path)\n        img.verify()  # validate file content\n        img = Image.open(path).convert('RGB')  # reopen for resizing\n        img = img.resize((384, 384))\n        img.save(os.path.join(resized_dir, row['file_name']))  # save to new folder\n        valid_images.append(row['file_name'])\n    except Exception as e:\n        print(f\"[X] Rusak: {row['file_name']} - {e}\")\n        invalid_images.append(row['file_name'])\n\n# Keep only valid images in the DataFrame\ndata = data[data['file_name'].isin(valid_images)]\n\n# 4. Remove duplicate images based on hash comparison\nhash_dict = {}\nduplicate_files = []\n\ndef get_hash(file_path):\n    with open(file_path, 'rb') as f:\n        return hashlib.md5(f.read()).hexdigest()\n\nfor fname in tqdm(data['file_name']):\n    path = os.path.join(resized_dir, fname)\n    try:\n        img_hash = get_hash(path)\n        if img_hash in hash_dict:\n            duplicate_files.append(fname)\n        else:\n            hash_dict[img_hash] = fname\n    except Exception as e:\n        print(f\"Hashing error: {fname} - {e}\")\n\nif duplicate_files:\n    print(f\"Menghapus {len(duplicate_files)} duplikat gambar.\")\n    data = data[~data['file_name'].isin(duplicate_files)]\n\n# Reset index and save to a new CSV\ndata = data.reset_index(drop=True)\ndata.to_csv('cleaned_train.csv', index=False)\n\nprint(f\"✅ Total data bersih: {len(data)}\")\nprint(\"DataFrame disimpan ke 'cleaned_train.csv'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-03T03:45:22.565042Z","iopub.execute_input":"2025-07-03T03:45:22.565605Z","iopub.status.idle":"2025-07-03T04:00:03.640583Z","shell.execute_reply.started":"2025-07-03T03:45:22.565578Z","shell.execute_reply":"2025-07-03T04:00:03.639857Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EDA","metadata":{"papermill":{"duration":0.004748,"end_time":"2025-06-15T18:54:38.318087","exception":false,"start_time":"2025-06-15T18:54:38.313339","status":"completed"},"tags":[]}},{"cell_type":"code","source":"raw_data = pd.read_csv('/kaggle/input/aptos2019-blindness-detection/train.csv')\ncleaned_data = pd.read_csv('/kaggle/working/cleaned_train.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-03T04:00:03.641675Z","iopub.execute_input":"2025-07-03T04:00:03.641928Z","iopub.status.idle":"2025-07-03T04:00:03.653233Z","shell.execute_reply.started":"2025-07-03T04:00:03.641911Z","shell.execute_reply":"2025-07-03T04:00:03.652706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Number of samples Before Data Cleaning: ', raw_data.shape[0])\ndisplay(raw_data.head())","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:00:03.654028Z","iopub.execute_input":"2025-07-03T04:00:03.654281Z","iopub.status.idle":"2025-07-03T04:00:03.673097Z","shell.execute_reply.started":"2025-07-03T04:00:03.654254Z","shell.execute_reply":"2025-07-03T04:00:03.672414Z"},"papermill":{"duration":0.029789,"end_time":"2025-06-15T18:54:38.394107","exception":false,"start_time":"2025-06-15T18:54:38.364318","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Number of samples After Data Cleaning: ', cleaned_data.shape[0])\ndisplay(cleaned_data.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-03T04:00:03.674657Z","iopub.execute_input":"2025-07-03T04:00:03.674896Z","iopub.status.idle":"2025-07-03T04:00:03.682288Z","shell.execute_reply.started":"2025-07-03T04:00:03.674881Z","shell.execute_reply":"2025-07-03T04:00:03.681785Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"raw_data['diagnosis'].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-03T04:00:03.682912Z","iopub.execute_input":"2025-07-03T04:00:03.683659Z","iopub.status.idle":"2025-07-03T04:00:03.700509Z","shell.execute_reply.started":"2025-07-03T04:00:03.683633Z","shell.execute_reply":"2025-07-03T04:00:03.700029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cleaned_data['diagnosis'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:00:03.701144Z","iopub.execute_input":"2025-07-03T04:00:03.701654Z","iopub.status.idle":"2025-07-03T04:00:03.717422Z","shell.execute_reply.started":"2025-07-03T04:00:03.701637Z","shell.execute_reply":"2025-07-03T04:00:03.716902Z"},"papermill":{"duration":0.021748,"end_time":"2025-06-15T18:54:38.420965","exception":false,"start_time":"2025-06-15T18:54:38.399217","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Mapping label numeric to text\nlabel_names = {\n    0: \"No DR/Normal\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\",\n    4: \"Proliferative DR\"\n}\n\n# Copy data and change diagnosis to string label\ndata_viz = raw_data.copy()\ndata_viz[\"diagnosis\"] = data_viz[\"diagnosis\"].map(label_names)\n\n# Set label order manually\nlabel_order = [\n    \"No DR/Normal\",\n    \"Mild\",\n    \"Moderate\",\n    \"Severe\",\n    \"Proliferative DR\"\n]\ndata_viz[\"diagnosis\"] = pd.Categorical(data_viz[\"diagnosis\"], categories=label_order, ordered=True)\n\n# Set display\nplt.figure(figsize=(12, 7), facecolor=\"white\")\nsns.set_style(\"whitegrid\")\n\n# Barplot\nax = sns.countplot(x=\"diagnosis\", data=data_viz, palette=\"Blues_d\")\n\n# Data count\nfor p in ax.patches:\n    height = int(p.get_height())\n    ax.annotate(f'{height}', \n                (p.get_x() + p.get_width() / 2., height), \n                ha='center', va='bottom', fontsize=12, color='black')\n\n# Title & Label\nax.set_title(\"Distribusi Kelas Retinopati Diabetik Sebelum Data Cleaning\", fontsize=16, weight='bold')\nax.set_xlabel(\"Kategori Diagnosis\", fontsize=13)\nax.set_ylabel(\"Jumlah Gambar\", fontsize=13)\nplt.xticks(fontsize=12)\nplt.yticks(fontsize=12)\nsns.despine()\n\n# Save to file\nplt.tight_layout()\nplt.savefig(\"distribusi_kelas_before.png\", dpi=300, facecolor='white')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-03T04:00:03.718060Z","iopub.execute_input":"2025-07-03T04:00:03.718281Z","iopub.status.idle":"2025-07-03T04:00:04.584469Z","shell.execute_reply.started":"2025-07-03T04:00:03.718262Z","shell.execute_reply":"2025-07-03T04:00:04.583770Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Mapping label numeric to text\nlabel_names = {\n    0: \"No DR/Normal\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\",\n    4: \"Proliferative DR\"\n}\n\n# Copy data and change diagnosis to string label\ndata_viz = cleaned_data.copy()\ndata_viz[\"diagnosis\"] = data_viz[\"diagnosis\"].map(label_names)\n\n# Set label order manually\nlabel_order = [\n    \"No DR/Normal\",\n    \"Mild\",\n    \"Moderate\",\n    \"Severe\",\n    \"Proliferative DR\"\n]\ndata_viz[\"diagnosis\"] = pd.Categorical(data_viz[\"diagnosis\"], categories=label_order, ordered=True)\n\n# Set display\nplt.figure(figsize=(12, 7), facecolor=\"white\")\nsns.set_style(\"whitegrid\")\n\n# Barplot\nax = sns.countplot(x=\"diagnosis\", data=data_viz, palette=\"Blues_d\")\n\n# Data count\nfor p in ax.patches:\n    height = int(p.get_height())\n    ax.annotate(f'{height}', \n                (p.get_x() + p.get_width() / 2., height), \n                ha='center', va='bottom', fontsize=12, color='black')\n\n# Title & Label\nax.set_title(\"Distribusi Kelas Retinopati Diabetik Sesudah Data Cleaning\", fontsize=16, weight='bold')\nax.set_xlabel(\"Kategori Diagnosis\", fontsize=13)\nax.set_ylabel(\"Jumlah Gambar\", fontsize=13)\nplt.xticks(fontsize=12)\nplt.yticks(fontsize=12)\nsns.despine()\n\n# Save to file\nplt.tight_layout()\nplt.savefig(\"distribusi_kelas_after.png\", dpi=300, facecolor='white')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:00:04.585134Z","iopub.execute_input":"2025-07-03T04:00:04.585338Z","iopub.status.idle":"2025-07-03T04:00:05.325804Z","shell.execute_reply.started":"2025-07-03T04:00:04.585321Z","shell.execute_reply":"2025-07-03T04:00:05.325053Z"},"papermill":{"duration":0.792611,"end_time":"2025-06-15T18:54:39.218684","exception":false,"start_time":"2025-06-15T18:54:38.426073","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Setting the style for the plot\nsns.set_style(\"white\")\n\n# Mapping class labels to their corresponding categories\nlevel_to_category = {\n    0: \"No_DR\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\",\n    4: \"Proliferate_DR\"\n}\n\n# Plotting the first 15 images along with their labels\ncount = 1\nplt.figure(figsize=[20, 20])\n\nfor img_name in data['id_code'][:15]:  # Assuming 'train' contains the dataset\n    img = cv2.imread(f\"/kaggle/working/resized_cleaned_images_384/{img_name}.png\")[..., [2, 1, 0]]  # Reading the image\n    \n    # Getting the label (class) for the image\n    label = data[data['id_code'] == img_name]['diagnosis'].values[0]  # Assuming 'diagnosis' is the label column\n    \n    # Setting up the subplot with image and label\n    plt.subplot(5, 5, count)\n    plt.imshow(img)\n    plt.title(f\"Citra {count}: {level_to_category[label]}\")  # Display the class label\n    count += 1\n    \n# Display the plot\nplt.savefig('/kaggle/working/imagebeforepreprecssing.png')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:00:05.326707Z","iopub.execute_input":"2025-07-03T04:00:05.326978Z","iopub.status.idle":"2025-07-03T04:00:10.430415Z","shell.execute_reply.started":"2025-07-03T04:00:05.326955Z","shell.execute_reply":"2025-07-03T04:00:10.429526Z"},"papermill":{"duration":20.138579,"end_time":"2025-06-15T18:54:59.363147","exception":false,"start_time":"2025-06-15T18:54:39.224568","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Preprocessing","metadata":{"papermill":{"duration":0.029048,"end_time":"2025-06-15T18:54:59.420230","exception":false,"start_time":"2025-06-15T18:54:59.391182","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Set input and output directories\ninput_dir = '/kaggle/working/resized_cleaned_images_384'\noutput_dir = '/kaggle/working/processed_images/'\n\n# Ensure the output directory exists\nos.makedirs(output_dir, exist_ok=True)\n\n# Load the CSV containing cleanded image names and labels\ndf = pd.read_csv('/kaggle/working/cleaned_train.csv')","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:00:10.432900Z","iopub.execute_input":"2025-07-03T04:00:10.433119Z","iopub.status.idle":"2025-07-03T04:00:10.441484Z","shell.execute_reply.started":"2025-07-03T04:00:10.433102Z","shell.execute_reply":"2025-07-03T04:00:10.440728Z"},"papermill":{"duration":0.048358,"end_time":"2025-06-15T18:54:59.576124","exception":false,"start_time":"2025-06-15T18:54:59.527766","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to crop the image based on grayscale threshold\ndef crop_image_from_gray(img, tol=7):\n    if img.ndim == 2:\n        mask = img > tol\n        return img[np.ix_(mask.any(1), mask.any(0))]\n    elif img.ndim == 3:\n        gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        mask = gray_img > tol\n        \n        check_shape = img[:,:,0][np.ix_(mask.any(1), mask.any(0))].shape[0]\n        if check_shape == 0:  # Image is too dark so that we crop out everything\n            return img  # Return original image\n        else:\n            img1 = img[:,:,0][np.ix_(mask.any(1), mask.any(0))]\n            img2 = img[:,:,1][np.ix_(mask.any(1), mask.any(0))]\n            img3 = img[:,:,2][np.ix_(mask.any(1), mask.any(0))]\n            img = np.stack([img1, img2, img3], axis=-1)\n        return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-03T04:00:10.442290Z","iopub.execute_input":"2025-07-03T04:00:10.442501Z","iopub.status.idle":"2025-07-03T04:00:10.457185Z","shell.execute_reply.started":"2025-07-03T04:00:10.442487Z","shell.execute_reply":"2025-07-03T04:00:10.456339Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_image(row, sigmaX=10):\n    sample_image_id = row['id_code']\n    sample_image_file = sample_image_id + '.png'\n    sample_image_path = os.path.join(input_dir, sample_image_file)\n    \n    if os.path.exists(sample_image_path):\n        # Ben Graham Preprocessing\n        image = cv2.imread(sample_image_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        image = crop_image_from_gray(image)\n        image = cv2.resize(image, (384, 384))\n        image = cv2.addWeighted(image, 4, cv2.GaussianBlur(image, (0, 0), sigmaX), -4, 128)\n        \n        # Save the processed image to the output directory\n        output_path = os.path.join(output_dir, sample_image_file)\n        cv2.imwrite(output_path, cv2.cvtColor(image, cv2.COLOR_RGB2BGR))","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:00:10.458116Z","iopub.execute_input":"2025-07-03T04:00:10.458401Z","iopub.status.idle":"2025-07-03T04:00:10.477895Z","shell.execute_reply.started":"2025-07-03T04:00:10.458377Z","shell.execute_reply":"2025-07-03T04:00:10.477267Z"},"papermill":{"duration":0.03566,"end_time":"2025-06-15T18:54:59.641037","exception":false,"start_time":"2025-06-15T18:54:59.605377","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Using ThreadPoolExecutor to process images in parallel\nwith concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:\n    list(tqdm(executor.map(process_image, [row for _, row in df.iterrows()]), total=df.shape[0], desc=\"Processing images\", unit=\"image\"))\n\nprint(\"Processing complete for all images.\")","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:00:10.478531Z","iopub.execute_input":"2025-07-03T04:00:10.478771Z","iopub.status.idle":"2025-07-03T04:01:01.167878Z","shell.execute_reply.started":"2025-07-03T04:00:10.478748Z","shell.execute_reply":"2025-07-03T04:01:01.167191Z"},"papermill":{"duration":288.337349,"end_time":"2025-06-15T18:59:48.007335","exception":false,"start_time":"2025-06-15T18:54:59.669986","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Setting the style for the plot\nsns.set_style(\"white\")\n\n# Mapping class labels to their corresponding categories`\nlevel_to_category = {\n    0: \"No_DR\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\",\n    4: \"Proliferate_DR\"\n}\n\n# Plotting the first 15 images along with their labels\ncount = 1\nplt.figure(figsize=[20, 20])\n\nfor img_name in data['id_code'][:15]:  # Assuming 'train' contains the dataset\n    img = cv2.imread(f\"/kaggle/working/processed_images/{img_name}.png\")[..., [2, 1, 0]]  # Reading the image\n    \n    # Getting the label (class) for the image\n    label = data[data['id_code'] == img_name]['diagnosis'].values[0]  # Assuming 'diagnosis' is the label column\n    \n    # Setting up the subplot with image and label\n    plt.subplot(5, 5, count)\n    plt.imshow(img)\n    plt.title(f\"Citra {count}: {level_to_category[label]}\")  # Display the class label\n    count += 1\n\n# Display the plot\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:01:01.168518Z","iopub.execute_input":"2025-07-03T04:01:01.168805Z","iopub.status.idle":"2025-07-03T04:01:04.406440Z","shell.execute_reply.started":"2025-07-03T04:01:01.168778Z","shell.execute_reply":"2025-07-03T04:01:04.405328Z"},"papermill":{"duration":3.661238,"end_time":"2025-06-15T18:59:51.734477","exception":false,"start_time":"2025-06-15T18:59:48.073239","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Split and Transformation","metadata":{"papermill":{"duration":0.112811,"end_time":"2025-06-15T18:59:51.965232","exception":false,"start_time":"2025-06-15T18:59:51.852421","status":"completed"},"tags":[]}},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/working/processed_images\"\nCSV_PATH = \"/kaggle/working/cleaned_train.csv\"\nMODEL_PATH = \"./kaggle/working/\"\nLEARNING_RATE = 1e-4\nTRAIN_BATCH_SIZE = 16\nVALID_BATCH_SIZE = 16\nTEST_BATCH_SIZE = 16\nTRAIN_SPLIT = 0.7\nVAL_SPLIT = 0.2\nTEST_SPLIT = 0.1\nNUM_WORKERS = 4\nUSE_AMP = True\nEPOCHS = 20","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:01:04.407273Z","iopub.execute_input":"2025-07-03T04:01:04.407595Z","iopub.status.idle":"2025-07-03T04:01:04.413965Z","shell.execute_reply.started":"2025-07-03T04:01:04.407567Z","shell.execute_reply":"2025-07-03T04:01:04.413132Z"},"papermill":{"duration":0.115298,"end_time":"2025-06-15T18:59:52.187313","exception":false,"start_time":"2025-06-15T18:59:52.072015","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RetinopathyDataset(Dataset):\n    def __init__(self, image_dir, csv_file, transforms=None):\n        self.data = pd.read_csv(csv_file)\n        self.transforms = transforms\n        self.image_dir = image_dir\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        img_name = os.path.join(self.image_dir, self.data.loc[idx, 'id_code'] + '.png')\n\n        tensor_image = read_image(img_name)\n        label = torch.tensor(self.data.loc[idx, 'diagnosis'], dtype=torch.long)\n\n        if self.transforms is not None:\n            tensor_image = self.transforms(tensor_image)\n\n        return (tensor_image, label)","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:01:04.414874Z","iopub.execute_input":"2025-07-03T04:01:04.415408Z","iopub.status.idle":"2025-07-03T04:01:04.436603Z","shell.execute_reply.started":"2025-07-03T04:01:04.415379Z","shell.execute_reply":"2025-07-03T04:01:04.435751Z"},"papermill":{"duration":0.114854,"end_time":"2025-06-15T18:59:52.406468","exception":false,"start_time":"2025-06-15T18:59:52.291614","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train Augmentation\ntrain_transforms = T.Compose([\n    T.RandomResizedCrop(size=384, scale=(0.8, 1.2)),          # Zoom (scaling)\n    T.RandomHorizontalFlip(p=0.5),                            # Flip horizontal\n    T.RandomVerticalFlip(p=0.5),                              # Flip vertical\n    T.RandomRotation(degrees=25),                             # Rotation\n    T.RandomAffine(degrees=0, translate=(0.1, 0.1)),          # Height & width shift (translation)\n    T.ConvertImageDtype(torch.float32),\n    T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n# Val & Test Augementation\nval_test_transforms = T.Compose([\n    T.Resize((384, 384)),\n    T.ConvertImageDtype(torch.float32),\n    T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n# Load Dataset\ntrain_base_dataset = RetinopathyDataset(DATA_DIR, CSV_PATH, transforms=train_transforms)\nval_base_dataset = RetinopathyDataset(DATA_DIR, CSV_PATH, transforms=val_test_transforms)\ntest_base_dataset = RetinopathyDataset(DATA_DIR, CSV_PATH, transforms=val_test_transforms)\nlabels = train_base_dataset.data['diagnosis'].values\n\n# Split index\ntrain_idx, test_idx = train_test_split(np.arange(len(train_base_dataset)), test_size=TEST_SPLIT, \n                                       stratify=labels, random_state=42)\ntrain_idx, val_idx = train_test_split(train_idx, test_size=VAL_SPLIT/(1 - TEST_SPLIT), \n                                      stratify=labels[train_idx], random_state=42)\n\n# Use Subset to divide the dataset\ntrain_dataset = Subset(train_base_dataset, train_idx)\nval_dataset = Subset(val_base_dataset, val_idx)\ntest_dataset = Subset(test_base_dataset, test_idx)\n\n# DataLoader for each dataset\ntrain_loader = DataLoader(train_dataset, batch_size=TRAIN_BATCH_SIZE, shuffle=True, \n                          num_workers=NUM_WORKERS, drop_last=True, pin_memory=True,\n                          prefetch_factor=2)\n\nval_loader = DataLoader(val_dataset, batch_size=VALID_BATCH_SIZE, shuffle=False, \n                        num_workers=NUM_WORKERS, drop_last=False, pin_memory=True,\n                          prefetch_factor=2)\n\ntest_loader = DataLoader(test_dataset, batch_size=TEST_BATCH_SIZE, shuffle=False, \n                         num_workers=NUM_WORKERS, drop_last=False, pin_memory=True,\n                          prefetch_factor=2)\n\nfrom collections import Counter\n# Jumlah per kelas\ndef count_labels(indices, label_array):\n    counter = Counter(label_array[indices])\n    return dict(sorted(counter.items()))\n\nprint(f\"\\n Distribusi Dataset:\")\nprint(f\"Total Dataset: {len(train_base_dataset)}\")\nprint(f\"Train Set: {len(train_dataset)} samples\")\nprint(f\"Validation Set: {len(val_dataset)} samples\")\nprint(f\"Test Set: {len(test_dataset)} samples\")\n\nprint(\"\\n\\n Jumlah Data per Kelas:\")\nprint(\"Train:\", count_labels(train_idx, labels))\nprint(\"Validation:\", count_labels(val_idx, labels))\nprint(\"Test:\", count_labels(test_idx, labels))","metadata":{"execution":{"iopub.status.busy":"2025-07-03T04:01:04.438786Z","iopub.execute_input":"2025-07-03T04:01:04.439108Z","iopub.status.idle":"2025-07-03T04:01:04.478795Z","shell.execute_reply.started":"2025-07-03T04:01:04.439082Z","shell.execute_reply":"2025-07-03T04:01:04.478156Z"},"papermill":{"duration":0.161314,"end_time":"2025-06-15T18:59:52.672361","exception":false,"start_time":"2025-06-15T18:59:52.511047","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Fine Tune the Model","metadata":{"papermill":{"duration":0.106755,"end_time":"2025-06-15T18:59:52.883230","exception":false,"start_time":"2025-06-15T18:59:52.776475","status":"completed"},"tags":[]}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")","metadata":{"execution":{"iopub.status.busy":"2025-07-01T11:20:50.763260Z","iopub.execute_input":"2025-07-01T11:20:50.763541Z","iopub.status.idle":"2025-07-01T11:20:50.850203Z","shell.execute_reply.started":"2025-07-01T11:20:50.763523Z","shell.execute_reply":"2025-07-01T11:20:50.849250Z"},"papermill":{"duration":0.197718,"end_time":"2025-06-15T18:59:53.188296","exception":false,"start_time":"2025-06-15T18:59:52.990578","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# List of model\nMODEL_NAME = \"convnextv2_base.fcmae_ft_in22k_in1k_384\"\nMODEL_SAVE = \"/kaggle/working/deit_training_results.csv\"\nCHECKPOINT_PATH = f\"/kaggle/working/{MODEL_NAME}_best.pt\"\n\n# Load Model\nmodel = timm.create_model(MODEL_NAME, pretrained=True, num_classes=5)\nmodel.to(device)","metadata":{"execution":{"iopub.status.busy":"2025-07-01T11:20:50.950194Z","iopub.execute_input":"2025-07-01T11:20:50.950470Z","iopub.status.idle":"2025-07-01T11:20:54.981217Z","shell.execute_reply.started":"2025-07-01T11:20:50.950451Z","shell.execute_reply":"2025-07-01T11:20:54.980514Z"},"papermill":{"duration":4.027474,"end_time":"2025-06-15T18:59:57.321599","exception":false,"start_time":"2025-06-15T18:59:53.294125","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Handle Imbalance Data","metadata":{}},{"cell_type":"code","source":"# Calculate class weights\nlabels = data['diagnosis'].values\nclasses = sorted(data['diagnosis'].unique())\nclass_weights = compute_class_weight(class_weight='balanced', classes=classes, y=labels)\nclass_weights_tensor = torch.tensor(class_weights, dtype=torch.float).to(device)\n\nprint(f\"Class Weights: {class_weights_tensor}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-01T11:20:54.982451Z","iopub.execute_input":"2025-07-01T11:20:54.982657Z","iopub.status.idle":"2025-07-01T11:20:55.356693Z","shell.execute_reply.started":"2025-07-01T11:20:54.982642Z","shell.execute_reply":"2025-07-01T11:20:55.355882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Loss function & optimizer\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\noptimizer = optim.AdamW(model.parameters(), lr=1e-4)\n\n# Mixed Precision Training\nscaler = torch.cuda.amp.GradScaler() if torch.cuda.is_available() else None","metadata":{"execution":{"iopub.status.busy":"2025-07-01T11:20:55.357703Z","iopub.execute_input":"2025-07-01T11:20:55.357937Z","iopub.status.idle":"2025-07-01T11:20:55.363834Z","shell.execute_reply.started":"2025-07-01T11:20:55.357920Z","shell.execute_reply":"2025-07-01T11:20:55.363041Z"},"papermill":{"duration":0.112762,"end_time":"2025-06-15T18:59:58.506078","exception":false,"start_time":"2025-06-15T18:59:58.393316","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training","metadata":{"papermill":{"duration":0.104161,"end_time":"2025-06-15T18:59:58.717812","exception":false,"start_time":"2025-06-15T18:59:58.613651","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Training Function\ndef train_step(model, train_loader, criterion, optimizer, device, scaler):\n    model.train()\n    total_loss, correct, total_samples = 0, 0, 0\n\n    with tqdm(train_loader, desc=\"Training\", unit=\"batch\") as pbar:\n        for inputs, target in pbar:\n            inputs, target = inputs.to(device), target.to(device)\n            optimizer.zero_grad()\n\n            if scaler:\n                with torch.cuda.amp.autocast():\n                    output = model(inputs)\n                    loss = criterion(output, target)\n                scaler.scale(loss).backward()\n                scaler.step(optimizer)\n                scaler.update()\n            else:\n                output = model(inputs)\n                loss = criterion(output, target)\n                loss.backward()\n                optimizer.step()\n\n            _, predicted = output.max(1)\n            total_loss += loss.item() * inputs.size(0)\n            correct += predicted.eq(target).sum().item()\n            total_samples += inputs.size(0)\n\n            pbar.set_postfix({\"Loss\": f\"{total_loss / total_samples:.4f}\", \"Accuracy\": f\"{100.0 * correct / total_samples:.2f}%\"})\n\n    return {\"loss\": total_loss / total_samples, \"accuracy\": 100.0 * correct / total_samples}\n\n# Validation Function\n@torch.no_grad()\ndef val_step(model, val_loader, criterion, device):\n    model.eval()\n    total_loss, correct, total_samples = 0, 0, 0\n\n    with tqdm(val_loader, desc=\"Validation\", unit=\"batch\") as pbar:\n        for inputs, target in pbar:\n            inputs, target = inputs.to(device), target.to(device)\n            output = model(inputs)\n            loss = criterion(output, target)\n\n            _, predicted = output.max(1)\n            total_loss += loss.item() * inputs.size(0)\n            correct += predicted.eq(target).sum().item()\n            total_samples += inputs.size(0)\n\n            pbar.set_postfix({\"Loss\": f\"{total_loss / total_samples:.4f}\", \"Accuracy\": f\"{100.0 * correct / total_samples:.2f}%\"})\n\n    return {\"loss\": total_loss / total_samples, \"accuracy\": 100.0 * correct / total_samples}","metadata":{"execution":{"iopub.status.busy":"2025-07-01T11:20:55.365399Z","iopub.execute_input":"2025-07-01T11:20:55.365592Z","iopub.status.idle":"2025-07-01T11:20:55.381947Z","shell.execute_reply.started":"2025-07-01T11:20:55.365578Z","shell.execute_reply":"2025-07-01T11:20:55.381215Z"},"papermill":{"duration":0.115359,"end_time":"2025-06-15T18:59:58.938413","exception":false,"start_time":"2025-06-15T18:59:58.823054","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training Loop\nbest_val_acc = 0\nbest_model_wts = copy.deepcopy(model.state_dict())\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n\ntrain_loss, train_acc, val_loss, val_acc = [], [], [], []\n\nfor epoch in range(EPOCHS):\n    print(f\"\\nEpoch {epoch+1}/{EPOCHS}\")\n\n    train_metrics = train_step(model, train_loader, criterion, optimizer, device, scaler)\n    val_metrics = val_step(model, val_loader, criterion, device)\n\n    scheduler.step()\n\n    train_loss.append(train_metrics[\"loss\"])\n    train_acc.append(train_metrics[\"accuracy\"])\n    val_loss.append(val_metrics[\"loss\"])\n    val_acc.append(val_metrics[\"accuracy\"])\n\n    if val_metrics[\"accuracy\"] > best_val_acc:\n        best_val_acc = val_metrics[\"accuracy\"]\n        best_model_wts = copy.deepcopy(model.state_dict())\n        torch.save(best_model_wts, CHECKPOINT_PATH)\n        print(f\"=> Model saved at {CHECKPOINT_PATH}\")\n\n# Save Metrics\nmetrics_df = pd.DataFrame({\n    \"epoch\": range(1, len(train_loss) + 1),\n    \"train_loss\": train_loss,\n    \"train_accuracy\": train_acc,\n    \"val_loss\": val_loss,\n    \"val_accuracy\": val_acc\n})\nmetrics_df.to_csv(MODEL_SAVE, index=False)\n\n# Plot Training Results\nplt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nsns.lineplot(x='epoch', y='train_loss', data=metrics_df, label='Train Loss')\nsns.lineplot(x='epoch', y='val_loss', data=metrics_df, label='Validation Loss')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Loss per Epoch\")\nplt.legend()\n\nplt.subplot(1, 2, 2)\nsns.lineplot(x='epoch', y='train_accuracy', data=metrics_df, label='Train Accuracy')\nsns.lineplot(x='epoch', y='val_accuracy', data=metrics_df, label='Validation Accuracy')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.title(\"Accuracy per Epoch\")\nplt.legend()\nplt.show()\n\n# Load Best Model for Testing\nmodel.load_state_dict(torch.load(CHECKPOINT_PATH, map_location=device))\nmodel.to(device)\nmodel.eval()","metadata":{"execution":{"iopub.status.busy":"2025-07-01T11:20:55.382673Z","iopub.execute_input":"2025-07-01T11:20:55.382885Z","iopub.status.idle":"2025-07-01T12:24:13.414050Z","shell.execute_reply.started":"2025-07-01T11:20:55.382862Z","shell.execute_reply":"2025-07-01T12:24:13.413215Z"},"papermill":{"duration":4549.012758,"end_time":"2025-06-15T20:15:48.063520","exception":false,"start_time":"2025-06-15T18:59:59.050762","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluation","metadata":{"papermill":{"duration":0.480524,"end_time":"2025-06-15T20:15:49.111852","exception":false,"start_time":"2025-06-15T20:15:48.631328","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Function to Evaluate Test Set\n@torch.no_grad()\ndef evaluate_test_set(model, test_loader, device):\n    y_true, y_pred = [], []\n\n    for inputs, labels in tqdm(test_loader, desc=\"Testing\"):\n        inputs, labels = inputs.to(device), labels.to(device)\n        outputs = model(inputs)\n        _, preds = torch.max(outputs, 1)\n\n        y_true.extend(labels.cpu().numpy())\n        y_pred.extend(preds.cpu().numpy())\n\n    return np.array(y_true), np.array(y_pred)\n\n# Evaluate on Test Set\ny_true_test, y_pred_test = evaluate_test_set(model, test_loader, device)\n\n# Calculate Test Accuracy\ntest_accuracy = accuracy_score(y_true_test, y_pred_test) * 100\nprint(f\"\\n Test Accuracy: {test_accuracy:.3f}%\")\n\n# Generate Classification Report\nclass_report = classification_report(y_true_test, y_pred_test, digits=3)\nprint(\"\\n Classification Report:\\n\", class_report)\n\n# Label DR\nclass_labels = ['No DR', 'Mild', 'Moderate', 'Severe', 'Proliferative DR']\n\n# Calculate confusion matrix\ncm = confusion_matrix(y_true_test, y_pred_test)\n\n# Plot confusion matrix\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=class_labels,\n            yticklabels=class_labels)\n\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.title('Confusion Matrix - Klasifikasi Retinopati Diabetik')\nplt.tight_layout()\nplt.show()","metadata":{"papermill":{"duration":19.66162,"end_time":"2025-06-15T20:16:09.306385","exception":false,"start_time":"2025-06-15T20:15:49.644765","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-01T12:24:13.415700Z","iopub.execute_input":"2025-07-01T12:24:13.415910Z","iopub.status.idle":"2025-07-01T12:24:29.992958Z","shell.execute_reply.started":"2025-07-01T12:24:13.415888Z","shell.execute_reply":"2025-07-01T12:24:29.992229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save Weights\nsave_weight = \"best_weights.pt\"\ntorch.save(model.state_dict(), save_weight)\nprint(f\"Model weights saved to {save_weight}\")","metadata":{"papermill":{"duration":1.025654,"end_time":"2025-06-15T20:16:10.866454","exception":false,"start_time":"2025-06-15T20:16:09.840800","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-01T12:24:29.993980Z","iopub.execute_input":"2025-07-01T12:24:29.994257Z","iopub.status.idle":"2025-07-01T12:24:30.465692Z","shell.execute_reply.started":"2025-07-01T12:24:29.994232Z","shell.execute_reply":"2025-07-01T12:24:30.465056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 5))\n\n# Loss\nplt.subplot(1, 2, 1)\nsns.lineplot(x='epoch', y='train_loss', data=metrics_df, label='Train Loss')\nsns.lineplot(x='epoch', y='val_loss', data=metrics_df, label='Validation Loss')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Loss per Epoch\")\nplt.legend()\n\n# Accuracy\nplt.subplot(1, 2, 2)\nsns.lineplot(x='epoch', y='train_accuracy', data=metrics_df, label='Train Accuracy')\nsns.lineplot(x='epoch', y='val_accuracy', data=metrics_df, label='Validation Accuracy')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.title(\"Accuracy per Epoch\")\nplt.legend()\n\nplt.tight_layout()\nplt.savefig(\"training_curves.png\")\nplt.close()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-01T12:24:30.466332Z","iopub.execute_input":"2025-07-01T12:24:30.466509Z","iopub.status.idle":"2025-07-01T12:24:31.070586Z","shell.execute_reply.started":"2025-07-01T12:24:30.466495Z","shell.execute_reply":"2025-07-01T12:24:31.069969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=class_labels,\n            yticklabels=class_labels)\n\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.title('Confusion Matrix - Skenario 4')\nplt.tight_layout()\nplt.savefig(\"confusion_matrix.png\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-01T12:24:31.071327Z","iopub.execute_input":"2025-07-01T12:24:31.071537Z","iopub.status.idle":"2025-07-01T12:24:31.545763Z","shell.execute_reply.started":"2025-07-01T12:24:31.071521Z","shell.execute_reply":"2025-07-01T12:24:31.545029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}