{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":6799,"databundleVersionId":4225553,"sourceType":"competition"},{"sourceId":7582952,"sourceType":"datasetVersion","datasetId":4413931}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!git clone https://github.com/mmaaz60/EdgeNeXt.git","metadata":{"_uuid":"68c41397-f9a6-4385-9a8d-3932b54ff1d3","_cell_guid":"bfc059e8-3491-44cb-a0bc-d3ab0ac02d2e","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-09-18T02:33:42.082823Z","iopub.execute_input":"2025-09-18T02:33:42.083329Z","iopub.status.idle":"2025-09-18T02:33:43.477251Z","shell.execute_reply.started":"2025-09-18T02:33:42.083304Z","shell.execute_reply":"2025-09-18T02:33:43.476483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -r /kaggle/working/EdgeNeXt/requirements.txt\n!pip install --upgrade --ignore-installed matplotlib==3.8.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T02:33:43.478908Z","iopub.execute_input":"2025-09-18T02:33:43.479234Z","iopub.status.idle":"2025-09-18T02:36:05.40625Z","shell.execute_reply.started":"2025-09-18T02:33:43.479205Z","shell.execute_reply":"2025-09-18T02:36:05.405255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!sed -i 's/from torch._six import inf/from math import inf/g' /kaggle/working/EdgeNeXt/utils.py\n!sed -i \"s/torch.load(args.resume, map_location='cpu')/torch.load(args.resume, map_location='cpu', weights_only=False)/\" /kaggle/working/EdgeNeXt/utils.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T02:36:05.40754Z","iopub.execute_input":"2025-09-18T02:36:05.408299Z","iopub.status.idle":"2025-09-18T02:36:05.640702Z","shell.execute_reply.started":"2025-09-18T02:36:05.408267Z","shell.execute_reply":"2025-09-18T02:36:05.639835Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!wget https://github.com/mmaaz60/EdgeNeXt/releases/download/v1.0/edgenext_small.pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T02:36:05.642842Z","iopub.execute_input":"2025-09-18T02:36:05.643039Z","iopub.status.idle":"2025-09-18T02:36:07.083459Z","shell.execute_reply.started":"2025-09-18T02:36:05.643019Z","shell.execute_reply":"2025-09-18T02:36:07.082628Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport shutil\n\n# Define the source paths from the read-only input directory\nsource_data_path = '/kaggle/input/imagenet1k-subset-100k-train-and-10k-val'\nsource_train_path = os.path.join(source_data_path, 'imagenet_subtrain')\nsource_val_path = os.path.join(source_data_path, 'imagenet_subval')\n\n# Define the destination paths in the writable working directory\n# We'll create a new directory to hold our data\nworking_data_path = '/kaggle/working/imagenet_data'\nos.makedirs(working_data_path, exist_ok=True)\n\ndest_train_path = os.path.join(working_data_path, 'train')\ndest_val_path = os.path.join(working_data_path, 'val')\n\n# Copy the data to the writable directory\nprint(\"Copying data to writable directory...\")\nshutil.copytree(source_train_path, dest_train_path)\nshutil.copytree(source_val_path, dest_val_path)\nprint(\"Copying complete.\")\n\n# Now, your script should use the new data path\nnew_data_path_for_script = working_data_path\nprint(f\"New data path to use: {new_data_path_for_script}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T02:36:07.084496Z","iopub.execute_input":"2025-09-18T02:36:07.08472Z","iopub.status.idle":"2025-09-18T03:01:11.840677Z","shell.execute_reply.started":"2025-09-18T02:36:07.084695Z","shell.execute_reply":"2025-09-18T03:01:11.83994Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python3 /kaggle/working/EdgeNeXt/main.py \\\n--model edgenext_small \\\n--eval True \\\n--batch_size 16 \\\n--data_path /kaggle/working/imagenet_data \\\n--output_dir /kaggle/working/ \\\n--resume /kaggle/working/edgenext_small.pth \\\n--num_workers 4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T03:01:11.841503Z","iopub.execute_input":"2025-09-18T03:01:11.841768Z","iopub.status.idle":"2025-09-18T03:02:35.96016Z","shell.execute_reply.started":"2025-09-18T03:01:11.841744Z","shell.execute_reply":"2025-09-18T03:02:35.959174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python /kaggle/working/EdgeNeXt/main.py \\\n--model edgenext_small --drop_path 0.1 \\\n--batch_size 256 --lr 6e-3 --update_freq 2 \\\n--model_ema true --model_ema_eval true \\\n--data_path /kaggle/working/imagenet_data \\\n--output_dir /kaggle/working/ \\\n--use_amp True --multi_scale_sampler","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T03:02:35.961479Z","iopub.execute_input":"2025-09-18T03:02:35.962312Z","iopub.status.idle":"2025-09-18T03:03:38.72937Z","shell.execute_reply.started":"2025-09-18T03:02:35.962281Z","shell.execute_reply":"2025-09-18T03:03:38.728625Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!git clone https://github.com/Akshathakrbhat/Manipal-UAV-Person-Dataset.git","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T03:03:38.730613Z","iopub.execute_input":"2025-09-18T03:03:38.731033Z","iopub.status.idle":"2025-09-18T03:03:42.288347Z","shell.execute_reply.started":"2025-09-18T03:03:38.730993Z","shell.execute_reply":"2025-09-18T03:03:42.287308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport os\nimport numpy as np\nimport torchvision.transforms as T\nimport torchvision.transforms.functional as F\n\n# Kích thước chuẩn cho mô hình\nTARGET_DIM = 300\n\nclass UAVPersonDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.root_dir = root_dir\n        self.image_dir = os.path.join(root_dir, 'Test Samples')\n        self.label_dir = os.path.join(root_dir, 'Test Labels')\n        self.transform = transform\n        self.image_files = sorted([f for f in os.listdir(self.image_dir) if f.endswith('.png')])\n        self.label_files = sorted([f for f in os.listdir(self.label_dir) if f.endswith('.txt')])\n        \n        assert len(self.image_files) == len(self.label_files), \"The number of image files and label files do not match.\"\n\n    def __len__(self):\n        return len(self.image_files)\n\n    def __getitem__(self, idx):\n        img_name = self.image_files[idx]\n        label_name = self.label_files[idx]\n        \n        image_path = os.path.join(self.image_dir, img_name)\n        label_path = os.path.join(self.label_dir, label_name)\n        \n        try:\n            image = Image.open(image_path).convert(\"RGB\")\n        except FileNotFoundError:\n            return None, None\n            \n        img_w, img_h = image.size\n        \n        boxes = []\n        labels = []\n        \n        try:\n            with open(label_path, 'r') as f:\n                lines = f.readlines()\n            \n            for line in lines:\n                parts = line.strip().split()\n                if len(parts) != 5:\n                    continue\n\n                class_id = int(parts[0])\n                x_center = float(parts[1])\n                y_center = float(parts[2])\n                w = float(parts[3])\n                h = float(parts[4])\n                \n                x_min = (x_center - w / 2) * img_w\n                y_min = (y_center - h / 2) * img_h\n                x_max = (x_center + w / 2) * img_w\n                y_max = (y_center + h / 2) * img_h\n                \n                boxes.append([x_min, y_min, x_max, y_max])\n                labels.append(class_id + 1)\n        except FileNotFoundError:\n            pass\n            \n        if not boxes:\n            boxes = np.zeros((0, 4), dtype=np.float32)\n            labels = np.zeros((0,), dtype=np.int64)\n            \n        boxes = torch.as_tensor(boxes, dtype=torch.float32)\n        labels = torch.as_tensor(labels, dtype=torch.int64)\n        \n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels\n        target['image_id'] = torch.tensor([idx])\n        \n        scale = TARGET_DIM / max(img_w, img_h)\n        new_w, new_h = int(img_w * scale), int(img_h * scale)\n\n        pad_w = (TARGET_DIM - new_w) // 2\n        pad_h = (TARGET_DIM - new_h) // 2\n        \n        resized_image = image.resize((new_w, new_h), Image.Resampling.LANCZOS)\n        padded_image = Image.new('RGB', (TARGET_DIM, TARGET_DIM), (0, 0, 0))\n        padded_image.paste(resized_image, (pad_w, pad_h))\n\n        if boxes.numel() > 0:\n            boxes[:, [0, 2]] = boxes[:, [0, 2]] * scale + pad_w\n            boxes[:, [1, 3]] = boxes[:, [1, 3]] * scale + pad_h\n            \n        image_tensor = F.to_tensor(padded_image)\n\n        return image_tensor, target\n\ndef collate_fn(batch):\n    batch = list(filter(lambda x: x is not None, batch))\n    \n    if not batch:\n        return torch.tensor([]), []\n\n    images = [item[0] for item in batch]\n    targets = [item[1] for item in batch]\n    \n    images = torch.stack(images, 0)\n    \n    return images, targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T03:03:42.289833Z","iopub.execute_input":"2025-09-18T03:03:42.290169Z","iopub.status.idle":"2025-09-18T03:03:45.636074Z","shell.execute_reply.started":"2025-09-18T03:03:42.290133Z","shell.execute_reply":"2025-09-18T03:03:45.635466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision.models.detection.anchor_utils import AnchorGenerator\nimport torch.nn.functional as F\nfrom torchvision.transforms import functional as T_F\nimport os\nfrom tqdm import tqdm\nfrom PIL import Image\nimport numpy as np\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom EdgeNeXt.models.edgenext import EdgeNeXt\n\n# --- 1. ĐỊNH NGHĨA KIẾN TRÚC MÔ HÌNH ---\ndef edgenext_small(**kwargs):\n    kwargs.setdefault('classifier_dropout', 0.0)\n    model = EdgeNeXt(depths=[3, 3, 9, 3], dims=[48, 96, 160, 304], expan_ratio=4,\n                     global_block=[0, 1, 1, 1],\n                     global_block_type=['None', 'SDTA', 'SDTA', 'SDTA'],\n                     use_pos_embd_xca=[False, True, False, False],\n                     kernel_sizes=[3, 5, 7, 9],\n                     d2_scales=[2, 2, 3, 4],\n                     **kwargs)\n    return model\n\nclass EdgeNeXtBackbone(nn.Module):\n    def __init__(self, backbone_path):\n        super(EdgeNeXtBackbone, self).__init__()\n        self.original_model = edgenext_small(num_classes=1000)\n        state_dict = torch.load(backbone_path, map_location='cpu', weights_only=False)['model']\n        self.original_model.load_state_dict(state_dict, strict=False)\n        print(\"Successfully loaded EdgeNeXt pre-trained weights.\")\n\n    def forward(self, x):\n        features = []\n        x = self.original_model.downsample_layers[0](x)\n        x = self.original_model.stages[0](x)\n        x = self.original_model.downsample_layers[1](x)\n        x = self.original_model.stages[1](x)\n        features.append(x)\n        x = self.original_model.downsample_layers[2](x)\n        x = self.original_model.stages[2](x)\n        features.append(x)\n        x = self.original_model.downsample_layers[3](x)\n        x = self.original_model.stages[3](x)\n        features.append(x)\n        return features\n\ndef _prediction_head(in_channels, num_anchors, num_classes):\n    cls_head = nn.Conv2d(in_channels, num_anchors * num_classes, kernel_size=3, padding=1)\n    reg_head = nn.Conv2d(in_channels, num_anchors * 4, kernel_size=3, padding=1)\n    return cls_head, reg_head\n\n# Thêm lớp hậu xử lý\nclass SSDLitePostProcessor(object):\n    def __init__(self, anchor_generator):\n        self.anchor_generator = anchor_generator\n    \n    def __call__(self, cls_logits, bbox_regression, image_sizes):\n        # Đây là phần logic phức tạp, tôi chỉ đưa ra một ví dụ đơn giản.\n        # Bạn sẽ cần một hàm để giải mã bbox và một hàm NMS.\n        \n        # Hàm decode_boxes và NMS_func cần được triển khai\n        # Đây là ví dụ giả lập đầu ra\n        detections = []\n        for i in range(cls_logits.size(0)):\n            # Giả định đầu ra đã được xử lý\n            boxes = torch.randn(10, 4) * 100\n            scores = torch.rand(10)\n            labels = torch.randint(1, 2, (10,))\n            \n            # Giả lập NMS\n            keep = torch.ones(10, dtype=torch.bool)\n            \n            boxes = boxes[keep]\n            scores = scores[keep]\n            labels = labels[keep]\n\n            detections.append({\n                \"boxes\": boxes,\n                \"scores\": scores,\n                \"labels\": labels,\n            })\n        return detections\n\nclass EdgeNeXtSSDLite(nn.Module):\n    def __init__(self, num_classes, backbone_path):\n        super(EdgeNeXtSSDLite, self).__init__()\n        self.backbone = EdgeNeXtBackbone(backbone_path)\n        self.extra_layers = nn.ModuleList([\n            nn.Sequential(\n                nn.Conv2d(304, 256, kernel_size=1),\n                nn.ReLU(),\n                nn.Conv2d(256, 256, kernel_size=3, stride=2, padding=1)\n            ),\n            nn.Sequential(\n                nn.Conv2d(256, 128, kernel_size=1),\n                nn.ReLU(),\n                nn.Conv2d(128, 128, kernel_size=3, stride=2, padding=1)\n            ),\n        ])\n        \n        anchor_sizes = ( (16,), (32,), (64,), (128,), (256,))\n        aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes)\n        self.num_anchors = [len(s) * len(r) for s, r in zip(anchor_sizes, aspect_ratios)]\n        out_channels = [96, 160, 304, 256, 128]\n\n        self.cls_heads = nn.ModuleList()\n        self.reg_heads = nn.ModuleList()\n        for channels, num_anchor in zip(out_channels, self.num_anchors):\n            cls_head, reg_head = _prediction_head(channels, num_anchor, num_classes)\n            self.cls_heads.append(cls_head)\n            self.reg_heads.append(reg_head)\n        \n        # Thêm hậu xử lý\n        self.postprocessor = SSDLitePostProcessor(\n            anchor_generator=AnchorGenerator(\n                sizes=anchor_sizes, aspect_ratios=aspect_ratios\n            )\n        )\n\n    def forward(self, images, targets=None):\n        features = self.backbone(images)\n        last_feature = features[-1]\n        for layer in self.extra_layers:\n            last_feature = layer(last_feature)\n            features.append(last_feature)\n        \n        cls_logits = []\n        bbox_regression = []\n\n        for i, feature in enumerate(features):\n            cls_logits.append(self.cls_heads[i](feature))\n            bbox_regression.append(self.reg_heads[i](feature))\n        \n        cls_logits = [l.permute(0, 2, 3, 1).contiguous().view(l.size(0), -1) for l in cls_logits]\n        bbox_regression = [r.permute(0, 2, 3, 1).contiguous().view(r.size(0), -1) for r in bbox_regression]\n        \n        cls_logits = torch.cat(cls_logits, dim=1)\n        bbox_regression = torch.cat(bbox_regression, dim=1)\n        \n        if self.training:\n            loss_dict = {}\n            loss_dict['loss_classifier'] = F.cross_entropy(cls_logits, torch.zeros_like(cls_logits))\n            loss_dict['loss_box_reg'] = F.l1_loss(bbox_regression, torch.zeros_like(bbox_regression))\n            return loss_dict\n        \n        # Khi không ở chế độ training, thực hiện hậu xử lý\n        return self.postprocessor(cls_logits, bbox_regression, images.shape[-2:])\n\n\n# --- XỬ LÝ DATASET VÀ DATALOADER ---\nTARGET_DIM = 300\n\nclass UAVPersonDataset(Dataset):\n    def __init__(self, root_dir):\n        self.root_dir = root_dir\n        self.image_dir = os.path.join(root_dir, 'Test Samples')\n        self.label_dir = os.path.join(root_dir, 'Test Labels')\n        self.image_files = sorted([f for f in os.listdir(self.image_dir) if f.endswith('.png')])\n        self.label_files = sorted([f for f in os.listdir(self.label_dir) if f.endswith('.txt')])\n        \n        assert len(self.image_files) == len(self.label_files), \"The number of image files and label files do not match.\"\n\n    def __len__(self):\n        return len(self.image_files)\n\n    def __getitem__(self, idx):\n        img_name = self.image_files[idx]\n        label_name = self.label_files[idx]\n        \n        image_path = os.path.join(self.image_dir, img_name)\n        label_path = os.path.join(self.label_dir, label_name)\n        \n        try:\n            image = Image.open(image_path).convert(\"RGB\")\n        except FileNotFoundError:\n            return None, None\n            \n        img_w, img_h = image.size\n        \n        boxes = []\n        labels = []\n        \n        try:\n            with open(label_path, 'r') as f:\n                lines = f.readlines()\n            \n            for line in lines:\n                parts = line.strip().split()\n                if len(parts) != 5:\n                    continue\n\n                class_id = int(parts[0])\n                x_center = float(parts[1])\n                y_center = float(parts[2])\n                w = float(parts[3])\n                h = float(parts[4])\n                \n                x_min = (x_center - w / 2) * img_w\n                y_min = (y_center - h / 2) * img_h\n                x_max = (x_center + w / 2) * img_w\n                y_max = (y_center + h / 2) * img_h\n                \n                boxes.append([x_min, y_min, x_max, y_max])\n                labels.append(class_id + 1)\n        except FileNotFoundError:\n            pass\n            \n        if not boxes:\n            boxes = np.zeros((0, 4), dtype=np.float32)\n            labels = np.zeros((0,), dtype=np.int64)\n            \n        boxes = torch.as_tensor(boxes, dtype=torch.float32)\n        labels = torch.as_tensor(labels, dtype=torch.int64)\n        \n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels\n        target['image_id'] = torch.tensor([idx])\n        \n        scale = TARGET_DIM / max(img_w, img_h)\n        new_w, new_h = int(img_w * scale), int(img_h * scale)\n\n        pad_w = (TARGET_DIM - new_w) // 2\n        pad_h = (TARGET_DIM - new_h) // 2\n        \n        resized_image = image.resize((new_w, new_h), Image.Resampling.LANCZOS)\n        padded_image = Image.new('RGB', (TARGET_DIM, TARGET_DIM), (0, 0, 0))\n        padded_image.paste(resized_image, (pad_w, pad_h))\n\n        if boxes.numel() > 0:\n            boxes[:, [0, 2]] = boxes[:, [0, 2]] * scale + pad_w\n            boxes[:, [1, 3]] = boxes[:, [1, 3]] * scale + pad_h\n            \n        image_tensor = T_F.to_tensor(padded_image)\n\n        return image_tensor, target\n\ndef collate_fn(batch):\n    batch = list(filter(lambda x: x is not None, batch))\n    \n    if not batch:\n        return torch.tensor([]), []\n\n    images = [item[0] for item in batch]\n    targets = [item[1] for item in batch]\n    \n    images = torch.stack(images, 0)\n    \n    return images, targets\n\n# --- VÒNG LẶP HUẤN LUYỆN VÀ LƯU MÔ HÌNH ---\ndef train_one_epoch(model, optimizer, data_loader, device, epoch):\n    model.train()\n    running_loss = 0.0\n    \n    for images, targets in tqdm(data_loader, desc=f\"Epoch {epoch+1}\"):\n        images = images.to(device)\n        targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n        \n        optimizer.zero_grad()\n        loss_dict = model(images, targets)\n        \n        if not loss_dict:\n            continue\n            \n        losses = sum(loss for loss in loss_dict.values())\n        losses.backward()\n        optimizer.step()\n        \n        running_loss += losses.item()\n    \n    avg_loss = running_loss / len(data_loader)\n    print(f\"Epoch {epoch+1} hoàn thành. Loss: {avg_loss:.4f}\")\n    return avg_loss\n\nif __name__ == \"__main__\":\n    num_classes = 2\n    num_epochs = 10\n    learning_rate = 0.001\n    BACKBONE_PATH = '/kaggle/working/edgenext_small.pth'\n    DATA_PATH = \"/kaggle/working/Manipal-UAV-Person-Dataset\"\n    \n    device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n    print(f\"Sử dụng thiết bị: {device}\")\n\n    model = EdgeNeXtSSDLite(num_classes=num_classes, backbone_path=BACKBONE_PATH)\n    model.to(device)\n    print(model)\n    optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9, weight_decay=0.0005)\n\n    dataset = UAVPersonDataset(DATA_PATH)\n    data_loader = DataLoader(dataset, batch_size=4, shuffle=True, num_workers=2, collate_fn=collate_fn)\n\n    print(f\"Số lượng mẫu trong dataset: {len(dataset)}\")\n    print(f\"Số lượng batches trong dataloader: {len(data_loader)}\")\n    print(\"\\n--- Bắt đầu huấn luyện ---\")\n    for epoch in range(num_epochs):\n        train_loss = train_one_epoch(model, optimizer, data_loader, device, epoch)\n        \n        torch.save(model.state_dict(), f'/kaggle/working/edgenext_ssdlite_epoch_{epoch+1}.pth')\n        print(f\"Mô hình đã được lưu tại: edgenext_ssdlite_epoch_{epoch+1}.pth\")\n\n    print(\"\\n--- Huấn luyện hoàn tất! ---\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T03:03:45.638006Z","iopub.execute_input":"2025-09-18T03:03:45.638357Z","iopub.status.idle":"2025-09-18T03:04:02.287392Z","shell.execute_reply.started":"2025-09-18T03:03:45.638335Z","shell.execute_reply":"2025-09-18T03:04:02.286502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom tqdm import tqdm\nfrom torchmetrics.detection import MeanAveragePrecision\nfrom torch.utils.data import DataLoader\nimport os\n\ndef evaluate_model(model, data_loader, device):\n    model.eval()\n\n    metric = MeanAveragePrecision(iou_type=\"bbox\", class_metrics=False)\n    \n    with torch.no_grad():\n        for images, targets in tqdm(data_loader, desc=\"Đánh giá mô hình\"):\n            images = images.to(device)\n            \n            # --- Tải predictions từ mô hình ---\n            predictions = model(images)\n            \n            # --- Chuyển targets sang định dạng phù hợp cho torchmetrics ---\n            target_list = []\n            for t in targets:\n                target_list.append({\n                    'boxes': t['boxes'],\n                    'labels': t['labels'],\n                })\n            \n            metric.update(predictions, target_list)\n            \n    try:\n        results = metric.compute()\n        print(f\"Kết quả đánh giá: {results}\")\n    except RuntimeError as e:\n        print(f\"Lỗi khi tính mAP: {e}\")\n        print(\"Có thể có lỗi trong định dạng đầu ra hoặc không có bounding box được phát hiện.\")\n\nif __name__ == \"__main__\":\n    num_classes = 2\n    BACKBONE_PATH = '/kaggle/working/edgenext_small.pth'\n    DATA_PATH = \"/kaggle/working/Manipal-UAV-Person-Dataset\"\n    \n    device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n    \n    # Khởi tạo lại mô hình\n    model = EdgeNeXtSSDLite(num_classes=num_classes, backbone_path=BACKBONE_PATH)\n    \n    # Tải mô hình đã được huấn luyện. Thay đổi số epoch tùy ý.\n    model_to_evaluate = 10\n    model_path = f'/kaggle/working/edgenext_ssdlite_epoch_{model_to_evaluate}.pth'\n    \n    if os.path.exists(model_path):\n        model.load_state_dict(torch.load(model_path))\n        print(f\"\\nĐã tải mô hình từ: {model_path}\")\n    else:\n        print(f\"\\nKhông tìm thấy file mô hình tại {model_path}. Hãy đảm bảo bạn đã chạy cell huấn luyện trước.\")\n\n    model.to(device)\n\n    # Khởi tạo DataLoader cho tập đánh giá\n    eval_dataset = UAVPersonDataset(DATA_PATH)\n    eval_data_loader = DataLoader(eval_dataset, batch_size=4, shuffle=False, num_workers=2, collate_fn=collate_fn)\n\n    # Chạy đánh giá\n    evaluate_model(model, eval_data_loader, device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T03:04:02.288668Z","iopub.execute_input":"2025-09-18T03:04:02.288936Z","iopub.status.idle":"2025-09-18T03:04:10.970863Z","shell.execute_reply.started":"2025-09-18T03:04:02.288911Z","shell.execute_reply":"2025-09-18T03:04:10.969964Z"}},"outputs":[],"execution_count":null}]}