{"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":126777,"databundleVersionId":15314950,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np \nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport torch\nimport torchvision\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, utils\nfrom pathlib import Path\nfrom PIL import Image\nimport torch.nn.functional as F\nfrom tqdm import tqdm\nimport torchvision.models as models","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.598437Z","iopub.execute_input":"2026-02-12T15:30:29.599372Z","iopub.status.idle":"2026-02-12T15:30:29.604854Z","shell.execute_reply.started":"2026-02-12T15:30:29.599325Z","shell.execute_reply":"2026-02-12T15:30:29.603873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train = pd.read_csv(\"/kaggle/input/jaguar-re-id/train.csv\")\ndf_test = pd.read_csv(\"/kaggle/input/jaguar-re-id/test.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.606228Z","iopub.execute_input":"2026-02-12T15:30:29.606554Z","iopub.status.idle":"2026-02-12T15:30:29.707243Z","shell.execute_reply.started":"2026-02-12T15:30:29.606497Z","shell.execute_reply":"2026-02-12T15:30:29.706367Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"classes = df_train[\"ground_truth\"].unique()\n\nclass_to_idx = {x: i for i,x in enumerate(classes)}\nidx_to_class = classes\n\noutput_size = len(classes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.708712Z","iopub.execute_input":"2026-02-12T15:30:29.708993Z","iopub.status.idle":"2026-02-12T15:30:29.714935Z","shell.execute_reply.started":"2026-02-12T15:30:29.708966Z","shell.execute_reply":"2026-02-12T15:30:29.713714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class JaguarTrainDataset(Dataset):\n    def __init__(self, df, transform=None):\n        super().__init__()\n        self.df = df\n\n        self.base_path = Path(\"/kaggle/input/jaguar-re-id/train/train\")\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        img_name = row[\"filename\"] \n        label = class_to_idx[row[\"ground_truth\"]]\n        \n        img_path = self.base_path / img_name\n        \n        image = Image.open(img_path).convert(\"RGB\")\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        return image, torch.tensor(label, dtype=torch.long)\n\n\nclass JaguarTestDataset(Dataset):\n    def __init__(self, image_list, base_path, transform=None):\n        super().__init__()\n\n        self.image_list = image_list\n        self.base_path = Path(base_path) \n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.image_list)\n\n    def __getitem__(self, idx):\n        img_name = self.image_list[idx]\n        \n        img_path = self.base_path / img_name\n        \n        image = Image.open(img_path).convert(\"RGB\")\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        return image, img_name\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.716135Z","iopub.execute_input":"2026-02-12T15:30:29.716468Z","iopub.status.idle":"2026-02-12T15:30:29.726913Z","shell.execute_reply.started":"2026-02-12T15:30:29.716440Z","shell.execute_reply":"2026-02-12T15:30:29.726067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomCrop((224, 224)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(degrees=15),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.01),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    transforms.RandomErasing(p=0.5, scale=(0.02, 0.15)) \n])\n\ntrain_dataset = JaguarTrainDataset(df_train, transform=train_transforms)\n\ntrain_loader = DataLoader(\n    dataset=train_dataset, \n    batch_size=32, \n    shuffle=True,\n    num_workers=2\n)\n\ntest_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) \n])\n\nunique_test_images = pd.concat([df_test[\"query_image\"], df_test[\"gallery_image\"]]).unique()\ntest_base_path = \"/kaggle/input/jaguar-re-id/test/test\"\n\ntest_dataset = JaguarTestDataset(\n    image_list=unique_test_images, \n    base_path=test_base_path, \n    transform=test_transforms\n)\n\ntest_loader = DataLoader(\n    dataset=test_dataset, \n    batch_size=32, \n    shuffle=False,  \n    num_workers=2 \n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.729023Z","iopub.execute_input":"2026-02-12T15:30:29.729400Z","iopub.status.idle":"2026-02-12T15:30:29.770271Z","shell.execute_reply.started":"2026-02-12T15:30:29.729366Z","shell.execute_reply":"2026-02-12T15:30:29.769585Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset[0][0].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.771174Z","iopub.execute_input":"2026-02-12T15:30:29.771457Z","iopub.status.idle":"2026-02-12T15:30:29.963587Z","shell.execute_reply.started":"2026-02-12T15:30:29.771433Z","shell.execute_reply":"2026-02-12T15:30:29.962708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train(model, train_loader, loss_fn, optimizer, device, epochs, save_path=\"jaguar_model.pth\"):\n    \n    for epoch in range(epochs):\n        print(f\"\\nEpoch {epoch+1}/{epochs}\")\n\n        # === TRAINING === \n        model.train()\n        total_train_loss = 0\n        train_correct = 0\n        total_samples = 0\n        \n        for i, (X, y) in enumerate(train_loader):\n            X, y = X.to(device), y.to(device)\n            \n            # Forward pass\n            y_pred = model(X)\n            loss = loss_fn(y_pred, y)\n            \n            # Backward pass\n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n            \n            # Stats\n            total_train_loss += loss.item()\n            \n            # Acc\n            preds = torch.argmax(y_pred, dim=1)\n            train_correct += (preds == y).sum().item()\n            total_samples += y.size(0)\n\n            if i % 10 == 0:\n                print(\".\", end=\"\", flush=True)\n\n        print()\n        \n        avg_train_loss = total_train_loss / len(train_loader)\n        train_accuracy = 100 * train_correct / total_samples\n        \n        print(f\"Epoch {epoch+1} | Train Loss: {avg_train_loss:.4f} | Train Acc: %{train_accuracy:.2f}\")\n\n        torch.save(model.state_dict(), save_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.964938Z","iopub.execute_input":"2026-02-12T15:30:29.965234Z","iopub.status.idle":"2026-02-12T15:30:29.973369Z","shell.execute_reply.started":"2026-02-12T15:30:29.965206Z","shell.execute_reply":"2026-02-12T15:30:29.972359Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class JaguarClassification(nn.Module):\n    def __init__(self, output_layer):\n        super().__init__()\n        \n        self.base_model = models.resnet18(weights=\"DEFAULT\")\n        num_features = self.base_model.fc.in_features \n        \n        self.base_model.fc = nn.Identity() \n        \n        self.embedding_layers = nn.Sequential(\n            \n            nn.Linear(num_features, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            \n            nn.Linear(256, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            \n            nn.Linear(256, 128) \n        )\n        \n        self.classifier_head = nn.Linear(128, output_layer)\n\n    def forward(self, x):\n        features = self.base_model(x)\n        embeddings = self.embedding_layers(features)\n        \n        if self.training:\n            return self.classifier_head(embeddings)\n        else:\n            return embeddings","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.974484Z","iopub.execute_input":"2026-02-12T15:30:29.974803Z","iopub.status.idle":"2026-02-12T15:30:29.988730Z","shell.execute_reply.started":"2026-02-12T15:30:29.974775Z","shell.execute_reply":"2026-02-12T15:30:29.987735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:29.990099Z","iopub.execute_input":"2026-02-12T15:30:29.990489Z","iopub.status.idle":"2026-02-12T15:30:30.009285Z","shell.execute_reply.started":"2026-02-12T15:30:29.990447Z","shell.execute_reply":"2026-02-12T15:30:30.008544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = JaguarClassification(output_size).to(device)\n\nLEARNING_RATE = 5e-4\nEPOCHS = 20\nMODEL_SAVE_PATH = \"jaguar_reid_model.pth\"\n\nloss_fn = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\ntrain(\n    model=model, \n    train_loader=train_loader, \n    loss_fn=loss_fn, \n    optimizer=optimizer, \n    device=device, \n    epochs=EPOCHS, \n    save_path=MODEL_SAVE_PATH\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:30.010459Z","iopub.execute_input":"2026-02-12T15:30:30.010834Z","iopub.status.idle":"2026-02-12T15:30:41.116505Z","shell.execute_reply.started":"2026-02-12T15:30:30.010797Z","shell.execute_reply":"2026-02-12T15:30:41.114601Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = JaguarClassification(output_size).to(device)\nmodel.load_state_dict(torch.load(\"jaguar_reid_model.pth\"))\nmodel.eval()\n\nembedding_dict = {}\n\nprint(\"Extracting embeddings...\")\n\nwith torch.inference_mode():\n    for images, names in tqdm(test_loader):\n        images = images.to(device)\n        embeddings = model(images)\n        \n        for i, name in enumerate(names):\n            embedding_dict[name] = embeddings[i].cpu()\n\nprint(f\"\\nExtracted features for {len(embedding_dict)} images.\")\nprint(\"Calculating similarity scores...\")\n\npredictions = []\n\nfor index, row in tqdm(df_test.iterrows(), total=len(df_test)):\n    img_a_name = row[\"query_image\"]\n    img_b_name = row[\"gallery_image\"]\n    \n    vec_a = embedding_dict[img_a_name]\n    vec_b = embedding_dict[img_b_name]\n    \n    similarity = F.cosine_similarity(vec_a, vec_b, dim=0).item()\n    predictions.append(max(0.0, similarity))\n\nsubmission = pd.DataFrame({\n    \"row_id\": df_test[\"row_id\"],\n    \"similarity\": predictions\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\n\nprint(\"Submission file 'submission.csv' created successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T15:30:41.118058Z","iopub.status.idle":"2026-02-12T15:30:41.118610Z","shell.execute_reply.started":"2026-02-12T15:30:41.118338Z","shell.execute_reply":"2026-02-12T15:30:41.118374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}