{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":124685,"databundleVersionId":14664296,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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\nimport numpy as np # linear algebra\nimport 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\nimport os\nfor 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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:03:49.653868Z","iopub.execute_input":"2026-02-14T18:03:49.654252Z","iopub.status.idle":"2026-02-14T18:03:54.110911Z","shell.execute_reply.started":"2026-02-14T18:03:49.654228Z","shell.execute_reply":"2026-02-14T18:03:54.110040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import socket\ntry:\n    socket.create_connection((\"huggingface.co\", 80))\n    print(\"Internet enabled\")\nexcept:\n    print(\"Internet disabled\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:03:54.112284Z","iopub.execute_input":"2026-02-14T18:03:54.112627Z","iopub.status.idle":"2026-02-14T18:03:54.163853Z","shell.execute_reply.started":"2026-02-14T18:03:54.112603Z","shell.execute_reply":"2026-02-14T18:03:54.163158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install timm -q\n!pip install transformers -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:03:54.164893Z","iopub.execute_input":"2026-02-14T18:03:54.165181Z","iopub.status.idle":"2026-02-14T18:04:01.307713Z","shell.execute_reply.started":"2026-02-14T18:03:54.165151Z","shell.execute_reply":"2026-02-14T18:04:01.306957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom transformers import AutoImageProcessor, AutoModelForImageClassification\nimport csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:01.308870Z","iopub.execute_input":"2026-02-14T18:04:01.309124Z","iopub.status.idle":"2026-02-14T18:04:27.031262Z","shell.execute_reply.started":"2026-02-14T18:04:01.309094Z","shell.execute_reply":"2026-02-14T18:04:27.030626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Configuration\n\nCFG = {\n    \"device\": \"cuda\" if torch.cuda.is_available() else \"cpu\",\n    \"batch_size\": 2,\n    \"image_size\": 518,\n    \"num_workers\": 2,\n    \"top_k\": 5\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:27.033167Z","iopub.execute_input":"2026-02-14T18:04:27.033635Z","iopub.status.idle":"2026-02-14T18:04:27.037491Z","shell.execute_reply.started":"2026-02-14T18:04:27.033609Z","shell.execute_reply":"2026-02-14T18:04:27.036802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_ROOT = \"/kaggle/input/plantclef-2026\"\n\nTEST_IMG_DIR = f\"{DATA_ROOT}/PlantCLEF2025_test_images/PlantCLEF2025_test_images\"\nTEST_CSV = f\"{DATA_ROOT}/PlantCLEF2025_test.csv\"\nSPECIES_CSV = f\"{DATA_ROOT}/species_ids.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:27.038483Z","iopub.execute_input":"2026-02-14T18:04:27.038809Z","iopub.status.idle":"2026-02-14T18:04:27.055566Z","shell.execute_reply.started":"2026-02-14T18:04:27.038770Z","shell.execute_reply":"2026-02-14T18:04:27.055007Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Load test Metadata\n\ntest_df = pd.read_csv(TEST_CSV, sep=\";\", engine=\"python\")\ntest_df.columns = test_df.columns.str.strip().str.strip('\"')\n\nprint(\"Total test images: \", len(test_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:27.056428Z","iopub.execute_input":"2026-02-14T18:04:27.056712Z","iopub.status.idle":"2026-02-14T18:04:27.085267Z","shell.execute_reply.started":"2026-02-14T18:04:27.056681Z","shell.execute_reply":"2026-02-14T18:04:27.084710Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Load species Mapping\n\nspecies_df = pd.read_csv(SPECIES_CSV)\nspecies_ids = species_df[\"species_id\"].values\n\nprint(\"Total species\", len(species_ids))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:27.085983Z","iopub.execute_input":"2026-02-14T18:04:27.086304Z","iopub.status.idle":"2026-02-14T18:04:27.100423Z","shell.execute_reply.started":"2026-02-14T18:04:27.086283Z","shell.execute_reply":"2026-02-14T18:04:27.099590Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Dataset for Quadrat Images\n\nclass QuadratDataset(Dataset):\n    def __init__(self, df, img_dir, processor):\n        self.df = df\n        self.img_dir = img_dir\n        self.processor = processor\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        quadrat_id = row[\"quadrat_id\"]\n        img_path = os.path.join(self.img_dir, f\"{quadrat_id}.jpg\")\n\n        image = Image.open(img_path).convert(\"RGB\")\n\n        inputs = self.processor(\n            images = image,\n            return_tensors=\"pt\"\n        )\n\n        #remove batch dimension\n        pixel_values = inputs[\"pixel_values\"].squeeze(0)\n\n        return pixel_values, quadrat_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:27.101245Z","iopub.execute_input":"2026-02-14T18:04:27.101514Z","iopub.status.idle":"2026-02-14T18:04:27.106482Z","shell.execute_reply.started":"2026-02-14T18:04:27.101491Z","shell.execute_reply":"2026-02-14T18:04:27.105837Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Loading pretrained DINOv2 model\n\nprocessor = AutoImageProcessor.from_pretrained(\"gerald29/plantclef2024\")\nmodel = AutoModelForImageClassification.from_pretrained(\"gerald29/plantclef2024\")\n\nmodel = model.to(CFG[\"device\"])\nmodel = model.float()\nmodel.eval()\n\nprint(\"model loaded successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:27.107291Z","iopub.execute_input":"2026-02-14T18:04:27.107527Z","iopub.status.idle":"2026-02-14T18:04:30.296902Z","shell.execute_reply.started":"2026-02-14T18:04:27.107505Z","shell.execute_reply":"2026-02-14T18:04:30.296192Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Detect model dtype\n\nmodel_dtype = next(model.parameters()).dtype\nprint(\"Model dtype: \", model_dtype)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:30.298131Z","iopub.execute_input":"2026-02-14T18:04:30.298752Z","iopub.status.idle":"2026-02-14T18:04:30.302546Z","shell.execute_reply.started":"2026-02-14T18:04:30.298699Z","shell.execute_reply":"2026-02-14T18:04:30.301901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#DataLoader\n\ntest_dataset = QuadratDataset(\n    test_df,\n    TEST_IMG_DIR,\n    processor\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=CFG[\"batch_size\"],\n    shuffle=False,\n    num_workers=CFG[\"num_workers\"],\n    pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:04:30.303564Z","iopub.execute_input":"2026-02-14T18:04:30.303941Z","iopub.status.idle":"2026-02-14T18:04:30.366995Z","shell.execute_reply.started":"2026-02-14T18:04:30.303907Z","shell.execute_reply":"2026-02-14T18:04:30.366142Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#inference AMP\nfrom torchvision.transforms.functional import hflip\n\n#Enhance inference with TTA + Temperature\n\ndef run_inference_with_tta(model, test_loader, temperature=1.0):\n    all_logits = []\n    all_ids = []\n\n    model.eval()\n\n    with torch.no_grad():\n        for pixel_values, ids in tqdm(test_loader):\n\n            pixel_values = pixel_values.to(CFG[\"device\"]).float()\n\n            outputs_orig = model(pixel_values=pixel_values)\n            logits_orig = outputs_orig.logits\n\n            #Horizontal Flip\n            flipped = torch.flip(pixel_values, dims=[3])\n            outputs_flip = model(pixel_values=flipped)\n            logits_flip = outputs_flip.logits\n\n            #Average logits\n            logits = (logits_orig + logits_flip) / 2.0\n\n            #Temperature scaling\n            logits = logits / temperature\n\n            all_logits.append(logits.cpu())\n            all_ids.extend(ids)\n\n    all_logits = torch.cat(all_logits, dim=0)\n\n    return all_logits, all_ids","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:05:24.935814Z","iopub.execute_input":"2026-02-14T18:05:24.936177Z","iopub.status.idle":"2026-02-14T18:05:24.942936Z","shell.execute_reply.started":"2026-02-14T18:05:24.936144Z","shell.execute_reply":"2026-02-14T18:05:24.942139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Auto tune temperature\n\ntemperature_grid = [0.7, 0.8, 1.0, 1.2, 1.5]\n\nbest_temperature = 1.0\ntarget_entropy = 0.6\n\nentropy_scores = []\n\nfor T in temperature_grid:\n\n    logits, _ = run_inference_with_tta(model, test_loader, temperature=T)\n    probs = torch.softmax(logits, dim=1).numpy()\n\n    entropies = []\n\n    for p in probs:\n        p = p + 1e-12\n        ent = -np.sum(p * np.log(p))\n        entropies.append(ent)\n\n    mean_entropy = np.mean(entropies)\n    entropy_scores.append((T, mean_entropy))\n\n    print(f\"T={T} -> Mean Entropy: {mean_entropy:.4f}\")\n\n\n#Choose temperature closest to target entropy\nentropy_array = np.array(entropy_scores)\nbest_idx = np.argmin(np.abs(entropy_array[:,1] - target_entropy))\nbest_temperature = entropy_array[best_idx][0]\n\nprint(\"Selected Temperature: \", best_temperature)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:05:27.318861Z","iopub.execute_input":"2026-02-14T18:05:27.319579Z","iopub.status.idle":"2026-02-14T18:33:33.680112Z","shell.execute_reply.started":"2026-02-14T18:05:27.319546Z","shell.execute_reply":"2026-02-14T18:33:33.679379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Final inference using the best temperature\n\nfinal_logits, all_ids = run_inference_with_tta(\n    model, \n    test_loader,\n    temperature=best_temperature\n)\n\nfinal_probs = torch.softmax(final_logits, dim=1).numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:33:45.433484Z","iopub.execute_input":"2026-02-14T18:33:45.433850Z","iopub.status.idle":"2026-02-14T18:39:20.386956Z","shell.execute_reply.started":"2026-02-14T18:33:45.433815Z","shell.execute_reply":"2026-02-14T18:39:20.386055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Pseudo label confidence refinement\n\ndef refine_probabilities(probs):\n\n    refined = probs.copy()\n\n    max_conf = probs.max()\n\n    #High confidence sharpening\n    if max_conf > 0.6:\n        refined = refined ** 1.5\n\n    #Low confidence smoothing\n    elif max_conf < 0.15:\n        refined = refined ** 0.75\n\n    #Re-normalize\n    refined = refined / refined.sum()\n\n    return refined","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:40:40.267221Z","iopub.execute_input":"2026-02-14T18:40:40.267533Z","iopub.status.idle":"2026-02-14T18:40:40.272198Z","shell.execute_reply.started":"2026-02-14T18:40:40.267507Z","shell.execute_reply":"2026-02-14T18:40:40.271514Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Automatic threshold Grid Search\n\nthreshold_grid = np.arange(0.05, 0.40, 0.05)\n\nresults = []\ntarget_avg_species = 4.5\n\nfor threshold in threshold_grid:\n\n    counts = []\n\n    for i in range(len(final_probs)):\n        probs = final_probs[i]\n        selected = np.where(probs >= threshold)[0]\n        counts.append(len(selected))\n\n    avg_species = np.mean(counts)\n\n    results.append((threshold, avg_species))\n    print(f\"Threshold {threshold:.2f} -> Avg species per image: {avg_species:.2f}\")\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:43:22.135572Z","iopub.execute_input":"2026-02-14T18:43:22.136003Z","iopub.status.idle":"2026-02-14T18:43:22.295137Z","shell.execute_reply.started":"2026-02-14T18:43:22.135966Z","shell.execute_reply":"2026-02-14T18:43:22.294304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results_array = np.array(results)\n\n#Find threshold closest to target average\nbest_idx = np.argmin(np.abs(results_array[:, 1] - target_avg_species))\n\nbest_threshold = results_array[best_idx][0]\n\nprint(\"Selected best threshold:\", best_threshold)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:43:29.563313Z","iopub.execute_input":"2026-02-14T18:43:29.563994Z","iopub.status.idle":"2026-02-14T18:43:29.569411Z","shell.execute_reply.started":"2026-02-14T18:43:29.563954Z","shell.execute_reply":"2026-02-14T18:43:29.568689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Entropy based dynamic K\n\ndef compute_entropy(probs):\n    probs = probs + 1e-12\n    return -np.sum(probs * np.log(probs))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:43:33.150526Z","iopub.execute_input":"2026-02-14T18:43:33.150865Z","iopub.status.idle":"2026-02-14T18:43:33.155012Z","shell.execute_reply.started":"2026-02-14T18:43:33.150838Z","shell.execute_reply":"2026-02-14T18:43:33.154167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Dynamic K logic\n\nmax_species = 8\nmin_species = 1\nbase_threshold = best_threshold\n\nsubmission_rows = []\n\nfor i in range(len(final_probs)):\n    probs = refine_probabilities(final_probs[i])\n\n    entropy = compute_entropy(probs)\n\n    #Normalize entropy\n    normalized_entropy = entropy / np.log(len(probs))\n\n    #Dynamic K from entropy\n    dynamic_k = int(min_species + normalized_entropy * (max_species - min_species))\n    dynamic_k = max(min_species, min(dynamic_k, max_species))\n\n    #Primary threshold selection\n    selected_indices = np.where(probs >= base_threshold)[0]\n\n    #If too many -> limit using dynamic k\n    if len(selected_indices) > dynamic_k:\n        top_indices = np.argsort(probs)[-dynamic_k:]\n        selected_indices = top_indices\n\n    #if none -> fallback\n    if len(selected_indices) == 0:\n        selected_indices = [np.argmax(probs)]\n\n    predicted_species = species_ids[selected_indices]\n\n    species_list_str = \"[\" + \", \".join(map(str, predicted_species)) + \"]\"\n\n    submission_rows.append({\n        \"quadrat_id\" : all_ids[i],\n        \"species_ids\" : species_list_str\n    })\n\nsubmission_df = pd.DataFrame(submission_rows)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:43:37.064498Z","iopub.execute_input":"2026-02-14T18:43:37.065123Z","iopub.status.idle":"2026-02-14T18:43:37.375064Z","shell.execute_reply.started":"2026-02-14T18:43:37.065093Z","shell.execute_reply":"2026-02-14T18:43:37.374263Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Save submission\n\nimport csv\n\nsubmission_df.to_csv(\n    \"submission_advanced.csv\",\n    sep=\",\",\n    index=False,\n    quoting=csv.QUOTE_ALL\n)\n\nprint(\"Submission file created\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:43:41.082613Z","iopub.execute_input":"2026-02-14T18:43:41.083389Z","iopub.status.idle":"2026-02-14T18:43:41.098373Z","shell.execute_reply.started":"2026-02-14T18:43:41.083358Z","shell.execute_reply":"2026-02-14T18:43:41.097588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T18:43:42.058433Z","iopub.execute_input":"2026-02-14T18:43:42.059002Z","iopub.status.idle":"2026-02-14T18:43:42.082061Z","shell.execute_reply.started":"2026-02-14T18:43:42.058963Z","shell.execute_reply":"2026-02-14T18:43:42.081390Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}