{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":91448,"databundleVersionId":11249847,"sourceType":"competition"}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"###Install required libraries:\n!pip install git+https://github.com/mlfoundations/open_clip.git -q\n!pip install faiss-gpu -qq ","metadata":{"execution":{"iopub.status.busy":"2025-04-28T07:58:59.476797Z","iopub.execute_input":"2025-04-28T07:58:59.47703Z","iopub.status.idle":"2025-04-28T08:00:24.210757Z","shell.execute_reply.started":"2025-04-28T07:58:59.477002Z","shell.execute_reply":"2025-04-28T08:00:24.209818Z"},"tags":["formatted"],"trusted":true,"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nimport yaml\nfrom pathlib import Path\nfrom types import SimpleNamespace\nimport argparse\n\nimport numpy as np\nimport pandas as pd\nimport torch\nimport faiss\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, TensorDataset, Dataset\nfrom torchvision import transforms\nfrom torchvision.datasets import ImageFolder\n\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom torchvision import transforms as tfms\nimport torchvision.transforms as T\nimport open_clip\n\nfrom typing import Sequence, Tuple, Any, Dict, List, Optional, Union\nfrom collections import defaultdict\nimport importlib","metadata":{"execution":{"iopub.status.busy":"2025-04-28T08:00:24.214415Z","iopub.execute_input":"2025-04-28T08:00:24.214641Z","iopub.status.idle":"2025-04-28T08:00:27.827726Z","shell.execute_reply.started":"2025-04-28T08:00:24.214621Z","shell.execute_reply":"2025-04-28T08:00:27.826833Z"},"tags":["formatted"],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_path = '/kaggle/input/fungi-clef-2025/'","metadata":{"execution":{"iopub.status.busy":"2025-04-24T16:31:29.939922Z","iopub.status.idle":"2025-04-24T16:31:29.940155Z","shell.execute_reply.started":"2025-04-24T16:31:29.940048Z","shell.execute_reply":"2025-04-24T16:31:29.940059Z"},"tags":["formatted"],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_ds_raw = FungiTastic(\n#     root=data_path,\n#     split='train',\n#     transform=transforms.ToTensor()          # yields torch.Tensor C×H×W in [0,1]\n# )\n\n# # 2) Accumulate sums and squared sums per channel\n# sum_c  = torch.zeros(3)\n# sum_sq = torch.zeros(3)\n# pix_cnt = 0\n\n# for img, *_ in tqdm(train_ds_raw, desc=\"Computing mean/std\"):  \n#     # img has shape [C, H, W], values in [0,1]\n#     C, H, W = img.shape\n#     sum_c  += img.sum(dim=[1,2])          # sum over H and W → [3]\n#     sum_sq += (img**2).sum(dim=[1,2])     # sum of squares → [3]\n#     pix_cnt += H * W\n\n# # 3) Compute mean and std\n# mean = sum_c  / pix_cnt\n# var  = (sum_sq / pix_cnt) - mean**2\n# std  = torch.sqrt(var)\n\n# print(\"Computed means:\", mean.tolist())\n# print(\"Computed stds: \", std.tolist())\n\n\nMEAN = [0.44550442695617676, 0.42286601662635803, 0.3496400713920593] \nSTD = [0.2475634068250656, 0.2405535727739334, 0.23902718722820282] ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-24T16:31:19.956671Z","iopub.execute_input":"2025-04-24T16:31:19.95688Z","iopub.status.idle":"2025-04-24T16:31:20.030961Z","shell.execute_reply.started":"2025-04-24T16:31:19.956853Z","shell.execute_reply":"2025-04-24T16:31:20.029997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BioCLIP(torch.nn.Module):\n    def __init__(self, device):\n        super(BioCLIP, self).__init__()\n        self.device = device\n        self.model = None\n        self.processor = None\n        self.size = (224, 224)\n\n    def load(self):\n        self.model, _, self.processor = open_clip.create_model_and_transforms('hf-hub:imageomics/bioclip')\n        self.model.to(self.device)\n\n    def extract_features(self, image):\n        if self.model is None:\n            raise ValueError('Model not loaded')\n        image = image.resize(self.size, Image.BICUBIC)\n        image_tensor_proc = self.processor(image)[None]\n        features = self.model.encode_image(image_tensor_proc.to(self.device))\n        return self.normalize_embedding(features)\n\n\n    @staticmethod\n    def normalize_embedding(embs):\n        return torch.nn.functional.normalize(embs.float(), dim=1, p=2)\n","metadata":{"execution":{"iopub.status.busy":"2025-04-20T08:36:13.443164Z","iopub.execute_input":"2025-04-20T08:36:13.443389Z","iopub.status.idle":"2025-04-20T08:36:13.456798Z","shell.execute_reply.started":"2025-04-20T08:36:13.443358Z","shell.execute_reply":"2025-04-20T08:36:13.456005Z"},"tags":["formatted"],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class FungiTastic(torch.nn.Module):\n    \n    SPLIT2STR = {'train': 'Train', 'val': 'Val', 'test': 'Test'}\n\n    def __init__(self, root: str, split: str = 'val', transform=None):\n        super().__init__()\n        self.split = split\n        self.transform = transform\n        self.df = self._get_df(root, split)\n\n        assert \"image_path\" in self.df\n        if self.split != 'test':\n            assert \"category_id\" in self.df\n            self.n_classes = len(self.df['category_id'].unique())\n            self.category_id2label = {\n                k: v[0] for k, v in self.df.groupby('category_id')['species'].unique().to_dict().items()\n            }\n            self.label2category_id = {\n                v: k for k, v in self.category_id2label.items()\n            }\n\n    def add_embeddings(self, embeddings: pd.DataFrame):\n        assert isinstance(embeddings, pd.DataFrame), \"Embeddings must be a pandas DataFrame.\"\n        assert \"embedding\" in embeddings.columns, \"Embeddings DataFrame must have an 'embedding' column.\"\n        assert len(embeddings) == len(self.df), \"Embeddings must match dataset length.\"\n\n        self.df = pd.merge(self.df, embeddings, on=\"filename\", how=\"inner\")\n\n    def get_embeddings_for_class(self, id):\n        # return the embeddings for class class_idx\n        class_idxs = self.df[self.df['category_id'] == id].index\n        return self.df.iloc[class_idxs]['embedding']\n    \n    @staticmethod\n    def _get_df(data_path: str, split: str) -> pd.DataFrame:\n        df_path = os.path.join(\n            data_path,\n            \"metadata\",\n            \"FungiTastic-FewShot\",\n            f\"FungiTastic-FewShot-{FungiTastic.SPLIT2STR[split]}.csv\"\n        )\n        df = pd.read_csv(df_path)\n        df[\"image_path\"] = df.filename.apply(\n            lambda x: os.path.join(data_path, \"FungiTastic-FewShot\", split, '300p', x)\n        )\n        return df\n\n    def __getitem__(self, idx: int):\n        file_path = self.df[\"image_path\"].iloc[idx].replace('FungiTastic-FewShot', 'images/FungiTastic-FewShot')\n    \n        if self.split != 'test':\n            category_id = self.df[\"category_id\"].iloc[idx]\n        else:\n            category_id = None\n    \n        image = Image.open(file_path)\n    \n        if self.transform:\n            image = self.transform(image)\n    \n        # Check if embeddings exist\n        if \"embedding\" in self.df.columns:\n            emb = torch.tensor(self.df.iloc[idx]['embedding'], dtype=torch.float32).squeeze()\n        else:\n            emb = None  # No embeddings available\n\n        return image, category_id, file_path, emb\n\n\n    def __len__(self):\n        return len(self.df)\n\n    def get_class_id(self, idx: int) -> int:\n        return self.df[\"category_id\"].iloc[idx]\n\n    def show_sample(self, idx: int) -> None:\n        image, category_id, _, _ = self.__getitem__(idx)\n        class_name = self.category_id2label[category_id]\n\n        plt.imshow(image)\n        plt.title(f\"Class: {class_name}; id: {idx}\")\n        plt.axis('off')\n        plt.show()\n\n    def get_category_idxs(self, category_id: int) -> List[int]:\n        return self.df[self.df.category_id == category_id].index.tolist()","metadata":{"execution":{"iopub.status.busy":"2025-04-20T08:36:13.457698Z","iopub.execute_input":"2025-04-20T08:36:13.457961Z","iopub.status.idle":"2025-04-20T08:36:13.475367Z","shell.execute_reply.started":"2025-04-20T08:36:13.457933Z","shell.execute_reply":"2025-04-20T08:36:13.474764Z"},"tags":["formatted"],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_embeddings(dataset):\n\n    idxs = np.arange(len(dataset))\n    im_names, embs = [], []\n    for idx in tqdm(idxs):\n        im, label, file_path, _ = dataset[idx]\n\n        with torch.no_grad():\n            feat = model.extract_features(im)\n            \n        # feat_quant = model.quantize_normalized_embedding(feat)\n\n        im_names.append(os.path.basename(file_path))\n        embs.append(feat.detach().cpu().numpy())\n\n    embeddings = pd.DataFrame({'filename': im_names, 'embedding': embs})\n\n    return embeddings","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T08:36:13.476191Z","iopub.execute_input":"2025-04-20T08:36:13.476462Z","iopub.status.idle":"2025-04-20T08:36:13.495378Z","shell.execute_reply.started":"2025-04-20T08:36:13.476435Z","shell.execute_reply":"2025-04-20T08:36:13.494576Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🏃🏻‍♂4. Run the few-shot classifier on the precomputed features","metadata":{}},{"cell_type":"code","source":"class NNClassifier():\n    def __init__(self, train_embeddings, device='cuda'):\n        \"\"\"\n        :param cfg: config object, namespace\n        :param train_embeddings: list of C torch arrays of shape [N_C, D] where N_C is the number of training samples\n        of class C and D is the dimensionality of the embeddings\n        \"\"\"\n        \n        self.device = device\n        self.index, self.idx2cls = self.build_index(train_embeddings)\n\n    def make_prediction(self, embeddings, plot_sim_hist=False, ret_probs=False):\n        \"\"\"\n        :param embeddings: torch.Tensor of shape (batch_size, n_channels, height, width)\n        :return: probabilities of shape (batch_size, n_classes) computed based on\n        the similarity of the embeddings to the class prototypes\n        \"\"\"\n        # compute the similarity of each embedding to each prototype\n        # embeddings - [N, D], class_prototypes - [C, D]\n        similarities, indices = self.index.search(embeddings, 1)\n        # get the classes for the indices\n        cls = self.idx2cls[indices.squeeze()]\n        # get the confidence of the prediction\n        conf = similarities\n        \n        return cls, conf\n\n    def build_index(self, train_dataset):\n        idx2cls = train_dataset.df.category_id.values\n        # concatenate the embeddings\n        embs = np.array(train_dataset.df.embedding.values.tolist(), dtype=np.float32).squeeze()\n        # build the index for cosine similarity search\n        index = faiss.IndexFlatIP(embs.shape[1])\n        index.add(embs)\n        return index, idx2cls","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T08:36:13.498053Z","iopub.execute_input":"2025-04-20T08:36:13.498247Z","iopub.status.idle":"2025-04-20T08:36:13.512587Z","shell.execute_reply.started":"2025-04-20T08:36:13.498224Z","shell.execute_reply":"2025-04-20T08:36:13.511855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class PrototypeClassifier(torch.nn.Module):\n    def __init__(self, train_dataset, device='cuda'):\n        super().__init__()\n        self.device = device\n        self.train_dataset = train_dataset\n\n        class_embeddings, _ = self._get_classifier_embeddings(train_dataset)\n        self.class_prototypes = torch.nn.Parameter(self.get_prototypes(class_embeddings), requires_grad=False)\n\n    def _get_classifier_embeddings(self, dataset_train):\n        class_embeddings = []\n        empty_classes = []\n        n_classes = min(torch.inf, dataset_train.n_classes)\n        for cls in range(n_classes):\n            cls_embs = dataset_train.get_embeddings_for_class(cls)\n            if len(cls_embs) == 0:\n                # if no embeddings for class, use zeros\n                empty_classes.append(cls)\n                class_embeddings.append(torch.zeros(1, dataset_train.emb_dim))\n            else:\n                class_embeddings.append(torch.tensor(np.vstack(cls_embs.values)))\n        return class_embeddings, empty_classes\n\n    def get_prototypes(self, embeddings):\n        return torch.stack([class_embs.mean(dim=0) for class_embs in embeddings])\n\n    def make_prediction(self, embeddings):\n        similarities = torch.nn.functional.cosine_similarity(embeddings, self.class_prototypes, dim=-1)\n        cls = torch.argmax(similarities, dim=1)\n        conf = torch.nn.functional.softmax(similarities, dim=1).max(dim=1).values\n        return cls, conf","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T08:36:13.514165Z","iopub.execute_input":"2025-04-20T08:36:13.514408Z","iopub.status.idle":"2025-04-20T08:36:13.530004Z","shell.execute_reply.started":"2025-04-20T08:36:13.514389Z","shell.execute_reply":"2025-04-20T08:36:13.529255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def generate_embeddings(dataset):\n\n#     idxs = np.arange(len(dataset))\n#     im_names, embs = [], []\n#     for idx in tqdm(idxs):\n#         im, label, file_path, _ = dataset[idx]\n\n#         with torch.no_grad():\n#             feat = model.extract_features(im)\n            \n#         # feat_quant = model.quantize_normalized_embedding(feat)\n\n#         im_names.append(os.path.basename(file_path))\n#         embs.append(feat.detach().cpu().numpy())\n\n#     embeddings = pd.DataFrame({'filename': im_names, 'embedding': embs})\n\n#     return embeddings\n\ndef generate_embeddings_raw(dataset):\n    im_names, embs = [], []\n    for idx in tqdm(range(len(dataset)), desc=\"Extracting raw embeddings\"):\n\n        raw_path = dataset.df.loc[idx, \"image_path\"]\n        raw_path = raw_path.replace(\n            'FungiTastic-FewShot',\n            'images/FungiTastic-FewShot'\n        )\n        img = Image.open(raw_path).convert('RGB')\n        with torch.no_grad():\n            feat = model.extract_features(img)  # this resizes+tokenizes inside\n        im_names.append(os.path.basename(raw_path))\n        embs.append(feat.cpu().numpy().squeeze())\n    return pd.DataFrame({'filename': im_names, 'embedding': embs})\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:01:15.831248Z","iopub.execute_input":"2025-04-20T10:01:15.831546Z","iopub.status.idle":"2025-04-20T10:01:15.838131Z","shell.execute_reply.started":"2025-04-20T10:01:15.831507Z","shell.execute_reply":"2025-04-20T10:01:15.837211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_obs_embeddings(df: pd.DataFrame) -> pd.DataFrame:\n    rows = []\n    for obs_id, grp in df.groupby('observationID'):\n        # each grp.embedding is a 1‑D numpy array\n        embs = np.vstack(grp['embedding'].values)   # (V, D)\n        rows.append({\n            'observationID': obs_id,\n            'category_id'  : grp['category_id'].iloc[0],\n            'embedding'    : embs.mean(axis=0)\n        })\n    return pd.DataFrame(rows)\n\n\ndef make_obs_embeddings_test(df):\n    \"\"\"\n    Only aggregates embeddings per observation,\n    doesn’t require category_id.\n    \"\"\"\n    rows = []\n    for obs_id, grp in df.groupby('observationID'):\n        emb = np.vstack(grp['embedding'].values).mean(axis=0)\n        rows.append({\n            'observationID': obs_id,\n            'embedding'    : emb\n        })\n    return pd.DataFrame(rows)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:28:12.488908Z","iopub.execute_input":"2025-04-20T10:28:12.489246Z","iopub.status.idle":"2025-04-20T10:28:12.495035Z","shell.execute_reply.started":"2025-04-20T10:28:12.489219Z","shell.execute_reply":"2025-04-20T10:28:12.493999Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class FineTuneCLIP(nn.Module):\n    def __init__(self, clipModel, numClasses, freezeBackbone=True):\n        super().__init__()\n        # use the visual tower\n        self.backbone = clipModel.visual\n        if freezeBackbone:\n            # freeze all but the final transformer block\n            for name, p in self.backbone.named_parameters():\n                if not name.startswith('transformer.resblocks.11'):  # last block\n                    p.requires_grad = False\n\n        self.norm       = nn.LayerNorm(self.backbone.output_dim)\n        self.classifier = nn.Linear(self.backbone.output_dim, numClasses)\n\n    def forward(self, x):\n        feats = self.backbone(x)                      # (B, D)\n        feats = feats / feats.norm(dim=-1, keepdim=True)\n        feats = self.norm(feats)\n        return self.classifier(feats)                 # (B, C)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T08:46:11.782671Z","iopub.execute_input":"2025-04-20T08:46:11.782991Z","iopub.status.idle":"2025-04-20T08:46:11.788427Z","shell.execute_reply.started":"2025-04-20T08:46:11.782968Z","shell.execute_reply":"2025-04-20T08:46:11.787532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trainTransform = T.Compose([\n    T.RandomResizedCrop(224, scale=(0.8,1.0), ratio=(0.75,1.33)),\n    T.RandomHorizontalFlip(p=0.5),\n    T.RandomVerticalFlip(p=0.2),\n    T.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4, hue=0.1),\n    T.RandomRotation(degrees=30),\n    T.RandomGrayscale(p=0.1),\n    T.GaussianBlur(kernel_size=(5,9), sigma=(0.1,5)),\n    T.ToTensor(),\n    T.Normalize(mean=MEAN, std=STD),\n    T.RandomErasing(p=0.3, scale=(0.02,0.2), ratio=(0.3,3.3))\n])\n\n# simple deterministic pipeline for val/test\nvalTransform = T.Compose([\n    T.Resize(256),\n    T.CenterCrop(224),\n    T.ToTensor(),\n    T.Normalize(mean=MEAN, std=STD)\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nensembleSpecs = [\n    ('hf-hub:imageomics/bioclip',     None),               # BioCLIP\n    ('ViT-L-14',                       'openai'),           # official OpenAI CLIP\n    ('ViT-L-14',                       'laion2b_s32b_b82k') # LAION 2B variant\n]\nfor modelName, pretrainedTag in ensembleSpecs:\n    if pretrainedTag:\n        m, _, proc = open_clip.create_model_and_transforms(\n            modelName,\n            pretrained=pretrainedTag\n        )\n    else:\n        m, _, proc = open_clip.create_model_and_transforms(modelName)\n    m.to(device).eval()\n    ensembleModels.append(m)\n    ensembleProcessors.append(proc)\n\nprint(f\"Loaded {len(ensembleModels)} models\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:59:24.805891Z","iopub.execute_input":"2025-04-20T09:59:24.806186Z","iopub.status.idle":"2025-04-20T10:00:08.925872Z","shell.execute_reply.started":"2025-04-20T09:59:24.806164Z","shell.execute_reply":"2025-04-20T10:00:08.924936Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_ensemble_embeddings(dataset):\n    \"\"\"\n    For each image in `dataset`, run it through all models in\n    `ensembleModels` + `ensembleProcessors`, L2‑normalize each\n    embedding, then average them and renormalize.\n    Returns a DataFrame ['filename','embedding'] just like before.\n    \"\"\"\n    rows = []\n    for idx in tqdm(range(len(dataset)), desc=\"Ensemble Embeds\"):\n        img, _, file_path, _ = dataset[idx]\n        feats = []\n        with torch.no_grad():\n            for m, proc in zip(ensembleModels, ensembleProcessors):\n                # proc is a torchvision.Compose: resize + normalize\n                inp = proc(img).unsqueeze(0).to(device)   # (1,3,H,W)\n                f   = m.encode_image(inp)                # (1, D_i)\n                f   = F.normalize(f, dim=-1)             # L2 norm\n                feats.append(f)\n        # stack across models, mean, then renorm\n        fused = torch.stack(feats, dim=0).mean(0)      # (1, D_i) if all D_i same\n        fused = F.normalize(fused, dim=-1).cpu().numpy().squeeze()\n        rows.append({\n            'filename' : os.path.basename(file_path),\n            'embedding': fused\n        })\n    return pd.DataFrame(rows)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:00:53.488826Z","iopub.execute_input":"2025-04-20T10:00:53.489173Z","iopub.status.idle":"2025-04-20T10:00:53.495454Z","shell.execute_reply.started":"2025-04-20T10:00:53.489151Z","shell.execute_reply":"2025-04-20T10:00:53.494517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_ensemble_embeddings_raw(dataset):\n    \"\"\"\n    Iterate over dataset.df, open each image from disk as a PIL.Image,\n    run it through each (model,proc), normalize, fuse, and return a DF\n    ['filename','embedding'].\n    \"\"\"\n    rows = []\n    for _, row in tqdm(dataset.df.iterrows(),\n                      total=len(dataset.df),\n                      desc=\"Ensemble Embeds\"):\n        # build the exact same path as your dataset does:\n        fp = row.image_path.replace(\n            'FungiTastic-FewShot',\n            'images/FungiTastic-FewShot'\n        )\n        img = Image.open(fp).convert(\"RGB\")\n\n        feats = []\n        with torch.no_grad():\n            for m, proc in zip(ensembleModels, ensembleProcessors):\n                # proc expects a PIL.Image\n                tensor = proc(img).unsqueeze(0).to(device)  # (1,3,H,W)\n                f      = m.encode_image(tensor)            # (1, D_i)\n                f      = F.normalize(f, dim=-1)            # L2 norm\n                feats.append(f)\n\n        fused = torch.cat(feats, dim=1)           # shape [1, 512+768+…]\n        fused = F.normalize(fused, dim=-1)        # re‑normalize length\n        rows.append({\n            'filename' : Path(fp).name,\n            'embedding': fused.cpu().numpy().squeeze()\n        })\n\n    return pd.DataFrame(rows)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:04:51.189277Z","iopub.execute_input":"2025-04-20T10:04:51.1896Z","iopub.status.idle":"2025-04-20T10:04:51.195912Z","shell.execute_reply.started":"2025-04-20T10:04:51.189569Z","shell.execute_reply":"2025-04-20T10:04:51.194867Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_emb_df = generate_ensemble_embeddings_raw(trainDataset)\ntrainDataset.add_embeddings(train_emb_df)\n\nval_emb_df   = generate_ensemble_embeddings_raw(valDataset)\nvalDataset.add_embeddings(val_emb_df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:04:52.439463Z","iopub.execute_input":"2025-04-20T10:04:52.439817Z","iopub.status.idle":"2025-04-20T10:21:23.969843Z","shell.execute_reply.started":"2025-04-20T10:04:52.439788Z","shell.execute_reply":"2025-04-20T10:21:23.96897Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trainObs = make_obs_embeddings(trainDataset.df)\nvalObs   = make_obs_embeddings(valDataset.df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:21:23.971073Z","iopub.execute_input":"2025-04-20T10:21:23.971396Z","iopub.status.idle":"2025-04-20T10:21:24.646795Z","shell.execute_reply.started":"2025-04-20T10:21:23.971364Z","shell.execute_reply":"2025-04-20T10:21:24.646109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"proto_df    = (\n    trainObs\n      .groupby('category_id')['embedding']\n      .apply(lambda arrs: np.stack(arrs.values).mean(axis=0))\n      .reset_index()\n)\nproto_labels = proto_df['category_id'].values                        # (C,)\nproto_embs   = np.stack(proto_df['embedding'].values).astype('float32')  # (C, D_ensemble)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:21:24.648356Z","iopub.execute_input":"2025-04-20T10:21:24.648611Z","iopub.status.idle":"2025-04-20T10:21:24.812162Z","shell.execute_reply.started":"2025-04-20T10:21:24.648591Z","shell.execute_reply":"2025-04-20T10:21:24.811466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"faiss.normalize_L2(proto_embs)\nindex = faiss.IndexFlatIP(proto_embs.shape[1])\nindex.add(proto_embs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:21:24.813462Z","iopub.execute_input":"2025-04-20T10:21:24.813777Z","iopub.status.idle":"2025-04-20T10:21:24.829444Z","shell.execute_reply.started":"2025-04-20T10:21:24.813747Z","shell.execute_reply":"2025-04-20T10:21:24.828718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_embs = np.stack(valObs['embedding'].values).astype('float32')\nfaiss.normalize_L2(val_embs)\nK = 5\nD, I = index.search(val_embs, K)              # top‑K\npreds = proto_labels[I]                      # shape (N_val, 5)\ntrue  = valObs['category_id'].values\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:21:24.830331Z","iopub.execute_input":"2025-04-20T10:21:24.830584Z","iopub.status.idle":"2025-04-20T10:21:24.901074Z","shell.execute_reply.started":"2025-04-20T10:21:24.830564Z","shell.execute_reply":"2025-04-20T10:21:24.900136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"recall5 = np.mean([ true[i] in preds[i] for i in range(len(true)) ])\nprint(f\"Ensemble Val Recall@5 = {recall5:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:21:24.901977Z","iopub.execute_input":"2025-04-20T10:21:24.902256Z","iopub.status.idle":"2025-04-20T10:21:24.911858Z","shell.execute_reply.started":"2025-04-20T10:21:24.902224Z","shell.execute_reply":"2025-04-20T10:21:24.911126Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"testDataset = FungiTastic(root=data_path, split='test', transform=valTransform)\ntest_emb_df = generate_ensemble_embeddings_raw(testDataset)\ntestDataset.add_embeddings(test_emb_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:21:24.91262Z","iopub.execute_input":"2025-04-20T10:21:24.912971Z","iopub.status.idle":"2025-04-20T10:24:47.411632Z","shell.execute_reply.started":"2025-04-20T10:21:24.91294Z","shell.execute_reply":"2025-04-20T10:24:47.410772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"testObs = make_obs_embeddings_test(testDataset.df)\ntest_embs = np.stack(testObs['embedding'].values).astype('float32')\nfaiss.normalize_L2(test_embs)\n_, I_test = index.search(test_embs, 10)  # top‑10 for submission\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:28:46.9388Z","iopub.execute_input":"2025-04-20T10:28:46.939101Z","iopub.status.idle":"2025-04-20T10:28:47.0911Z","shell.execute_reply.started":"2025-04-20T10:28:46.939079Z","shell.execute_reply":"2025-04-20T10:28:47.090367Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_strs = [\" \".join(map(str, proto_labels[idxs])) for idxs in I_test]\nsubmission = pd.DataFrame({\n    'observationId': testObs['observationID'].values,\n    'predictions':   pred_strs\n})\nsubmission.to_csv('submission_ensemble.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T10:28:50.095507Z","iopub.execute_input":"2025-04-20T10:28:50.095961Z","iopub.status.idle":"2025-04-20T10:28:50.111433Z","shell.execute_reply.started":"2025-04-20T10:28:50.095923Z","shell.execute_reply":"2025-04-20T10:28:50.110758Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}