{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.12"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"datasetVersion","sourceId":14955028,"datasetId":9571822,"databundleVersionId":15825674}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":10531.810347,"end_time":"2026-02-28T03:24:54.783949","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-02-28T00:29:22.973602","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"65dac4da","cell_type":"markdown","source":"# Cell 1","metadata":{"papermill":{"duration":0.003529,"end_time":"2026-02-28T00:29:25.435507","exception":false,"start_time":"2026-02-28T00:29:25.431978","status":"completed"},"tags":[]}},{"id":"1d9de071","cell_type":"code","source":"import os, glob, random\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\n\nimport torchvision\nfrom torchvision.models.detection import fasterrcnn_resnet50_fpn_v2\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nimport torchvision.ops as ops\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nfrom sklearn.model_selection import train_test_split\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"DEVICE:\", DEVICE, \"| torch\", torch.__version__, \"| tv\", torchvision.__version__)\n\ndef merge_boxes_nms(boxes_list, scores_list, iou_thr=0.55):\n    boxes_t, scores_t = [], []\n    for b, s in zip(boxes_list, scores_list):\n        if b is None or len(b) == 0:\n            continue\n        boxes_t.append(torch.as_tensor(b, dtype=torch.float32))\n        scores_t.append(torch.as_tensor(s, dtype=torch.float32))\n    if len(boxes_t) == 0:\n        return np.zeros((0,4), np.float32), np.array([], np.float32)\n\n    boxes_t = torch.cat(boxes_t, 0)\n    scores_t = torch.cat(scores_t, 0)\n    keep = ops.nms(boxes_t, scores_t, iou_thr)\n    return boxes_t[keep].cpu().numpy(), scores_t[keep].cpu().numpy()","metadata":{"execution":{"iopub.status.busy":"2026-03-01T11:05:38.455574Z","iopub.execute_input":"2026-03-01T11:05:38.455841Z","iopub.status.idle":"2026-03-01T11:05:49.040468Z","shell.execute_reply.started":"2026-03-01T11:05:38.455815Z","shell.execute_reply":"2026-03-01T11:05:49.039554Z"},"papermill":{"duration":12.910786,"end_time":"2026-02-28T00:29:38.348929","exception":false,"start_time":"2026-02-28T00:29:25.438143","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"4e03884f","cell_type":"markdown","source":"# Cell 2","metadata":{"papermill":{"duration":0.002701,"end_time":"2026-02-28T00:29:38.354395","exception":false,"start_time":"2026-02-28T00:29:38.351694","status":"completed"},"tags":[]}},{"id":"e72f347b","cell_type":"code","source":"SEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED); torch.cuda.manual_seed_all(SEED)\n\nTRAIN_IMAGES_DIR = \"/kaggle/input/datasets/asdhjasdjhka/testing/public/train/images\"\nTRAIN_LABELS_DIR = \"/kaggle/input/datasets/asdhjasdjhka/testing/public/train/labels\"    \nTEST_IMAGES_DIR  = \"/kaggle/input/datasets/asdhjasdjhka/testing/public/test/images\"\n\nEPOCHS = 18\nBATCH_SIZE = 4\nLR = 2e-4\nWEIGHT_DECAY = 1e-4\n\nSCORE_THR = 0.25  \nNMS_THR_TTA = 0.6     \nDET_PER_IMG = 100\n\nMIN_WH = 1.0","metadata":{"execution":{"iopub.status.busy":"2026-03-01T11:05:49.042776Z","iopub.execute_input":"2026-03-01T11:05:49.043399Z","iopub.status.idle":"2026-03-01T11:05:49.056310Z","shell.execute_reply.started":"2026-03-01T11:05:49.043359Z","shell.execute_reply":"2026-03-01T11:05:49.055593Z"},"papermill":{"duration":0.014745,"end_time":"2026-02-28T00:29:38.371700","exception":false,"start_time":"2026-02-28T00:29:38.356955","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"508abbb7","cell_type":"markdown","source":"# Cell 3","metadata":{"papermill":{"duration":0.002549,"end_time":"2026-02-28T00:29:38.376793","exception":false,"start_time":"2026-02-28T00:29:38.374244","status":"completed"},"tags":[]}},{"id":"ca31a68c","cell_type":"code","source":"class VinfastDataset(Dataset):\n    def __init__(self, images_dir, labels_dir=None, image_files=None, train=True):\n        self.images_dir = images_dir\n        self.labels_dir = labels_dir\n        self.train = train\n\n        if image_files is None:\n            self.image_paths = sorted(glob.glob(os.path.join(images_dir, \"*.*\")))\n        else:\n            self.image_paths = [os.path.join(images_dir, f) for f in image_files]\n\n        if train:\n            self.aug = A.Compose([\n                A.HorizontalFlip(p=0.5),\n                A.RandomBrightnessContrast(p=0.5),\n                A.HueSaturationValue(p=0.3),\n                A.GaussianBlur(p=0.15),\n                A.GaussNoise(p=0.15),\n                ToTensorV2()\n            ], bbox_params=A.BboxParams(format=\"pascal_voc\", label_fields=[\"labels\"], min_visibility=0.0))\n        else:\n            self.aug = A.Compose([ToTensorV2()],\n                                 bbox_params=A.BboxParams(format=\"pascal_voc\", label_fields=[\"labels\"], min_visibility=0.0))\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def _load_yolo_boxes(self, label_path, w, h):\n        boxes = []\n        if not os.path.exists(label_path):\n            return np.zeros((0,4), np.float32)\n\n        with open(label_path, \"r\") as f:\n            for line in f:\n                p = line.strip().split()\n                if len(p) < 5: \n                    continue\n                _, xc, yc, bw, bh = map(float, p[:5])\n                x1 = (xc - bw/2) * w\n                y1 = (yc - bh/2) * h\n                x2 = (xc + bw/2) * w\n                y2 = (yc + bh/2) * h\n                # clamp\n                x1 = max(0, min(x1, w-1)); x2 = max(0, min(x2, w-1))\n                y1 = max(0, min(y1, h-1)); y2 = max(0, min(y2, h-1))\n                if x2 > x1 and y2 > y1:\n                    boxes.append([x1,y1,x2,y2])\n\n        if len(boxes) == 0:\n            return np.zeros((0,4), np.float32)\n        return np.array(boxes, np.float32)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        image_id = os.path.splitext(os.path.basename(img_path))[0]\n        img = np.array(Image.open(img_path).convert(\"RGB\"))\n        h, w = img.shape[:2]\n\n        boxes = np.zeros((0,4), np.float32)\n        if self.labels_dir is not None:\n            label_path = os.path.join(self.labels_dir, image_id + \".txt\")\n            boxes = self._load_yolo_boxes(label_path, w, h)\n\n        labels = np.ones((len(boxes),), dtype=np.int64)  # 1-class => 1\n\n        tr = self.aug(image=img, bboxes=boxes.tolist(), labels=labels.tolist())\n        img_t = tr[\"image\"].float() / 255.0\n\n        b = np.array(tr[\"bboxes\"], dtype=np.float32)\n        l = np.array(tr[\"labels\"], dtype=np.int64)\n\n        if b.size == 0:\n            b = np.zeros((0,4), np.float32)\n            l = np.zeros((0,), np.int64)\n        else:\n            b = b.reshape(-1,4)\n            l = l.reshape(-1)\n\n        target = {\n            \"boxes\": torch.tensor(b, dtype=torch.float32),\n            \"labels\": torch.tensor(l, dtype=torch.int64),\n            \"image_id\": torch.tensor([idx]),\n        }\n        return img_t, target, image_id\n\ndef collate_fn(batch):\n    imgs, targets, ids = zip(*batch)\n    return list(imgs), list(targets), list(ids)","metadata":{"execution":{"iopub.status.busy":"2026-03-01T11:05:49.057422Z","iopub.execute_input":"2026-03-01T11:05:49.057745Z","iopub.status.idle":"2026-03-01T11:05:49.076771Z","shell.execute_reply.started":"2026-03-01T11:05:49.057711Z","shell.execute_reply":"2026-03-01T11:05:49.075863Z"},"papermill":{"duration":0.019164,"end_time":"2026-02-28T00:29:38.398550","exception":false,"start_time":"2026-02-28T00:29:38.379386","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"1fc954e3","cell_type":"markdown","source":"# Cell 4","metadata":{"papermill":{"duration":0.00246,"end_time":"2026-02-28T00:29:38.403587","exception":false,"start_time":"2026-02-28T00:29:38.401127","status":"completed"},"tags":[]}},{"id":"eaeef92b","cell_type":"code","source":"def build_model():\n    model = fasterrcnn_resnet50_fpn_v2(weights=\"DEFAULT\")\n    in_features = model.roi_heads.box_predictor.cls_score.in_features\n    model.roi_heads.box_predictor = FastRCNNPredictor(in_features, 2)  \n\n    model.roi_heads.detections_per_img = DET_PER_IMG\n    model.roi_heads.nms_thresh = 0.6\n    return model","metadata":{"execution":{"iopub.status.busy":"2026-03-01T11:05:49.078074Z","iopub.execute_input":"2026-03-01T11:05:49.078578Z","iopub.status.idle":"2026-03-01T11:05:49.092546Z","shell.execute_reply.started":"2026-03-01T11:05:49.078554Z","shell.execute_reply":"2026-03-01T11:05:49.091737Z"},"papermill":{"duration":0.007868,"end_time":"2026-02-28T00:29:38.413981","exception":false,"start_time":"2026-02-28T00:29:38.406113","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"41264752","cell_type":"markdown","source":"# Cell 5","metadata":{"papermill":{"duration":0.002515,"end_time":"2026-02-28T00:29:38.419233","exception":false,"start_time":"2026-02-28T00:29:38.416718","status":"completed"},"tags":[]}},{"id":"cd913e17","cell_type":"code","source":"def train_one_epoch(model, loader, optimizer, scaler=None, max_norm=1.0):\n    model.train()\n    total = 0.0\n\n    for imgs, targets, _ in loader:\n        imgs = [im.to(DEVICE) for im in imgs]\n        targets = [{k: v.to(DEVICE) for k, v in t.items()} for t in targets]\n\n        optimizer.zero_grad(set_to_none=True)\n\n        if scaler is not None:\n            with torch.amp.autocast(device_type=\"cuda\"):\n                loss_dict = model(imgs, targets)\n                loss = sum(loss_dict.values())\n            scaler.scale(loss).backward()\n            scaler.unscale_(optimizer)\n            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)\n            scaler.step(optimizer)\n            scaler.update()\n        else:\n            loss_dict = model(imgs, targets)\n            loss = sum(loss_dict.values())\n            loss.backward()\n            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)\n            optimizer.step()\n\n        total += float(loss.detach().cpu().item())\n\n    return total / max(1, len(loader))","metadata":{"execution":{"iopub.status.busy":"2026-03-01T11:05:49.093517Z","iopub.execute_input":"2026-03-01T11:05:49.094041Z","iopub.status.idle":"2026-03-01T11:05:49.105026Z","shell.execute_reply.started":"2026-03-01T11:05:49.094016Z","shell.execute_reply":"2026-03-01T11:05:49.104278Z"},"papermill":{"duration":0.01056,"end_time":"2026-02-28T00:29:38.432366","exception":false,"start_time":"2026-02-28T00:29:38.421806","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"6930b5a1","cell_type":"markdown","source":"# Cell 6","metadata":{"papermill":{"duration":0.002506,"end_time":"2026-02-28T00:29:38.437385","exception":false,"start_time":"2026-02-28T00:29:38.434879","status":"completed"},"tags":[]}},{"id":"09c9b355","cell_type":"code","source":"@torch.no_grad()\ndef quick_val_stats(model, loader, score_thr=0.25):\n    model.eval()\n    n_img = 0\n    n_pred = 0\n    for imgs, targets, _ in loader:\n        imgs = [im.to(DEVICE) for im in imgs]\n        outs = model(imgs)\n        for out, t in zip(outs, targets):\n            scores = out[\"scores\"].detach().cpu().numpy()\n            n = int((scores >= score_thr).sum())\n            n_pred += n\n            n_img += 1\n    return {\"images\": n_img, \"pred_boxes\": n_pred, \"avg_boxes_per_img\": n_pred/max(1,n_img)}","metadata":{"execution":{"iopub.status.busy":"2026-03-01T11:05:49.106080Z","iopub.execute_input":"2026-03-01T11:05:49.106502Z","iopub.status.idle":"2026-03-01T11:05:49.120098Z","shell.execute_reply.started":"2026-03-01T11:05:49.106460Z","shell.execute_reply":"2026-03-01T11:05:49.119247Z"},"papermill":{"duration":0.008978,"end_time":"2026-02-28T00:29:38.448866","exception":false,"start_time":"2026-02-28T00:29:38.439888","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"a4b8a1ec","cell_type":"markdown","source":"# Cell 7","metadata":{"papermill":{"duration":0.002593,"end_time":"2026-02-28T00:29:38.454035","exception":false,"start_time":"2026-02-28T00:29:38.451442","status":"completed"},"tags":[]}},{"id":"815ad7a5","cell_type":"code","source":"train_paths = sorted(glob.glob(os.path.join(TRAIN_IMAGES_DIR, \"*.*\")))\ntrain_files = [os.path.basename(p) for p in train_paths]\nprint(\"Train images:\", len(train_files))\n\ntr_files, va_files = train_test_split(train_files, test_size=0.15, random_state=SEED, shuffle=True)\nprint(\"train:\", len(tr_files), \"val:\", len(va_files))\n\nds_tr = VinfastDataset(TRAIN_IMAGES_DIR, TRAIN_LABELS_DIR, tr_files, train=True)\nds_va = VinfastDataset(TRAIN_IMAGES_DIR, TRAIN_LABELS_DIR, va_files, train=False)\n\ndl_tr = DataLoader(ds_tr, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, collate_fn=collate_fn)\ndl_va = DataLoader(ds_va, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, collate_fn=collate_fn)\n\nmodel = build_model().to(DEVICE)\noptimizer = AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\nscheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS)\nscaler = torch.amp.GradScaler(\"cuda\") if DEVICE.type == \"cuda\" else None\n\nfor epoch in range(EPOCHS):\n    loss = train_one_epoch(model, dl_tr, optimizer, scaler=scaler, max_norm=1.0)\n    scheduler.step()\n    stats = quick_val_stats(model, dl_va, score_thr=SCORE_THR)\n    print(f\"Epoch {epoch+1}/{EPOCHS} | loss={loss:.4f} | val_avg_boxes={stats['avg_boxes_per_img']:.3f}\")\n\ntorch.save(model.state_dict(), \"v3pp_model.pth\")\nprint(\"Saved checkpoint: v3pp_model.pth\")\n","metadata":{"execution":{"iopub.status.busy":"2026-03-01T11:05:49.122166Z","iopub.execute_input":"2026-03-01T11:05:49.122498Z","execution_failed":"2026-03-01T11:07:44.331Z"},"papermill":{"duration":10367.299224,"end_time":"2026-02-28T03:22:25.755887","exception":false,"start_time":"2026-02-28T00:29:38.456663","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"91242059","cell_type":"code","source":"def clean_boxes(boxes, scores, w, h, min_wh=1.0):\n    if boxes is None or len(boxes) == 0:\n        return np.zeros((0,4), np.float32), np.array([], np.float32)\n    b = boxes.astype(np.float32).copy()\n    s = scores.astype(np.float32).copy()\n\n    b[:, [0,2]] = np.clip(b[:, [0,2]], 0, w-1)\n    b[:, [1,3]] = np.clip(b[:, [1,3]], 0, h-1)\n\n    wh = (b[:,2]-b[:,0], b[:,3]-b[:,1])\n    keep = (wh[0] >= min_wh) & (wh[1] >= min_wh)\n    b, s = b[keep], s[keep]\n    return b, s\n\n@torch.no_grad()\ndef tta_predict(model, img_t, score_thr=0.25, nms_thr=0.55):\n    model.eval()\n    img_t = img_t.to(DEVICE)\n\n    out1 = model([img_t])[0]\n    b1 = out1[\"boxes\"].detach().cpu().numpy()\n    s1 = out1[\"scores\"].detach().cpu().numpy()\n\n    img_flip = torch.flip(img_t, dims=[2])\n    out2 = model([img_flip])[0]\n    b2 = out2[\"boxes\"].detach().cpu().numpy()\n    s2 = out2[\"scores\"].detach().cpu().numpy()\n\n    W = img_t.shape[2]\n    if len(b2) > 0:\n        x1 = b2[:,0].copy()\n        x2 = b2[:,2].copy()\n        b2[:,0] = W - x2\n        b2[:,2] = W - x1\n\n    # threshold\n    k1 = s1 >= score_thr\n    k2 = s2 >= score_thr\n    b1, s1 = b1[k1], s1[k1]\n    b2, s2 = b2[k2], s2[k2]\n\n    b, s = merge_boxes_nms([b1, b2], [s1, s2], iou_thr=nms_thr)\n    return b, s","metadata":{"execution":{"execution_failed":"2026-03-01T11:07:44.331Z"},"papermill":{"duration":0.020379,"end_time":"2026-02-28T03:22:25.780150","exception":false,"start_time":"2026-02-28T03:22:25.759771","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"cc66f5dd","cell_type":"markdown","source":"# Cell 8","metadata":{"papermill":{"duration":0.003542,"end_time":"2026-02-28T03:22:25.787309","exception":false,"start_time":"2026-02-28T03:22:25.783767","status":"completed"},"tags":[]}},{"id":"cb904fa6","cell_type":"code","source":"if \"model\" not in globals():\n    model = build_model().to(DEVICE)\n    model.load_state_dict(torch.load(\"v3pp_model.pth\", map_location=DEVICE))\n    print(\"Loaded checkpoint: v3pp_model.pth\")\nmodel.eval()\n","metadata":{"execution":{"execution_failed":"2026-03-01T11:07:44.331Z"},"papermill":{"duration":0.014333,"end_time":"2026-02-28T03:22:25.805191","exception":false,"start_time":"2026-02-28T03:22:25.790858","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"b70a6cb8","cell_type":"code","source":"def format_prediction_string(boxes, scores):\n    if boxes is None or len(boxes) == 0:\n        return \" \" \n    parts = []\n    for (x1, y1, x2, y2), sc in zip(boxes, scores):\n        parts.append(f\"{sc:.6f} {int(x1)} {int(y1)} {int(x2)} {int(y2)}\")\n    return \" \".join(parts)\n\nSAMPLE_SUB_PATH = \"/kaggle/input/datasets/asdhjasdjhka/testing/public/sample_submission.csv\"\nsample = pd.read_csv(SAMPLE_SUB_PATH)\nexpected_ids = sample[\"image_id\"].astype(str).tolist()\n\ntest_paths = sorted(glob.glob(os.path.join(TEST_IMAGES_DIR, \"*.*\")))\ntest_files = [os.path.basename(p) for p in test_paths]\nds_test = VinfastDataset(TEST_IMAGES_DIR, labels_dir=None, image_files=test_files, train=False)\n\nall_test_paths = glob.glob(os.path.join(TEST_IMAGES_DIR, \"*.*\"))\npath_map = {os.path.basename(p): p for p in all_test_paths}\nstem_map = {os.path.splitext(os.path.basename(p))[0]: p for p in all_test_paths}\n\ndef get_actual_path(image_id):\n    if image_id in path_map: return path_map[image_id]\n    if image_id in stem_map: return stem_map[image_id]\n    return None\n\nrows = []\nmodel.eval()\nprint(f\"Bắt đầu dự đoán {len(expected_ids)} ảnh...\")\n\nwith torch.no_grad():\n    for image_id in expected_ids:\n        img_path = get_actual_path(image_id)\n        \n        if img_path is None:\n            rows.append({\"image_id\": image_id, \"prediction_string\": \" \"})\n            continue\n\n        img_pil = Image.open(img_path).convert(\"RGB\")\n        w, h = img_pil.size\n        \n        img_np = np.array(img_pil)\n        transformed = ds_test.aug(image=img_np, bboxes=[], labels=[])\n        img_t = transformed[\"image\"].float().to(DEVICE) / 255.0\n\n        boxes, scores = tta_predict(model, img_t, score_thr=SCORE_THR, nms_thr=NMS_THR_TTA)\n        \n        boxes, scores = clean_boxes(boxes, scores, w=w, h=h, min_wh=MIN_WH)\n\n        pred_str = format_prediction_string(boxes, scores)\n        rows.append({\"image_id\": image_id, \"prediction_string\": pred_str})\n\nsub = pd.DataFrame(rows)\nsub.to_csv(\"submission.csv\", index=False)\n\nsub.head()","metadata":{"execution":{"execution_failed":"2026-03-01T11:07:44.331Z"},"papermill":{"duration":146.124948,"end_time":"2026-02-28T03:24:51.934235","exception":false,"start_time":"2026-02-28T03:22:25.809287","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"4522a974","cell_type":"markdown","source":"# Cell 9","metadata":{"papermill":{"duration":0.004189,"end_time":"2026-02-28T03:24:51.942576","exception":false,"start_time":"2026-02-28T03:24:51.938387","status":"completed"},"tags":[]}},{"id":"1e5d6fa8","cell_type":"code","source":"sub_path = \"submission.csv\"\ndf = pd.read_csv(sub_path)\n\nprint(\"Rows:\", len(df), \"Cols:\", df.columns.tolist())\nprint(\"Empty preds:\", (df[\"prediction_string\"] == \" \").sum())\n\nbad = 0\nfor s in df[\"prediction_string\"].astype(str).values:\n    if s == \" \":\n        continue\n    parts = s.strip().split()\n    if len(parts) % 5 != 0:\n        bad += 1\nprint(\"Bad rows (len%5!=0):\", bad)\n\ndf.head()","metadata":{"execution":{"execution_failed":"2026-03-01T11:07:44.331Z"},"papermill":{"duration":0.017978,"end_time":"2026-02-28T03:24:51.964730","exception":false,"start_time":"2026-02-28T03:24:51.946752","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null}]}