{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from pathlib import Path\nimport os\n\nCOMP_DIR = Path(\"/kaggle/input/competitions\")\n\nprint(\"COMP_DIR exists:\", COMP_DIR.exists())\n\nfor item in os.listdir(COMP_DIR):\n    print(\"-\", item)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:16:57.382159Z","iopub.execute_input":"2026-06-10T15:16:57.382827Z","iopub.status.idle":"2026-06-10T15:16:57.388738Z","shell.execute_reply.started":"2026-06-10T15:16:57.382794Z","shell.execute_reply":"2026-06-10T15:16:57.387693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport os\n\nBASE_DIRS = [\n    Path(\"/kaggle/input\"),\n    Path(\"/kaggle/input/competitions\")\n]\n\nDATA_DIR = None\n\nfor base in BASE_DIRS:\n    if base.exists():\n        for folder in base.rglob(\"*\"):\n            if folder.is_dir():\n                try:\n                    files = os.listdir(folder)\n                    if \"train.csv\" in files and \"train_label_coordinates.csv\" in files:\n                        DATA_DIR = folder\n                        break\n                except:\n                    pass\n        if DATA_DIR is not None:\n            break\n\nif DATA_DIR is None:\n    raise FileNotFoundError(\"Dataset files not found. Need to check nested folder manually.\")\n\nTRAIN_IMG_DIR = DATA_DIR / \"train_images\"\nTEST_IMG_DIR  = DATA_DIR / \"test_images\"\n\nWORK_DIR = Path(\"/kaggle/working\")\nPRE_DIR = WORK_DIR / \"preprocessed_rsna\"\nCROP_DIR = PRE_DIR / \"sample_crops_25d\"\n\nPRE_DIR.mkdir(parents=True, exist_ok=True)\nCROP_DIR.mkdir(parents=True, exist_ok=True)\n\nprint(\"✅ Using DATA_DIR:\", DATA_DIR)\nprint(\"Files inside DATA_DIR:\")\nfor f in os.listdir(DATA_DIR):\n    print(\"-\", f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:00.668878Z","iopub.execute_input":"2026-06-10T15:17:00.669826Z","iopub.status.idle":"2026-06-10T15:17:00.680195Z","shell.execute_reply.started":"2026-06-10T15:17:00.669794Z","shell.execute_reply":"2026-06-10T15:17:00.679280Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport os\n\nBASE_DIRS = [\n    Path(\"/kaggle/input\"),\n    Path(\"/kaggle/input/competitions\")\n]\n\nDATA_DIR = None\n\nfor base in BASE_DIRS:\n    if base.exists():\n        for folder in base.rglob(\"*\"):\n            if folder.is_dir():\n                try:\n                    files = os.listdir(folder)\n                    if \"train.csv\" in files and \"train_label_coordinates.csv\" in files:\n                        DATA_DIR = folder\n                        break\n                except:\n                    pass\n        if DATA_DIR is not None:\n            break\n\nif DATA_DIR is None:\n    raise FileNotFoundError(\"Dataset files not found. Need to check nested folder manually.\")\n\nTRAIN_IMG_DIR = DATA_DIR / \"train_images\"\nTEST_IMG_DIR  = DATA_DIR / \"test_images\"\n\nWORK_DIR = Path(\"/kaggle/working\")\nPRE_DIR = WORK_DIR / \"preprocessed_rsna\"\nCROP_DIR = PRE_DIR / \"sample_crops_25d\"\n\nPRE_DIR.mkdir(parents=True, exist_ok=True)\nCROP_DIR.mkdir(parents=True, exist_ok=True)\n\nprint(\"✅ Using DATA_DIR:\", DATA_DIR)\nprint(\"Files inside DATA_DIR:\")\nfor f in os.listdir(DATA_DIR):\n    print(\"-\", f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:06.884470Z","iopub.execute_input":"2026-06-10T15:17:06.884919Z","iopub.status.idle":"2026-06-10T15:17:06.895438Z","shell.execute_reply.started":"2026-06-10T15:17:06.884890Z","shell.execute_reply":"2026-06-10T15:17:06.894534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ntrain = pd.read_csv(DATA_DIR / \"train.csv\")\ncoords = pd.read_csv(DATA_DIR / \"train_label_coordinates.csv\")\nseries = pd.read_csv(DATA_DIR / \"train_series_descriptions.csv\")\nsample_sub = pd.read_csv(DATA_DIR / \"sample_submission.csv\")\n\nprint(\"train shape:\", train.shape)\nprint(\"coords shape:\", coords.shape)\nprint(\"series shape:\", series.shape)\nprint(\"sample submission shape:\", sample_sub.shape)\n\ndisplay(train.head())\ndisplay(coords.head())\ndisplay(series.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:11.522026Z","iopub.execute_input":"2026-06-10T15:17:11.522865Z","iopub.status.idle":"2026-06-10T15:17:11.732475Z","shell.execute_reply.started":"2026-06-10T15:17:11.522820Z","shell.execute_reply":"2026-06-10T15:17:11.731739Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label_cols = [c for c in train.columns if c != \"study_id\"]\n\nprint(\"Total label columns:\", len(label_cols))\nprint(label_cols)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:16.696809Z","iopub.execute_input":"2026-06-10T15:17:16.697098Z","iopub.status.idle":"2026-06-10T15:17:16.702649Z","shell.execute_reply.started":"2026-06-10T15:17:16.697076Z","shell.execute_reply":"2026-06-10T15:17:16.701594Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"long_df = train.melt(\n    id_vars=\"study_id\",\n    value_vars=label_cols,\n    var_name=\"target\",\n    value_name=\"label\"\n)\n\nlevels = [\"l1_l2\", \"l2_l3\", \"l3_l4\", \"l4_l5\", \"l5_s1\"]\n\ndef split_target(target):\n    for lv in levels:\n        if target.endswith(lv):\n            condition = target.replace(\"_\" + lv, \"\")\n            return condition, lv\n    return None, None\n\nlong_df[[\"condition_key\", \"level_key\"]] = long_df[\"target\"].apply(\n    lambda x: pd.Series(split_target(x))\n)\n\nlabel_map = {\n    \"Normal/Mild\": 0,\n    \"Moderate\": 1,\n    \"Severe\": 2\n}\n\nlong_df[\"label_id\"] = long_df[\"label\"].map(label_map)\n\n# Missing labels বাদ দিচ্ছি\nlong_df = long_df.dropna(subset=[\"label_id\"]).reset_index(drop=True)\nlong_df[\"label_id\"] = long_df[\"label_id\"].astype(int)\n\ndisplay(long_df.head())\nprint(\"long_df shape:\", long_df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:19.919881Z","iopub.execute_input":"2026-06-10T15:17:19.920836Z","iopub.status.idle":"2026-06-10T15:17:24.305758Z","shell.execute_reply.started":"2026-06-10T15:17:19.920791Z","shell.execute_reply":"2026-06-10T15:17:24.304922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Overall label distribution:\")\ndisplay(long_df[\"label\"].value_counts())\n\nprint(\"Percentage:\")\ndisplay((long_df[\"label\"].value_counts(normalize=True) * 100).round(2))\n\nprint(\"Condition-wise distribution:\")\ndisplay(pd.crosstab(long_df[\"condition_key\"], long_df[\"label\"]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:24.307060Z","iopub.execute_input":"2026-06-10T15:17:24.307680Z","iopub.status.idle":"2026-06-10T15:17:24.374081Z","shell.execute_reply.started":"2026-06-10T15:17:24.307652Z","shell.execute_reply":"2026-06-10T15:17:24.373041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clean_condition_name(x):\n    return str(x).lower().replace(\" \", \"_\")\n\ndef clean_level_name(x):\n    return str(x).lower().replace(\"/\", \"_\")\n\ncoords[\"condition_key\"] = coords[\"condition\"].apply(clean_condition_name)\ncoords[\"level_key\"] = coords[\"level\"].apply(clean_level_name)\n\ncoords[\"study_id\"] = coords[\"study_id\"].astype(int)\ncoords[\"series_id\"] = coords[\"series_id\"].astype(int)\ncoords[\"instance_number\"] = coords[\"instance_number\"].astype(int)\n\ndisplay(coords.head())\n\nprint(\"Coordinate condition counts:\")\ndisplay(coords[\"condition_key\"].value_counts())\n\nprint(\"Coordinate level counts:\")\ndisplay(coords[\"level_key\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:28.687587Z","iopub.execute_input":"2026-06-10T15:17:28.688441Z","iopub.status.idle":"2026-06-10T15:17:28.757477Z","shell.execute_reply.started":"2026-06-10T15:17:28.688412Z","shell.execute_reply":"2026-06-10T15:17:28.756319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"series[\"study_id\"] = series[\"study_id\"].astype(int)\nseries[\"series_id\"] = series[\"series_id\"].astype(int)\n\ncoords_series = coords.merge(\n    series,\n    on=[\"study_id\", \"series_id\"],\n    how=\"left\"\n)\n\ndisplay(coords_series.head())\n\nprint(\"Series description counts:\")\ndisplay(coords_series[\"series_description\"].value_counts())\n\nprint(\"Missing series descriptions:\", coords_series[\"series_description\"].isna().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:34.059260Z","iopub.execute_input":"2026-06-10T15:17:34.059914Z","iopub.status.idle":"2026-06-10T15:17:34.097556Z","shell.execute_reply.started":"2026-06-10T15:17:34.059885Z","shell.execute_reply":"2026-06-10T15:17:34.096868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"crop_df = coords_series.merge(\n    long_df[[\"study_id\", \"condition_key\", \"level_key\", \"target\", \"label\", \"label_id\"]],\n    on=[\"study_id\", \"condition_key\", \"level_key\"],\n    how=\"left\"\n)\n\ncrop_df = crop_df.dropna(subset=[\"label_id\"]).reset_index(drop=True)\ncrop_df[\"label_id\"] = crop_df[\"label_id\"].astype(int)\n\ndisplay(crop_df.head())\n\nprint(\"crop_df shape:\", crop_df.shape)\nprint(\"Label distribution in crop_df:\")\ndisplay(crop_df[\"label\"].value_counts())\n\nprint(\"Missing label:\", crop_df[\"label\"].isna().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:39.798827Z","iopub.execute_input":"2026-06-10T15:17:39.799521Z","iopub.status.idle":"2026-06-10T15:17:39.897385Z","shell.execute_reply.started":"2026-06-10T15:17:39.799488Z","shell.execute_reply":"2026-06-10T15:17:39.896463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\nWORK_DIR = Path(\"/kaggle/working\")\nPRE_DIR = WORK_DIR / \"preprocessed_rsna\"\nPRE_DIR.mkdir(parents=True, exist_ok=True)\n\nlong_df.to_csv(PRE_DIR / \"train_long_labels.csv\", index=False)\ncoords_series.to_csv(PRE_DIR / \"coordinates_with_series.csv\", index=False)\ncrop_df.to_csv(PRE_DIR / \"crop_metadata_all.csv\", index=False)\n\nprint(\"Saved files:\")\nprint(PRE_DIR / \"train_long_labels.csv\")\nprint(PRE_DIR / \"coordinates_with_series.csv\")\nprint(PRE_DIR / \"crop_metadata_all.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:44.164883Z","iopub.execute_input":"2026-06-10T15:17:44.165650Z","iopub.status.idle":"2026-06-10T15:17:45.301328Z","shell.execute_reply.started":"2026-06-10T15:17:44.165618Z","shell.execute_reply":"2026-06-10T15:17:45.300469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_IMG_DIR = DATA_DIR / \"train_images\"\nTEST_IMG_DIR = DATA_DIR / \"test_images\"\n\nprint(\"TRAIN_IMG_DIR:\", TRAIN_IMG_DIR)\nprint(\"Exists:\", TRAIN_IMG_DIR.exists())\n\nsample_row = crop_df.iloc[0]\n\nstudy_id = sample_row[\"study_id\"]\nseries_id = sample_row[\"series_id\"]\ninstance_number = sample_row[\"instance_number\"]\n\ndicom_path = TRAIN_IMG_DIR / str(study_id) / str(series_id) / f\"{instance_number}.dcm\"\n\nprint(\"Sample DICOM path:\")\nprint(dicom_path)\nprint(\"Exists:\", dicom_path.exists())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:48.183942Z","iopub.execute_input":"2026-06-10T15:17:48.184643Z","iopub.status.idle":"2026-06-10T15:17:48.199034Z","shell.execute_reply.started":"2026-06-10T15:17:48.184611Z","shell.execute_reply":"2026-06-10T15:17:48.198121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"try:\n    import pydicom\n    print(\"pydicom already installed\")\nexcept:\n    !pip install -q pydicom\n    import pydicom\n    print(\"pydicom installed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:52.110019Z","iopub.execute_input":"2026-06-10T15:17:52.110818Z","iopub.status.idle":"2026-06-10T15:17:52.717921Z","shell.execute_reply.started":"2026-06-10T15:17:52.110784Z","shell.execute_reply":"2026-06-10T15:17:52.717135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\n\ndef normalize_mri(img):\n    img = img.astype(np.float32)\n    \n    low, high = np.percentile(img, [1, 99.5])\n    img = np.clip(img, low, high)\n    \n    img = img - img.min()\n    img = img / (img.max() + 1e-6)\n    \n    return img.astype(np.float32)\n\n\ndef read_dicom_image(dicom_path):\n    dcm = pydicom.dcmread(str(dicom_path))\n    img = dcm.pixel_array.astype(np.float32)\n    \n    if getattr(dcm, \"PhotometricInterpretation\", \"\") == \"MONOCHROME1\":\n        img = img.max() - img\n    \n    img = normalize_mri(img)\n    return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:17:55.672094Z","iopub.execute_input":"2026-06-10T15:17:55.672893Z","iopub.status.idle":"2026-06-10T15:17:55.679196Z","shell.execute_reply.started":"2026-06-10T15:17:55.672857Z","shell.execute_reply":"2026-06-10T15:17:55.678310Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_row = crop_df.iloc[0]\n\ndicom_path = TRAIN_IMG_DIR / str(sample_row[\"study_id\"]) / str(sample_row[\"series_id\"]) / f\"{sample_row['instance_number']}.dcm\"\n\nimg = read_dicom_image(dicom_path)\n\nplt.figure(figsize=(6, 6))\nplt.imshow(img, cmap=\"gray\")\nplt.scatter(sample_row[\"x\"], sample_row[\"y\"], c=\"red\", s=50)\nplt.title(f\"{sample_row['condition']} | {sample_row['level']} | {sample_row['label']}\")\nplt.axis(\"off\")\nplt.show()\n\nprint(\"Image shape:\", img.shape)\nprint(\"Condition:\", sample_row[\"condition\"])\nprint(\"Level:\", sample_row[\"level\"])\nprint(\"Label:\", sample_row[\"label\"])\nprint(\"Series:\", sample_row[\"series_description\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:00.053551Z","iopub.execute_input":"2026-06-10T15:18:00.054109Z","iopub.status.idle":"2026-06-10T15:18:00.387611Z","shell.execute_reply.started":"2026-06-10T15:18:00.054076Z","shell.execute_reply":"2026-06-10T15:18:00.386604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\n\ndef crop_around_point(img, x, y, crop_size=224):\n    h, w = img.shape\n    x = int(round(x))\n    y = int(round(y))\n    \n    half = crop_size // 2\n    \n    x1 = x - half\n    x2 = x + half\n    y1 = y - half\n    y2 = y + half\n    \n    pad_left = max(0, -x1)\n    pad_top = max(0, -y1)\n    pad_right = max(0, x2 - w)\n    pad_bottom = max(0, y2 - h)\n    \n    if pad_left > 0 or pad_top > 0 or pad_right > 0 or pad_bottom > 0:\n        img = np.pad(\n            img,\n            ((pad_top, pad_bottom), (pad_left, pad_right)),\n            mode=\"constant\",\n            constant_values=0\n        )\n        \n        x1 += pad_left\n        x2 += pad_left\n        y1 += pad_top\n        y2 += pad_top\n    \n    crop = img[y1:y2, x1:x2]\n    \n    if crop.shape != (crop_size, crop_size):\n        crop = cv2.resize(crop, (crop_size, crop_size))\n    \n    return crop.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:05.498747Z","iopub.execute_input":"2026-06-10T15:18:05.499648Z","iopub.status.idle":"2026-06-10T15:18:05.850534Z","shell.execute_reply.started":"2026-06-10T15:18:05.499613Z","shell.execute_reply":"2026-06-10T15:18:05.849588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"crop = crop_around_point(\n    img,\n    x=sample_row[\"x\"],\n    y=sample_row[\"y\"],\n    crop_size=224\n)\n\nplt.figure(figsize=(5, 5))\nplt.imshow(crop, cmap=\"gray\")\nplt.title(f\"Crop | {sample_row['condition']} | {sample_row['label']}\")\nplt.axis(\"off\")\nplt.show()\n\nprint(\"Crop shape:\", crop.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:09.505932Z","iopub.execute_input":"2026-06-10T15:18:09.506660Z","iopub.status.idle":"2026-06-10T15:18:09.658409Z","shell.execute_reply.started":"2026-06-10T15:18:09.506625Z","shell.execute_reply":"2026-06-10T15:18:09.657429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_series_instance_paths(study_id, series_id):\n    series_path = TRAIN_IMG_DIR / str(study_id) / str(series_id)\n    files = list(series_path.glob(\"*.dcm\"))\n    \n    instance_paths = []\n    \n    for f in files:\n        try:\n            instance_num = int(f.stem)\n            instance_paths.append((instance_num, f))\n        except:\n            pass\n    \n    instance_paths = sorted(instance_paths, key=lambda x: x[0])\n    return instance_paths","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:13.646901Z","iopub.execute_input":"2026-06-10T15:18:13.647379Z","iopub.status.idle":"2026-06-10T15:18:13.653045Z","shell.execute_reply.started":"2026-06-10T15:18:13.647341Z","shell.execute_reply":"2026-06-10T15:18:13.651952Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_25d_crop(study_id, series_id, instance_number, x, y, crop_size=224, neighbor=1):\n    instance_paths = get_series_instance_paths(study_id, series_id)\n    \n    if len(instance_paths) == 0:\n        raise FileNotFoundError(f\"No DICOM files found for study={study_id}, series={series_id}\")\n    \n    instances = [i for i, p in instance_paths]\n    paths = [p for i, p in instance_paths]\n    \n    if instance_number in instances:\n        center_idx = instances.index(instance_number)\n    else:\n        center_idx = int(np.argmin(np.abs(np.array(instances) - instance_number)))\n    \n    selected_indices = [\n        max(0, center_idx - neighbor),\n        center_idx,\n        min(len(paths) - 1, center_idx + neighbor)\n    ]\n    \n    crops = []\n    \n    for idx in selected_indices:\n        img_slice = read_dicom_image(paths[idx])\n        crop_slice = crop_around_point(\n            img_slice,\n            x=x,\n            y=y,\n            crop_size=crop_size\n        )\n        crops.append(crop_slice)\n    \n    crop_25d = np.stack(crops, axis=-1)\n    \n    return crop_25d.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:17.578187Z","iopub.execute_input":"2026-06-10T15:18:17.579054Z","iopub.status.idle":"2026-06-10T15:18:17.586458Z","shell.execute_reply.started":"2026-06-10T15:18:17.579017Z","shell.execute_reply":"2026-06-10T15:18:17.585517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"crop_25d = make_25d_crop(\n    study_id=sample_row[\"study_id\"],\n    series_id=sample_row[\"series_id\"],\n    instance_number=sample_row[\"instance_number\"],\n    x=sample_row[\"x\"],\n    y=sample_row[\"y\"],\n    crop_size=224,\n    neighbor=1\n)\n\nprint(\"2.5D crop shape:\", crop_25d.shape)\nprint(\"Min:\", crop_25d.min())\nprint(\"Max:\", crop_25d.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:20.761809Z","iopub.execute_input":"2026-06-10T15:18:20.762311Z","iopub.status.idle":"2026-06-10T15:18:20.857749Z","shell.execute_reply.started":"2026-06-10T15:18:20.762271Z","shell.execute_reply":"2026-06-10T15:18:20.856948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 4))\n\nplt.subplot(1, 3, 1)\nplt.imshow(crop_25d[:, :, 0], cmap=\"gray\")\nplt.title(\"Previous Slice\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 2)\nplt.imshow(crop_25d[:, :, 1], cmap=\"gray\")\nplt.title(\"Current Slice\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 3)\nplt.imshow(crop_25d[:, :, 2], cmap=\"gray\")\nplt.title(\"Next Slice\")\nplt.axis(\"off\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:23.767472Z","iopub.execute_input":"2026-06-10T15:18:23.768146Z","iopub.status.idle":"2026-06-10T15:18:24.049025Z","shell.execute_reply.started":"2026-06-10T15:18:23.768113Z","shell.execute_reply":"2026-06-10T15:18:24.048387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\nCROP_DIR = PRE_DIR / \"sample_crops_25d\"\nCROP_DIR.mkdir(parents=True, exist_ok=True)\n\nprint(\"Crop save folder:\", CROP_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:28.154156Z","iopub.execute_input":"2026-06-10T15:18:28.154937Z","iopub.status.idle":"2026-06-10T15:18:28.160037Z","shell.execute_reply.started":"2026-06-10T15:18:28.154901Z","shell.execute_reply":"2026-06-10T15:18:28.159265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.auto import tqdm\n\ndef save_25d_crops(df, out_dir, max_rows=200, crop_size=224, neighbor=1):\n    out_dir = Path(out_dir)\n    out_dir.mkdir(parents=True, exist_ok=True)\n    \n    if max_rows is not None:\n        run_df = df.head(max_rows).copy()\n    else:\n        run_df = df.copy()\n    \n    saved_rows = []\n    failed_rows = []\n    \n    for idx, row in tqdm(run_df.iterrows(), total=len(run_df)):\n        try:\n            crop_25d = make_25d_crop(\n                study_id=row[\"study_id\"],\n                series_id=row[\"series_id\"],\n                instance_number=row[\"instance_number\"],\n                x=row[\"x\"],\n                y=row[\"y\"],\n                crop_size=crop_size,\n                neighbor=neighbor\n            )\n            \n            fname = (\n                f\"{row['study_id']}_\"\n                f\"{row['series_id']}_\"\n                f\"{row['instance_number']}_\"\n                f\"{row['condition_key']}_\"\n                f\"{row['level_key']}.npy\"\n            )\n            \n            save_path = out_dir / fname\n            np.save(save_path, crop_25d)\n            \n            saved_row = row.to_dict()\n            saved_row[\"crop_path\"] = str(save_path)\n            saved_rows.append(saved_row)\n            \n        except Exception as e:\n            failed_row = row.to_dict()\n            failed_row[\"error\"] = str(e)\n            failed_rows.append(failed_row)\n    \n    saved_df = pd.DataFrame(saved_rows)\n    failed_df = pd.DataFrame(failed_rows)\n    \n    return saved_df, failed_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:30.792157Z","iopub.execute_input":"2026-06-10T15:18:30.792913Z","iopub.status.idle":"2026-06-10T15:18:30.873092Z","shell.execute_reply.started":"2026-06-10T15:18:30.792876Z","shell.execute_reply":"2026-06-10T15:18:30.872490Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_crop_df, failed_df = save_25d_crops(\n    df=crop_df,\n    out_dir=CROP_DIR,\n    max_rows=200,\n    crop_size=224,\n    neighbor=1\n)\n\nprint(\"Saved crops:\", len(sample_crop_df))\nprint(\"Failed crops:\", len(failed_df))\n\ndisplay(sample_crop_df.head())\ndisplay(failed_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:35.441051Z","iopub.execute_input":"2026-06-10T15:18:35.441802Z","iopub.status.idle":"2026-06-10T15:18:43.813740Z","shell.execute_reply.started":"2026-06-10T15:18:35.441766Z","shell.execute_reply":"2026-06-10T15:18:43.813011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"one_crop_path = sample_crop_df.iloc[0][\"crop_path\"]\n\narr = np.load(one_crop_path)\n\nprint(\"Loaded crop shape:\", arr.shape)\nprint(\"Min:\", arr.min())\nprint(\"Max:\", arr.max())\n\nplt.figure(figsize=(5, 5))\nplt.imshow(arr[:, :, 1], cmap=\"gray\")\nplt.title(f\"Saved Crop | Label: {sample_crop_df.iloc[0]['label']}\")\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:47.816545Z","iopub.execute_input":"2026-06-10T15:18:47.817165Z","iopub.status.idle":"2026-06-10T15:18:47.966301Z","shell.execute_reply.started":"2026-06-10T15:18:47.817122Z","shell.execute_reply":"2026-06-10T15:18:47.965262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_crop_df.to_csv(PRE_DIR / \"sample_crop_metadata.csv\", index=False)\nfailed_df.to_csv(PRE_DIR / \"failed_crop_rows.csv\", index=False)\n\nprint(\"Saved:\")\nprint(PRE_DIR / \"sample_crop_metadata.csv\")\nprint(PRE_DIR / \"failed_crop_rows.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:51.855106Z","iopub.execute_input":"2026-06-10T15:18:51.856268Z","iopub.status.idle":"2026-06-10T15:18:51.867211Z","shell.execute_reply.started":"2026-06-10T15:18:51.856208Z","shell.execute_reply":"2026-06-10T15:18:51.866249Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Sample crop label distribution:\")\ndisplay(sample_crop_df[\"label\"].value_counts())\n\nprint(\"Condition distribution:\")\ndisplay(sample_crop_df[\"condition_key\"].value_counts())\n\nprint(\"Series distribution:\")\ndisplay(sample_crop_df[\"series_description\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:54.849329Z","iopub.execute_input":"2026-06-10T15:18:54.850345Z","iopub.status.idle":"2026-06-10T15:18:54.866066Z","shell.execute_reply.started":"2026-06-10T15:18:54.850280Z","shell.execute_reply":"2026-06-10T15:18:54.865091Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"sample_crop_df shape:\", sample_crop_df.shape)\ndisplay(sample_crop_df.head())\n\nprint(sample_crop_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:18:57.758458Z","iopub.execute_input":"2026-06-10T15:18:57.759420Z","iopub.status.idle":"2026-06-10T15:18:57.775064Z","shell.execute_reply.started":"2026-06-10T15:18:57.759385Z","shell.execute_reply":"2026-06-10T15:18:57.774193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n# Data type ঠিক করা\nsample_crop_df[\"label_id\"] = sample_crop_df[\"label_id\"].astype(int)\nsample_crop_df[\"study_id\"] = sample_crop_df[\"study_id\"].astype(int)\n\n# Same study_id যেন train/val/test এ mix না হয়\nunique_studies = sample_crop_df[\"study_id\"].unique()\n\n# 70% train, 30% temporary\ntrain_ids, temp_ids = train_test_split(\n    unique_studies,\n    test_size=0.30,\n    random_state=42\n)\n\n# 30% temporary → 15% validation + 15% test\nval_ids, test_ids = train_test_split(\n    temp_ids,\n    test_size=0.50,\n    random_state=42\n)\n\ntrain_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(train_ids)].reset_index(drop=True)\nval_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(val_ids)].reset_index(drop=True)\ntest_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(test_ids)].reset_index(drop=True)\n\nprint(\"Train:\", train_df.shape)\nprint(\"Validation:\", val_df.shape)\nprint(\"Test:\", test_df.shape)\n\ntotal = len(sample_crop_df)\n\nprint(\"\\nPercentage:\")\nprint(\"Train %:\", round(len(train_df) / total * 100, 2))\nprint(\"Validation %:\", round(len(val_df) / total * 100, 2))\nprint(\"Test %:\", round(len(test_df) / total * 100, 2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:19:00.978970Z","iopub.execute_input":"2026-06-10T15:19:00.979767Z","iopub.status.idle":"2026-06-10T15:19:00.998509Z","shell.execute_reply.started":"2026-06-10T15:19:00.979733Z","shell.execute_reply":"2026-06-10T15:19:00.997615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Train label distribution:\")\ndisplay(train_df[\"label\"].value_counts())\n\nprint(\"Validation label distribution:\")\ndisplay(val_df[\"label\"].value_counts())\n\nprint(\"Test label distribution:\")\ndisplay(test_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:19:06.211177Z","iopub.execute_input":"2026-06-10T15:19:06.212032Z","iopub.status.idle":"2026-06-10T15:19:06.228532Z","shell.execute_reply.started":"2026-06-10T15:19:06.211996Z","shell.execute_reply":"2026-06-10T15:19:06.227445Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.to_csv(PRE_DIR / \"train_split_70.csv\", index=False)\nval_df.to_csv(PRE_DIR / \"val_split_15.csv\", index=False)\ntest_df.to_csv(PRE_DIR / \"test_split_15.csv\", index=False)\n\nprint(\"Saved:\")\nprint(PRE_DIR / \"train_split_70.csv\")\nprint(PRE_DIR / \"val_split_15.csv\")\nprint(PRE_DIR / \"test_split_15.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:19:09.321208Z","iopub.execute_input":"2026-06-10T15:19:09.322255Z","iopub.status.idle":"2026-06-10T15:19:09.334464Z","shell.execute_reply.started":"2026-06-10T15:19:09.322191Z","shell.execute_reply":"2026-06-10T15:19:09.333780Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:19:12.726102Z","iopub.execute_input":"2026-06-10T15:19:12.726997Z","iopub.status.idle":"2026-06-10T15:19:12.732129Z","shell.execute_reply.started":"2026-06-10T15:19:12.726960Z","shell.execute_reply":"2026-06-10T15:19:12.731170Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SpineCropDataset(Dataset):\n    def __init__(self, df):\n        self.df = df.reset_index(drop=True)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n\n        img = np.load(row[\"crop_path\"])  # H, W, 3\n\n        # H,W,3 → 3,H,W\n        img = img.transpose(2, 0, 1)\n\n        img = torch.tensor(img, dtype=torch.float32)\n        label = torch.tensor(int(row[\"label_id\"]), dtype=torch.long)\n\n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:19:15.885178Z","iopub.execute_input":"2026-06-10T15:19:15.885643Z","iopub.status.idle":"2026-06-10T15:19:15.891405Z","shell.execute_reply.started":"2026-06-10T15:19:15.885610Z","shell.execute_reply":"2026-06-10T15:19:15.890763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\n\n# PRE_DIR না থাকলে define করা\nPRE_DIR = Path(\"/kaggle/working/preprocessed_rsna\")\n\n# sample_crop_df না থাকলে saved file থেকে load করা\nif \"sample_crop_df\" not in globals():\n    sample_crop_df = pd.read_csv(PRE_DIR / \"sample_crop_metadata.csv\")\n\nprint(\"sample_crop_df shape:\", sample_crop_df.shape)\n\n# data type ঠিক করা\nsample_crop_df[\"label_id\"] = sample_crop_df[\"label_id\"].astype(int)\nsample_crop_df[\"study_id\"] = sample_crop_df[\"study_id\"].astype(int)\n\n# unique study_id দিয়ে split\nunique_studies = sample_crop_df[\"study_id\"].unique()\n\n# 70% train, 30% temp\ntrain_ids, temp_ids = train_test_split(\n    unique_studies,\n    test_size=0.30,\n    random_state=42\n)\n\n# temp 30% → 15% validation + 15% test\nval_ids, test_ids = train_test_split(\n    temp_ids,\n    test_size=0.50,\n    random_state=42\n)\n\ntrain_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(train_ids)].reset_index(drop=True)\nval_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(val_ids)].reset_index(drop=True)\ntest_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(test_ids)].reset_index(drop=True)\n\nprint(\"Train:\", train_df.shape)\nprint(\"Validation:\", val_df.shape)\nprint(\"Test:\", test_df.shape)\n\ntotal = len(sample_crop_df)\nprint(\"Train %:\", round(len(train_df) / total * 100, 2))\nprint(\"Validation %:\", round(len(val_df) / total * 100, 2))\nprint(\"Test %:\", round(len(test_df) / total * 100, 2))\n\nprint(\"\\nTrain label distribution:\")\ndisplay(train_df[\"label\"].value_counts())\n\nprint(\"\\nValidation label distribution:\")\ndisplay(val_df[\"label\"].value_counts())\n\nprint(\"\\nTest label distribution:\")\ndisplay(test_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:19:25.002713Z","iopub.execute_input":"2026-06-10T15:19:25.003162Z","iopub.status.idle":"2026-06-10T15:19:25.030129Z","shell.execute_reply.started":"2026-06-10T15:19:25.003131Z","shell.execute_reply":"2026-06-10T15:19:25.028843Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = SpineCropDataset(train_df)\nval_dataset = SpineCropDataset(val_df)\ntest_dataset = SpineCropDataset(test_df)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=2\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2\n)\n\nimages, labels = next(iter(train_loader))\n\nprint(\"Image batch shape:\", images.shape)\nprint(\"Label batch shape:\", labels.shape)\nprint(\"Labels:\", labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:19:39.010604Z","iopub.execute_input":"2026-06-10T15:19:39.011518Z","iopub.status.idle":"2026-06-10T15:19:39.175856Z","shell.execute_reply.started":"2026-06-10T15:19:39.011476Z","shell.execute_reply":"2026-06-10T15:19:39.174776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\n\nclass SimpleCNN(nn.Module):\n    def __init__(self, num_classes=3):\n        super().__init__()\n\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 16, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(16, 32, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(64, 128, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.AdaptiveAvgPool2d((1, 1))\n        )\n\n        self.classifier = nn.Linear(128, num_classes)\n\n    def forward(self, x):\n        x = self.features(x)\n        x = x.view(x.size(0), -1)\n        x = self.classifier(x)\n        return x\n\n\nmodel = SimpleCNN(num_classes=3).to(device)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:20:28.949495Z","iopub.execute_input":"2026-06-10T15:20:28.949885Z","iopub.status.idle":"2026-06-10T15:20:29.277632Z","shell.execute_reply.started":"2026-06-10T15:20:28.949852Z","shell.execute_reply":"2026-06-10T15:20:29.276731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = torch.tensor([1.0, 2.0, 4.0], dtype=torch.float32).to(device)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-3,\n    weight_decay=1e-4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:20:41.817545Z","iopub.execute_input":"2026-06-10T15:20:41.818146Z","iopub.status.idle":"2026-06-10T15:20:45.055986Z","shell.execute_reply.started":"2026-06-10T15:20:41.818112Z","shell.execute_reply":"2026-06-10T15:20:45.054951Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader, optimizer, criterion, device):\n    model.train()\n    total_loss = 0\n\n    for images, labels in loader:\n        images = images.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item() * images.size(0)\n\n    avg_loss = total_loss / len(loader.dataset)\n    return avg_loss\n\n\ndef evaluate_model(model, loader, criterion, device):\n    model.eval()\n    total_loss = 0\n    correct = 0\n    total = 0\n\n    all_probs = []\n    all_labels = []\n\n    with torch.no_grad():\n        for images, labels in loader:\n            images = images.to(device)\n            labels = labels.to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            probs = torch.softmax(outputs, dim=1)\n            preds = torch.argmax(probs, dim=1)\n\n            total_loss += loss.item() * images.size(0)\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n            all_probs.append(probs.cpu().numpy())\n            all_labels.append(labels.cpu().numpy())\n\n    avg_loss = total_loss / len(loader.dataset)\n    accuracy = correct / total\n\n    all_probs = np.concatenate(all_probs)\n    all_labels = np.concatenate(all_labels)\n\n    return avg_loss, accuracy, all_probs, all_labels","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:20:56.455139Z","iopub.execute_input":"2026-06-10T15:20:56.456330Z","iopub.status.idle":"2026-06-10T15:20:56.465280Z","shell.execute_reply.started":"2026-06-10T15:20:56.456292Z","shell.execute_reply":"2026-06-10T15:20:56.464443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(\n        model=model,\n        loader=train_loader,\n        optimizer=optimizer,\n        criterion=criterion,\n        device=device\n    )\n\n    val_loss, val_acc, val_probs, val_labels = evaluate_model(\n        model=model,\n        loader=val_loader,\n        criterion=criterion,\n        device=device\n    )\n\n    print(\n        f\"Epoch {epoch+1}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:21:10.302510Z","iopub.execute_input":"2026-06-10T15:21:10.303267Z","iopub.status.idle":"2026-06-10T15:21:13.584305Z","shell.execute_reply.started":"2026-06-10T15:21:10.303189Z","shell.execute_reply":"2026-06-10T15:21:13.583356Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, test_acc, test_probs, test_labels = evaluate_model(\n    model=model,\n    loader=test_loader,\n    criterion=criterion,\n    device=device\n)\n\nprint(\"Test Loss:\", round(test_loss, 4))\nprint(\"Test Accuracy:\", round(test_acc, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:21:25.110007Z","iopub.execute_input":"2026-06-10T15:21:25.110928Z","iopub.status.idle":"2026-06-10T15:21:25.287038Z","shell.execute_reply.started":"2026-06-10T15:21:25.110889Z","shell.execute_reply":"2026-06-10T15:21:25.286095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report\n\ntest_preds = np.argmax(test_probs, axis=1)\n\ncm = confusion_matrix(test_labels, test_preds)\n\nprint(\"Confusion Matrix:\")\nprint(cm)\n\nprint(\"\\nClassification Report:\")\nprint(classification_report(\n    test_labels,\n    test_preds,\n    target_names=[\"Normal/Mild\", \"Moderate\", \"Severe\"],\n    zero_division=0\n))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:21:44.815995Z","iopub.execute_input":"2026-06-10T15:21:44.817031Z","iopub.status.idle":"2026-06-10T15:21:44.837994Z","shell.execute_reply.started":"2026-06-10T15:21:44.816990Z","shell.execute_reply":"2026-06-10T15:21:44.837205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report\nimport numpy as np\n\ntest_preds = np.argmax(test_probs, axis=1)\n\ncm = confusion_matrix(test_labels, test_preds)\n\nprint(\"Confusion Matrix:\")\nprint(cm)\n\nprint(\"\\nClassification Report:\")\nprint(classification_report(\n    test_labels,\n    test_preds,\n    target_names=[\"Normal/Mild\", \"Moderate\", \"Severe\"],\n    zero_division=0\n))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:23:20.002258Z","iopub.execute_input":"2026-06-10T15:23:20.003175Z","iopub.status.idle":"2026-06-10T15:23:20.018936Z","shell.execute_reply.started":"2026-06-10T15:23:20.003135Z","shell.execute_reply":"2026-06-10T15:23:20.017938Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import log_loss\nimport numpy as np\n\ndef weighted_log_loss(y_true, y_prob):\n    class_weights = np.array([1.0, 2.0, 4.0])\n    sample_weights = class_weights[y_true]\n\n    return log_loss(\n        y_true,\n        y_prob,\n        labels=[0, 1, 2],\n        sample_weight=sample_weights\n    )\n\ntest_wll = weighted_log_loss(test_labels, test_probs)\n\nprint(\"Weighted Log Loss:\", round(test_wll, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:23:29.527826Z","iopub.execute_input":"2026-06-10T15:23:29.528125Z","iopub.status.idle":"2026-06-10T15:23:29.539168Z","shell.execute_reply.started":"2026-06-10T15:23:29.528099Z","shell.execute_reply":"2026-06-10T15:23:29.538163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"result_df = pd.DataFrame({\n    \"true_label\": test_labels,\n    \"pred_label\": test_preds,\n    \"prob_normal_mild\": test_probs[:, 0],\n    \"prob_moderate\": test_probs[:, 1],\n    \"prob_severe\": test_probs[:, 2]\n})\n\nresult_df.to_csv(PRE_DIR / \"sample_test_predictions.csv\", index=False)\n\nprint(\"Saved:\", PRE_DIR / \"sample_test_predictions.csv\")\ndisplay(result_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:23:44.182651Z","iopub.execute_input":"2026-06-10T15:23:44.183107Z","iopub.status.idle":"2026-06-10T15:23:44.199693Z","shell.execute_reply.started":"2026-06-10T15:23:44.183074Z","shell.execute_reply":"2026-06-10T15:23:44.198636Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"result_df = pd.DataFrame({\n    \"true_label\": test_labels,\n    \"pred_label\": test_preds,\n    \"prob_normal_mild\": test_probs[:, 0],\n    \"prob_moderate\": test_probs[:, 1],\n    \"prob_severe\": test_probs[:, 2]\n})\n\nresult_df.to_csv(PRE_DIR / \"sample_test_predictions.csv\", index=False)\n\nprint(\"Saved:\", PRE_DIR / \"sample_test_predictions.csv\")\ndisplay(result_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:24:30.677477Z","iopub.execute_input":"2026-06-10T15:24:30.678142Z","iopub.status.idle":"2026-06-10T15:24:30.691926Z","shell.execute_reply.started":"2026-06-10T15:24:30.678109Z","shell.execute_reply":"2026-06-10T15:24:30.691173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"result_df = pd.DataFrame({\n    \"true_label\": test_labels,\n    \"pred_label\": test_preds,\n    \"prob_normal_mild\": test_probs[:, 0],\n    \"prob_moderate\": test_probs[:, 1],\n    \"prob_severe\": test_probs[:, 2]\n})\n\nresult_df.to_csv(PRE_DIR / \"sample_test_predictions.csv\", index=False)\n\nprint(\"Saved:\", PRE_DIR / \"sample_test_predictions.csv\")\ndisplay(result_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:24:38.535194Z","iopub.execute_input":"2026-06-10T15:24:38.536511Z","iopub.status.idle":"2026-06-10T15:24:38.551504Z","shell.execute_reply.started":"2026-06-10T15:24:38.536474Z","shell.execute_reply":"2026-06-10T15:24:38.550204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CROP_DIR_2000 = PRE_DIR / \"sample_crops_25d_2000\"\nCROP_DIR_2000.mkdir(parents=True, exist_ok=True)\n\nsample_crop_df_2000, failed_df_2000 = save_25d_crops(\n    df=crop_df,\n    out_dir=CROP_DIR_2000,\n    max_rows=2000,\n    crop_size=224,\n    neighbor=1\n)\n\nprint(\"Saved crops:\", len(sample_crop_df_2000))\nprint(\"Failed crops:\", len(failed_df_2000))\n\nsample_crop_df_2000.to_csv(PRE_DIR / \"sample_crop_metadata_2000.csv\", index=False)\nfailed_df_2000.to_csv(PRE_DIR / \"failed_crop_rows_2000.csv\", index=False)\n\ndisplay(sample_crop_df_2000.head())\ndisplay(sample_crop_df_2000[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:24:49.129475Z","iopub.execute_input":"2026-06-10T15:24:49.129967Z","iopub.status.idle":"2026-06-10T15:26:30.920379Z","shell.execute_reply.started":"2026-06-10T15:24:49.129919Z","shell.execute_reply":"2026-06-10T15:26:30.919277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_crop_df = sample_crop_df_2000.copy()\n\nsample_crop_df[\"label_id\"] = sample_crop_df[\"label_id\"].astype(int)\nsample_crop_df[\"study_id\"] = sample_crop_df[\"study_id\"].astype(int)\n\nunique_studies = sample_crop_df[\"study_id\"].unique()\n\ntrain_ids, temp_ids = train_test_split(\n    unique_studies,\n    test_size=0.30,\n    random_state=42\n)\n\nval_ids, test_ids = train_test_split(\n    temp_ids,\n    test_size=0.50,\n    random_state=42\n)\n\ntrain_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(train_ids)].reset_index(drop=True)\nval_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(val_ids)].reset_index(drop=True)\ntest_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(test_ids)].reset_index(drop=True)\n\nprint(\"Train:\", train_df.shape)\nprint(\"Validation:\", val_df.shape)\nprint(\"Test:\", test_df.shape)\n\nprint(\"\\nTrain label:\")\ndisplay(train_df[\"label\"].value_counts())\n\nprint(\"\\nValidation label:\")\ndisplay(val_df[\"label\"].value_counts())\n\nprint(\"\\nTest label:\")\ndisplay(test_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:26:36.358097Z","iopub.execute_input":"2026-06-10T15:26:36.358564Z","iopub.status.idle":"2026-06-10T15:26:36.384678Z","shell.execute_reply.started":"2026-06-10T15:26:36.358531Z","shell.execute_reply":"2026-06-10T15:26:36.383832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = SpineCropDataset(train_df)\nval_dataset = SpineCropDataset(val_df)\ntest_dataset = SpineCropDataset(test_df)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=2\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2\n)\n\nimages, labels = next(iter(train_loader))\n\nprint(\"Image batch shape:\", images.shape)\nprint(\"Label batch shape:\", labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:26:40.489367Z","iopub.execute_input":"2026-06-10T15:26:40.489671Z","iopub.status.idle":"2026-06-10T15:26:40.682115Z","shell.execute_reply.started":"2026-06-10T15:26:40.489647Z","shell.execute_reply":"2026-06-10T15:26:40.681203Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = SimpleCNN(num_classes=3).to(device)\n\nclass_weights = torch.tensor([1.0, 2.0, 4.0], dtype=torch.float32).to(device)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-3,\n    weight_decay=1e-4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:26:43.918103Z","iopub.execute_input":"2026-06-10T15:26:43.919096Z","iopub.status.idle":"2026-06-10T15:26:43.931020Z","shell.execute_reply.started":"2026-06-10T15:26:43.919052Z","shell.execute_reply":"2026-06-10T15:26:43.930132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\n\nhistory = []\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(\n        model=model,\n        loader=train_loader,\n        optimizer=optimizer,\n        criterion=criterion,\n        device=device\n    )\n\n    val_loss, val_acc, val_probs, val_labels = evaluate_model(\n        model=model,\n        loader=val_loader,\n        criterion=criterion,\n        device=device\n    )\n\n    val_wll = weighted_log_loss(val_labels, val_probs)\n\n    history.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"val_loss\": val_loss,\n        \"val_acc\": val_acc,\n        \"val_weighted_log_loss\": val_wll\n    })\n\n    print(\n        f\"Epoch {epoch+1}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f} | \"\n        f\"Val WLL: {val_wll:.4f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:26:47.225627Z","iopub.execute_input":"2026-06-10T15:26:47.225910Z","iopub.status.idle":"2026-06-10T15:27:10.049912Z","shell.execute_reply.started":"2026-06-10T15:26:47.225886Z","shell.execute_reply":"2026-06-10T15:27:10.048553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, test_acc, test_probs, test_labels = evaluate_model(\n    model=model,\n    loader=test_loader,\n    criterion=criterion,\n    device=device\n)\n\ntest_wll = weighted_log_loss(test_labels, test_probs)\n\nprint(\"Test Loss:\", round(test_loss, 4))\nprint(\"Test Accuracy:\", round(test_acc, 4))\nprint(\"Test Weighted Log Loss:\", round(test_wll, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:27:10.506445Z","iopub.execute_input":"2026-06-10T15:27:10.506754Z","iopub.status.idle":"2026-06-10T15:27:10.936651Z","shell.execute_reply.started":"2026-06-10T15:27:10.506721Z","shell.execute_reply":"2026-06-10T15:27:10.935688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report\nimport numpy as np\n\ntest_preds = np.argmax(test_probs, axis=1)\n\nprint(\"Confusion Matrix:\")\nprint(confusion_matrix(test_labels, test_preds))\n\nprint(\"\\nClassification Report:\")\nprint(classification_report(\n    test_labels,\n    test_preds,\n    target_names=[\"Normal/Mild\", \"Moderate\", \"Severe\"],\n    zero_division=0\n))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:28:50.957829Z","iopub.execute_input":"2026-06-10T15:28:50.958781Z","iopub.status.idle":"2026-06-10T15:28:50.976840Z","shell.execute_reply.started":"2026-06-10T15:28:50.958740Z","shell.execute_reply":"2026-06-10T15:28:50.976103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_df = pd.DataFrame(history)\ndisplay(history_df)\n\nhistory_df.to_csv(PRE_DIR / \"training_history_2000_simplecnn.csv\", index=False)\n\nplt.figure(figsize=(8, 5))\nplt.plot(history_df[\"epoch\"], history_df[\"train_loss\"], label=\"Train Loss\")\nplt.plot(history_df[\"epoch\"], history_df[\"val_loss\"], label=\"Validation Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training and Validation Loss\")\nplt.legend()\nplt.grid(True)\nplt.show()\n\nplt.figure(figsize=(8, 5))\nplt.plot(history_df[\"epoch\"], history_df[\"val_acc\"], label=\"Validation Accuracy\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.title(\"Validation Accuracy\")\nplt.legend()\nplt.grid(True)\nplt.show()\n\nplt.figure(figsize=(8, 5))\nplt.plot(history_df[\"epoch\"], history_df[\"val_weighted_log_loss\"], label=\"Validation Weighted Log Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Weighted Log Loss\")\nplt.title(\"Validation Weighted Log Loss\")\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:28:59.547998Z","iopub.execute_input":"2026-06-10T15:28:59.548721Z","iopub.status.idle":"2026-06-10T15:29:00.010373Z","shell.execute_reply.started":"2026-06-10T15:28:59.548687Z","shell.execute_reply":"2026-06-10T15:29:00.009621Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_result_df = pd.DataFrame({\n    \"true_label\": test_labels,\n    \"pred_label\": test_preds,\n    \"prob_normal_mild\": test_probs[:, 0],\n    \"prob_moderate\": test_probs[:, 1],\n    \"prob_severe\": test_probs[:, 2]\n})\n\ntest_result_df.to_csv(PRE_DIR / \"test_predictions_2000_simplecnn.csv\", index=False)\n\ndisplay(test_result_df.head())\nprint(\"Saved:\", PRE_DIR / \"test_predictions_2000_simplecnn.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:29:13.792543Z","iopub.execute_input":"2026-06-10T15:29:13.792847Z","iopub.status.idle":"2026-06-10T15:29:13.810698Z","shell.execute_reply.started":"2026-06-10T15:29:13.792821Z","shell.execute_reply":"2026-06-10T15:29:13.809729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MODEL_DIR = PRE_DIR / \"models\"\nMODEL_DIR.mkdir(parents=True, exist_ok=True)\n\ntorch.save(model.state_dict(), MODEL_DIR / \"simplecnn_2000_crop_model.pth\")\n\nprint(\"Saved model:\")\nprint(MODEL_DIR / \"simplecnn_2000_crop_model.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:29:23.018595Z","iopub.execute_input":"2026-06-10T15:29:23.019381Z","iopub.status.idle":"2026-06-10T15:29:23.035333Z","shell.execute_reply.started":"2026-06-10T15:29:23.019332Z","shell.execute_reply":"2026-06-10T15:29:23.034312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"try:\n    import timm\n    print(\"timm already installed\")\nexcept:\n    !pip install -q timm\n    import timm\n    print(\"timm installed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:29:33.010959Z","iopub.execute_input":"2026-06-10T15:29:33.011680Z","iopub.status.idle":"2026-06-10T15:29:38.120772Z","shell.execute_reply.started":"2026-06-10T15:29:33.011647Z","shell.execute_reply":"2026-06-10T15:29:38.119969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TimmSpineModel(nn.Module):\n    def __init__(self, model_name=\"efficientnet_b0\", num_classes=3):\n        super().__init__()\n        self.model = timm.create_model(\n            model_name,\n            pretrained=True,\n            in_chans=3,\n            num_classes=num_classes\n        )\n\n    def forward(self, x):\n        return self.model(x)\n\n\nmodel = TimmSpineModel(\n    model_name=\"efficientnet_b0\",\n    num_classes=3\n).to(device)\n\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:29:45.693352Z","iopub.execute_input":"2026-06-10T15:29:45.694207Z","iopub.status.idle":"2026-06-10T15:29:47.853595Z","shell.execute_reply.started":"2026-06-10T15:29:45.694177Z","shell.execute_reply":"2026-06-10T15:29:47.852882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = torch.tensor([1.0, 2.0, 4.0], dtype=torch.float32).to(device)\n\ncriterion = nn.CrossEntropyLoss(\n    weight=class_weights,\n    label_smoothing=0.03\n)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=2e-4,\n    weight_decay=1e-4\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=10\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:30:24.204648Z","iopub.execute_input":"2026-06-10T15:30:24.205451Z","iopub.status.idle":"2026-06-10T15:30:24.213114Z","shell.execute_reply.started":"2026-06-10T15:30:24.205411Z","shell.execute_reply":"2026-06-10T15:30:24.212288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\n\nhistory_effnet = []\n\nbest_val_wll = 999\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(\n        model=model,\n        loader=train_loader,\n        optimizer=optimizer,\n        criterion=criterion,\n        device=device\n    )\n\n    val_loss, val_acc, val_probs, val_labels = evaluate_model(\n        model=model,\n        loader=val_loader,\n        criterion=criterion,\n        device=device\n    )\n\n    val_wll = weighted_log_loss(val_labels, val_probs)\n    scheduler.step()\n\n    history_effnet.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"val_loss\": val_loss,\n        \"val_acc\": val_acc,\n        \"val_weighted_log_loss\": val_wll\n    })\n\n    print(\n        f\"Epoch {epoch+1}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f} | \"\n        f\"Val WLL: {val_wll:.4f}\"\n    )\n\n    if val_wll < best_val_wll:\n        best_val_wll = val_wll\n        torch.save(model.state_dict(), MODEL_DIR / \"best_effnet_b0_2000_crop.pth\")\n        print(\"Best model saved!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:30:37.927909Z","iopub.execute_input":"2026-06-10T15:30:37.928518Z","iopub.status.idle":"2026-06-10T15:30:38.646932Z","shell.execute_reply.started":"2026-06-10T15:30:37.928485Z","shell.execute_reply":"2026-06-10T15:30:38.645679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)\n\nif device.type == \"cuda\":\n    print(\"GPU:\", torch.cuda.get_device_name(0))\nelse:\n    print(\"GPU not active. Kaggle Notebook Settings থেকে Accelerator = GPU করুন.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:32:01.634365Z","iopub.execute_input":"2026-06-10T15:32:01.635326Z","iopub.status.idle":"2026-06-10T15:32:01.642712Z","shell.execute_reply.started":"2026-06-10T15:32:01.635274Z","shell.execute_reply":"2026-06-10T15:32:01.641518Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gc\nimport torch\n\ngc.collect()\n\nif torch.cuda.is_available():\n    torch.cuda.empty_cache()\n\nprint(\"Memory cleaned\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:32:11.322972Z","iopub.execute_input":"2026-06-10T15:32:11.323336Z","iopub.status.idle":"2026-06-10T15:32:11.693731Z","shell.execute_reply.started":"2026-06-10T15:32:11.323305Z","shell.execute_reply":"2026-06-10T15:32:11.693024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=8,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=8,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=8,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nimages, labels = next(iter(train_loader))\nprint(images.shape, labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:32:21.097583Z","iopub.execute_input":"2026-06-10T15:32:21.097884Z","iopub.status.idle":"2026-06-10T15:32:21.259074Z","shell.execute_reply.started":"2026-06-10T15:32:21.097860Z","shell.execute_reply":"2026-06-10T15:32:21.258177Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"try:\n    import timm\n    print(\"timm ready\")\nexcept:\n    !pip install -q timm\n    import timm\n    print(\"timm installed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:32:30.341160Z","iopub.execute_input":"2026-06-10T15:32:30.341523Z","iopub.status.idle":"2026-06-10T15:32:30.348097Z","shell.execute_reply.started":"2026-06-10T15:32:30.341489Z","shell.execute_reply":"2026-06-10T15:32:30.347068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TimmSpineModel(nn.Module):\n    def __init__(self, model_name=\"mobilenetv3_small_100\", num_classes=3):\n        super().__init__()\n        self.model = timm.create_model(\n            model_name,\n            pretrained=True,\n            in_chans=3,\n            num_classes=num_classes\n        )\n\n    def forward(self, x):\n        return self.model(x)\n\n\nmodel = TimmSpineModel(\n    model_name=\"mobilenetv3_small_100\",\n    num_classes=3\n).to(device)\n\nprint(\"Model ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:32:40.162415Z","iopub.execute_input":"2026-06-10T15:32:40.163361Z","iopub.status.idle":"2026-06-10T15:32:41.280261Z","shell.execute_reply.started":"2026-06-10T15:32:40.163313Z","shell.execute_reply":"2026-06-10T15:32:41.279270Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = torch.tensor([1.0, 2.0, 4.0], dtype=torch.float32).to(device)\n\ncriterion = nn.CrossEntropyLoss(\n    weight=class_weights,\n    label_smoothing=0.03\n)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=2e-4,\n    weight_decay=1e-4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:33:01.251261Z","iopub.execute_input":"2026-06-10T15:33:01.252161Z","iopub.status.idle":"2026-06-10T15:33:01.258907Z","shell.execute_reply.started":"2026-06-10T15:33:01.252120Z","shell.execute_reply":"2026-06-10T15:33:01.258144Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 3\n\nhistory_mobilenet = []\nbest_val_wll = 999\n\nMODEL_DIR = PRE_DIR / \"models\"\nMODEL_DIR.mkdir(parents=True, exist_ok=True)\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(\n        model=model,\n        loader=train_loader,\n        optimizer=optimizer,\n        criterion=criterion,\n        device=device\n    )\n\n    val_loss, val_acc, val_probs, val_labels = evaluate_model(\n        model=model,\n        loader=val_loader,\n        criterion=criterion,\n        device=device\n    )\n\n    val_wll = weighted_log_loss(val_labels, val_probs)\n\n    history_mobilenet.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"val_loss\": val_loss,\n        \"val_acc\": val_acc,\n        \"val_weighted_log_loss\": val_wll\n    })\n\n    print(\n        f\"Epoch {epoch+1}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f} | \"\n        f\"Val WLL: {val_wll:.4f}\"\n    )\n\n    if val_wll < best_val_wll:\n        best_val_wll = val_wll\n        torch.save(model.state_dict(), MODEL_DIR / \"best_mobilenet_2000_crop.pth\")\n        print(\"Best model saved\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:33:14.180113Z","iopub.execute_input":"2026-06-10T15:33:14.181131Z","iopub.status.idle":"2026-06-10T15:33:31.219886Z","shell.execute_reply.started":"2026-06-10T15:33:14.181098Z","shell.execute_reply":"2026-06-10T15:33:31.218928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(MODEL_DIR / \"best_mobilenet_2000_crop.pth\"))\n\ntest_loss, test_acc, test_probs, test_labels = evaluate_model(\n    model=model,\n    loader=test_loader,\n    criterion=criterion,\n    device=device\n)\n\ntest_wll = weighted_log_loss(test_labels, test_probs)\n\nprint(\"MobileNet Test Loss:\", round(test_loss, 4))\nprint(\"MobileNet Test Accuracy:\", round(test_acc, 4))\nprint(\"MobileNet Test Weighted Log Loss:\", round(test_wll, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:33:43.629281Z","iopub.execute_input":"2026-06-10T15:33:43.629789Z","iopub.status.idle":"2026-06-10T15:33:44.384788Z","shell.execute_reply.started":"2026-06-10T15:33:43.629749Z","shell.execute_reply":"2026-06-10T15:33:44.383863Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"comparison_df = pd.DataFrame({\n    \"model\": [\"SimpleCNN\", \"MobileNetV3\"],\n    \"num_crops\": [2000, 2000],\n    \"test_accuracy\": [0.7673, 0.5346],\n    \"test_weighted_log_loss\": [0.9653, 1.2333]\n})\n\ndisplay(comparison_df)\n\ncomparison_df.to_csv(PRE_DIR / \"model_comparison_initial.csv\", index=False)\nprint(\"Saved:\", PRE_DIR / \"model_comparison_initial.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:35:09.428315Z","iopub.execute_input":"2026-06-10T15:35:09.428988Z","iopub.status.idle":"2026-06-10T15:35:09.445915Z","shell.execute_reply.started":"2026-06-10T15:35:09.428956Z","shell.execute_reply":"2026-06-10T15:35:09.444921Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nprint(\"crop_df shape:\", crop_df.shape)\ndisplay(crop_df[\"label\"].value_counts())\n\n\ncrop_df[\"label_id\"] = crop_df[\"label_id\"].astype(int)\n\nTARGET_PER_CLASS = 1700\n\nbalanced_parts = []\n\nfor label_id in sorted(crop_df[\"label_id\"].unique()):\n    part = crop_df[crop_df[\"label_id\"] == label_id]\n    \n    n = min(len(part), TARGET_PER_CLASS)\n    \n    sampled_part = part.sample(\n        n=n,\n        random_state=42\n    )\n    \n    balanced_parts.append(sampled_part)\n\nbalanced_crop_source_df = pd.concat(balanced_parts).sample(\n    frac=1,\n    random_state=42\n).reset_index(drop=True)\n\nprint(\"Balanced source shape:\", balanced_crop_source_df.shape)\ndisplay(balanced_crop_source_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:35:23.711782Z","iopub.execute_input":"2026-06-10T15:35:23.712084Z","iopub.status.idle":"2026-06-10T15:35:23.758692Z","shell.execute_reply.started":"2026-06-10T15:35:23.712058Z","shell.execute_reply":"2026-06-10T15:35:23.758008Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CROP_DIR_5000 = PRE_DIR / \"sample_crops_25d_5000_balanced\"\nCROP_DIR_5000.mkdir(parents=True, exist_ok=True)\n\nsample_crop_df_5000, failed_df_5000 = save_25d_crops(\n    df=balanced_crop_source_df,\n    out_dir=CROP_DIR_5000,\n    max_rows=5000,\n    crop_size=224,\n    neighbor=1\n)\n\nprint(\"Saved crops:\", len(sample_crop_df_5000))\nprint(\"Failed crops:\", len(failed_df_5000))\n\nsample_crop_df_5000.to_csv(PRE_DIR / \"sample_crop_metadata_5000_balanced.csv\", index=False)\nfailed_df_5000.to_csv(PRE_DIR / \"failed_crop_rows_5000_balanced.csv\", index=False)\n\ndisplay(sample_crop_df_5000.head())\ndisplay(sample_crop_df_5000[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:35:34.844366Z","iopub.execute_input":"2026-06-10T15:35:34.845051Z","iopub.status.idle":"2026-06-10T15:40:38.154022Z","shell.execute_reply.started":"2026-06-10T15:35:34.845017Z","shell.execute_reply":"2026-06-10T15:40:38.153271Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# crop_df অবশ্যই থাকতে হবে\nprint(\"crop_df shape:\", crop_df.shape)\ndisplay(crop_df[\"label\"].value_counts())\n\n# label_id integer করা\ncrop_df[\"label_id\"] = crop_df[\"label_id\"].astype(int)\n\n# প্রতি class থেকে sample নেওয়া\nTARGET_PER_CLASS = 1700\n\nbalanced_parts = []\n\nfor label_id in sorted(crop_df[\"label_id\"].unique()):\n    part = crop_df[crop_df[\"label_id\"] == label_id]\n    \n    n = min(len(part), TARGET_PER_CLASS)\n    \n    sampled_part = part.sample(\n        n=n,\n        random_state=42\n    )\n    \n    balanced_parts.append(sampled_part)\n\nbalanced_crop_source_df = pd.concat(balanced_parts).sample(\n    frac=1,\n    random_state=42\n).reset_index(drop=True)\n\nprint(\"Balanced source shape:\", balanced_crop_source_df.shape)\ndisplay(balanced_crop_source_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:40:55.299596Z","iopub.execute_input":"2026-06-10T15:40:55.300053Z","iopub.status.idle":"2026-06-10T15:40:55.340792Z","shell.execute_reply.started":"2026-06-10T15:40:55.300020Z","shell.execute_reply":"2026-06-10T15:40:55.340086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_crop_df = sample_crop_df_5000.copy()\n\nsample_crop_df[\"label_id\"] = sample_crop_df[\"label_id\"].astype(int)\nsample_crop_df[\"study_id\"] = sample_crop_df[\"study_id\"].astype(int)\n\nunique_studies = sample_crop_df[\"study_id\"].unique()\n\ntrain_ids, temp_ids = train_test_split(\n    unique_studies,\n    test_size=0.30,\n    random_state=42\n)\n\nval_ids, test_ids = train_test_split(\n    temp_ids,\n    test_size=0.50,\n    random_state=42\n)\n\ntrain_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(train_ids)].reset_index(drop=True)\nval_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(val_ids)].reset_index(drop=True)\ntest_df = sample_crop_df[sample_crop_df[\"study_id\"].isin(test_ids)].reset_index(drop=True)\n\nprint(\"Train:\", train_df.shape)\nprint(\"Validation:\", val_df.shape)\nprint(\"Test:\", test_df.shape)\n\nprint(\"\\nTrain labels:\")\ndisplay(train_df[\"label\"].value_counts())\n\nprint(\"\\nValidation labels:\")\ndisplay(val_df[\"label\"].value_counts())\n\nprint(\"\\nTest labels:\")\ndisplay(test_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:43:08.827500Z","iopub.execute_input":"2026-06-10T15:43:08.828401Z","iopub.status.idle":"2026-06-10T15:43:08.859283Z","shell.execute_reply.started":"2026-06-10T15:43:08.828363Z","shell.execute_reply":"2026-06-10T15:43:08.858073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = SpineCropDataset(train_df)\nval_dataset = SpineCropDataset(val_df)\ntest_dataset = SpineCropDataset(test_df)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nimages, labels = next(iter(train_loader))\n\nprint(\"Image batch:\", images.shape)\nprint(\"Labels:\", labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:43:19.770528Z","iopub.execute_input":"2026-06-10T15:43:19.771416Z","iopub.status.idle":"2026-06-10T15:43:19.983207Z","shell.execute_reply.started":"2026-06-10T15:43:19.771381Z","shell.execute_reply":"2026-06-10T15:43:19.982043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = SimpleCNN(num_classes=3).to(device)\n\nclass_weights = torch.tensor([1.0, 2.0, 4.0], dtype=torch.float32).to(device)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-3,\n    weight_decay=1e-4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:43:30.753301Z","iopub.execute_input":"2026-06-10T15:43:30.753837Z","iopub.status.idle":"2026-06-10T15:43:30.770041Z","shell.execute_reply.started":"2026-06-10T15:43:30.753797Z","shell.execute_reply":"2026-06-10T15:43:30.768923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\n\nhistory_simplecnn_5000 = []\n\nbest_val_wll = 999\n\nMODEL_DIR = PRE_DIR / \"models\"\nMODEL_DIR.mkdir(parents=True, exist_ok=True)\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(\n        model=model,\n        loader=train_loader,\n        optimizer=optimizer,\n        criterion=criterion,\n        device=device\n    )\n\n    val_loss, val_acc, val_probs, val_labels = evaluate_model(\n        model=model,\n        loader=val_loader,\n        criterion=criterion,\n        device=device\n    )\n\n    val_wll = weighted_log_loss(val_labels, val_probs)\n\n    history_simplecnn_5000.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"val_loss\": val_loss,\n        \"val_acc\": val_acc,\n        \"val_weighted_log_loss\": val_wll\n    })\n\n    print(\n        f\"Epoch {epoch+1}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f} | \"\n        f\"Val WLL: {val_wll:.4f}\"\n    )\n\n    if val_wll < best_val_wll:\n        best_val_wll = val_wll\n        torch.save(model.state_dict(), MODEL_DIR / \"best_simplecnn_5000_balanced.pth\")\n        print(\"Best model saved\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:43:44.883543Z","iopub.execute_input":"2026-06-10T15:43:44.884413Z","iopub.status.idle":"2026-06-10T15:44:29.822556Z","shell.execute_reply.started":"2026-06-10T15:43:44.884378Z","shell.execute_reply":"2026-06-10T15:44:29.821446Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(MODEL_DIR / \"best_simplecnn_5000_balanced.pth\"))\n\ntest_loss, test_acc, test_probs, test_labels = evaluate_model(\n    model=model,\n    loader=test_loader,\n    criterion=criterion,\n    device=device\n)\n\ntest_wll = weighted_log_loss(test_labels, test_probs)\n\nprint(\"SimpleCNN 5000 Balanced Test Loss:\", round(test_loss, 4))\nprint(\"SimpleCNN 5000 Balanced Test Accuracy:\", round(test_acc, 4))\nprint(\"SimpleCNN 5000 Balanced Test Weighted Log Loss:\", round(test_wll, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:44:29.831183Z","iopub.execute_input":"2026-06-10T15:44:29.831641Z","iopub.status.idle":"2026-06-10T15:44:30.637762Z","shell.execute_reply.started":"2026-06-10T15:44:29.831608Z","shell.execute_reply":"2026-06-10T15:44:30.636779Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report\nimport numpy as np\n\ntest_preds = np.argmax(test_probs, axis=1)\n\nprint(\"Confusion Matrix:\")\nprint(confusion_matrix(test_labels, test_preds))\n\nprint(\"\\nClassification Report:\")\nprint(classification_report(\n    test_labels,\n    test_preds,\n    target_names=[\"Normal/Mild\", \"Moderate\", \"Severe\"],\n    zero_division=0\n))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:44:50.818116Z","iopub.execute_input":"2026-06-10T15:44:50.818664Z","iopub.status.idle":"2026-06-10T15:44:50.838018Z","shell.execute_reply.started":"2026-06-10T15:44:50.818624Z","shell.execute_reply":"2026-06-10T15:44:50.837019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Full crop_df shape:\", crop_df.shape)\ndisplay(crop_df.head())\n\nprint(\"Label distribution:\")\ndisplay(crop_df[\"label\"].value_counts())\n\nprint(\"Condition distribution:\")\ndisplay(crop_df[\"condition_key\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:45:33.183430Z","iopub.execute_input":"2026-06-10T15:45:33.184159Z","iopub.status.idle":"2026-06-10T15:45:33.218450Z","shell.execute_reply.started":"2026-06-10T15:45:33.184122Z","shell.execute_reply":"2026-06-10T15:45:33.217743Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\ncrop_df[\"label_id\"] = crop_df[\"label_id\"].astype(int)\ncrop_df[\"study_id\"] = crop_df[\"study_id\"].astype(int)\n\nunique_studies = crop_df[\"study_id\"].unique()\n\ntrain_ids, temp_ids = train_test_split(\n    unique_studies,\n    test_size=0.30,\n    random_state=42\n)\n\nval_ids, test_ids = train_test_split(\n    temp_ids,\n    test_size=0.50,\n    random_state=42\n)\n\ntrain_df = crop_df[crop_df[\"study_id\"].isin(train_ids)].reset_index(drop=True)\nval_df = crop_df[crop_df[\"study_id\"].isin(val_ids)].reset_index(drop=True)\ntest_df = crop_df[crop_df[\"study_id\"].isin(test_ids)].reset_index(drop=True)\n\nprint(\"Train:\", train_df.shape)\nprint(\"Validation:\", val_df.shape)\nprint(\"Test:\", test_df.shape)\n\ntotal = len(crop_df)\nprint(\"Train %:\", round(len(train_df) / total * 100, 2))\nprint(\"Validation %:\", round(len(val_df) / total * 100, 2))\nprint(\"Test %:\", round(len(test_df) / total * 100, 2))\n\nprint(\"\\nTrain labels:\")\ndisplay(train_df[\"label\"].value_counts())\n\nprint(\"\\nValidation labels:\")\ndisplay(val_df[\"label\"].value_counts())\n\nprint(\"\\nTest labels:\")\ndisplay(test_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:45:44.997956Z","iopub.execute_input":"2026-06-10T15:45:44.998535Z","iopub.status.idle":"2026-06-10T15:45:45.048435Z","shell.execute_reply.started":"2026-06-10T15:45:44.998495Z","shell.execute_reply":"2026-06-10T15:45:45.047655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\n\nclass SpineOnTheFlyDataset(Dataset):\n    def __init__(self, df, crop_size=224, neighbor=1):\n        self.df = df.reset_index(drop=True)\n        self.crop_size = crop_size\n        self.neighbor = neighbor\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n\n        crop_25d = make_25d_crop(\n            study_id=row[\"study_id\"],\n            series_id=row[\"series_id\"],\n            instance_number=row[\"instance_number\"],\n            x=row[\"x\"],\n            y=row[\"y\"],\n            crop_size=self.crop_size,\n            neighbor=self.neighbor\n        )\n\n        # H,W,3 → 3,H,W\n        img = crop_25d.transpose(2, 0, 1)\n\n        img = torch.tensor(img, dtype=torch.float32)\n        label = torch.tensor(int(row[\"label_id\"]), dtype=torch.long)\n\n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:45:59.724592Z","iopub.execute_input":"2026-06-10T15:45:59.724909Z","iopub.status.idle":"2026-06-10T15:45:59.732491Z","shell.execute_reply.started":"2026-06-10T15:45:59.724884Z","shell.execute_reply":"2026-06-10T15:45:59.731423Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = SpineOnTheFlyDataset(train_df, crop_size=224, neighbor=1)\nval_dataset = SpineOnTheFlyDataset(val_df, crop_size=224, neighbor=1)\ntest_dataset = SpineOnTheFlyDataset(test_df, crop_size=224, neighbor=1)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=8,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=8,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=8,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nimages, labels = next(iter(train_loader))\n\nprint(\"Image batch:\", images.shape)\nprint(\"Label batch:\", labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:46:15.175151Z","iopub.execute_input":"2026-06-10T15:46:15.175962Z","iopub.status.idle":"2026-06-10T15:46:16.299512Z","shell.execute_reply.started":"2026-06-10T15:46:15.175927Z","shell.execute_reply":"2026-06-10T15:46:16.298598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label_counts = train_df[\"label_id\"].value_counts().sort_index()\nprint(label_counts)\n\ntotal = label_counts.sum()\nclass_weights = total / (3 * label_counts)\n\nclass_weights = torch.tensor(\n    class_weights.values,\n    dtype=torch.float32\n).to(device)\n\nprint(\"Class weights:\", class_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:46:25.493835Z","iopub.execute_input":"2026-06-10T15:46:25.494877Z","iopub.status.idle":"2026-06-10T15:46:25.623375Z","shell.execute_reply.started":"2026-06-10T15:46:25.494836Z","shell.execute_reply":"2026-06-10T15:46:25.622318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = SimpleCNN(num_classes=3).to(device)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-3,\n    weight_decay=1e-4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:48:42.363429Z","iopub.execute_input":"2026-06-10T15:48:42.364518Z","iopub.status.idle":"2026-06-10T15:48:42.374304Z","shell.execute_reply.started":"2026-06-10T15:48:42.364474Z","shell.execute_reply":"2026-06-10T15:48:42.373368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5\n\nhistory_full_simplecnn = []\nbest_val_wll = 999\n\nMODEL_DIR = PRE_DIR / \"models\"\nMODEL_DIR.mkdir(parents=True, exist_ok=True)\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(\n        model=model,\n        loader=train_loader,\n        optimizer=optimizer,\n        criterion=criterion,\n        device=device\n    )\n\n    val_loss, val_acc, val_probs, val_labels = evaluate_model(\n        model=model,\n        loader=val_loader,\n        criterion=criterion,\n        device=device\n    )\n\n    val_wll = weighted_log_loss(val_labels, val_probs)\n\n    history_full_simplecnn.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"val_loss\": val_loss,\n        \"val_acc\": val_acc,\n        \"val_weighted_log_loss\": val_wll\n    })\n\n    print(\n        f\"Epoch {epoch+1}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f} | \"\n        f\"Val WLL: {val_wll:.4f}\"\n    )\n\n    if val_wll < best_val_wll:\n        best_val_wll = val_wll\n        torch.save(model.state_dict(), MODEL_DIR / \"best_simplecnn_full_data.pth\")\n        print(\"Best model saved\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:48:46.408175Z","iopub.execute_input":"2026-06-10T15:48:46.409140Z","iopub.status.idle":"2026-06-10T15:51:41.454256Z","shell.execute_reply.started":"2026-06-10T15:48:46.409104Z","shell.execute_reply":"2026-06-10T15:51:41.451156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gc\nimport torch\n\ngc.collect()\n\nif torch.cuda.is_available():\n    torch.cuda.empty_cache()\n\nprint(\"Memory cleaned\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:51:47.316856Z","iopub.execute_input":"2026-06-10T15:51:47.317464Z","iopub.status.idle":"2026-06-10T15:51:47.681532Z","shell.execute_reply.started":"2026-06-10T15:51:47.317423Z","shell.execute_reply":"2026-06-10T15:51:47.680796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=0,\n    pin_memory=False\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=0,\n    pin_memory=False\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=0,\n    pin_memory=False\n)\n\nimages, labels = next(iter(train_loader))\n\nprint(\"Image batch:\", images.shape)\nprint(\"Label batch:\", labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:51:58.738701Z","iopub.execute_input":"2026-06-10T15:51:58.738997Z","iopub.status.idle":"2026-06-10T15:51:59.534629Z","shell.execute_reply.started":"2026-06-10T15:51:58.738973Z","shell.execute_reply":"2026-06-10T15:51:59.533827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=8,\n    shuffle=True,\n    num_workers=0,\n    pin_memory=False\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=8,\n    shuffle=False,\n    num_workers=0,\n    pin_memory=False\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=8,\n    shuffle=False,\n    num_workers=0,\n    pin_memory=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:52:12.818994Z","iopub.execute_input":"2026-06-10T15:52:12.819892Z","iopub.status.idle":"2026-06-10T15:52:12.826291Z","shell.execute_reply.started":"2026-06-10T15:52:12.819858Z","shell.execute_reply":"2026-06-10T15:52:12.825279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5\n\nhistory = []\n\nbest_val_wll = 999\n\nMODEL_DIR = PRE_DIR / \"models\"\nMODEL_DIR.mkdir(parents=True, exist_ok=True)\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(\n        model=model,\n        loader=train_loader,\n        optimizer=optimizer,\n        criterion=criterion,\n        device=device\n    )\n\n    val_loss, val_acc, val_probs, val_labels = evaluate_model(\n        model=model,\n        loader=val_loader,\n        criterion=criterion,\n        device=device\n    )\n\n    val_wll = weighted_log_loss(val_labels, val_probs)\n\n    history.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"val_loss\": val_loss,\n        \"val_acc\": val_acc,\n        \"val_weighted_log_loss\": val_wll\n    })\n\n    print(\n        f\"Epoch {epoch+1}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f} | \"\n        f\"Val WLL: {val_wll:.4f}\"\n    )\n\n    if val_wll < best_val_wll:\n        best_val_wll = val_wll\n        torch.save(model.state_dict(), MODEL_DIR / \"best_model_fixed_loader.pth\")\n        print(\"Best model saved\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-10T15:52:32.182353Z","iopub.execute_input":"2026-06-10T15:52:32.182755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}