{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.13.15","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":"markdown","source":"# Inference with my own trained weights: 2-model ResNeXt50 ensemble + flip TTA (384 px)\n\nThis notebook predicts the test images by averaging **two models that I trained myself**, both ResNeXt50, fold 0, 384 px, 8 epochs:\n1. The **baseline** model (plain cross-entropy), and\n2. The **label smoothing** model (Modification A).\n\nIt also uses flip test-time augmentation (TTA). It does not use any of the winners' released weights.\n\n**Result:** Private score **0.8857** an Public score **0.8851**. The log of the committed run shows `Models loaded: 2` and `TTA used: True | models: 2`.\n\n| Version | Private LB | Public LB |\n|---|---|---|\n| Baseline, no TTA | 0.8742 | 0.8706 |\n| Label smoothing, no TTA | 0.8779 | 0.8792 |\n| Label smoothing + flip TTA | 0.8816 | 0.8876 |\n| **Baseline + label smoothing, averaged, + flip TTA (this notebook)** | **0.8857** | **0.8851** |\n\nThe ensemble gave the best private score so far (+0.0041 over label smoothing with TTA alone). The public score was slightly lower (-0.0025). Public and private are different parts of the test set, so a small drop on one split next to a gain on the other is within what I would expect from noise. I therefore claim a gain on the private split only, and \"about even\" on the public split. These are single submissions, not repeated experiments.\n\n**How it works:** Both models are loaded. For every test image there are four views (original, horizontal flip, vertical flip, transpose) and two models, so eight sets of class probabilities. They are averaged with equal weight, and the class with the highest average probability is submitted. This is the same idea as the winners' ensemble, where the ResNeXt and ViT probabilities are averaged, but on a much smaller scale: two models from one fold, against the winners' 12 models.\n\n**Rest of the code:** The same as `inference-own-resnext-384` (resize to 384 x 384, normalise, `argmax`), with a loop over the two weight files. Both weight paths are set in `WEIGHTS_PATHS`.\n\n**Engineering note:** My first submission from this notebook scored exactly the same as an earlier notebook (0.8779 / 0.8792), which was suspicious. I checked the version and log behind each submission, and the log of the version that scored 0.8857 / 0.8851 confirmed the two-model ensemble.\n\n**Related notebooks:**\n- Baseline weights: https://www.kaggle.com/code/ananyaamadhursharma/inference-own-resnext-384\n- Label smoothing weights, with TTA: https://www.kaggle.com/code/ananyaamadhursharma/inference-own-resnext-384-labelsmooth-tta\n- Training notebooks: https://www.kaggle.com/code/ananyaamadhursharma/train-label-smoothing-384 and Cassava Leaf Disease ResNeXt50 https://www.kaggle.com/code/ananyaamadhursharma/cassava-leaf-disease-resnext50\n- Full winning ensemble: https://www.kaggle.com/code/ananyaamadhursharma/final-version-inference","metadata":{}},{"cell_type":"code","source":"import os\n\nprint(\"Input folders:\", os.listdir(\"/kaggle/input\"))\nprint()\nfor root, dirs, files in os.walk(\"/kaggle/input\"):\n    for f in files:\n        if f.endswith(\".pth\"):\n            print(\"WEIGHTS:\", os.path.join(root, f))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-10-14T19:38:24.569881Z","iopub.execute_input":"2026-10-14T19:38:24.570225Z","iopub.status.idle":"2026-10-14T19:38:49.087182Z","shell.execute_reply.started":"2026-10-14T19:38:24.570195Z","shell.execute_reply":"2026-10-14T19:38:49.086139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Ensemble of my two ResNeXt50 models (baseline + label smoothing) + flip TTA\n# ====================================================\nimport os, sys\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader, Dataset\nfrom albumentations import Compose, Normalize, Resize\nfrom albumentations.pytorch import ToTensorV2\n\nsys.path.append('../input/pytorch-image-models/pytorch-image-models-master')\nimport timm\n\n# ---------------- settings ----------------\nWEIGHTS_PATHS = [\n    \"/kaggle/input/datasets/ananyaamadhursharma/cassava-resnext-fold0-384-baseline/resnext50_32x4d_fold0_best.pth\",\n    \"/kaggle/input/datasets/ananyaamadhursharma/cassava-resnext-fold0-384-labelsmooth/resnext50_32x4d_fold0_best.pth\",\n]\nSIZE = 384\nUSE_TTA = True\n\ncandidates = [\n    \"../input/cassava-leaf-disease-classification/test_images/\",\n    \"/kaggle/input/cassava-leaf-disease-classification/test_images/\",\n    \"/kaggle/input/competitions/cassava-leaf-disease-classification/test_images/\",\n]\nTEST_DIR = next(p for p in candidates if os.path.exists(p))\nprint(\"Using test folder:\", TEST_DIR)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(\"Device:\", device)\n\nclass CustomResNext(nn.Module):\n    def __init__(self, model_name='resnext50_32x4d', pretrained=False):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=pretrained)\n        n_features = self.model.fc.in_features\n        self.model.fc = nn.Linear(n_features, 5)\n\n    def forward(self, x):\n        return self.model(x)\n\nclass TestDataset(Dataset):\n    def __init__(self, files, transform=None):\n        self.files = files\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.files)\n\n    def __getitem__(self, idx):\n        image = cv2.imread(self.files[idx])\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        return self.transform(image=image)['image']\n\ntransform = Compose([\n    Resize(SIZE, SIZE),\n    Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ToTensorV2(),\n])\n\n# ---------------- load both models ----------------\nmodels = []\nfor p in WEIGHTS_PATHS:\n    m = CustomResNext(pretrained=False)\n    state = torch.load(p, map_location=\"cpu\", weights_only=False)\n    m.load_state_dict(state['model'])\n    m.to(device)\n    m.eval()\n    models.append(m)\nprint(\"Models loaded:\", len(models))\n\n# ---------------- predict: average over models and TTA views ----------------\ndef predict_batch(x):\n    views = [x]\n    if USE_TTA:\n        views += [x.flip(3), x.flip(2), x.transpose(2, 3)]\n    probs = []\n    for m in models:\n        for v in views:\n            probs.append(m(v).softmax(1))\n    return torch.stack(probs).mean(0)\n\nimage_ids = sorted(os.listdir(TEST_DIR))\nfiles = [TEST_DIR + i for i in image_ids]\nloader = DataLoader(TestDataset(files, transform), batch_size=16,\n                    shuffle=False, num_workers=2, pin_memory=True)\n\nall_probs = []\nwith torch.no_grad():\n    for images in loader:\n        all_probs.append(predict_batch(images.to(device)).cpu().numpy())\nall_probs = np.concatenate(all_probs)\n\nsubmission = pd.DataFrame({\"image_id\": image_ids, \"label\": all_probs.argmax(1)})\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"TTA used:\", USE_TTA, \"| models:\", len(models))\nprint(submission.head())\nprint(\"Rows:\", len(submission))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T19:41:22.865405Z","iopub.execute_input":"2026-10-14T19:41:22.86616Z","iopub.status.idle":"2026-10-14T19:42:27.840231Z","shell.execute_reply.started":"2026-10-14T19:41:22.866125Z","shell.execute_reply":"2026-10-14T19:42:27.839159Z"}},"outputs":[],"execution_count":null}]}