{"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":[{"sourceType":"competition","sourceId":126777,"databundleVersionId":15314950}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.metrics.pairwise import cosine_similarity\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nimport torchvision.models as models\nfrom PIL import Image\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Using device:\", DEVICE)\n\nDATA_PATH = \"/kaggle/input/jaguar-re-id\"\n\n# 🔥 FIXED PATHS (important)\nTRAIN_DIR = f\"{DATA_PATH}/train/train\"\nTEST_DIR  = f\"{DATA_PATH}/test/test\"\n\nBATCH_SIZE = 32\nEPOCHS = 12\nLR = 1e-4\nEMBED_DIM = 512","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T13:05:52.937743Z","iopub.execute_input":"2026-02-20T13:05:52.938697Z","iopub.status.idle":"2026-02-20T13:05:52.947107Z","shell.execute_reply.started":"2026-02-20T13:05:52.938653Z","shell.execute_reply":"2026-02-20T13:05:52.946310Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class JaguarDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None, train=True):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n        self.train = train\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = os.path.join(self.img_dir, row[\"filename\"])\n        image = Image.open(img_path).convert(\"RGB\")\n\n        if self.transform:\n            image = self.transform(image)\n\n        if self.train:\n            return image, row[\"label\"]\n        else:\n            return image, row[\"filename\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T13:05:52.948571Z","iopub.execute_input":"2026-02-20T13:05:52.948827Z","iopub.status.idle":"2026-02-20T13:05:52.961578Z","shell.execute_reply.started":"2026-02-20T13:05:52.948800Z","shell.execute_reply":"2026-02-20T13:05:52.961009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ReIDModel(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        self.backbone = models.resnet50(weights=\"IMAGENET1K_V1\")\n        self.backbone.fc = nn.Identity()\n        self.embedding = nn.Linear(2048, EMBED_DIM)\n        self.classifier = nn.Linear(EMBED_DIM, num_classes)\n\n    def forward(self, x, return_embedding=False):\n        features = self.backbone(x)\n        embedding = self.embedding(features)\n        embedding = F.normalize(embedding, p=2, dim=1)\n\n        if return_embedding:\n            return embedding\n\n        logits = self.classifier(embedding)\n        return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T13:05:52.962488Z","iopub.execute_input":"2026-02-20T13:05:52.962753Z","iopub.status.idle":"2026-02-20T13:05:52.972957Z","shell.execute_reply.started":"2026-02-20T13:05:52.962726Z","shell.execute_reply":"2026-02-20T13:05:52.972158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(f\"{DATA_PATH}/train.csv\")\n\nle = LabelEncoder()\ntrain_df[\"label\"] = le.fit_transform(train_df[\"ground_truth\"])\n\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(0.2,0.2,0.2,0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],\n                         [0.229,0.224,0.225])\n])\n\ntrain_dataset = JaguarDataset(\n    train_df,\n    TRAIN_DIR,   # 🔥 FIXED\n    transform=train_transform,\n    train=True\n)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T13:05:52.973897Z","iopub.execute_input":"2026-02-20T13:05:52.974212Z","iopub.status.idle":"2026-02-20T13:05:52.994850Z","shell.execute_reply.started":"2026-02-20T13:05:52.974190Z","shell.execute_reply":"2026-02-20T13:05:52.994119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = ReIDModel(num_classes=len(le.classes_)).to(DEVICE)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=LR)\n\nfor epoch in range(EPOCHS):\n    model.train()\n    total_loss = 0\n\n    for images, labels in tqdm(train_loader):\n        images = images.to(DEVICE)\n        labels = labels.to(DEVICE)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n\n    print(f\"Epoch {epoch+1}/{EPOCHS}, Loss: {total_loss/len(train_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T13:05:52.996229Z","iopub.execute_input":"2026-02-20T13:05:52.996476Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_pairs = pd.read_csv(f\"{DATA_PATH}/test.csv\")\n\ntest_images = pd.DataFrame({\n    \"filename\": sorted(os.listdir(TEST_DIR))\n})\n\ntest_transform = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],\n                         [0.229,0.224,0.225])\n])\n\ntest_dataset = JaguarDataset(\n    test_images,\n    TEST_DIR,   # 🔥 FIXED\n    transform=test_transform,\n    train=False\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=32,\n    shuffle=False,\n    num_workers=2\n)\n\nmodel.eval()\nembeddings = {}\n\nwith torch.no_grad():\n    for images, filenames in tqdm(test_loader):\n        images = images.to(DEVICE)\n        embs = model(images, return_embedding=True)\n\n        for fname, emb in zip(filenames, embs):\n            embeddings[fname] = emb.cpu().numpy()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"similarities = []\n\nfor _, row in tqdm(test_pairs.iterrows(), total=len(test_pairs)):\n    e1 = embeddings[row[\"query_image\"]]\n    e2 = embeddings[row[\"gallery_image\"]]\n\n    sim = cosine_similarity([e1], [e2])[0][0]\n    sim = (sim + 1) / 2  # scale to [0,1]\n\n    similarities.append(sim)\n\nsubmission = pd.DataFrame({\n    \"row_id\": test_pairs[\"row_id\"],\n    \"similarity\": similarities\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv created!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}