{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91451,"databundleVersionId":11223220,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"#### LOAD AND INITIALIZE DATASET ####","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tqdm import tqdm\nfrom sklearn.utils import shuffle\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import accuracy_score\nimport pandas as pd\nimport os\nimport cv2\nimport numpy as np","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:17:45.838654Z","iopub.execute_input":"2025-04-22T23:17:45.838969Z","iopub.status.idle":"2025-04-22T23:18:00.522045Z","shell.execute_reply.started":"2025-04-22T23:17:45.838945Z","shell.execute_reply":"2025-04-22T23:18:00.521259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('../input/animal-clef-2025/metadata.csv')\ndf.tail()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:18:00.523080Z","iopub.execute_input":"2025-04-22T23:18:00.523581Z","iopub.status.idle":"2025-04-22T23:18:00.622000Z","shell.execute_reply.started":"2025-04-22T23:18:00.523552Z","shell.execute_reply":"2025-04-22T23:18:00.621314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Define your data directories (Update with actual paths)\ndata_dirs = {    \n    \"SeaTurtlesD\": \"/kaggle/input/animal-clef-2025/images/SeaTurtleID2022/database/turtles-data/data/images/t001\",\n    \"SeaTurtlesQ\": \"/kaggle/input/animal-clef-2025/images/SeaTurtleID2022/query/images\",\n    \"LynxsD\": \"/kaggle/input/animal-clef-2025/images/LynxID2025/database\",\n    \"LynxsQ\": \"/kaggle/input/animal-clef-2025/images/LynxID2025/query\",\n    \"SalamandersD\": \"/kaggle/input/animal-clef-2025/images/SalamanderID2025/database/images\",\n    \"SalamandersQ\": \"/kaggle/input/animal-clef-2025/images/SalamanderID2025/query/images\"\n}\n\n# Check if directories exist\nfor label, path in data_dirs.items():\n    if not os.path.exists(path):\n        print(f\"⚠ Warning: Directory {path} does not exist for {label}.\")\n    else:\n        print(f\"✅ Directory found: {path} ({label})\")\n\ndef show_sample_images():\n    valid_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff')  # Add more formats if needed\n\n    for label, dir_path in data_dirs.items():\n        if not os.path.exists(dir_path):\n            print(f\"⚠ Skipping {label}: Directory does not exist.\")\n            continue\n\n        # Load images\n        sample_images = []\n        for root, _, files in os.walk(dir_path):\n            for img_name in files:\n                img_path = os.path.join(root, img_name)\n                if img_path.lower().endswith(valid_formats):\n                    img = cv2.imread(img_path)\n                    if img is not None:\n                        img = cv2.resize(img, (150, 150))\n                        sample_images.append(img)\n                    if len(sample_images) == 5:  # Stop after 5 images\n                        break\n            if len(sample_images) == 5:\n                break\n\n        # Plot images\n        if sample_images:\n            plt.figure(figsize=(10, 10))\n            for i, img in enumerate(sample_images):\n                plt.subplot(1, 5, i + 1)\n                plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n                plt.axis('off')\n                plt.title(f\"{label}\")\n            plt.show()\n        else:\n            print(f\"⚠ No valid images found in {dir_path} for {label}.\")\n\n# Call the function to display images\nshow_sample_images()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:18:00.623762Z","iopub.execute_input":"2025-04-22T23:18:00.624084Z","iopub.status.idle":"2025-04-22T23:18:18.683429Z","shell.execute_reply.started":"2025-04-22T23:18:00.624062Z","shell.execute_reply":"2025-04-22T23:18:18.682635Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set random seed for reproducibility\ntf.random.set_seed(42)\n\nIMG_SIZE=150","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:18:18.684657Z","iopub.execute_input":"2025-04-22T23:18:18.684975Z","iopub.status.idle":"2025-04-22T23:18:18.689275Z","shell.execute_reply.started":"2025-04-22T23:18:18.684933Z","shell.execute_reply":"2025-04-22T23:18:18.688465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to load images\ndef load_images():\n    X = []  # Image data\n    Y = []  # Labels\n\n    valid_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff')  # Supported formats\n\n    for label, dir_path in data_dirs.items():\n        if not os.path.exists(dir_path):\n            print(f\"⚠ Warning: Directory {dir_path} does not exist for {label}. Skipping...\")\n            continue\n\n        # Read images from the directory\n        for img_name in os.listdir(dir_path):\n            img_path = os.path.join(dir_path, img_name)\n            if img_path.lower().endswith(valid_formats):\n                img = cv2.imread(img_path)\n                if img is not None:\n                    img = cv2.resize(img, (150, 150))  # Resize for consistency\n                    X.append(img)\n                    Y.append(label)  # Store label\n    \n    return X, Y\n\n# Load the dataset\nX, Y = load_images()\n\n# Convert to numpy arrays\nif len(X) == 0 or len(Y) == 0 or len(X) != len(Y):\n    raise ValueError(f\"Mismatch in dataset sizes: X={len(X)}, Y={len(Y)}\")\n\nX = np.array(X, dtype='float32') / 255.0  # Normalize images\nY = np.array([list(data_dirs.keys()).index(y) for y in Y], dtype='int32')  # Convert labels to indices\n\n# Shuffle and split dataset\nX, Y = shuffle(X, Y, random_state=42)\nX_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=42)\n\nprint(f\"✅ Dataset successfully loaded: Train={len(X_train)}, Test={len(X_test)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:18:18.690225Z","iopub.execute_input":"2025-04-22T23:18:18.690517Z","iopub.status.idle":"2025-04-22T23:20:39.157703Z","shell.execute_reply.started":"2025-04-22T23:18:18.690489Z","shell.execute_reply":"2025-04-22T23:20:39.156941Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### ADVANCED IDENTIFICATION APPROACH ####","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom torchvision.models import ResNet50_Weights\nfrom sklearn.neighbors import NearestNeighbors\nimport random\nfrom PIL import Image\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:39.158381Z","iopub.execute_input":"2025-04-22T23:20:39.158580Z","iopub.status.idle":"2025-04-22T23:20:47.051546Z","shell.execute_reply.started":"2025-04-22T23:20:39.158562Z","shell.execute_reply":"2025-04-22T23:20:47.050879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  Variable setup \nIMAGE_SIZE = IMG_SIZE\nBATCH_SIZE = 32\nLEARNING_RATE = 0.0001\nEPOCHS = 20\nEMBEDDING_SIZE = 512\nNEW_THRESHOLD = 0.6  # Tunable threshold for 'new_individual'\nK_NEIGHBORS=5\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-04-23T00:35:44.985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Directories based on existing data_dirs\nTRAIN_DIR = {\n    \"SeaTurtle\": data_dirs[\"SeaTurtlesD\"],\n    \"Salamander\": data_dirs[\"SalamandersD\"],\n    \"Lynx\": data_dirs[\"LynxsD\"]\n}\nTEST_DIR = {\n    \"SeaTurtle\": data_dirs[\"SeaTurtlesQ\"],\n    \"Salamander\": data_dirs[\"SalamandersQ\"],\n    \"Lynx\": data_dirs[\"LynxsQ\"]\n}\nSPECIES_LIST = list(TRAIN_DIR.keys())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.059289Z","iopub.execute_input":"2025-04-22T23:20:47.059529Z","iopub.status.idle":"2025-04-22T23:20:47.075132Z","shell.execute_reply.started":"2025-04-22T23:20:47.059510Z","shell.execute_reply":"2025-04-22T23:20:47.074403Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\n# Loads each species’ dataset and merges them into one large training set to train a single model on multiple species\ndef create_combined_species_dataset(species_list, train_dir, transform):\n    datasets = []\n    for species in species_list:\n        ds = AdvancedAnimalDataset(train_dir[species], species_label=species, transform=transform)\n        datasets.append(ds)\n    combined_dataset = torch.utils.data.ConcatDataset(datasets)\n    return combined_dataset\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.076403Z","iopub.execute_input":"2025-04-22T23:20:47.076603Z","iopub.status.idle":"2025-04-22T23:20:47.087072Z","shell.execute_reply.started":"2025-04-22T23:20:47.076586Z","shell.execute_reply":"2025-04-22T23:20:47.086263Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  Retrieve images for training dataset and save paths\nclass AdvancedAnimalDataset(Dataset):\n    def __init__(self, root_dir, species_label, transform=None):\n        self.root_dir = root_dir  \n        self.species_label = species_label  \n        self.transform = transform\n\n        self.image_paths = []\n        self.labels = []\n\n        # Assign numerical label to each species\n        label_map = {\"SeaTurtle\": 0, \"Salamander\": 1, \"Lynx\": 2}\n        self.label = label_map[species_label]  # Integer label for the folder\n\n        valid_exts = (\".jpg\", \".jpeg\", \".png\", \".bmp\", \".tiff\")\n\n        for img_file in os.listdir(self.root_dir):\n            if img_file.lower().endswith(valid_exts):\n                img_path = os.path.join(self.root_dir, img_file)\n                self.image_paths.append(img_path)\n                self.labels.append(self.label)\n\n        print(f\"[INFO] Loaded {len(self.image_paths)} images from {self.species_label}\")\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        label = self.labels[idx]\n\n        image = Image.open(img_path).convert('RGB')\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label, self.species_label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.087877Z","iopub.execute_input":"2025-04-22T23:20:47.088144Z","iopub.status.idle":"2025-04-22T23:20:47.103069Z","shell.execute_reply.started":"2025-04-22T23:20:47.088108Z","shell.execute_reply":"2025-04-22T23:20:47.102299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  Retrieve images for test dataset and save paths\nclass AdvancedTestDataset(Dataset):\n    def __init__(self, root_dir, species, transform=None):\n        self.root_dir = root_dir\n        if not os.path.exists(self.root_dir):\n            self.root_dir = os.path.join(root_dir, f'{species}ID2022' if 'SeaTurtle' in species else f'{species}ID2025', 'query') \n        self.transform = transform\n        self.image_paths = []\n        self.image_ids = []\n        self.species = species\n        self.label_map = {\"SeaTurtle\": 0, \"Salamander\": 1, \"Lynx\": 2}\n        self._load_data()\n\n    def _load_data(self):\n        for filename in os.listdir(self.root_dir):\n            if filename.lower().endswith(('.png', '.jpg', '.jpeg')):\n                self.image_paths.append(os.path.join(self.root_dir, filename))\n                self.image_ids.append(filename.split('.')[0])\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        image = Image.open(img_path).convert('RGB')\n        image_id = self.image_ids[idx]\n        class_label = self.label_map[self.species]  # new line\n        if self.transform:\n            image = self.transform(image)\n        return image, image_id, self.species, class_label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:55:03.063827Z","iopub.execute_input":"2025-04-22T23:55:03.064157Z","iopub.status.idle":"2025-04-22T23:55:03.071245Z","shell.execute_reply.started":"2025-04-22T23:55:03.064134Z","shell.execute_reply":"2025-04-22T23:55:03.070168Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Re-identification model class\nclass ReIDModel(nn.Module):\n    def __init__(self, num_classes, embedding_size=512, weights=None): \n        super(ReIDModel, self).__init__()\n\n        # Load ResNet with or without weights\n        resnet = models.resnet50(weights=weights)\n\n        # Remove the classification head\n        self.backbone = nn.Sequential(*list(resnet.children())[:-2])  # Remove avgpool and fc\n        self.global_pool = nn.AdaptiveAvgPool2d(1)\n\n        # Add embedding and classification heads\n        self.fc = nn.Linear(resnet.fc.in_features, embedding_size)\n        self.bn = nn.BatchNorm1d(embedding_size)\n        self.classifier = nn.Linear(embedding_size, num_classes)\n\n        # Initialize weights\n        nn.init.kaiming_normal_(self.fc.weight, mode='fan_out')\n        nn.init.constant_(self.fc.bias, 0)\n        nn.init.constant_(self.bn.weight, 1)\n        nn.init.constant_(self.bn.bias, 0)\n        nn.init.normal_(self.classifier.weight, std=0.001)\n        nn.init.constant_(self.classifier.bias, 0)\n\n    def forward(self, x):\n        x = self.backbone(x)\n        x = self.global_pool(x)\n        x = torch.flatten(x, 1)\n        embedding = self.fc(x)\n        embedding = self.bn(embedding)\n        out = self.classifier(embedding)\n        return embedding, out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.117010Z","iopub.execute_input":"2025-04-22T23:20:47.117295Z","iopub.status.idle":"2025-04-22T23:20:47.138421Z","shell.execute_reply.started":"2025-04-22T23:20:47.117270Z","shell.execute_reply":"2025-04-22T23:20:47.137781Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TripletLoss(nn.Module):\n    def __init__(self, margin=1.0):\n        super(TripletLoss, self).__init__()\n        self.margin = margin\n        \n    def calc_euclidean(self, x1, x2):\n        return (x1 - x2).pow(2).sum(1)\n    \n    def forward(self, anchor: torch.Tensor, positive: torch.Tensor, negative: torch.Tensor) -> torch.Tensor:\n        distance_positive = self.calc_euclidean(anchor, positive)\n        distance_negative = self.calc_euclidean(anchor, negative)\n        losses = torch.relu(distance_positive - distance_negative + self.margin)\n\n        return losses.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.139284Z","iopub.execute_input":"2025-04-22T23:20:47.139567Z","iopub.status.idle":"2025-04-22T23:20:47.153306Z","shell.execute_reply.started":"2025-04-22T23:20:47.139540Z","shell.execute_reply":"2025-04-22T23:20:47.152548Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_reid_model(model, train_loader, optimizer, criterion, device):\n    model.train()\n    total_loss = 0.0\n    triplet_batches = 0\n\n    for images, labels, _ in train_loader:\n        images = images.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad()\n        embeddings, _ = model(images)\n\n        # Group embeddings by label\n        anchor_list, positive_list, negative_list = [], [], []\n\n        for i in range(len(labels)):\n            anchor = embeddings[i]\n            label = labels[i]\n\n            # Find indices of positive and negative samples\n            positive_indices = (labels == label).nonzero(as_tuple=False).squeeze(1)\n            negative_indices = (labels != label).nonzero(as_tuple=False).squeeze(1)\n\n            # Skip if there are no valid positives/negatives\n            if len(positive_indices) <= 1 or len(negative_indices) == 0:\n                continue\n\n            # Choose one positive (not the anchor itself)\n            pos_index = positive_indices[positive_indices != i]\n            if len(pos_index) == 0:\n                continue\n            pos_index = pos_index[0]\n\n            # Choose hardest negative (closest to anchor)\n            neg_embeds = embeddings[negative_indices]\n            anchor_dist = torch.norm(anchor - neg_embeds, dim=1)\n            neg_index = negative_indices[anchor_dist.argmin()]\n\n            # Append triplet\n            anchor_list.append(anchor)\n            positive_list.append(embeddings[pos_index])\n            negative_list.append(embeddings[neg_index])\n\n        if len(anchor_list) == 0:\n            continue  # Skip batch if no valid triplets\n\n        anchor_tensor   = torch.stack(anchor_list)\n        positive_tensor = torch.stack(positive_list)\n        negative_tensor = torch.stack(negative_list)\n\n        # Compute triplet loss\n        loss = criterion(anchor_tensor, positive_tensor, negative_tensor)\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n        triplet_batches += 1\n\n        if triplet_batches == 0:\n            print(\" No valid triplets found in this epoch.\")\n            return torch.tensor(0.0)\n        avg_loss = total_loss / triplet_batches\n        print(f'Train Loss: {avg_loss:.4f}')\n        return avg_loss\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.154083Z","iopub.execute_input":"2025-04-22T23:20:47.154333Z","iopub.status.idle":"2025-04-22T23:20:47.174518Z","shell.execute_reply.started":"2025-04-22T23:20:47.154307Z","shell.execute_reply":"2025-04-22T23:20:47.173829Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  Extract features from training data\ndef extract_features(model, data_loader, device):\n    model.eval()\n    all_features = []\n    all_individuals = []\n    with torch.no_grad():\n        for images, labels, individuals in data_loader:\n            images = images.to(device)\n            embeddings, _ = model(images)\n            all_features.append(embeddings.cpu().numpy())\n            all_individuals.extend(individuals)\n    return np.concatenate(all_features, axis=0), np.array(all_individuals)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.175362Z","iopub.execute_input":"2025-04-22T23:20:47.175547Z","iopub.status.idle":"2025-04-22T23:20:47.193466Z","shell.execute_reply.started":"2025-04-22T23:20:47.175531Z","shell.execute_reply":"2025-04-22T23:20:47.192694Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from collections import Counter\ndef train_model():\n     set_seed()\n     device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n     print(f\"Using device: {device}\")\n\n     train_transform = transforms.Compose([\n         transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n         transforms.RandomHorizontalFlip(),\n         transforms.ToTensor(),\n         transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n     ])\n\n     print('--- Training on All Species ---')\n     train_dataset = create_combined_species_dataset(SPECIES_LIST, TRAIN_DIR, train_transform)\n     combined_labels = []\n     for _, label, _ in train_dataset:\n         combined_labels.append(label)\n     print(\"Combined label distribution:\", Counter(combined_labels))\n     \n     train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)\n     \n     # Standard training setup\n     num_classes = 3\n     model = ReIDModel(num_classes, embedding_size=EMBEDDING_SIZE, weights=None).to(device)\n     optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n     criterion = TripletLoss(margin=0.3)\n   \n     for epoch in range(EPOCHS):\n         print(f'Epoch {epoch+1}/{EPOCHS}')\n         train_reid_model(model, train_loader, optimizer, criterion, device)\n     # --- After training the model on all species ---\n     # Extract features from the combined training set\n     features, individuals = extract_features(model, train_loader, device)\n     # Save the unified trained model and features\n     torch.save(model.state_dict(), \"reid_model_all_species.pth\")\n     torch.save(features, \"train_features.pth\")\n     torch.save(individuals, \"train_individuals.pth\")\n     \n     print(\"Training complete and model saved.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.194161Z","iopub.execute_input":"2025-04-22T23:20:47.194399Z","iopub.status.idle":"2025-04-22T23:20:47.203372Z","shell.execute_reply.started":"2025-04-22T23:20:47.194381Z","shell.execute_reply":"2025-04-22T23:20:47.202648Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Prediction Approach\ndef predict_advanced(model, test_loader, train_features, train_individuals, device, new_threshold=NEW_THRESHOLD, k_neighbors=5):\n    model.eval()\n    predictions = []\n    image_ids = []\n    knn = NearestNeighbors(n_neighbors=k_neighbors, metric='cosine')\n    knn.fit(train_features)\n\n    with torch.no_grad():\n        for images, img_ids, species, _ in test_loader:\n            images = images.to(device)\n            embeddings, _ = model(images)\n            for embedding in embeddings.cpu().numpy():\n                distances, indices = knn.kneighbors(embedding.reshape(1, -1))\n                avg_distance = np.mean(distances)\n\n                if avg_distance > new_threshold:\n                    predictions.append('new_individual')\n                else:\n                    nearest_individuals = train_individuals[indices[0]]\n                    unique_individuals, counts = np.unique(nearest_individuals, return_counts=True)\n                    predicted_individual_base = unique_individuals[np.argmax(counts)]\n\n                    year = '2022' if 'SeaTurtle' in species else '2025'\n                    predictions.append(f'{species}ID{year}_{predicted_individual_base}')\n            image_ids.extend(list(img_ids))\n    return predictions, image_ids","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.224272Z","iopub.execute_input":"2025-04-22T23:20:47.224547Z","iopub.status.idle":"2025-04-22T23:20:47.239942Z","shell.execute_reply.started":"2025-04-22T23:20:47.224520Z","shell.execute_reply":"2025-04-22T23:20:47.239114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_model():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\" Inference on device: {device}\")\n\n    label_inv = {0: \"SeaTurtle\", 1: \"Salamander\", 2: \"Lynx\"}        \n    species_to_num = {v: k for k, v in label_inv.items()}          \n    \n    #  Load trained model & saved features \n    model = ReIDModel(num_classes=3,\n                      embedding_size=EMBEDDING_SIZE,\n                      weights=None).to(device)\n    model.load_state_dict(torch.load(\"reid_model_all_species.pth\", map_location=device, weights_only=True))    \n    model.eval()\n\n    train_features   = torch.load(\"train_features.pth\")\n    train_individual = torch.load(\"train_individuals.pth\")\n    train_features = torch.tensor(train_features)  # convert to torch tensor\n    train_features = F.normalize(train_features, dim=1).cpu().numpy()\n\n\n    # Build query dataset \n    test_transform = transforms.Compose([\n        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406],\n                             [0.229, 0.224, 0.225])\n    ])\n\n    query_ds   = []\n    for sp in [\"SeaTurtle\", \"Salamander\", \"Lynx\"]:\n        query_ds.extend( AdvancedTestDataset(TEST_DIR[sp], sp, transform=test_transform) )\n\n    query_loader = DataLoader(query_ds,\n                              batch_size=BATCH_SIZE,\n                              shuffle=False)\n\n    #  Predict with thresholding \n    preds, img_ids = predict_advanced(model,\n                                      query_loader,\n                                      train_features,\n                                      np.array(train_individual),\n                                      device,\n                                      new_threshold=NEW_THRESHOLD,\n                                      k_neighbors=K_NEIGHBORS)\n\n    # Optional accuracy at species level \n    # build true label list from dataset items\n    true_species = [species_to_num[item[2]] for item in query_ds]  # item = (img, id, species)\n    pred_species = []\n    for p in preds:\n        if \"SeaTurtle\" in p:   pred_species.append(0)\n        elif \"Salamander\" in p:pred_species.append(1)\n        elif \"Lynx\" in p:      pred_species.append(2)\n        else:                  pred_species.append(-1)   # new_individual\n\n    valid = [i for i, ps in enumerate(pred_species) if ps != -1]\n    if valid:\n        acc = accuracy_score([true_species[i] for i in valid],\n                             [pred_species[i] for i in valid])\n        print(f\" Species‑level top‑1 accuracy on known individuals: {acc:.4f}\")\n    else:\n        print(\" No known‑individual predictions; species accuracy not computed.\")\n\n    # Save CSV \n    out_df = pd.DataFrame({\"image_id\": img_ids,\n                           \"identity\" : preds})\n    out_df.to_csv(\"animal_predictions.csv\", index=False)\n    print(\"Saved predictions to  /kaggle/working/animal_predictions.csv\")\n\n    #  Preview first 10 rows -----\n    print(\"\\nFirst 10 rows:\")\n    print(out_df.head(10))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:57:15.977681Z","iopub.execute_input":"2025-04-22T23:57:15.978086Z","iopub.status.idle":"2025-04-22T23:57:15.992730Z","shell.execute_reply.started":"2025-04-22T23:57:15.978053Z","shell.execute_reply":"2025-04-22T23:57:15.991836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_model()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:20:47.256191Z","iopub.execute_input":"2025-04-22T23:20:47.256363Z","iopub.status.idle":"2025-04-22T23:51:30.008867Z","shell.execute_reply.started":"2025-04-22T23:20:47.256348Z","shell.execute_reply":"2025-04-22T23:51:30.007913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predict_model()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:57:19.161825Z","iopub.execute_input":"2025-04-22T23:57:19.162176Z","iopub.status.idle":"2025-04-22T23:58:01.307177Z","shell.execute_reply.started":"2025-04-22T23:57:19.162151Z","shell.execute_reply":"2025-04-22T23:58:01.305968Z"}},"outputs":[],"execution_count":null}]}