{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.12"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"databundleVersionId":3437841,"sourceId":34478,"sourceType":"competition"}],"dockerImageVersionId":31329,"isGpuEnabled":true,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"papermill":{"default_parameters":{},"duration":3493.534561,"end_time":"2026-06-09T16:54:06.960551+00:00","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-06-09T15:55:53.42599+00:00","version":"2.7.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"c3fd86b8","cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\n# import numpy as np # linear algebra\n# import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# # Input data files are available in the read-only \"../input/\" directory\n# # For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2026-06-09T15:55:56.052103Z","iopub.status.busy":"2026-06-09T15:55:56.05161Z","iopub.status.idle":"2026-06-09T15:55:56.055948Z","shell.execute_reply":"2026-06-09T15:55:56.055172Z"},"papermill":{"duration":0.017991,"end_time":"2026-06-09T15:55:56.057288+00:00","exception":false,"start_time":"2026-06-09T15:55:56.039297+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"e26b5677","cell_type":"code","source":"import os\nimport torch\nimport timm\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (classification_report, confusion_matrix,\n                              roc_curve, auc, precision_recall_curve)\nfrom sklearn.preprocessing import label_binarize\nfrom sklearn.manifold import TSNE\nfrom sklearn.decomposition import PCA","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:16:30.844089Z","iopub.execute_input":"2026-07-05T13:16:30.844719Z","iopub.status.idle":"2026-07-05T13:16:44.913949Z","shell.execute_reply.started":"2026-07-05T13:16:30.844687Z","shell.execute_reply":"2026-07-05T13:16:44.913071Z"},"papermill":{"duration":17.969579,"end_time":"2026-06-09T15:56:14.036325+00:00","exception":false,"start_time":"2026-06-09T15:55:56.066746+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"4c0c57af","cell_type":"code","source":"# import shutil\n\n# shutil.rmtree(\"/kaggle/working/snakeclef\", ignore_errors=True)","metadata":{"execution":{"iopub.execute_input":"2026-06-09T15:56:14.056778Z","iopub.status.busy":"2026-06-09T15:56:14.05601Z","iopub.status.idle":"2026-06-09T15:56:14.059646Z","shell.execute_reply":"2026-06-09T15:56:14.058812Z"},"papermill":{"duration":0.015041,"end_time":"2026-06-09T15:56:14.061064+00:00","exception":false,"start_time":"2026-06-09T15:56:14.046023+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"2c264f63","cell_type":"code","source":"# import shutil\n\n# shutil.copytree(\"/kaggle/input/competitions/snakeclef2022/SnakeCLEF2022-medium_size\", \"/kaggle/working/snakeclef\")","metadata":{"execution":{"iopub.execute_input":"2026-06-09T15:56:14.079755Z","iopub.status.busy":"2026-06-09T15:56:14.079442Z","iopub.status.idle":"2026-06-09T15:56:14.082447Z","shell.execute_reply":"2026-06-09T15:56:14.081778Z"},"papermill":{"duration":0.014027,"end_time":"2026-06-09T15:56:14.083855+00:00","exception":false,"start_time":"2026-06-09T15:56:14.069828+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"b77a6ca0","cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:16:56.536845Z","iopub.execute_input":"2026-07-05T13:16:56.53742Z","iopub.status.idle":"2026-07-05T13:16:56.802545Z","shell.execute_reply.started":"2026-07-05T13:16:56.537388Z","shell.execute_reply":"2026-07-05T13:16:56.801574Z"},"papermill":{"duration":0.265038,"end_time":"2026-06-09T15:56:14.35795+00:00","exception":false,"start_time":"2026-06-09T15:56:14.092912+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"9eafc27e","cell_type":"code","source":"BASE_PATH = \"/kaggle/input/competitions/snakeclef2022\"\n\nTRAIN_METADATA = BASE_PATH + \"/SnakeCLEF2022-TrainMetadata.csv\"\nTRAIN_IMG_DIR  = BASE_PATH + \"/SnakeCLEF2022-medium_size/SnakeCLEF2022-medium_size\"","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:16:59.424401Z","iopub.execute_input":"2026-07-05T13:16:59.424907Z","iopub.status.idle":"2026-07-05T13:16:59.428685Z","shell.execute_reply.started":"2026-07-05T13:16:59.42488Z","shell.execute_reply":"2026-07-05T13:16:59.427935Z"},"papermill":{"duration":0.014806,"end_time":"2026-06-09T15:56:14.382509+00:00","exception":false,"start_time":"2026-06-09T15:56:14.367703+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"d104fd81","cell_type":"code","source":"df = pd.read_csv(TRAIN_METADATA)\n\nprint(\"Total samples:\", len(df))\nprint(\"Total classes:\", df[\"class_id\"].nunique())","metadata":{"execution":{"iopub.execute_input":"2026-06-09T15:56:14.401837Z","iopub.status.busy":"2026-06-09T15:56:14.401599Z","iopub.status.idle":"2026-06-09T15:56:15.089862Z","shell.execute_reply":"2026-06-09T15:56:15.087854Z"},"papermill":{"duration":0.700741,"end_time":"2026-06-09T15:56:15.092268+00:00","exception":false,"start_time":"2026-06-09T15:56:14.391527+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"d428cb27","cell_type":"code","source":"# =============================================\n# FILTER: Indian snake species (via ISO mapping)\n# =============================================\n\nISO_MAPPING = BASE_PATH + \"/SnakeCLEF2022-ISOxSpeciesMapping.csv\"\niso_df = pd.read_csv(ISO_MAPPING)\n\n# Step 1 — Get all species native to India\nindia_species = iso_df[iso_df['india'] == 1]['binomial'].tolist()\nprint(f\"Total species native to India: {len(india_species)}\")\n\n# Step 2 — Filter FULL TrainMetadata (df from Cell 6, not pre-filtered)\n# Re-read to make sure we're using the full dataset\nfull_df = pd.read_csv(TRAIN_METADATA)\nindia_df = full_df[full_df['binomial_name'].isin(india_species)]\nprint(f\"Total rows: {len(india_df)}\")\nprint(f\"Unique species: {india_df['binomial_name'].nunique()}\")\n\n# Step 3 — Keep species with >= 100 images\nclass_counts = india_df['binomial_name'].value_counts()\ntop_species = class_counts[(class_counts > 100) & (class_counts < 600)].index\ndf = india_df[india_df['binomial_name'].isin(top_species)].copy()\n\nprint(f\"\\nAfter >= 100 filter:\")\nprint(f\"  Dataset size : {len(df)}\")\nprint(f\"  Classes      : {df['binomial_name'].nunique()}\")\nprint()\nprint(df.groupby('binomial_name').size()\n        .reset_index(name='count')\n        .sort_values('count', ascending=False)\n        .to_string(index=False))","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:03.259087Z","iopub.execute_input":"2026-07-05T13:17:03.259541Z","iopub.status.idle":"2026-07-05T13:17:04.03142Z","shell.execute_reply.started":"2026-07-05T13:17:03.259512Z","shell.execute_reply":"2026-07-05T13:17:04.030717Z"},"papermill":{"duration":0.525154,"end_time":"2026-06-09T15:56:15.631429+00:00","exception":false,"start_time":"2026-06-09T15:56:15.106275+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"c64cf045","cell_type":"code","source":"# =============================================\n# CLASS IMBALANCE VISUALIZATION\n# =============================================\n\nclass_counts_plot = df['binomial_name'].value_counts().sort_values(ascending=False)\n\nplt.figure(figsize=(18, 5))\nplt.bar(range(len(class_counts_plot)), class_counts_plot.values, color='steelblue', edgecolor='white', linewidth=0.4)\nplt.xticks(\n    ticks=range(len(class_counts_plot)),\n    labels=class_counts_plot.index,\n    rotation=90, fontsize=7\n)\nplt.xlabel('Species')\nplt.ylabel('Number of Images')\nplt.title('Class Distribution — Indian Snake Species (filtered dataset)')\nplt.axhline(y=class_counts_plot.mean(), color='tomato', linestyle='--', linewidth=1.2, label=f'Mean: {class_counts_plot.mean():.0f}')\nplt.legend()\nplt.tight_layout()\nplt.show()\n\nprint(f\"Min images per class : {class_counts_plot.min()}\")\nprint(f\"Max images per class : {class_counts_plot.max()}\")\nprint(f\"Mean images per class: {class_counts_plot.mean():.1f}\")\nprint(f\"Std dev              : {class_counts_plot.std():.1f}\")\n","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:09.725617Z","iopub.execute_input":"2026-07-05T13:17:09.726212Z","iopub.status.idle":"2026-07-05T13:17:10.24102Z","shell.execute_reply.started":"2026-07-05T13:17:09.726181Z","shell.execute_reply":"2026-07-05T13:17:10.240172Z"},"papermill":{"duration":0.508509,"end_time":"2026-06-09T15:56:16.150564+00:00","exception":false,"start_time":"2026-06-09T15:56:15.642055+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"39310d8b","cell_type":"markdown","source":"**NO SNAKE CLASS**","metadata":{"papermill":{"duration":0.010267,"end_time":"2026-06-09T15:56:16.171747+00:00","exception":false,"start_time":"2026-06-09T15:56:16.16148+00:00","status":"completed"},"tags":[]}},{"id":"9f016533","cell_type":"code","source":"# =============================================\n# ADD NO-SNAKE CLASS TO DATAFRAME\n# =============================================\n\nNO_SNAKE_TRAIN_DIR = \"/kaggle/input/datasets/rounak221bs/snake-like-objects/no_snake_dataset/no_snake_training\"\n\nno_snake_files = [\n    f for f in os.listdir(NO_SNAKE_TRAIN_DIR)\n    if f.lower().endswith((\".jpg\", \".jpeg\", \".png\"))\n]\n\nno_snake_df = pd.DataFrame({\n    \"file_path\":    [os.path.join(NO_SNAKE_TRAIN_DIR, f) for f in no_snake_files],\n    \"binomial_name\": \"no_snake\",\n    \"class_id\":     -1   # remapped below\n})\n\ndf = pd.concat([df, no_snake_df], ignore_index=True)\n\nprint(f\"Snake classes        : {df[df['binomial_name'] != 'no_snake']['binomial_name'].nunique()}\")\nprint(f\"No-snake images      : {len(no_snake_df)}\")\nprint(f\"Total dataset size   : {len(df)}\")\nprint(f\"Total classes        : {df['binomial_name'].nunique()}\")","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:15.611119Z","iopub.execute_input":"2026-07-05T13:17:15.611656Z","iopub.status.idle":"2026-07-05T13:17:15.659438Z","shell.execute_reply.started":"2026-07-05T13:17:15.611627Z","shell.execute_reply":"2026-07-05T13:17:15.658627Z"},"papermill":{"duration":0.077662,"end_time":"2026-06-09T15:56:16.259523+00:00","exception":false,"start_time":"2026-06-09T15:56:16.181861+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"7cf3038f","cell_type":"code","source":"# __getitem__ is defined inside SnakeDataset below — this cell is intentionally left empty","metadata":{"execution":{"iopub.execute_input":"2026-06-09T15:56:16.281098Z","iopub.status.busy":"2026-06-09T15:56:16.280877Z","iopub.status.idle":"2026-06-09T15:56:16.284167Z","shell.execute_reply":"2026-06-09T15:56:16.283626Z"},"papermill":{"duration":0.015741,"end_time":"2026-06-09T15:56:16.285612+00:00","exception":false,"start_time":"2026-06-09T15:56:16.269871+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"6eb823b6","cell_type":"markdown","source":"### Train/Validation Split","metadata":{"papermill":{"duration":0.010434,"end_time":"2026-06-09T15:56:16.306642+00:00","exception":false,"start_time":"2026-06-09T15:56:16.296208+00:00","status":"completed"},"tags":[]}},{"id":"7432905d","cell_type":"code","source":"train_df, val_df = train_test_split(\n    df,\n    test_size=0.30,\n    stratify=df[\"class_id\"],\n    random_state=42\n)","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:20.856227Z","iopub.execute_input":"2026-07-05T13:17:20.856891Z","iopub.status.idle":"2026-07-05T13:17:20.872325Z","shell.execute_reply.started":"2026-07-05T13:17:20.856863Z","shell.execute_reply":"2026-07-05T13:17:20.87149Z"},"papermill":{"duration":0.027651,"end_time":"2026-06-09T15:56:16.344225+00:00","exception":false,"start_time":"2026-06-09T15:56:16.316574+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"9170393d","cell_type":"markdown","source":"### Fix Labels","metadata":{"papermill":{"duration":0.010074,"end_time":"2026-06-09T15:56:16.364603+00:00","exception":false,"start_time":"2026-06-09T15:56:16.354529+00:00","status":"completed"},"tags":[]}},{"id":"e23f4907","cell_type":"code","source":"unique_classes = sorted(train_df[\"class_id\"].unique())\n\nclass_to_idx = {cls: idx for idx, cls in enumerate(unique_classes)}\n\ntrain_df[\"class_id\"] = train_df[\"class_id\"].map(class_to_idx)\nval_df[\"class_id\"]   = val_df[\"class_id\"].map(class_to_idx)\n\nnum_classes = len(unique_classes)\n\nprint(\"Num classes:\", num_classes)","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:24.422015Z","iopub.execute_input":"2026-07-05T13:17:24.422663Z","iopub.status.idle":"2026-07-05T13:17:24.434155Z","shell.execute_reply.started":"2026-07-05T13:17:24.422632Z","shell.execute_reply":"2026-07-05T13:17:24.433316Z"},"papermill":{"duration":0.021483,"end_time":"2026-06-09T15:56:16.396177+00:00","exception":false,"start_time":"2026-06-09T15:56:16.374694+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"50970a81","cell_type":"markdown","source":"### Transforms","metadata":{"papermill":{"duration":0.009831,"end_time":"2026-06-09T15:56:16.416182+00:00","exception":false,"start_time":"2026-06-09T15:56:16.406351+00:00","status":"completed"},"tags":[]}},{"id":"4243359f","cell_type":"code","source":"train_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomCrop((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.ColorJitter(0.2, 0.2, 0.2, 0.1),\n    transforms.RandAugment(num_ops=2, magnitude=9),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.CenterCrop((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:28.339107Z","iopub.execute_input":"2026-07-05T13:17:28.339715Z","iopub.status.idle":"2026-07-05T13:17:28.345657Z","shell.execute_reply.started":"2026-07-05T13:17:28.339684Z","shell.execute_reply":"2026-07-05T13:17:28.344885Z"},"papermill":{"duration":0.017101,"end_time":"2026-06-09T15:56:16.443242+00:00","exception":false,"start_time":"2026-06-09T15:56:16.426141+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"ab94b3f9","cell_type":"markdown","source":"### Dataset Class","metadata":{"papermill":{"duration":0.010241,"end_time":"2026-06-09T15:56:16.463592+00:00","exception":false,"start_time":"2026-06-09T15:56:16.453351+00:00","status":"completed"},"tags":[]}},{"id":"56915c73","cell_type":"code","source":"Image.LOAD_TRUNCATED_IMAGES = True\n\nclass SnakeDataset(Dataset):\n\n    def __init__(self, df, root_dir, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.root_dir = root_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        try:\n            row = self.df.iloc[idx]\n            # no_snake images already have absolute paths\n            if row[\"binomial_name\"] == \"no_snake\":\n                img_path = row[\"file_path\"]\n            else:\n                img_path = os.path.join(self.root_dir, row[\"file_path\"])\n            image = Image.open(img_path).convert(\"RGB\")\n        except:\n            return self.__getitem__((idx + 1) % len(self.df))\n\n        label = self.df.iloc[idx][\"class_id\"]\n        if self.transform:\n            image = self.transform(image)\n        return image, label","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:31.526212Z","iopub.execute_input":"2026-07-05T13:17:31.526935Z","iopub.status.idle":"2026-07-05T13:17:31.533018Z","shell.execute_reply.started":"2026-07-05T13:17:31.526905Z","shell.execute_reply":"2026-07-05T13:17:31.532127Z"},"papermill":{"duration":0.018978,"end_time":"2026-06-09T15:56:16.49319+00:00","exception":false,"start_time":"2026-06-09T15:56:16.474212+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"cb55440c","cell_type":"markdown","source":"### DataLoaders","metadata":{"papermill":{"duration":0.010214,"end_time":"2026-06-09T15:56:16.513742+00:00","exception":false,"start_time":"2026-06-09T15:56:16.503528+00:00","status":"completed"},"tags":[]}},{"id":"766d842f","cell_type":"code","source":"train_dataset = SnakeDataset(train_df, TRAIN_IMG_DIR, train_transform)\nval_dataset   = SnakeDataset(val_df,   TRAIN_IMG_DIR, val_transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True,  num_workers=4, pin_memory=True)\nval_loader   = DataLoader(val_dataset,   batch_size=32, shuffle=False, num_workers=4, pin_memory=True)\n","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:34.853639Z","iopub.execute_input":"2026-07-05T13:17:34.854071Z","iopub.status.idle":"2026-07-05T13:17:34.86386Z","shell.execute_reply.started":"2026-07-05T13:17:34.854019Z","shell.execute_reply":"2026-07-05T13:17:34.863093Z"},"papermill":{"duration":0.020922,"end_time":"2026-06-09T15:56:16.546769+00:00","exception":false,"start_time":"2026-06-09T15:56:16.525847+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"83607167","cell_type":"markdown","source":"# Model","metadata":{"papermill":{"duration":0.010659,"end_time":"2026-06-09T15:56:16.567476+00:00","exception":false,"start_time":"2026-06-09T15:56:16.556817+00:00","status":"completed"},"tags":[]}},{"id":"c8bedc92","cell_type":"code","source":"# print(\"Available Vision Transformer Models: \")\n# timm.list_models(\"vit*\")","metadata":{"execution":{"iopub.execute_input":"2026-06-09T15:56:16.589525Z","iopub.status.busy":"2026-06-09T15:56:16.589162Z","iopub.status.idle":"2026-06-09T15:56:16.592471Z","shell.execute_reply":"2026-06-09T15:56:16.591832Z"},"papermill":{"duration":0.016185,"end_time":"2026-06-09T15:56:16.593857+00:00","exception":false,"start_time":"2026-06-09T15:56:16.577672+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"9df42131","cell_type":"code","source":"model = timm.create_model(\n    \"resnet50\",\n    pretrained=True,\n    num_classes=num_classes\n)\n\nif torch.cuda.device_count() > 1:\n    print(f\"Using {torch.cuda.device_count()} GPUs!\")\n    model = torch.nn.DataParallel(model)\n\nmodel = model.to(device)\n","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:38.444673Z","iopub.execute_input":"2026-07-05T13:17:38.445312Z","iopub.status.idle":"2026-07-05T13:17:42.112108Z","shell.execute_reply.started":"2026-07-05T13:17:38.445271Z","shell.execute_reply":"2026-07-05T13:17:42.111498Z"},"papermill":{"duration":37.270343,"end_time":"2026-06-09T15:56:53.874879+00:00","exception":false,"start_time":"2026-06-09T15:56:16.604536+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"f5389a2e","cell_type":"code","source":"# freeze/unfreeze logic moved after checkpoint load below","metadata":{"execution":{"iopub.execute_input":"2026-06-09T15:56:53.897194Z","iopub.status.busy":"2026-06-09T15:56:53.896944Z","iopub.status.idle":"2026-06-09T15:56:53.900653Z","shell.execute_reply":"2026-06-09T15:56:53.899885Z"},"papermill":{"duration":0.016198,"end_time":"2026-06-09T15:56:53.901983+00:00","exception":false,"start_time":"2026-06-09T15:56:53.885785+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"ed747a1b","cell_type":"markdown","source":"### Loss + Optimizer","metadata":{"papermill":{"duration":0.010029,"end_time":"2026-06-09T15:56:53.922475+00:00","exception":false,"start_time":"2026-06-09T15:56:53.912446+00:00","status":"completed"},"tags":[]}},{"id":"7c170d4a","cell_type":"code","source":"criterion = torch.nn.CrossEntropyLoss(label_smoothing=0.1)\nscaler    = torch.amp.GradScaler(\"cuda\")","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:46.654531Z","iopub.execute_input":"2026-07-05T13:17:46.654927Z","iopub.status.idle":"2026-07-05T13:17:46.658997Z","shell.execute_reply.started":"2026-07-05T13:17:46.654899Z","shell.execute_reply":"2026-07-05T13:17:46.65823Z"},"papermill":{"duration":0.016243,"end_time":"2026-06-09T15:56:53.949147+00:00","exception":false,"start_time":"2026-06-09T15:56:53.932904+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"98c0b443","cell_type":"code","source":"# import os\n\n# def file_exists(row):\n#     path = os.path.join(TRAIN_IMG_DIR, row[\"file_path\"])\n#     return os.path.exists(path)\n\n# df = df[df.apply(file_exists, axis=1)]\n\n# print(\"After filtering:\", len(df))","metadata":{"execution":{"iopub.execute_input":"2026-06-09T15:56:53.973229Z","iopub.status.busy":"2026-06-09T15:56:53.973023Z","iopub.status.idle":"2026-06-09T15:56:53.97623Z","shell.execute_reply":"2026-06-09T15:56:53.975678Z"},"papermill":{"duration":0.018195,"end_time":"2026-06-09T15:56:53.977598+00:00","exception":false,"start_time":"2026-06-09T15:56:53.959403+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"8c4cc6b4","cell_type":"code","source":"def save_checkpoint(epoch, batch_idx, model, optimizer, scheduler, best_acc):\n    torch.save({\n        \"epoch\": epoch,\n        \"batch_idx\": batch_idx,\n        \"model_state\": model.state_dict(),\n        \"optimizer_state\": optimizer.state_dict(),\n        \"scheduler_state\": scheduler.state_dict(),\n        \"best_acc\": best_acc\n    }, \"/kaggle/working/checkpoint.pth\")","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:53.043215Z","iopub.execute_input":"2026-07-05T13:17:53.043839Z","iopub.status.idle":"2026-07-05T13:17:53.048535Z","shell.execute_reply.started":"2026-07-05T13:17:53.043812Z","shell.execute_reply":"2026-07-05T13:17:53.047415Z"},"papermill":{"duration":0.016233,"end_time":"2026-06-09T15:56:54.004045+00:00","exception":false,"start_time":"2026-06-09T15:56:53.987812+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"1e394851","cell_type":"code","source":"start_epoch = 0\nstart_batch = 0\nbest_acc    = 0\ncheckpoint  = None\n\n# NOTE: the old checkpoint dataset (\"rounakkkhehehek/checkpoint\") was trained\n# on the ViT backbone and is NOT compatible with ResNet50 — do not load it here.\n# Once you have your own ResNet checkpoint saved as a Kaggle dataset, point\n# CHECKPOINT_PATH at it to resume training across sessions.\nCHECKPOINT_PATH = \"/kaggle/input/datasets/YOUR_USERNAME/resnet-checkpoint/checkpoint.pth\"\n\nif os.path.exists(CHECKPOINT_PATH):\n    checkpoint = torch.load(CHECKPOINT_PATH, weights_only=False)\n    model.load_state_dict(checkpoint[\"model_state\"])\n    start_epoch = checkpoint[\"epoch\"]\n    start_batch = checkpoint[\"batch_idx\"]\n    best_acc    = checkpoint[\"best_acc\"]\n    print(f\"Resuming from epoch {start_epoch}, batch {start_batch}\")\n\nbase_model = model.module if hasattr(model, \"module\") else model\n\n# =========================\n# FREEZE / UNFREEZE BASED ON LOADED EPOCH\n# ResNet needs far less warm-up than the ViT did, so we only freeze\n# the backbone for the first 2 epochs before unfreezing everything.\n# =========================\nFREEZE_EPOCHS = 2\n\nif start_epoch < FREEZE_EPOCHS:\n    print(\"Freezing backbone...\")\n    for param in model.parameters():\n        param.requires_grad = False\n    for param in base_model.fc.parameters():\n        param.requires_grad = True\nelse:\n    print(f\"Epoch {start_epoch} >= {FREEZE_EPOCHS}, unfreezing backbone...\")\n    for param in model.parameters():\n        param.requires_grad = True\n","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:17:57.008059Z","iopub.execute_input":"2026-07-05T13:17:57.008469Z","iopub.status.idle":"2026-07-05T13:17:57.016194Z","shell.execute_reply.started":"2026-07-05T13:17:57.00844Z","shell.execute_reply":"2026-07-05T13:17:57.01541Z"},"papermill":{"duration":20.36423,"end_time":"2026-06-09T15:57:14.378303+00:00","exception":false,"start_time":"2026-06-09T15:56:54.014073+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"78496f17","cell_type":"code","source":"# =========================\n# OPTIMIZER + SCHEDULER\n# Defined after checkpoint load so last_epoch is correct\n# ResNet trains much faster than the ViT did, so we use a higher LR\n# and a shorter schedule (EPOCHS below) to fit comfortably in the\n# T4 free-tier time budget.\n# =========================\nEPOCHS = 30\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\nif checkpoint is not None:\n    optimizer.load_state_dict(checkpoint[\"optimizer_state\"])\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=EPOCHS,\n    last_epoch=start_epoch - 1   # resumes LR from correct position\n)\n\n# Load optimizer+scheduler states if resuming\nif checkpoint is not None:\n    scheduler.load_state_dict(checkpoint[\"scheduler_state\"])\n","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:18:00.921464Z","iopub.execute_input":"2026-07-05T13:18:00.922073Z","iopub.status.idle":"2026-07-05T13:18:00.927809Z","shell.execute_reply.started":"2026-07-05T13:18:00.922044Z","shell.execute_reply":"2026-07-05T13:18:00.926926Z"},"papermill":{"duration":0.024094,"end_time":"2026-06-09T15:57:14.413154+00:00","exception":false,"start_time":"2026-06-09T15:57:14.38906+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"aea19d12","cell_type":"markdown","source":"### Training Loop","metadata":{"papermill":{"duration":0.010871,"end_time":"2026-06-09T15:57:14.43457+00:00","exception":false,"start_time":"2026-06-09T15:57:14.423699+00:00","status":"completed"},"tags":[]}},{"id":"b6f30d9e","cell_type":"code","source":"train_losses = []\nval_losses   = []\nval_accs     = []\nlr_history   = []\ngrad_norms   = []\n\n# =========================\n# TRAINING LOOP\n# =========================\nfor epoch in range(start_epoch, EPOCHS):\n\n    # Unfreeze backbone after FREEZE_EPOCHS\n    if epoch == FREEZE_EPOCHS:\n        for param in model.parameters():\n            param.requires_grad = True\n        print(\"Backbone unfrozen\")\n\n    model.train()\n    running_loss = 0\n\n    for batch_idx, (images, labels) in enumerate(train_loader):\n\n        # Resume support — skip already-trained batches\n        if epoch == start_epoch and batch_idx < start_batch:\n            continue\n\n        images = images.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad(set_to_none=True)\n\n        with torch.amp.autocast(\"cuda\"):\n            outputs = model(images)\n            loss    = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n\n        # Track gradient norm before clipping\n        total_norm = 0\n        for p in model.parameters():\n            if p.grad is not None:\n                total_norm += p.grad.data.norm(2).item() ** 2\n        grad_norms.append(total_norm ** 0.5)\n\n        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n        scaler.step(optimizer)\n        scaler.update()\n\n        running_loss += loss.item()\n\n        if batch_idx % 500 == 0 and batch_idx != 0:\n            save_checkpoint(epoch, batch_idx, model, optimizer, scheduler, best_acc)\n            print(f\"Checkpoint saved at batch {batch_idx}\")\n\n        if batch_idx % 100 == 0:\n            print(f\"Epoch {epoch+1} | Batch {batch_idx}/{len(train_loader)} | Loss {loss.item():.4f}\")\n\n    scheduler.step()\n    lr_history.append(scheduler.get_last_lr()[0])\n\n    num_batches_trained = batch_idx + 1 - (start_batch if epoch == start_epoch else 0)\n    epoch_loss = running_loss / num_batches_trained\n    train_losses.append(epoch_loss)\n\n    start_batch = 0  # reset so subsequent epochs run fully\n\n    # =========================\n    # VALIDATION\n    # =========================\n    model.eval()\n    all_preds, all_targets, all_probs = [], [], []\n    val_running_loss = 0\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images  = images.to(device)\n            labels  = labels.to(device)\n            outputs = model(images)\n\n            val_loss = criterion(outputs, labels)\n            val_running_loss += val_loss.item()\n\n            prob = torch.softmax(outputs, dim=1)\n            _, predicted = torch.max(outputs, 1)\n\n            all_preds.extend(predicted.cpu().numpy())\n            all_targets.extend(labels.cpu().numpy())\n            all_probs.extend(prob.cpu().numpy())\n\n    acc           = (np.array(all_preds) == np.array(all_targets)).mean()\n    val_epoch_loss = val_running_loss / len(val_loader)\n    val_accs.append(acc)\n    val_losses.append(val_epoch_loss)\n\n    print(f\"\\nEpoch {epoch+1} Completed\")\n    print(f\"  Train Loss : {epoch_loss:.4f}\")\n    print(f\"  Val Loss   : {val_epoch_loss:.4f}\")\n    print(f\"  Val Acc    : {acc:.4f}\\n\")\n\n    # Save best model\n    if acc > best_acc:\n        best_acc = acc\n        torch.save(model.state_dict(), \"/kaggle/working/best_model.pth\")\n        print(\"Best model saved!\")\n\n    # Save end-of-epoch checkpoint (epoch+1 so resume starts next epoch)\n    save_checkpoint(epoch + 1, 0, model, optimizer, scheduler, best_acc)\n","metadata":{"execution":{"iopub.status.busy":"2026-07-05T13:18:04.97734Z","iopub.execute_input":"2026-07-05T13:18:04.977735Z","iopub.status.idle":"2026-07-05T14:09:43.400833Z","shell.execute_reply.started":"2026-07-05T13:18:04.97771Z","shell.execute_reply":"2026-07-05T14:09:43.399741Z"},"papermill":{"duration":2657.349502,"end_time":"2026-06-09T16:41:31.794544+00:00","exception":false,"start_time":"2026-06-09T15:57:14.445042+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"ae4d9843","cell_type":"markdown","source":"# Save Model","metadata":{"papermill":{"duration":0.016175,"end_time":"2026-06-09T16:41:31.822619+00:00","exception":false,"start_time":"2026-06-09T16:41:31.806444+00:00","status":"completed"},"tags":[]}},{"id":"00c50836","cell_type":"code","source":"torch.save(model.state_dict(), \"/kaggle/working/snake_model.pth\")","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:09:59.456461Z","iopub.execute_input":"2026-07-05T14:09:59.45693Z","iopub.status.idle":"2026-07-05T14:09:59.592983Z","shell.execute_reply.started":"2026-07-05T14:09:59.456891Z","shell.execute_reply":"2026-07-05T14:09:59.592297Z"},"papermill":{"duration":1.76863,"end_time":"2026-06-09T16:41:33.602161+00:00","exception":false,"start_time":"2026-06-09T16:41:31.833531+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"d185b790","cell_type":"markdown","source":"# Visualizations\n## Group A — Training Curves","metadata":{"papermill":{"duration":0.012205,"end_time":"2026-06-09T16:41:33.627269+00:00","exception":false,"start_time":"2026-06-09T16:41:33.615064+00:00","status":"completed"},"tags":[]}},{"id":"5626c46f","cell_type":"markdown","source":"### 1. Train vs Val Loss","metadata":{"papermill":{"duration":0.011489,"end_time":"2026-06-09T16:41:33.650853+00:00","exception":false,"start_time":"2026-06-09T16:41:33.639364+00:00","status":"completed"},"tags":[]}},{"id":"8b8abbe1","cell_type":"code","source":"plt.figure(figsize=(10, 4))\nplt.plot(train_losses, marker='o', color='steelblue', label='Train Loss')\nplt.plot(val_losses,   marker='o', color='tomato',    label='Val Loss')\nplt.title(\"Train vs Validation Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/loss_curve.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:10:05.093998Z","iopub.execute_input":"2026-07-05T14:10:05.094856Z","iopub.status.idle":"2026-07-05T14:10:05.413205Z","shell.execute_reply.started":"2026-07-05T14:10:05.094828Z","shell.execute_reply":"2026-07-05T14:10:05.412323Z"},"papermill":{"duration":0.357379,"end_time":"2026-06-09T16:41:34.02269+00:00","exception":false,"start_time":"2026-06-09T16:41:33.665311+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"17c814ea","cell_type":"markdown","source":"### 2. Validation Accuracy","metadata":{"papermill":{"duration":0.013887,"end_time":"2026-06-09T16:41:34.048866+00:00","exception":false,"start_time":"2026-06-09T16:41:34.034979+00:00","status":"completed"},"tags":[]}},{"id":"39ed0ed5","cell_type":"code","source":"plt.figure(figsize=(10, 4))\nplt.plot(val_accs, marker='o', color='seagreen', label='Val Accuracy')\nplt.axhline(y=max(val_accs), color='gray', linestyle='--', linewidth=1,\n            label=f'Best: {max(val_accs):.4f}')\nplt.title(\"Validation Accuracy per Epoch\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/accuracy_curve.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:10:11.587438Z","iopub.execute_input":"2026-07-05T14:10:11.588289Z","iopub.status.idle":"2026-07-05T14:10:11.904524Z","shell.execute_reply.started":"2026-07-05T14:10:11.588221Z","shell.execute_reply":"2026-07-05T14:10:11.903716Z"},"papermill":{"duration":0.323437,"end_time":"2026-06-09T16:41:34.383647+00:00","exception":false,"start_time":"2026-06-09T16:41:34.06021+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"41a0cfb9","cell_type":"markdown","source":"### 3. Learning Rate Curve","metadata":{"papermill":{"duration":0.014929,"end_time":"2026-06-09T16:41:34.411282+00:00","exception":false,"start_time":"2026-06-09T16:41:34.396353+00:00","status":"completed"},"tags":[]}},{"id":"d9ca4eca","cell_type":"code","source":"plt.figure(figsize=(10, 4))\nplt.plot(lr_history, marker='o', color='darkorange', label='Learning Rate')\nplt.title(\"Learning Rate Schedule (Cosine Annealing)\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"LR\")\nplt.yscale('log')\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/lr_curve.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:10:23.541946Z","iopub.execute_input":"2026-07-05T14:10:23.542593Z","iopub.status.idle":"2026-07-05T14:10:24.106232Z","shell.execute_reply.started":"2026-07-05T14:10:23.542563Z","shell.execute_reply":"2026-07-05T14:10:24.10551Z"},"papermill":{"duration":0.472547,"end_time":"2026-06-09T16:41:34.896552+00:00","exception":false,"start_time":"2026-06-09T16:41:34.424005+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"ec84eb5d","cell_type":"markdown","source":"### 4. Gradient Norm per Batch","metadata":{"papermill":{"duration":0.013386,"end_time":"2026-06-09T16:41:34.924874+00:00","exception":false,"start_time":"2026-06-09T16:41:34.911488+00:00","status":"completed"},"tags":[]}},{"id":"744dcdc4","cell_type":"code","source":"plt.figure(figsize=(14, 4))\nplt.plot(grad_norms, color='purple', linewidth=0.7, alpha=0.8)\nplt.title(\"Gradient Norm per Batch\")\nplt.xlabel(\"Batch (across all epochs)\")\nplt.ylabel(\"L2 Norm\")\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/grad_norm.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:10:29.312526Z","iopub.execute_input":"2026-07-05T14:10:29.3133Z","iopub.status.idle":"2026-07-05T14:10:29.705584Z","shell.execute_reply.started":"2026-07-05T14:10:29.31327Z","shell.execute_reply":"2026-07-05T14:10:29.704778Z"},"papermill":{"duration":0.369439,"end_time":"2026-06-09T16:41:35.306996+00:00","exception":false,"start_time":"2026-06-09T16:41:34.937557+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"f4dc280a","cell_type":"markdown","source":"## Group B — Evaluation Metrics","metadata":{"papermill":{"duration":0.013412,"end_time":"2026-06-09T16:41:35.333835+00:00","exception":false,"start_time":"2026-06-09T16:41:35.320423+00:00","status":"completed"},"tags":[]}},{"id":"c9e11446","cell_type":"markdown","source":"### 5. Classification Report","metadata":{"papermill":{"duration":0.012919,"end_time":"2026-06-09T16:41:35.359558+00:00","exception":false,"start_time":"2026-06-09T16:41:35.346639+00:00","status":"completed"},"tags":[]}},{"id":"e79c96e1","cell_type":"code","source":"print(classification_report(all_targets, all_preds))","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:11:25.4286Z","iopub.execute_input":"2026-07-05T14:11:25.429556Z","iopub.status.idle":"2026-07-05T14:11:25.45311Z","shell.execute_reply.started":"2026-07-05T14:11:25.429524Z","shell.execute_reply":"2026-07-05T14:11:25.45242Z"},"papermill":{"duration":0.039919,"end_time":"2026-06-09T16:41:35.414056+00:00","exception":false,"start_time":"2026-06-09T16:41:35.374137+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"4b9dd15d","cell_type":"markdown","source":"### 6. Confusion Matrix","metadata":{"papermill":{"duration":0.012987,"end_time":"2026-06-09T16:41:35.440267+00:00","exception":false,"start_time":"2026-06-09T16:41:35.42728+00:00","status":"completed"},"tags":[]}},{"id":"c354a1e1","cell_type":"code","source":"cm = confusion_matrix(all_targets, all_preds)\nplt.figure(figsize=(14, 12))\nsns.heatmap(cm, annot=True, cmap=\"Blues\", fmt='d')\nplt.title(\"Confusion Matrix\")\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"Actual\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/confusion_matrix.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:12:39.514407Z","iopub.execute_input":"2026-07-05T14:12:39.514859Z","iopub.status.idle":"2026-07-05T14:12:45.565744Z","shell.execute_reply.started":"2026-07-05T14:12:39.514827Z","shell.execute_reply":"2026-07-05T14:12:45.564899Z"},"papermill":{"duration":1.38228,"end_time":"2026-06-09T16:41:36.835694+00:00","exception":false,"start_time":"2026-06-09T16:41:35.453414+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"bf08b767","cell_type":"markdown","source":"### 7. Per-Class Accuracy Bar Chart","metadata":{"papermill":{"duration":0.013916,"end_time":"2026-06-09T16:41:36.864086+00:00","exception":false,"start_time":"2026-06-09T16:41:36.85017+00:00","status":"completed"},"tags":[]}},{"id":"0cbbb946","cell_type":"code","source":"all_preds_arr   = np.array(all_preds)\nall_targets_arr = np.array(all_targets)\nn_cls = len(np.unique(all_targets_arr))\n\nper_class_acc = []\nfor c in range(n_cls):\n    mask = all_targets_arr == c\n    if mask.sum() > 0:\n        per_class_acc.append((all_preds_arr[mask] == c).mean())\n    else:\n        per_class_acc.append(0.0)\n\nplt.figure(figsize=(max(14, n_cls // 2), 5))\nplt.bar(range(n_cls), per_class_acc, color='steelblue', edgecolor='white', linewidth=0.4)\nplt.axhline(y=np.mean(per_class_acc), color='tomato', linestyle='--',\n            label=f'Mean: {np.mean(per_class_acc):.3f}')\nplt.title(\"Per-Class Accuracy\")\nplt.xlabel(\"Class Index\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/per_class_accuracy.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:04.902959Z","iopub.execute_input":"2026-07-05T14:13:04.903431Z","iopub.status.idle":"2026-07-05T14:13:05.406484Z","shell.execute_reply.started":"2026-07-05T14:13:04.903399Z","shell.execute_reply":"2026-07-05T14:13:05.40552Z"},"papermill":{"duration":0.538903,"end_time":"2026-06-09T16:41:37.418583+00:00","exception":false,"start_time":"2026-06-09T16:41:36.87968+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"55555a33","cell_type":"markdown","source":"### 8. Classification Report Heatmap","metadata":{"papermill":{"duration":0.016235,"end_time":"2026-06-09T16:41:37.451599+00:00","exception":false,"start_time":"2026-06-09T16:41:37.435364+00:00","status":"completed"},"tags":[]}},{"id":"38ece265","cell_type":"code","source":"from sklearn.metrics import classification_report\nreport = classification_report(all_targets, all_preds, output_dict=True)\nreport_df = pd.DataFrame(report).transpose()\nreport_df = report_df.drop(columns=[\"support\"], errors=\"ignore\")\nreport_df = report_df[:-3]  # drop macro/weighted avg rows for clarity\n\nplt.figure(figsize=(8, max(8, len(report_df) // 3)))\nsns.heatmap(report_df.astype(float), annot=True, fmt=\".2f\", cmap=\"YlGnBu\",\n            linewidths=0.5, cbar=True)\nplt.title(\"Classification Report Heatmap (Precision / Recall / F1)\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/classification_report_heatmap.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:08.850576Z","iopub.execute_input":"2026-07-05T14:13:08.851242Z","iopub.status.idle":"2026-07-05T14:13:10.04059Z","shell.execute_reply.started":"2026-07-05T14:13:08.851209Z","shell.execute_reply":"2026-07-05T14:13:10.03973Z"},"papermill":{"duration":1.390132,"end_time":"2026-06-09T16:41:38.855893+00:00","exception":false,"start_time":"2026-06-09T16:41:37.465761+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"bf5509d3","cell_type":"markdown","source":"### 9. ROC Curve","metadata":{"papermill":{"duration":0.016745,"end_time":"2026-06-09T16:41:38.890455+00:00","exception":false,"start_time":"2026-06-09T16:41:38.87371+00:00","status":"completed"},"tags":[]}},{"id":"f8e61065","cell_type":"code","source":"n_classes    = len(np.unique(all_targets))\ny_true_bin   = label_binarize(all_targets, classes=range(n_classes))\ny_score      = np.array(all_probs)\n\nplt.figure(figsize=(10, 6))\nfor i in range(n_classes):\n    fpr, tpr, _ = roc_curve(y_true_bin[:, i], y_score[:, i])\n    roc_auc = auc(fpr, tpr)\n    plt.plot(fpr, tpr, linewidth=0.6, alpha=0.5, label=f\"Class {i} (AUC={roc_auc:.2f})\")\n\nplt.plot([0, 1], [0, 1], 'k--', linewidth=1)\nplt.title(\"ROC Curve (per class)\")\nplt.xlabel(\"False Positive Rate\")\nplt.ylabel(\"True Positive Rate\")\nplt.legend(loc=\"lower right\", fontsize=6, ncol=3)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/roc_curve.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:14.520001Z","iopub.execute_input":"2026-07-05T14:13:14.520753Z","iopub.status.idle":"2026-07-05T14:13:15.638096Z","shell.execute_reply.started":"2026-07-05T14:13:14.520721Z","shell.execute_reply":"2026-07-05T14:13:15.637096Z"},"papermill":{"duration":1.297508,"end_time":"2026-06-09T16:41:40.205216+00:00","exception":false,"start_time":"2026-06-09T16:41:38.907708+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"b24f6a35","cell_type":"markdown","source":"### 10. Precision-Recall Curve","metadata":{"papermill":{"duration":0.018457,"end_time":"2026-06-09T16:41:40.243425+00:00","exception":false,"start_time":"2026-06-09T16:41:40.224968+00:00","status":"completed"},"tags":[]}},{"id":"1065bbac","cell_type":"code","source":"plt.figure(figsize=(10, 6))\nfor i in range(n_classes):\n    prec, rec, _ = precision_recall_curve(y_true_bin[:, i], y_score[:, i])\n    plt.plot(rec, prec, linewidth=0.6, alpha=0.5, label=f\"Class {i}\")\n\nplt.title(\"Precision-Recall Curve (per class)\")\nplt.xlabel(\"Recall\")\nplt.ylabel(\"Precision\")\nplt.legend(loc=\"lower left\", fontsize=6, ncol=3)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/pr_curve.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:19.502332Z","iopub.execute_input":"2026-07-05T14:13:19.503082Z","iopub.status.idle":"2026-07-05T14:13:20.639558Z","shell.execute_reply.started":"2026-07-05T14:13:19.503049Z","shell.execute_reply":"2026-07-05T14:13:20.638919Z"},"papermill":{"duration":1.513491,"end_time":"2026-06-09T16:41:41.774808+00:00","exception":false,"start_time":"2026-06-09T16:41:40.261317+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"a0d33e23","cell_type":"markdown","source":"### 11. Top-K Accuracy (Top-1, Top-3, Top-5)","metadata":{"papermill":{"duration":0.017329,"end_time":"2026-06-09T16:41:41.813907+00:00","exception":false,"start_time":"2026-06-09T16:41:41.796578+00:00","status":"completed"},"tags":[]}},{"id":"947ddca0","cell_type":"code","source":"y_score_t = np.array(all_probs)\ny_true_t  = np.array(all_targets)\n\ndef topk_acc(y_true, y_score, k):\n    topk = np.argsort(y_score, axis=1)[:, -k:]\n    return np.mean([y_true[i] in topk[i] for i in range(len(y_true))])\n\nks    = [1, 3, 5]\naccs  = [topk_acc(y_true_t, y_score_t, k) for k in ks]\n\nplt.figure(figsize=(6, 4))\nbars = plt.bar([f\"Top-{k}\" for k in ks], accs, color=['steelblue','seagreen','tomato'])\nfor bar, acc in zip(bars, accs):\n    plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.005,\n             f\"{acc:.4f}\", ha='center', fontsize=11)\nplt.title(\"Top-K Accuracy\")\nplt.ylabel(\"Accuracy\")\nplt.ylim(0, 1.05)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/topk_accuracy.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:24.630413Z","iopub.execute_input":"2026-07-05T14:13:24.631029Z","iopub.status.idle":"2026-07-05T14:13:24.89054Z","shell.execute_reply.started":"2026-07-05T14:13:24.630996Z","shell.execute_reply":"2026-07-05T14:13:24.889891Z"},"papermill":{"duration":0.291379,"end_time":"2026-06-09T16:41:42.122799+00:00","exception":false,"start_time":"2026-06-09T16:41:41.83142+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"6872dba8","cell_type":"markdown","source":"### 12. Confidence Score Distribution (Correct vs Incorrect)","metadata":{"papermill":{"duration":0.023631,"end_time":"2026-06-09T16:41:42.166307+00:00","exception":false,"start_time":"2026-06-09T16:41:42.142676+00:00","status":"completed"},"tags":[]}},{"id":"83f8c662","cell_type":"code","source":"confidences  = np.max(all_probs, axis=1)\ncorrect_mask = np.array(all_preds) == np.array(all_targets)\n\nplt.figure(figsize=(10, 4))\nplt.hist(confidences[correct_mask],  bins=40, alpha=0.6, color='seagreen', label='Correct')\nplt.hist(confidences[~correct_mask], bins=40, alpha=0.6, color='tomato',   label='Incorrect')\nplt.title(\"Confidence Distribution — Correct vs Incorrect\")\nplt.xlabel(\"Confidence\")\nplt.ylabel(\"Count\")\nplt.legend()\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/confidence_dist.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:30.541999Z","iopub.execute_input":"2026-07-05T14:13:30.542736Z","iopub.status.idle":"2026-07-05T14:13:30.96204Z","shell.execute_reply.started":"2026-07-05T14:13:30.542702Z","shell.execute_reply":"2026-07-05T14:13:30.961036Z"},"papermill":{"duration":0.468126,"end_time":"2026-06-09T16:41:42.6523+00:00","exception":false,"start_time":"2026-06-09T16:41:42.184174+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"d458bc3f","cell_type":"markdown","source":"### 13. Calibration Curve (Reliability Diagram)","metadata":{"papermill":{"duration":0.022971,"end_time":"2026-06-09T16:41:42.694968+00:00","exception":false,"start_time":"2026-06-09T16:41:42.671997+00:00","status":"completed"},"tags":[]}},{"id":"0e61e3eb","cell_type":"code","source":"from sklearn.calibration import calibration_curve\n\n# Use max confidence as the probability for the predicted class\nconfidences  = np.max(all_probs, axis=1)\ncorrect_mask = (np.array(all_preds) == np.array(all_targets)).astype(int)\n\nfraction_of_positives, mean_predicted_value = calibration_curve(\n    correct_mask, confidences, n_bins=10\n)\n\nplt.figure(figsize=(7, 5))\nplt.plot(mean_predicted_value, fraction_of_positives, marker='o',\n         color='steelblue', label='Model')\nplt.plot([0, 1], [0, 1], 'k--', label='Perfect calibration')\nplt.title(\"Calibration Curve (Reliability Diagram)\")\nplt.xlabel(\"Mean Predicted Confidence\")\nplt.ylabel(\"Fraction Correct\")\nplt.legend()\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/calibration_curve.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:37.468022Z","iopub.execute_input":"2026-07-05T14:13:37.468475Z","iopub.status.idle":"2026-07-05T14:13:37.779551Z","shell.execute_reply.started":"2026-07-05T14:13:37.468444Z","shell.execute_reply":"2026-07-05T14:13:37.778702Z"},"papermill":{"duration":0.360332,"end_time":"2026-06-09T16:41:43.072782+00:00","exception":false,"start_time":"2026-06-09T16:41:42.71245+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"97d9462a","cell_type":"markdown","source":"## Group C — Error Analysis","metadata":{"papermill":{"duration":0.018682,"end_time":"2026-06-09T16:41:43.111684+00:00","exception":false,"start_time":"2026-06-09T16:41:43.093002+00:00","status":"completed"},"tags":[]}},{"id":"4ce55023","cell_type":"markdown","source":"### 14. Most Confused Class Pairs","metadata":{"papermill":{"duration":0.017456,"end_time":"2026-06-09T16:41:43.150093+00:00","exception":false,"start_time":"2026-06-09T16:41:43.132637+00:00","status":"completed"},"tags":[]}},{"id":"6d89b42a","cell_type":"code","source":"cm = confusion_matrix(all_targets, all_preds)\nnp.fill_diagonal(cm, 0)  # zero out correct predictions\n\n# Top 15 most confused pairs\nconfused = []\nfor i in range(cm.shape[0]):\n    for j in range(cm.shape[1]):\n        if cm[i, j] > 0:\n            confused.append((cm[i, j], i, j))\nconfused.sort(reverse=True)\ntop_confused = confused[:15]\n\nlabels_pair = [f\"{t}→{p}\" for _, t, p in top_confused]\ncounts      = [c for c, _, _ in top_confused]\n\nplt.figure(figsize=(12, 5))\nplt.barh(labels_pair[::-1], counts[::-1], color='salmon')\nplt.title(\"Top 15 Most Confused Class Pairs (True → Predicted)\")\nplt.xlabel(\"Count\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/confused_pairs.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:41.64013Z","iopub.execute_input":"2026-07-05T14:13:41.640847Z","iopub.status.idle":"2026-07-05T14:13:42.010238Z","shell.execute_reply.started":"2026-07-05T14:13:41.640812Z","shell.execute_reply":"2026-07-05T14:13:42.009532Z"},"papermill":{"duration":0.426457,"end_time":"2026-06-09T16:41:43.594084+00:00","exception":false,"start_time":"2026-06-09T16:41:43.167627+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"13bb2aca","cell_type":"markdown","source":"### 15. High Confidence Wrong Predictions","metadata":{"papermill":{"duration":0.019954,"end_time":"2026-06-09T16:41:43.637687+00:00","exception":false,"start_time":"2026-06-09T16:41:43.617733+00:00","status":"completed"},"tags":[]}},{"id":"6c1a8900","cell_type":"code","source":"all_preds_arr   = np.array(all_preds)\nall_targets_arr = np.array(all_targets)\nall_probs_arr   = np.array(all_probs)\nconfidences     = np.max(all_probs_arr, axis=1)\n\nwrong_mask      = all_preds_arr != all_targets_arr\nhigh_conf_wrong = np.where(wrong_mask & (confidences > 0.9))[0]\n\nprint(f\"High confidence (>0.9) wrong predictions: {len(high_conf_wrong)}\")\nif len(high_conf_wrong) > 0:\n    show_n = min(10, len(high_conf_wrong))\n    fig, axes = plt.subplots(2, 5, figsize=(15, 6))\n    axes = axes.flatten()\n    for i, idx in enumerate(high_conf_wrong[:show_n]):\n        row = val_df.iloc[idx]\n        if row[\"binomial_name\"] == \"no_snake\":\n            img_path = row[\"file_path\"]\n        else:\n            img_path = os.path.join(TRAIN_IMG_DIR, row[\"file_path\"])\n        try:\n            img = Image.open(img_path).convert(\"RGB\")\n            axes[i].imshow(img)\n            axes[i].set_title(f\"True:{all_targets_arr[idx]}\\nPred:{all_preds_arr[idx]}\\nConf:{confidences[idx]:.2f}\",\n                              fontsize=8)\n        except:\n            axes[i].set_title(\"Load error\")\n        axes[i].axis(\"off\")\n    plt.suptitle(\"High Confidence Wrong Predictions (conf > 0.9)\")\n    plt.tight_layout()\n    plt.savefig(\"/kaggle/working/high_conf_wrong.png\", dpi=150)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:45.533931Z","iopub.execute_input":"2026-07-05T14:13:45.534718Z","iopub.status.idle":"2026-07-05T14:13:47.912774Z","shell.execute_reply.started":"2026-07-05T14:13:45.534687Z","shell.execute_reply":"2026-07-05T14:13:47.910836Z"},"papermill":{"duration":1.79657,"end_time":"2026-06-09T16:41:45.453215+00:00","exception":false,"start_time":"2026-06-09T16:41:43.656645+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"27ab7f7e","cell_type":"markdown","source":"### 16. Low Confidence Correct Predictions","metadata":{"papermill":{"duration":0.031223,"end_time":"2026-06-09T16:41:45.51697+00:00","exception":false,"start_time":"2026-06-09T16:41:45.485747+00:00","status":"completed"},"tags":[]}},{"id":"1cbc4670","cell_type":"code","source":"correct_mask     = all_preds_arr == all_targets_arr\nlow_conf_correct = np.where(correct_mask & (confidences < 0.5))[0]\n\nprint(f\"Low confidence (<0.5) correct predictions: {len(low_conf_correct)}\")\nif len(low_conf_correct) > 0:\n    show_n = min(10, len(low_conf_correct))\n    fig, axes = plt.subplots(2, 5, figsize=(15, 6))\n    axes = axes.flatten()\n    for i, idx in enumerate(low_conf_correct[:show_n]):\n        row = val_df.iloc[idx]\n        if row[\"binomial_name\"] == \"no_snake\":\n            img_path = row[\"file_path\"]\n        else:\n            img_path = os.path.join(TRAIN_IMG_DIR, row[\"file_path\"])\n        try:\n            img = Image.open(img_path).convert(\"RGB\")\n            axes[i].imshow(img)\n            axes[i].set_title(f\"True:{all_targets_arr[idx]}\\nPred:{all_preds_arr[idx]}\\nConf:{confidences[idx]:.2f}\",\n                              fontsize=8)\n        except:\n            axes[i].set_title(\"Load error\")\n        axes[i].axis(\"off\")\n    plt.suptitle(\"Low Confidence Correct Predictions (conf < 0.5)\")\n    plt.tight_layout()\n    plt.savefig(\"/kaggle/working/low_conf_correct.png\", dpi=150)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:13:59.323583Z","iopub.execute_input":"2026-07-05T14:13:59.324361Z","iopub.status.idle":"2026-07-05T14:14:01.191432Z","shell.execute_reply.started":"2026-07-05T14:13:59.324328Z","shell.execute_reply":"2026-07-05T14:14:01.190346Z"},"papermill":{"duration":1.867841,"end_time":"2026-06-09T16:41:47.414995+00:00","exception":false,"start_time":"2026-06-09T16:41:45.547154+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"bede488c","cell_type":"markdown","source":"### 17. Per-Class Confidence Box Plot","metadata":{"papermill":{"duration":0.046681,"end_time":"2026-06-09T16:41:47.530828+00:00","exception":false,"start_time":"2026-06-09T16:41:47.484147+00:00","status":"completed"},"tags":[]}},{"id":"cd0705fa","cell_type":"code","source":"n_cls       = len(np.unique(all_targets_arr))\nclass_confs = [confidences[all_targets_arr == c] for c in range(n_cls)]\n\nplt.figure(figsize=(max(14, n_cls // 2), 5))\nplt.boxplot(class_confs, patch_artist=True,\n            boxprops=dict(facecolor='lightblue'),\n            medianprops=dict(color='tomato', linewidth=2))\nplt.title(\"Per-Class Confidence Distribution\")\nplt.xlabel(\"Class Index\")\nplt.ylabel(\"Confidence\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/per_class_conf_boxplot.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:14:21.447428Z","iopub.execute_input":"2026-07-05T14:14:21.447728Z","iopub.status.idle":"2026-07-05T14:14:22.331785Z","shell.execute_reply.started":"2026-07-05T14:14:21.447703Z","shell.execute_reply":"2026-07-05T14:14:22.330977Z"},"papermill":{"duration":1.020173,"end_time":"2026-06-09T16:41:48.59535+00:00","exception":false,"start_time":"2026-06-09T16:41:47.575177+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"5c6f1998","cell_type":"markdown","source":"## Group D — Dataset Analysis","metadata":{"papermill":{"duration":0.043615,"end_time":"2026-06-09T16:41:48.686838+00:00","exception":false,"start_time":"2026-06-09T16:41:48.643223+00:00","status":"completed"},"tags":[]}},{"id":"eb2efe6e","cell_type":"markdown","source":"### 18. No-Snake vs Snake Balance","metadata":{"papermill":{"duration":0.042408,"end_time":"2026-06-09T16:41:48.77602+00:00","exception":false,"start_time":"2026-06-09T16:41:48.733612+00:00","status":"completed"},"tags":[]}},{"id":"04b550e0","cell_type":"code","source":"snake_count    = (df['binomial_name'] != 'no_snake').sum()\nno_snake_count = (df['binomial_name'] == 'no_snake').sum()\n\nplt.figure(figsize=(5, 5))\nplt.pie([snake_count, no_snake_count],\n        labels=[f'Snake\\n({snake_count})', f'No-Snake\\n({no_snake_count})'],\n        colors=['steelblue', 'tomato'], autopct='%1.1f%%', startangle=90)\nplt.title(\"Snake vs No-Snake Class Balance\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/class_balance_pie.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:14:25.222723Z","iopub.execute_input":"2026-07-05T14:14:25.223221Z","iopub.status.idle":"2026-07-05T14:14:25.379021Z","shell.execute_reply.started":"2026-07-05T14:14:25.223192Z","shell.execute_reply":"2026-07-05T14:14:25.378146Z"},"papermill":{"duration":0.220144,"end_time":"2026-06-09T16:41:49.041089+00:00","exception":false,"start_time":"2026-06-09T16:41:48.820945+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"a0999083","cell_type":"markdown","source":"### 19. Train vs Val Class Distribution","metadata":{"papermill":{"duration":0.041979,"end_time":"2026-06-09T16:41:49.1252+00:00","exception":false,"start_time":"2026-06-09T16:41:49.083221+00:00","status":"completed"},"tags":[]}},{"id":"996731a7","cell_type":"code","source":"train_counts = train_df['class_id'].value_counts().sort_index()\nval_counts   = val_df['class_id'].value_counts().sort_index()\n\nx = np.arange(len(train_counts))\nwidth = 0.4\n\nplt.figure(figsize=(max(14, len(x) // 2), 5))\nplt.bar(x - width/2, train_counts.values, width, label='Train', color='steelblue', alpha=0.8)\nplt.bar(x + width/2, val_counts.values,   width, label='Val',   color='tomato',    alpha=0.8)\nplt.title(\"Train vs Val Class Distribution\")\nplt.xlabel(\"Class Index\")\nplt.ylabel(\"Count\")\nplt.legend()\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/train_val_dist.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:14:29.908197Z","iopub.execute_input":"2026-07-05T14:14:29.909063Z","iopub.status.idle":"2026-07-05T14:14:30.499165Z","shell.execute_reply.started":"2026-07-05T14:14:29.909029Z","shell.execute_reply":"2026-07-05T14:14:30.497897Z"},"papermill":{"duration":0.701064,"end_time":"2026-06-09T16:41:49.869445+00:00","exception":false,"start_time":"2026-06-09T16:41:49.168381+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"3ffa7cb7","cell_type":"markdown","source":"## Group E — Feature Embeddings","metadata":{"papermill":{"duration":0.043503,"end_time":"2026-06-09T16:41:49.958675+00:00","exception":false,"start_time":"2026-06-09T16:41:49.915172+00:00","status":"completed"},"tags":[]}},{"id":"d3f27442","cell_type":"markdown","source":"### 20. PCA of Feature Embeddings","metadata":{"papermill":{"duration":0.042602,"end_time":"2026-06-09T16:41:50.046706+00:00","exception":false,"start_time":"2026-06-09T16:41:50.004104+00:00","status":"completed"},"tags":[]}},{"id":"a110cdd9","cell_type":"code","source":"# Extract features from validation set (sample 2000 for speed)\nmodel.eval()\nall_feats, all_lbls = [], []\nsample_limit = 2000\ncount = 0\n\nfeat_model = model.module if hasattr(model, 'module') else model\n\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images = images.to(device)\n        # Get features before classification head\n        feats = feat_model.forward_features(images)\n        # ResNet forward_features returns (B, C, H, W) feature maps -> global average pool.\n        # (Kept the ViT CLS-token branch too, in case you swap backbones again later.)\n        if feats.dim() == 4:\n            feats = feats.mean(dim=[2, 3])\n        elif feats.dim() == 3:\n            feats = feats[:, 0, :]\n        all_feats.append(feats.cpu().numpy())\n        all_lbls.extend(labels.numpy())\n        count += len(labels)\n        if count >= sample_limit:\n            break\n\nall_feats = np.concatenate(all_feats, axis=0)[:sample_limit]\nall_lbls  = np.array(all_lbls)[:sample_limit]\n\n# PCA\npca  = PCA(n_components=2)\nemb  = pca.fit_transform(all_feats)\n\nplt.figure(figsize=(10, 8))\nscatter = plt.scatter(emb[:, 0], emb[:, 1], c=all_lbls, cmap='tab20',\n                      s=5, alpha=0.6)\nplt.colorbar(scatter, label='Class')\nplt.title(f\"PCA of Feature Embeddings (n={len(all_lbls)})\")\nplt.xlabel(f\"PC1 ({pca.explained_variance_ratio_[0]*100:.1f}%)\")\nplt.ylabel(f\"PC2 ({pca.explained_variance_ratio_[1]*100:.1f}%)\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/pca_embeddings.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:14:33.685677Z","iopub.execute_input":"2026-07-05T14:14:33.686482Z","iopub.status.idle":"2026-07-05T14:14:43.25769Z","shell.execute_reply.started":"2026-07-05T14:14:33.686451Z","shell.execute_reply":"2026-07-05T14:14:43.256892Z"},"papermill":{"duration":721.149552,"end_time":"2026-06-09T16:53:51.246552+00:00","exception":false,"start_time":"2026-06-09T16:41:50.097+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"4a13cf6c","cell_type":"markdown","source":"### 21. t-SNE of Feature Embeddings","metadata":{"papermill":{"duration":0.044069,"end_time":"2026-06-09T16:53:51.332328+00:00","exception":false,"start_time":"2026-06-09T16:53:51.288259+00:00","status":"completed"},"tags":[]}},{"id":"26505e9b","cell_type":"code","source":"tsne = TSNE(n_components=2, perplexity=30, random_state=42, n_iter=1000)\nemb_tsne = tsne.fit_transform(all_feats)\n\nplt.figure(figsize=(10, 8))\nscatter = plt.scatter(emb_tsne[:, 0], emb_tsne[:, 1], c=all_lbls,\n                      cmap='tab20', s=5, alpha=0.6)\nplt.colorbar(scatter, label='Class')\nplt.title(f\"t-SNE of Feature Embeddings (n={len(all_lbls)})\")\nplt.xlabel(\"t-SNE 1\")\nplt.ylabel(\"t-SNE 2\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/tsne_embeddings.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:14:51.750355Z","iopub.execute_input":"2026-07-05T14:14:51.750661Z","iopub.status.idle":"2026-07-05T14:15:01.754092Z","shell.execute_reply.started":"2026-07-05T14:14:51.750632Z","shell.execute_reply":"2026-07-05T14:15:01.753497Z"},"papermill":{"duration":8.227434,"end_time":"2026-06-09T16:53:59.603643+00:00","exception":false,"start_time":"2026-06-09T16:53:51.376209+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"b80b80b2","cell_type":"markdown","source":"### 22. PCA Explained Variance","metadata":{"papermill":{"duration":0.044407,"end_time":"2026-06-09T16:53:59.697913+00:00","exception":false,"start_time":"2026-06-09T16:53:59.653506+00:00","status":"completed"},"tags":[]}},{"id":"8f916609","cell_type":"code","source":"pca_full = PCA(n_components=50)\npca_full.fit(all_feats)\n\ncumvar = np.cumsum(pca_full.explained_variance_ratio_) * 100\n\nplt.figure(figsize=(10, 4))\nplt.plot(range(1, len(cumvar)+1), cumvar, marker='o', markersize=3, color='steelblue')\nplt.axhline(y=90, color='tomato', linestyle='--', label='90% variance')\nplt.title(\"PCA Cumulative Explained Variance\")\nplt.xlabel(\"Number of Components\")\nplt.ylabel(\"Cumulative Explained Variance (%)\")\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/pca_variance.png\", dpi=150)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:15:05.734321Z","iopub.execute_input":"2026-07-05T14:15:05.735112Z","iopub.status.idle":"2026-07-05T14:15:06.897993Z","shell.execute_reply.started":"2026-07-05T14:15:05.735081Z","shell.execute_reply":"2026-07-05T14:15:06.897233Z"},"papermill":{"duration":1.016124,"end_time":"2026-06-09T16:54:00.758364+00:00","exception":false,"start_time":"2026-06-09T16:53:59.74224+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"435fe713","cell_type":"markdown","source":"# Save Model","metadata":{"papermill":{"duration":0.044837,"end_time":"2026-06-09T16:54:00.847927+00:00","exception":false,"start_time":"2026-06-09T16:54:00.80309+00:00","status":"completed"},"tags":[]}},{"id":"2cd27285","cell_type":"code","source":"torch.save(model.state_dict(), \"/kaggle/working/snake_model.pth\")\nprint(\"Model saved.\")","metadata":{"execution":{"iopub.status.busy":"2026-07-05T14:15:15.614478Z","iopub.execute_input":"2026-07-05T14:15:15.615006Z","iopub.status.idle":"2026-07-05T14:15:15.824514Z","shell.execute_reply.started":"2026-07-05T14:15:15.614977Z","shell.execute_reply":"2026-07-05T14:15:15.82359Z"},"papermill":{"duration":2.281236,"end_time":"2026-06-09T16:54:03.173108+00:00","exception":false,"start_time":"2026-06-09T16:54:00.891872+00:00","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null}]}