{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"gpu","dataSources":[{"sourceId":31703,"databundleVersionId":2871752,"sourceType":"competition"},{"sourceId":13789166,"sourceType":"datasetVersion","datasetId":8778388},{"sourceId":13793381,"sourceType":"datasetVersion","datasetId":8781569},{"sourceId":13798541,"sourceType":"datasetVersion","datasetId":8785211},{"sourceId":13800532,"sourceType":"datasetVersion","datasetId":8786700},{"sourceId":13801417,"sourceType":"datasetVersion","datasetId":8787344}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nfrom tqdm.notebook import tqdm\ntqdm.pandas()\nimport pandas as pd\nimport os\nimport cv2\nimport matplotlib.pyplot as plt\nimport glob\n\nimport shutil\nimport sys\nsys.path.append('../input/tensorflow-great-barrier-reef')\n\nfrom joblib import Parallel, delayed\n\nfrom IPython.display import display","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:34.715614Z","iopub.execute_input":"2025-11-20T07:13:34.715957Z","iopub.status.idle":"2025-11-20T07:13:35.309305Z","shell.execute_reply.started":"2025-11-20T07:13:34.715928Z","shell.execute_reply":"2025-11-20T07:13:35.308548Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"REMOVE_NOBBOX = True # remove images with no bbox\nROOT_DIR  = '/kaggle/input/tensorflow-great-barrier-reef/'\nIMAGE_DIR = '/kaggle/working/images' # directory to save images\nLABEL_DIR = '/kaggle/working/labels' # directory to save labels\n!mkdir -p {IMAGE_DIR}\n!mkdir -p {LABEL_DIR}\n# Cell này để cấu hình nơi lưu trữ ảnh","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:35.310507Z","iopub.execute_input":"2025-11-20T07:13:35.310893Z","iopub.status.idle":"2025-11-20T07:13:35.546935Z","shell.execute_reply.started":"2025-11-20T07:13:35.310868Z","shell.execute_reply":"2025-11-20T07:13:35.546086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train Data\ndf = pd.read_csv(f'{ROOT_DIR}/train.csv')\ndf['old_image_path'] = f'{ROOT_DIR}/train_images/video_'+df.video_id.astype(str)+'/'+df.video_frame.astype(str)+'.jpg'\ndf['image_path']  = f'{IMAGE_DIR}/'+df.image_id+'.jpg'\ndf['label_path']  = f'{LABEL_DIR}/'+df.image_id+'.txt'\ndf['annotations'] = df['annotations'].progress_apply(eval)\ndisplay(df.head(2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:35.547993Z","iopub.execute_input":"2025-11-20T07:13:35.548299Z","iopub.status.idle":"2025-11-20T07:13:36.002680Z","shell.execute_reply.started":"2025-11-20T07:13:35.548265Z","shell.execute_reply":"2025-11-20T07:13:36.002072Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Code in ra số lượng video (video_id) trước khi lọc ra các ảnh không có đối tượng\n# Đếm số lần xuất hiện của mỗi video_id\nvideo_id_counts = df['video_id'].value_counts()\n\nprint(\"\\n--- Số lần xuất hiện của mỗi Video ID trước khi lọc ảnh không có COTS ---\")\nprint(\"{:<15} {:<10}\".format(\"Video ID\", \"Số lượng ảnh\"))\nprint(\"-\" * 28) # Dòng kẻ ngang\nfor video_id, count in video_id_counts.items():\n    print(\"{:<15} {:<10}\".format(video_id, count))\nprint(\"-\" * 28)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:36.004096Z","iopub.execute_input":"2025-11-20T07:13:36.004659Z","iopub.status.idle":"2025-11-20T07:13:36.013469Z","shell.execute_reply.started":"2025-11-20T07:13:36.004640Z","shell.execute_reply":"2025-11-20T07:13:36.012854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df['num_bbox'] = df['annotations'].progress_apply(lambda x: len(x))\ndata = (df.num_bbox>0).value_counts(normalize=True)*100\n\nnum_images_with_bbox = (df.num_bbox > 0).sum()\n\n# Lấy tổng số hình ảnh\ntotal_images = len(df)\n\nprint(f\"No BBox: {data[0]:0.2f}% | With BBox: {data[1]:0.2f}%\")\nprint(f\"Số lượng chính xác hình ảnh có chứa bbox: {num_images_with_bbox}\")\nprint(f\"Tổng số hình ảnh: {total_images}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:36.014077Z","iopub.execute_input":"2025-11-20T07:13:36.014359Z","iopub.status.idle":"2025-11-20T07:13:36.071041Z","shell.execute_reply.started":"2025-11-20T07:13:36.014329Z","shell.execute_reply":"2025-11-20T07:13:36.070291Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if REMOVE_NOBBOX:\n    df = df.query(\"num_bbox>0\")\n# if REMOVE_NOBBOX:\n#     # tách ảnh empty & ảnh có bbox\n#     df_empty = df[df.num_bbox == 0]\n#     df_with_obj = df[df.num_bbox > 0]\n\n#     # giữ lại ngẫu nhiên 500 ảnh empty\n#     df_empty_sample = df_empty.sample(n=4000, random_state=42)\n\n#     # gộp lại\n#     df = pd.concat([df_with_obj, df_empty_sample], ignore_index=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:36.071897Z","iopub.execute_input":"2025-11-20T07:13:36.072149Z","iopub.status.idle":"2025-11-20T07:13:36.086179Z","shell.execute_reply.started":"2025-11-20T07:13:36.072123Z","shell.execute_reply":"2025-11-20T07:13:36.085478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Code in ra số lượng video (video_id) sau khi lọc ra các ảnh không có đối tượng\n# Đếm số lần xuất hiện của mỗi video_id\nvideo_id_counts = df['video_id'].value_counts()\n\nprint(\"\\n--- Số lần xuất hiện của mỗi Video ID sau khi lọc ảnh không có COTS ---\")\nprint(\"{:<15} {:<10}\".format(\"Video ID\", \"Số lượng ảnh\"))\nprint(\"-\" * 28) # Dòng kẻ ngang\nfor video_id, count in video_id_counts.items():\n    print(\"{:<15} {:<10}\".format(video_id, count))\nprint(\"-\" * 28)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:36.087009Z","iopub.execute_input":"2025-11-20T07:13:36.087317Z","iopub.status.idle":"2025-11-20T07:13:36.097639Z","shell.execute_reply.started":"2025-11-20T07:13:36.087294Z","shell.execute_reply":"2025-11-20T07:13:36.096835Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Lúc này sẽ lấy video_id = 2 để làm tập dữ liệu test**","metadata":{}},{"cell_type":"code","source":"__temp = (df.num_bbox>0).value_counts(normalize=True)*100\nprint(__temp)\nprint(df.head(1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:36.098489Z","iopub.execute_input":"2025-11-20T07:13:36.098784Z","iopub.status.idle":"2025-11-20T07:13:36.116360Z","shell.execute_reply.started":"2025-11-20T07:13:36.098759Z","shell.execute_reply":"2025-11-20T07:13:36.115728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Di chuyển ảnh từ dataset sang thư mục làm việc\ndef make_copy(row):\n    shutil.copyfile(row.old_image_path, row.image_path)\n    return\nimage_paths = df.old_image_path.tolist()\n_ = Parallel(n_jobs=-1, backend='threading')(delayed(make_copy)(row) for _, row in tqdm(df.iterrows(), total=len(df)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:36.117314Z","iopub.execute_input":"2025-11-20T07:13:36.117933Z","iopub.status.idle":"2025-11-20T07:13:52.191562Z","shell.execute_reply.started":"2025-11-20T07:13:36.117913Z","shell.execute_reply":"2025-11-20T07:13:52.190753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\ndef get_image_size(image_path):\n    with Image.open(image_path) as img:\n        width, height = img.size\n    return width, height\ndef bbox_to_yolo(row):\n    ano = row.annotations\n    imgPath = row.image_path\n    result = []\n    img_width, img_height = get_image_size(imgPath)\n    for bbox in ano:\n        x_min = bbox['x']\n        y_min = bbox['y']\n        w = bbox['width']\n        h = bbox['height']\n        # Tính tọa độ trung tâm\n        x_center = x_min + w / 2\n        y_center = y_min + h / 2\n    \n        # Chuẩn hóa các giá trị\n        x_center /= img_width\n        y_center /= img_height\n        w /= img_width\n        h /= img_height\n        result.append([x_center, y_center, w, h])\n    return result\n\ndf['bboxes'] = df.progress_apply(bbox_to_yolo, axis=1)\ndf.head(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:52.194067Z","iopub.execute_input":"2025-11-20T07:13:52.194327Z","iopub.status.idle":"2025-11-20T07:13:52.730949Z","shell.execute_reply.started":"2025-11-20T07:13:52.194308Z","shell.execute_reply":"2025-11-20T07:13:52.730231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\ndf_train_val, df_to_test = train_test_split(df, test_size=0.2, random_state=42, stratify=df['video_id'])\n\nprint(f\"Số lượng video test: {len(df_to_test)}\")\nprint(f\"Số lượng video train+val: {len(df_train_val)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:52.731707Z","iopub.execute_input":"2025-11-20T07:13:52.732044Z","iopub.status.idle":"2025-11-20T07:13:53.885772Z","shell.execute_reply.started":"2025-11-20T07:13:52.732027Z","shell.execute_reply":"2025-11-20T07:13:53.885067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !pip install -U albumentations ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:53.886478Z","iopub.execute_input":"2025-11-20T07:13:53.886798Z","iopub.status.idle":"2025-11-20T07:13:53.890442Z","shell.execute_reply.started":"2025-11-20T07:13:53.886772Z","shell.execute_reply":"2025-11-20T07:13:53.889746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import cv2\n# import numpy as np\n# import albumentations as A\n# import random\n# import shutil\n\n# def augment_image_to_meta(img, bboxes_yolo, img_id, img_path, save_dir, num_aug=2, seed=None):\n#     if seed is not None:\n#         random.seed(seed)\n#         np.random.seed(seed)\n\n#     os.makedirs(save_dir, exist_ok=True)\n#     h, w = img.shape[:2]\n\n#     # YOLO → Pascal VOC + clamp\n#     bboxes_pascal = []\n#     labels = []\n#     for bbox in bboxes_yolo:\n#         x_c, y_c, bw, bh = bbox\n#         x_min = max(0, min((x_c - bw/2) * w, w))\n#         y_min = max(0, min((y_c - bh/2) * h, h))\n#         x_max = max(0, min((x_c + bw/2) * w, w))\n#         y_max = max(0, min((y_c + bh/2) * h, h))\n#         bboxes_pascal.append([x_min, y_min, x_max, y_max])\n#         labels.append(0)  # dataset 1 class\n\n#     # Danh sách phép augment\n#     all_transforms = [\n#         A.Rotate(limit=45, border_mode=cv2.BORDER_REFLECT, p=1),\n#         A.HorizontalFlip(p=1),\n#         A.VerticalFlip(p=1),\n#         A.RandomBrightnessContrast(p=1),\n#         A.GaussianBlur(blur_limit=(3, 7), p=1),\n#         A.MotionBlur(blur_limit=7, p=1),\n#         A.RandomSizedBBoxSafeCrop(height=h, width=w, p=1)\n#     ]\n\n#     aug_meta = []\n\n#     # Copy ẢNH GỐC vào augmented\n#     original_copy_path = os.path.join(save_dir, f\"{img_id}.jpg\")\n#     if not os.path.exists(original_copy_path):\n#         shutil.copy(img_path, original_copy_path)\n\n#     aug_meta.append({\n#         \"image_id\": img_id,\n#         \"image_path\": original_copy_path,\n#         \"bboxes\": bboxes_yolo\n#     })\n\n#     # Sinh num_aug ảnh augment\n#     for i in range(1, num_aug+1):\n#         selected_transforms = random.sample(all_transforms, 3)\n#         aug = A.Compose(\n#             selected_transforms,\n#             bbox_params=A.BboxParams(\n#                 format='pascal_voc',\n#                 label_fields=['labels'],\n#                 check_each_transform=False\n#             )\n#         )\n\n#         transformed = aug(image=img, bboxes=bboxes_pascal, labels=labels)\n#         aug_img = transformed['image']\n#         aug_bboxes_pascal = transformed['bboxes']\n\n#         if len(aug_bboxes_pascal) == 0:\n#             continue\n\n#         # Pascal VOC → YOLO normalized\n#         aug_bboxes_yolo = []\n#         for (x_min, y_min, x_max, y_max) in aug_bboxes_pascal:\n#             x_min = max(0, min(x_min, w))\n#             y_min = max(0, min(y_min, h))\n#             x_max = max(0, min(x_max, w))\n#             y_max = max(0, min(y_max, h))\n#             bw = x_max - x_min\n#             bh = y_max - y_min\n#             if bw < 1 or bh < 1:\n#                 continue\n#             x_c = x_min + bw/2\n#             y_c = y_min + bh/2\n#             aug_bboxes_yolo.append([x_c / w, y_c / h, bw / w, bh / h])\n\n#         # Lưu ảnh augment\n#         aug_img_id = f\"{img_id}_aug_{i}\"\n#         aug_img_path = os.path.join(save_dir, f\"{aug_img_id}.jpg\")\n#         cv2.imwrite(aug_img_path, cv2.cvtColor(aug_img, cv2.COLOR_RGB2BGR))\n\n#         aug_meta.append({\n#             \"image_id\": aug_img_id,\n#             \"image_path\": aug_img_path,\n#             \"bboxes\": aug_bboxes_yolo\n#         })\n\n#     return aug_meta\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:53.891072Z","iopub.execute_input":"2025-11-20T07:13:53.891243Z","iopub.status.idle":"2025-11-20T07:13:53.905264Z","shell.execute_reply.started":"2025-11-20T07:13:53.891230Z","shell.execute_reply":"2025-11-20T07:13:53.904579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# save_dir = \"/kaggle/working/augmented\"\n# def augment_one_row(row):\n#     img_path = row['image_path']\n#     img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)\n\n#     # Lấy bboxes từ df (YOLO format)\n#     bboxes_yolo = row['bboxes']  \n#     if isinstance(bboxes_yolo, str):\n#         import ast\n#         bboxes_yolo = ast.literal_eval(bboxes_yolo)\n\n#     img_id = str(row['image_id'])\n#     video_id = row.get('video_id', None)\n#     aug_meta = augment_image_to_meta(img, bboxes_yolo, img_id,img_path, save_dir,num_aug=0, seed=42)\n\n#     # Thêm video_id cho metadata và giữ cả ảnh gốc\n#     for item in aug_meta:\n#         item['video_id'] = video_id\n\n#     aug_meta.append({\n#         'image_id': img_id,\n#         'image_path': img_path,\n#         'bboxes': bboxes_yolo,\n#         'video_id': video_id\n#     })\n\n#     return aug_meta\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:53.905956Z","iopub.execute_input":"2025-11-20T07:13:53.906160Z","iopub.status.idle":"2025-11-20T07:13:53.921310Z","shell.execute_reply.started":"2025-11-20T07:13:53.906141Z","shell.execute_reply":"2025-11-20T07:13:53.920530Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import warnings\n# warnings.filterwarnings(\"ignore\", message=\"Got processor for bboxes, but no transform to process it\")\n\n# import pandas as pd\n# from tqdm import tqdm\n# from multiprocessing import Pool, cpu_count\n# import shutil\n\n\n# if os.path.exists(save_dir):\n#     shutil.rmtree(save_dir)\n# os.makedirs(save_dir, exist_ok=True)\n\n\n# rows = [row for idx, row in df.iterrows()]\n\n# df_aug_list = []\n# with Pool(cpu_count()) as pool:\n#     for result in tqdm(pool.imap_unordered(augment_one_row, rows), total=len(rows)):\n#         df_aug_list.extend(result)\n\n# df_augmented = pd.DataFrame(df_aug_list)\ndf_augmented = df_train_val\nprint(f\"Tổng số ảnh augment + gốc: {len(df_augmented)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:53.922267Z","iopub.execute_input":"2025-11-20T07:13:53.922701Z","iopub.status.idle":"2025-11-20T07:13:53.935992Z","shell.execute_reply.started":"2025-11-20T07:13:53.922684Z","shell.execute_reply":"2025-11-20T07:13:53.935126Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df_augmented= df_augmented[~df_augmented['image_path'].str.contains(\"working/images\", na=False)]\n# df_augmented.head(20)\n# print(f\"Tổng số ảnh augment + gốc: {len(df_augmented)}\")\ndf_augmented.head(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:53.936818Z","iopub.execute_input":"2025-11-20T07:13:53.937176Z","iopub.status.idle":"2025-11-20T07:13:53.968285Z","shell.execute_reply.started":"2025-11-20T07:13:53.937152Z","shell.execute_reply":"2025-11-20T07:13:53.967532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# Giả sử df có cột 'video_id' để nhóm dữ liệu\n\n# Chia dữ liệu thành 80% train và 20% val\ndf_train, df_val = train_test_split(df_augmented, test_size=0.2, random_state=42, stratify=df_augmented['video_id'])\n\n\n# Gán cột 'fold' cho các phần dữ liệu\ndf_train['fold'] = 'train'\ndf_val['fold'] = 'val'\n\n\n# Kết hợp lại thành dataframe cuối cùng\ndf_augmented = pd.concat([df_train, df_val])\n\n# Kiểm tra phân phối fold\nprint(df_augmented['fold'].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:53.969216Z","iopub.execute_input":"2025-11-20T07:13:53.969503Z","iopub.status.idle":"2025-11-20T07:13:53.990547Z","shell.execute_reply.started":"2025-11-20T07:13:53.969480Z","shell.execute_reply":"2025-11-20T07:13:53.989760Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**từ này trở lên là 1 phần của yolov5**","metadata":{}},{"cell_type":"code","source":"!pip install timm --quiet\n!pip install torchvision --quiet\n!pip install pycocotools --quiet\n!pip -q install thop torchsummary","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:13:53.991436Z","iopub.execute_input":"2025-11-20T07:13:53.991963Z","iopub.status.idle":"2025-11-20T07:15:29.348710Z","shell.execute_reply.started":"2025-11-20T07:13:53.991944Z","shell.execute_reply":"2025-11-20T07:15:29.347817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport timm\nimport numpy as np\nimport time\nimport cv2\nimport torchvision.transforms as T\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nfrom tqdm.notebook import tqdm\nfrom scipy.optimize import linear_sum_assignment\nfrom torchvision.ops import box_iou\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:29.349797Z","iopub.execute_input":"2025-11-20T07:15:29.350039Z","iopub.status.idle":"2025-11-20T07:15:39.069159Z","shell.execute_reply.started":"2025-11-20T07:15:29.350014Z","shell.execute_reply":"2025-11-20T07:15:39.068603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DFDetectionDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df.reset_index(drop=True)\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        img_path = row['image_path']\n        bboxes = row['bboxes']  # [[cx, cy, w, h], ...]\n\n        img = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        boxes = torch.as_tensor(bboxes, dtype=torch.float32)\n        labels = torch.ones((boxes.shape[0],), dtype=torch.int64)  # chỉ 1 class\n\n        return img, boxes, labels\nprint(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.069921Z","iopub.execute_input":"2025-11-20T07:15:39.070172Z","iopub.status.idle":"2025-11-20T07:15:39.076293Z","shell.execute_reply.started":"2025-11-20T07:15:39.070149Z","shell.execute_reply":"2025-11-20T07:15:39.075407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math\nclass SinePositionalEncoding2D(nn.Module):\n    def __init__(self, hidden_dim, temperature=10000, normalize=True, scale=2*math.pi):\n        super().__init__()\n        assert hidden_dim % 2 == 0\n        self.hidden_dim = hidden_dim\n        self.num_pos_feats = hidden_dim // 2\n        self.temperature = temperature\n        self.normalize = normalize\n        self.scale = scale\n\n    def forward(self, H, W, device, dtype):\n        y_embed = torch.arange(H, device=device, dtype=dtype).unsqueeze(1).repeat(1, W)\n        x_embed = torch.arange(W, device=device, dtype=dtype).repeat(H, 1)\n\n        if self.normalize:\n            eps = 1e-6\n            y_embed = y_embed / (H - 1 + eps) * self.scale\n            x_embed = x_embed / (W - 1 + eps) * self.scale\n\n        dim_t = self.temperature ** (2 * torch.arange(self.num_pos_feats, device=device, dtype=dtype) / self.num_pos_feats)\n\n        pos_x = x_embed[..., None] / dim_t\n        pos_y = y_embed[..., None] / dim_t\n\n        pos_x = torch.stack((pos_x[..., 0::2].sin(), pos_x[..., 1::2].cos()), dim=-1).flatten(-2)\n        pos_y = torch.stack((pos_y[..., 0::2].sin(), pos_y[..., 1::2].cos()), dim=-1).flatten(-2)\n\n        pos = torch.cat((pos_y, pos_x), dim=-1)        # [H, W, hidden_dim]\n        return pos.view(H*W, self.hidden_dim)\nclass SwinBackboneFlexible(nn.Module):\n    def __init__(self, model_name='swin_tiny_patch4_window7_224',\n                 pretrained=True, img_size=(576, 1024)):\n        super().__init__()\n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=pretrained,\n            features_only=True,\n            out_indices=(-1,),\n            img_size=img_size,\n        )\n\n        # Tắt strict size + bật dynamic pad nếu có\n        be = None\n        if hasattr(self.backbone, 'model') and hasattr(self.backbone.model, 'patch_embed'):\n            be = self.backbone.model.patch_embed\n        elif hasattr(self.backbone, 'patch_embed'):\n            be = self.backbone.patch_embed\n        if be is not None:\n            be.strict_img_size = False\n            if hasattr(be, 'dynamic_img_pad'):\n                be.dynamic_img_pad = True\n\n        self.embed_dim = self.backbone.feature_info.channels()[-1]\n        self.pos_encoder = SinePositionalEncoding2D(self.embed_dim)\n\n    def forward(self, x):\n        feats = self.backbone(x)[-1]  # feature cuối (stage 4)\n        B = feats.shape[0]\n\n        # Tự động nhận layout (NCHW hoặc NHWC)\n        if feats.shape[1] == self.embed_dim:   # NCHW\n            _, C, H, W = feats.shape\n            feats = feats.flatten(2).transpose(1, 2)  # [B, HW, C]\n        elif feats.shape[-1] == self.embed_dim:  # NHWC\n            _, H, W, C = feats.shape\n            feats = feats.reshape(B, H * W, C)     # [B, HW, C]\n        else:\n            raise RuntimeError(f\"Không xác định được layout của feature: {feats.shape}\")\n\n        # Positional encoding toàn cục\n        pos = self.pos_encoder(H, W, feats.device, feats.dtype).unsqueeze(0)\n        feats = feats + pos                       # [B, HW, C]\n        return feats\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.077265Z","iopub.execute_input":"2025-11-20T07:15:39.077521Z","iopub.status.idle":"2025-11-20T07:15:39.103079Z","shell.execute_reply.started":"2025-11-20T07:15:39.077495Z","shell.execute_reply":"2025-11-20T07:15:39.102323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# class MLP(nn.Module):\n#     def __init__(self, input_dim, hidden_dim, output_dim, num_layers):\n#         super().__init__()\n#         layers = []\n#         for i in range(num_layers - 1):\n#             layers.append(nn.Linear(input_dim if i == 0 else hidden_dim, hidden_dim))\n#             layers.append(nn.ReLU())\n#         layers.append(nn.Linear(hidden_dim, output_dim))\n#         self.mlp = nn.Sequential(*layers)\n\n#     def forward(self, x):\n#         return self.mlp(x)\nclass MLP(nn.Module):\n    def __init__(self, input_dim, hidden_dim, output_dim, num_layers):\n        super().__init__()\n        if num_layers < 1:\n            raise ValueError(\"num_layers must be >= 1\")\n        if num_layers == 1:\n            self.mlp = nn.Sequential(nn.Linear(input_dim, output_dim))\n        else:\n            layers = []\n            in_dim = input_dim\n            for _ in range(num_layers - 1):\n                layers.append(nn.Linear(in_dim, hidden_dim))\n                layers.append(nn.ReLU())\n                in_dim = hidden_dim\n            layers.append(nn.Linear(in_dim, output_dim))  # in_dim == hidden_dim\n            self.mlp = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.mlp(x)\n\nclass DETRHead(nn.Module):\n    def __init__(self, d_model=192, num_queries=30, num_classes=2, num_decoder_layers=3, nhead=8, typeBBox = \"MLP\"):\n        super().__init__()\n        decoder_layer = nn.TransformerDecoderLayer(d_model, nhead=nhead)\n        self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)\n        self.query_embed = nn.Embedding(num_queries, d_model)\n        self.class_embed = nn.Linear(d_model, num_classes)  # background + object\n        nn.init.constant_(self.class_embed.bias, 0)\n        self.class_embed.bias.data[0] = -4.0  # 🚀 background bias âm\n        if typeBBox == \"MLP\":\n            self.bbox_embed = MLP(d_model, d_model, 4, 3)\n        else:\n            self.bbox_embed = nn.Linear(d_model, 4)\n        # thay vì Linear\n        # \n\n    def forward(self, memory):\n        memory = memory.permute(1, 0, 2)  # [num_patches, batch, dim]\n        bs = memory.shape[1]\n        tgt = self.query_embed.weight.unsqueeze(1).repeat(1, bs, 1)\n        hs = self.decoder(tgt, memory)\n        hs = hs.transpose(0, 1)  # [B, num_queries, dim]\n        return {\n            'pred_logits': self.class_embed(hs),\n            'pred_boxes': self.bbox_embed(hs).sigmoid()\n        }\nclass ViT_DETR(nn.Module):\n    def __init__(self, backbone, detr_head):\n        super().__init__()\n        self.backbone = backbone\n        self.detr_head = detr_head\n\n    def forward(self, x):\n        feats = self.backbone(x) # Gọi forward của ViTBackboneFlexible\n        return self.detr_head(feats)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.104015Z","iopub.execute_input":"2025-11-20T07:15:39.104714Z","iopub.status.idle":"2025-11-20T07:15:39.126164Z","shell.execute_reply.started":"2025-11-20T07:15:39.104693Z","shell.execute_reply":"2025-11-20T07:15:39.125556Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def box_cxcywh_to_xyxy(x):\n    cx, cy, w, h = x.unbind(-1)\n    return torch.stack([cx - 0.5 * w, cy - 0.5 * h,\n                        cx + 0.5 * w, cy + 0.5 * h], dim=-1)\n\n\ndef box_iou(boxes1, boxes2):\n    area1 = (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1])\n    area2 = (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1])\n\n    lt = torch.max(boxes1[:, None, :2], boxes2[:, :2])  # left-top\n    rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])  # right-bottom\n\n    wh = (rb - lt).clamp(min=0)\n    inter = wh[:, :, 0] * wh[:, :, 1]\n\n    union = area1[:, None] + area2 - inter\n    return inter / (union + 1e-6)\n\ndef detr_loss(pred_logits, pred_boxes, tgt_labels, tgt_boxes):\n    loss_ce, loss_bbox, loss_iou = 0, 0, 0\n    bs, num_queries = pred_logits.shape[:2]\n    device = pred_logits.device\n\n    for i in range(bs):\n        out_prob = pred_logits[i].softmax(-1)  # [num_queries, 2]\n        out_bbox = pred_boxes[i]               # [num_queries, 4]\n        tgt_ids = tgt_labels[i].to(device)     # [N]\n        tgt_bb = tgt_boxes[i].to(device)       # [N, 4]\n\n        n_gt = tgt_bb.shape[0]\n        ce_weight = torch.tensor([1.0, 2.0], device=device)  # background=1.0, object=2.0\n\n        # ẢNH KHÔNG CÓ BBOX → ép toàn bộ query predict background\n        if n_gt == 0:\n            target_bg = torch.zeros(num_queries, dtype=torch.long, device=device)\n            loss_ce += F.cross_entropy(pred_logits[i], target_bg, weight=ce_weight)\n            continue\n\n        # Hungarian matching cho ảnh có bbox\n        cost_class = -out_prob[:, 1].unsqueeze(1).repeat(1, n_gt)\n        cost_bbox = ((out_bbox.unsqueeze(1) - tgt_bb.unsqueeze(0))**2).sum(-1)\n        cost_iou = -box_iou(box_cxcywh_to_xyxy(out_bbox), box_cxcywh_to_xyxy(tgt_bb))\n        # cost = cost_class + cost_bbox + cost_iou\n        cost = 1.0*cost_class + 5.0*cost_bbox + 2.0*cost_iou\n\n        indices = linear_sum_assignment(cost.detach().cpu())\n        idx_pred = torch.as_tensor(indices[0], dtype=torch.int64, device=device)\n        idx_tgt = torch.as_tensor(indices[1], dtype=torch.int64, device=device)\n\n        # QUERY MATCH → dùng label object\n        matched_logits = pred_logits[i][idx_pred]\n        loss_ce += F.cross_entropy(matched_logits, tgt_ids[idx_tgt], weight=ce_weight)\n\n        # QUERY KHÔNG MATCH → ép predict background\n        unmatched_idx = torch.tensor([k for k in range(num_queries) if k not in idx_pred], device=device)\n        if unmatched_idx.numel() > 0:\n            loss_ce += F.cross_entropy(pred_logits[i][unmatched_idx],\n                                       torch.zeros(unmatched_idx.numel(), dtype=torch.long, device=device),\n                                       weight=ce_weight)\n\n        # BBox loss chỉ áp dụng cho query match\n        loss_bbox += F.l1_loss(pred_boxes[i][idx_pred], tgt_bb[idx_tgt])\n        iou = box_iou(box_cxcywh_to_xyxy(pred_boxes[i][idx_pred]),\n                      box_cxcywh_to_xyxy(tgt_bb[idx_tgt]))\n        loss_iou += 1 - iou.mean()\n\n    # Weight các loss lại\n    total_loss = loss_ce/bs + 5*(loss_bbox/bs) + 2*(loss_iou/bs)\n    return total_loss, loss_ce/bs, loss_bbox/bs, loss_iou/bs\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.126979Z","iopub.execute_input":"2025-11-20T07:15:39.127201Z","iopub.status.idle":"2025-11-20T07:15:39.145355Z","shell.execute_reply.started":"2025-11-20T07:15:39.127184Z","shell.execute_reply":"2025-11-20T07:15:39.144570Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from scipy.optimize import linear_sum_assignment\n\ndef evaluate_map50(model, loader, device, iou_threshold=0.5, score_threshold=0.3):\n    model.eval()\n    tp, fp, fn = 0, 0, 0\n    \n    with torch.no_grad():\n        for images, tgt_boxes, tgt_labels in loader:\n            images = images.to(device)\n            tgt_boxes = [b.to(device) for b in tgt_boxes]\n            tgt_labels = [l.to(device) for l in tgt_labels]\n\n            preds = model(images)\n            pred_logits = preds['pred_logits']      # [B, num_queries, num_classes]\n            pred_boxes = preds['pred_boxes']        # [B, num_queries, 4]\n\n            batch_size = images.size(0)\n\n            for b in range(batch_size):\n                probs = pred_logits[b].softmax(-1)\n                scores, labels = probs.max(-1)  # [num_queries]\n\n                # Chỉ lấy query dự đoán object (label=1) và score > threshold\n                keep = (labels == 1) & (scores > score_threshold)\n                boxes_keep = pred_boxes[b][keep]\n                scores_keep = scores[keep]\n\n                gt_boxes = tgt_boxes[b]\n                n_gt = gt_boxes.shape[0]\n\n                # Nếu ảnh không có ground truth box → tất cả box dự đoán đều FP\n                if n_gt == 0:\n                    fp += boxes_keep.size(0)\n                    continue\n                \n\n                # Hungarian matching giữa box dự đoán và gt box\n                cost_bbox = torch.cdist(boxes_keep, gt_boxes, p=1)  # L1 distance\n                cost = cost_bbox.detach().cpu()\n                idx_pred, idx_gt = linear_sum_assignment(cost)\n\n                matched_pred = set()\n                matched_gt = set()\n\n                for p_idx, g_idx in zip(idx_pred, idx_gt):\n                    iou = box_iou(\n                        box_cxcywh_to_xyxy(boxes_keep[p_idx].unsqueeze(0)),\n                        box_cxcywh_to_xyxy(gt_boxes[g_idx].unsqueeze(0))\n                    )\n                    if iou.item() >= iou_threshold:\n                        tp += 1\n                        matched_pred.add(p_idx)\n                        matched_gt.add(g_idx)\n\n                # Đếm FP: box dự đoán không match hoặc match nhưng IoU < threshold\n                fp += boxes_keep.size(0) - len(matched_pred)\n                # Đếm FN: gt box không match với query nào\n                fn += n_gt - len(matched_gt)\n\n    precision = tp / (tp + fp + 1e-6)\n    recall = tp / (tp + fn + 1e-6)\n    f1 = 2 * precision * recall / (precision + recall + 1e-6)\n\n    # mAP50 ở đây chỉ proxy bằng precision (vì chỉ có 1 class)\n    map50 = precision\n\n    return precision, recall, f1, map50\n\n\n\n# =========================\n# ⚙️ Training setup\n# =========================\n\ntransform = T.Compose([\n    T.Resize((576, 1024 )),\n    T.ToTensor(),\n    T.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),\n])\n\n# Dataset + DataLoader\ntrain_dataset = DFDetectionDataset(df_train, transform=transform)\nval_dataset = DFDetectionDataset(df_val, transform=transform)\n\ndef collate_fn(batch):\n    imgs, boxes, labels = zip(*batch)\n    return torch.stack(imgs), list(boxes), list(labels)\n# print(len(train_dataset))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.146324Z","iopub.execute_input":"2025-11-20T07:15:39.146803Z","iopub.status.idle":"2025-11-20T07:15:39.170896Z","shell.execute_reply.started":"2025-11-20T07:15:39.146779Z","shell.execute_reply":"2025-11-20T07:15:39.170173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import matplotlib.pyplot as plt\n# import random\n# import torch\n# from PIL import Image\n\n# def show_dataset_samples(dataset, num_samples=5):\n#     # Lấy ngẫu nhiên index trong dataset\n#     indices = random.sample(range(len(dataset)), num_samples)\n    \n#     for idx in indices:\n#         img, boxes, labels = dataset[idx]\n#         print(img.shape)\n#         # Convert tensor ảnh về numpy (0–1)\n#         img_np = img.permute(1, 2, 0).numpy()  # [H, W, C]\n#         img_np = (img_np * 0.5 + 0.5).clip(0, 1)  # undo Normalize([-1,1] về [0,1])\n        \n#         h, w = img_np.shape[:2]\n        \n#         # Vẽ ảnh\n#         plt.figure(figsize=(6, 6))\n#         plt.imshow(img_np)\n#         ax = plt.gca()\n        \n#         # Vẽ tất cả bbox\n#         for box in boxes:\n#             cx, cy, bw, bh = box.tolist()\n#             x_min = (cx - bw/2) * w\n#             y_min = (cy - bh/2) * h\n#             rect_w = bw * w\n#             rect_h = bh * h\n            \n#             rect = plt.Rectangle((x_min, y_min), rect_w, rect_h,\n#                                  fill=False, color='lime', linewidth=2)\n#             ax.add_patch(rect)\n        \n#         plt.title(f\"Sample index {idx} | {len(boxes)} boxes\")\n#         plt.axis(\"off\")\n#         plt.show()\n\n# #  Gọi hàm với train_dataset\n# show_dataset_samples(train_dataset, num_samples=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.171732Z","iopub.execute_input":"2025-11-20T07:15:39.172009Z","iopub.status.idle":"2025-11-20T07:15:39.185384Z","shell.execute_reply.started":"2025-11-20T07:15:39.171985Z","shell.execute_reply":"2025-11-20T07:15:39.184638Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef debug_show_img_with_boxes(img_tensor, gt_boxes, pred_boxes=None):\n    img = img_tensor.permute(1,2,0).detach().cpu().numpy()\n    img = (img * 0.5 + 0.5)  # unnormalize\n    h,w,_ = img.shape\n    img_draw = (img*255).astype('uint8').copy()\n\n    # Vẽ box ground truth (màu xanh)\n    for bbox in gt_boxes:\n        cx,cy,bw,bh = bbox.detach().cpu().numpy()\n        x_min = int((cx-bw/2)*w)\n        y_min = int((cy-bh/2)*h)\n        x_max = int((cx+bw/2)*w)\n        y_max = int((cy+bh/2)*h)\n        cv2.rectangle(img_draw,(x_min,y_min),(x_max,y_max),(0,255,0),2)\n\n    # Vẽ box predict (màu đỏ)\n    if pred_boxes is not None:\n        for bbox in pred_boxes :  # chỉ vẽ 3 box đầu tiên\n            cx,cy,bw,bh = bbox.detach().cpu().numpy()\n            x_min = int((cx-bw/2)*w)\n            y_min = int((cy-bh/2)*h)\n            x_max = int((cx+bw/2)*w)\n            y_max = int((cy+bh/2)*h)\n            cv2.rectangle(img_draw,(x_min,y_min),(x_max,y_max),(255,0,0),1)\n\n    plt.imshow(img_draw)\n    plt.axis('off')\n    plt.show()\nprint(len(df_augmented[(df_augmented.fold == 'train') & (df_augmented['bboxes'].str.len() > 0)]), len(df_augmented[(df_augmented.fold == 'val') & (df_augmented['bboxes'].str.len() > 0)]))\nprint(len(df_augmented[(df_augmented.fold == 'train') & (df_augmented['bboxes'].str.len() == 0)]), len(df_augmented[(df_augmented.fold == 'val') & (df_augmented['bboxes'].str.len() == 0)]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.186168Z","iopub.execute_input":"2025-11-20T07:15:39.186428Z","iopub.status.idle":"2025-11-20T07:15:39.216554Z","shell.execute_reply.started":"2025-11-20T07:15:39.186406Z","shell.execute_reply":"2025-11-20T07:15:39.215834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport time\n\ntry:\n    from thop import profile\n    _HAS_THOP = True\nexcept Exception:\n    _HAS_THOP = False\n\n@torch.inference_mode()\ndef benchmark_model(\n    model: torch.nn.Module,\n    input_size=(3, 576, 1024),         # kích thước thường dùng cho DETR\n    device=None,\n    warmup=10,\n    repeat=50,\n    backbone_attr: str = \"backbone\",   # tên thuộc tính backbone trong model (VD: model.backbone)\n    backbone_dim_attr: str = \"embed_dim\"  # tên thuộc tính dim trong backbone (VD: backbone.embed_dim)\n):\n    \"\"\"\n    In ra:\n      - d_model (dim)\n      - tham số backbone\n      - tham số tổng\n      - FLOPs (thop)\n      - FPS (batch=1)\n    \"\"\"\n    # ===== chọn device =====\n    if device is None:\n        device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n    model = model.to(device).eval()\n\n    # ===== tạo input giả =====\n    dummy = torch.randn(1, *input_size, device=device)\n\n    # ===== suy ra d_model (dim) =====\n    d_model = None\n    # 1) từ backbone.embed_dim (thường có ở ViT/DeiT/Swin)\n    backbone = getattr(model, backbone_attr, None)\n    if backbone is not None:\n        d_model = getattr(backbone, backbone_dim_attr, None)\n\n    # 2) nếu model tự có thuộc tính d_model\n    if d_model is None:\n        d_model = getattr(model, \"d_model\", None)\n\n    # 3) thử đoán bằng cách chạy backbone và lấy kênh cuối (nếu có backbone và forward trả (B,N,C) hoặc (B,C,H,W))\n    if d_model is None and backbone is not None and callable(getattr(backbone, \"forward\", None)):\n        try:\n            feats = backbone(dummy)\n            if isinstance(feats, torch.Tensor):\n                if feats.dim() == 3:      # [B, N, C]\n                    d_model = feats.shape[-1]\n                elif feats.dim() == 4:    # [B, C, H, W]\n                    d_model = feats.shape[1]\n        except Exception:\n            pass\n\n    # ===== đếm tham số =====\n    total_params = sum(p.numel() for p in model.parameters())\n    backbone_params = None\n    if backbone is not None:\n        backbone_params = sum(p.numel() for p in backbone.parameters())\n\n    # ===== in dim + tham số =====\n    dim_str = f\"{d_model}\" if d_model is not None else \"Unknown\"\n    print(f\"🔹 d_model (dim): {dim_str}\")\n    if backbone_params is not None:\n        print(f\"- Params (backbone): {backbone_params:,} (~{backbone_params/1e6:.2f} M)\")\n    else:\n        print(\"- Params (backbone): Unknown (không tìm thấy thuộc tính backbone)\")\n\n    print(f\"- Params (total):   {total_params:,} (~{total_params/1e6:.2f} M)\")\n\n    # ===== FLOPs (nếu có thop) =====\n    if _HAS_THOP:\n        try:\n            # flops, _ = profile(model, inputs=(dummy,), verbose=False)\n            macs, _ = profile(model, inputs=(dummy,), verbose=False) #1 lần nhân, 1 lần công tính 1 Macs\n            flops = 2 * macs\n            print(f\"- FLOPs: {flops/1e9:.2f} GFLOPs (batch=1, {input_size[1]}x{input_size[2]})\")\n        except Exception as e:\n            print(\"- FLOPs: không tính được với thop cho mô hình này.\")\n            print(f\"     Lý do: {type(e).__name__}: {e}\")\n    else:\n        print(\"- FLOPs: bỏ qua (chưa cài 'thop'). pip install thop\")\n\n    # ===== FPS =====\n    # làm nóng\n    for _ in range(warmup):\n        _ = model(dummy)\n    if device == \"cuda\":\n        torch.cuda.synchronize()\n\n    t0 = time.time()\n    for _ in range(repeat):\n        _ = model(dummy)\n    if device == \"cuda\":\n        torch.cuda.synchronize()\n    t1 = time.time()\n\n    fps = repeat / (t1 - t0)\n    print(f\"- FPS (batch=1): {fps:.2f}  [{device}]  input={input_size}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.217487Z","iopub.execute_input":"2025-11-20T07:15:39.217799Z","iopub.status.idle":"2025-11-20T07:15:39.232134Z","shell.execute_reply.started":"2025-11-20T07:15:39.217776Z","shell.execute_reply":"2025-11-20T07:15:39.231442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DeiTBackboneFlexible(nn.Module):\n    def __init__(self, model_name='deit_base_patch16_224', pretrained=True, img_size=(576, 1024)):\n        super().__init__()\n        self.vit = timm.create_model(\n            model_name,\n            pretrained=pretrained,\n            img_size=img_size,\n            num_classes=0,     # trả features\n            global_pool=''     # không pool\n        )\n        # nới lỏng size\n        if hasattr(self.vit, 'patch_embed'):\n            self.vit.patch_embed.strict_img_size = False\n            if hasattr(self.vit.patch_embed, 'dynamic_img_pad'):\n                self.vit.patch_embed.dynamic_img_pad = True\n\n        self.embed_dim = getattr(self.vit, 'num_features', None) or getattr(self.vit, 'embed_dim')\n        ps = getattr(self.vit.patch_embed, 'patch_size', 16)\n        self.patch_h = ps if isinstance(ps, int) else ps[0]\n        self.patch_w = ps if isinstance(ps, int) else ps[1]\n\n        # PE 2D cho memory (giúp DETR decoder)\n        self.pos_encoder = SinePositionalEncoding2D(self.embed_dim)\n\n        # (tuỳ chọn) chuẩn hoá nhẹ tokens sau encoder cho ổn định\n        self.post_ln = nn.LayerNorm(self.embed_dim)\n\n    def _infer_hw(self, tokens_no_cls, H_in, W_in):\n        # Theo patch size kỳ vọng\n        H = math.ceil(H_in / self.patch_h)\n        W = math.ceil(W_in / self.patch_w)\n        n = tokens_no_cls.shape[1]\n        if H * W == n:\n            return H, W\n        # fallback: tìm cặp nhân gần tỷ lệ H_in:W_in\n        best = None\n        ratio = W_in / max(1, H_in)\n        for hh in range(1, n + 1):\n            if n % hh: \n                continue\n            ww = n // hh\n            if best is None or abs((ww / hh) - ratio) < abs((best[1] / best[0]) - ratio):\n                best = (hh, ww)\n        return best if best else (H, W)\n\n    def forward(self, x):\n        B, _, H_in, W_in = x.shape\n        feats = self.vit.forward_features(x)  # thường [B, N, C] hoặc dict\n\n        if isinstance(feats, dict):\n            feats = feats.get('x', feats.get('tokens', None))\n            if feats is None:\n                raise RuntimeError(\"Không lấy được token sequence từ DeiT.\")\n\n        if feats.dim() != 3:\n            raise RuntimeError(f\"DeiT features shape lạ: {feats.shape}\")\n\n        B2, N, C = feats.shape\n        assert B2 == B and C == self.embed_dim\n\n        # Loại bỏ cls/dist tokens nếu có\n        # N có thể = HW (+1 cls) hoặc HW (+2: cls + dist)\n        extra = 0\n        if hasattr(self.vit, 'cls_token') and self.vit.cls_token is not None:\n            extra += 1\n        if hasattr(self.vit, 'dist_token') and self.vit.dist_token is not None:\n            extra += 1\n        if extra > 0 and N > extra:\n            patch_tokens = feats[:, extra:, :]          # [B, HW, C]\n        else:\n            patch_tokens = feats                        # [B, HW, C]\n\n        # Suy (H,W)\n        H, W = self._infer_hw(patch_tokens, H_in, W_in)\n        if patch_tokens.shape[1] != H * W:\n            patch_tokens = patch_tokens.reshape(B, H * W, C)\n\n        # (tuỳ chọn) LayerNorm nhẹ\n        patch_tokens = self.post_ln(patch_tokens)\n\n        # Thêm PE 2D cho memory (rất quan trọng cho DETR)\n        pos = self.pos_encoder(H, W, device=patch_tokens.device, dtype=patch_tokens.dtype).unsqueeze(0)  # [1,HW,C]\n        patch_tokens = patch_tokens + pos\n\n        return patch_tokens  # [B, HW, C]\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.235659Z","iopub.execute_input":"2025-11-20T07:15:39.236383Z","iopub.status.idle":"2025-11-20T07:15:39.250352Z","shell.execute_reply.started":"2025-11-20T07:15:39.236355Z","shell.execute_reply":"2025-11-20T07:15:39.249603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ViTBackboneFlexible(nn.Module):\n    def __init__(self, model_name='vit_tiny_patch16_224', pretrained=True):\n        super().__init__()\n        vit = timm.create_model(model_name, pretrained=pretrained)\n        vit.patch_embed.strict_img_size = False\n\n        self.patch_embed = vit.patch_embed\n        self.cls_token = vit.cls_token\n        self.pos_embed = vit.pos_embed\n        self.pos_drop = vit.pos_drop\n        self.blocks = vit.blocks\n        self.norm = vit.norm\n\n        self.embed_dim = vit.embed_dim\n        self.patch_size = vit.patch_embed.patch_size  # (16,16)\n\n    def forward(self, x):\n        B, C, H, W = x.shape\n        x = self.patch_embed(x)\n        cls_tokens = self.cls_token.expand(B, -1, -1)\n        x = torch.cat((cls_tokens, x), dim=1)\n        pos_embed = self.interpolate_pos_embed(H, W)\n        x = x + pos_embed\n        x = self.pos_drop(x)\n        x = self.blocks(x)\n        x = self.norm(x)\n        return x[:, 1:, :]  # bỏ cls token\n\n    def interpolate_pos_embed(self, H, W):\n        cls_pos = self.pos_embed[:, 0:1, :]\n        patch_pos = self.pos_embed[:, 1:, :]\n        old_size = int(patch_pos.shape[1] ** 0.5)\n        new_size = (H // self.patch_size[0], W // self.patch_size[1])\n\n        patch_pos = patch_pos.reshape(1, old_size, old_size, -1).permute(0, 3, 1, 2)\n        patch_pos = F.interpolate(patch_pos, size=new_size, mode='bilinear', align_corners=False)\n        patch_pos = patch_pos.permute(0, 2, 3, 1).reshape(1, new_size[0]*new_size[1], -1)\n\n        return torch.cat((cls_pos, patch_pos), dim=1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.251031Z","iopub.execute_input":"2025-11-20T07:15:39.251218Z","iopub.status.idle":"2025-11-20T07:15:39.269490Z","shell.execute_reply.started":"2025-11-20T07:15:39.251203Z","shell.execute_reply":"2025-11-20T07:15:39.268808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport timm\n\n\nclass SequencePooling(nn.Module):\n    \"\"\"CVT SeqPool: Linear -> softmax theo chiều N, rồi weighted-sum.\"\"\"\n    def __init__(self, dim):\n        super().__init__()\n        self.attn = nn.Linear(dim, 1)\n\n    def forward(self, x):  # x: (B, N, D)\n        w = torch.softmax(self.attn(x), dim=1)   # (B, N, 1)\n        pooled = torch.sum(w * x, dim=1)         # (B, D)\n        return pooled, w.squeeze(-1)             # (B, D), (B, N)\n\n\nclass CVTBackboneFlexible(nn.Module):\n    \"\"\"\n    Compact Vision Transformer (CVT) backbone (không CCT):\n    - KHÔNG có cls_token\n    - PE ViT (learned) tùy chọn: 'use_pos_embed' (mặc định False theo CVT)\n    - PE 2D sin-cos kiểu DETR: 'add_2d_pos_embed' (mặc định False). Nếu bật, backbone\n      sẽ tự cộng PE 2D vào tokens và TRẢ RA tensor (B, N, D) (không trả tuple).\n    - Trả về chuỗi token sau encoder blocks: (B, N, D)\n    - Có sẵn sequence_pooling() nếu muốn dùng cho head phân loại kiểu CVT (không dùng cho DETR).\n    \"\"\"\n    def __init__(\n        self,\n        model_name: str = 'vit_tiny_patch16_224',\n        pretrained: bool = True,\n        use_pos_embed: bool = False,       # PE học được của ViT (compat mode)\n        add_2d_pos_embed: bool = False,    # PE 2D sin-cos kiểu DETR (tự cộng vào tokens)\n        pos2d_temperature: float = 10000.0 # tham số PE 2D\n    ):\n        super().__init__()\n        self.add_2d_pos_embed = add_2d_pos_embed\n        self.pos2d_temperature = float(pos2d_temperature)\n\n        vit = timm.create_model(model_name, pretrained=pretrained)\n        vit.patch_embed.strict_img_size = False  # hỗ trợ H×W linh hoạt\n\n        # Tận dụng patch_embed + blocks + norm của ViT\n        self.patch_embed = vit.patch_embed     # -> (B, N, D)\n        self.blocks = vit.blocks\n        self.norm = vit.norm\n        self.embed_dim = vit.embed_dim\n        self.patch_size = self.patch_embed.patch_size  # (ph, pw) — vd (16,16)\n\n        # PE ViT (optional, chỉ phần patch — bỏ cls)\n        self.use_pos_embed = use_pos_embed\n        if use_pos_embed:\n            self.register_buffer('pos_embed_patch_only', vit.pos_embed[:, 1:, :].detach().clone())\n        else:\n            # tránh register_buffer(None) để không dính lỗi ở một số bản torch\n            self.pos_embed_patch_only = None\n\n        # SeqPool để dùng ngoài (không dùng trong forward backbone)\n        self.sequence_pool = SequencePooling(self.embed_dim)\n\n    @torch.no_grad()\n    def _interpolate_pos_embed(self, H, W, device):\n        \"\"\"\n        Nội suy PE học được (ViT) cho phần patch (không có cls).\n        Trả về: (1, N, D) hoặc None\n        \"\"\"\n        if self.pos_embed_patch_only is None:\n            return None\n\n        patch_pos = self.pos_embed_patch_only  # (1, N0, D)\n        D = patch_pos.shape[-1]\n        old_hw = int(patch_pos.shape[1] ** 0.5)\n        new_hw = (H // self.patch_size[0], W // self.patch_size[1])\n\n        patch_pos = patch_pos.reshape(1, old_hw, old_hw, D).permute(0, 3, 1, 2)  # (1, D, old_h, old_w)\n        patch_pos = F.interpolate(patch_pos, size=new_hw, mode='bilinear', align_corners=False)\n        patch_pos = patch_pos.permute(0, 2, 3, 1).reshape(1, new_hw[0] * new_hw[1], D)  # (1, N, D)\n        return patch_pos.to(device=device, dtype=torch.float32)\n\n    @torch.no_grad()\n    def _build_2d_sincos_pos_embed(self, D, Hp, Wp, device):\n        \"\"\"\n        Tạo PE 2D sin-cos kiểu DETR.\n        Trả về: (1, Hp*Wp, D)\n        \"\"\"\n        assert D % 2 == 0, \"embed_dim phải chẵn để chia đều cho (x,y)\"\n        dtype = torch.float32\n\n        y_embed = torch.arange(Hp, dtype=dtype, device=device).unsqueeze(1).repeat(1, Wp)\n        x_embed = torch.arange(Wp, dtype=dtype, device=device).unsqueeze(0).repeat(Hp, 1)\n\n        eps = 1e-6\n        y_embed = y_embed / (Hp - 1 + eps) * 2 * math.pi\n        x_embed = x_embed / (Wp - 1 + eps) * 2 * math.pi\n\n        half_dim = D // 2\n        dim_t = torch.arange(half_dim, dtype=dtype, device=device)\n        dim_t = self.pos2d_temperature ** (2 * (dim_t // 2) / half_dim)\n\n        pos_y = y_embed[:, :, None] / dim_t  # (Hp, Wp, D/2)\n        pos_x = x_embed[:, :, None] / dim_t  # (Hp, Wp, D/2)\n\n        pos_y = torch.stack((pos_y.sin(), pos_y.cos()), dim=3).flatten(2)  # (Hp, Wp, D)\n        pos_x = torch.stack((pos_x.sin(), pos_x.cos()), dim=3).flatten(2)  # (Hp, Wp, D)\n\n        pos = torch.cat([pos_y[..., :D//2], pos_x[..., :D//2]], dim=2)  # (Hp, Wp, D)\n        pos = pos.view(Hp * Wp, D).unsqueeze(0)  # (1, N, D)\n        return pos\n\n    def forward(self, x):\n        \"\"\"\n        x: (B, C, H, W) — ví dụ (B, 3, 576, 1024)\n        Output: tokens (B, N, D) — không CLS, không SeqPool\n        - Nếu self.add_2d_pos_embed=True: đã cộng sẵn PE 2D sin-cos vào tokens\n        \"\"\"\n        B, C, H, W = x.shape\n        device = x.device\n\n        # 1) Patch embedding (linear)\n        x = self.patch_embed(x)  # (B, N, D), N = (H/ph)*(W/pw)\n\n        # 2) (Optional) PE ViT learned\n        if self.use_pos_embed:\n            pos = self._interpolate_pos_embed(H, W, device)  # (1, N, D)\n            if pos is not None and pos.shape[1] == x.shape[1]:\n                x = x + pos\n\n        # 3) Encoder blocks + norm\n        x = self.blocks(x)  # (B, N, D)\n        x = self.norm(x)\n\n        # 4) (Optional) Cộng PE 2D sin-cos kiểu DETR ngay trong backbone\n        if self.add_2d_pos_embed:\n            Hp, Wp = H // self.patch_size[0], W // self.patch_size[1]\n            pos2d = self._build_2d_sincos_pos_embed(self.embed_dim, Hp, Wp, device=x.device)  # (1, N, D)\n            x = x + pos2d  # broadcast trên batch\n\n        return x  # (B, N, D)\n\n    # tiện ích ngoài nếu bạn muốn dùng SeqPool cho downstream classification\n    def sequence_pooling(self, tokens):\n        return self.sequence_pool(tokens)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.270186Z","iopub.execute_input":"2025-11-20T07:15:39.270422Z","iopub.status.idle":"2025-11-20T07:15:39.288459Z","shell.execute_reply.started":"2025-11-20T07:15:39.270405Z","shell.execute_reply":"2025-11-20T07:15:39.287702Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using:\", device)\n\n# Model + Optimizer\nbackbone_swin = SwinBackboneFlexible('swin_tiny_patch4_window7_224', pretrained=True)\ndetr_head_swin = DETRHead(d_model=backbone_swin.embed_dim, num_queries=15, num_classes=2, num_decoder_layers=3, nhead=12)\nmodel_swin_detr = ViT_DETR(backbone_swin, detr_head_swin).to(device)\nmodel_swin_detr.eval()\n\nbackbone_DeiT = DeiTBackboneFlexible('deit_tiny_patch16_224', pretrained=True, img_size=(576, 1024))\ndetr_head_DeiT = DETRHead(d_model=backbone_DeiT.embed_dim, num_queries=15, num_classes=2, num_decoder_layers=3, nhead=12)\nmodel_DeiT_detr = ViT_DETR(backbone_DeiT, detr_head_DeiT).to(device)\nmodel_DeiT_detr.eval()\n\nbackbone_ViT = ViTBackboneFlexible('vit_tiny_patch16_224', pretrained=True)\ndetr_head_ViT = DETRHead(d_model=backbone_ViT.embed_dim, num_queries=15, num_classes=2, num_decoder_layers=3,nhead=12)\nmodel_ViT_DETR = ViT_DETR(backbone_ViT, detr_head_ViT).to(device)\nmodel_ViT_DETR.eval()\n\n\nbackbone_CVT = CVTBackboneFlexible(\n    model_name='vit_tiny_patch16_224',\n    pretrained=True,\n    use_pos_embed=False,      # CVT chuẩn: bỏ PE ViT\n    add_2d_pos_embed=True     # backbone tự cộng PE 2D sin-cos (DETR-style)\n)\n\ndetr_head_CVT = DETRHead(d_model=backbone_CVT.embed_dim, num_queries=15, num_classes=2, num_decoder_layers=3,nhead=12)\nmodel_CVT_DETR = ViT_DETR(backbone_CVT, detr_head_CVT).to(device)\nmodel_CVT_DETR.eval()\n\n\n# print(\"=========model_DeiT_detr=========\")\n# benchmark_model(model_DeiT_detr, input_size=(3, 576, 1024))\n# print(\"\\n\")\n# print(\"=========model_ViT_detr=========\")\n# benchmark_model(model_ViT_DETR, input_size=(3, 576, 1024))\n# print(\"\\n\")\n# print(\"=========model_swin_detr=========\")\n# benchmark_model(model_swin_detr, input_size=(3, 576, 1024))\n# print(\"\\n\")\n# print(\"=========model_CVT_detr=========\")\nbenchmark_model(model_CVT_DETR, input_size=(3, 576, 1024))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:39.289287Z","iopub.execute_input":"2025-11-20T07:15:39.289585Z","iopub.status.idle":"2025-11-20T07:15:45.909296Z","shell.execute_reply.started":"2025-11-20T07:15:39.289562Z","shell.execute_reply":"2025-11-20T07:15:45.908644Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"model_DeiT_detr\n📦 Parameters: 9,266,502 (~9.27 M)\n⚙️  FLOPs: 12.66 GFLOPs (batch=1, 576x1024)\n🚀 FPS (batch=1): 54.43  [cuda]  input=(3, 576, 1024)\n\nmodel_ViT_detr\n📦 Parameters: 8,861,382 (~8.86 M)\n⚙️  FLOPs: 12.66 GFLOPs (batch=1, 576x1024)\n🚀 FPS (batch=1): 54.89  [cuda]  input=(3, 576, 1024)\n\nmodel_swin_detr\n📦 Parameters: 52,348,800 (~52.35 M)\n⚙️  FLOPs: 54.94 GFLOPs (batch=1, 576x1024)\n🚀 FPS (batch=1): 29.51  [cuda]  input=(3, 576, 1024)","metadata":{}},{"cell_type":"code","source":"# def train(device, model,optimizer, EPOCHS, DEBUG_EVERY, LOGING = True):\n#     scaler = torch.amp.GradScaler('cuda')\n#     best_map50 = -1.0\n    \n#     start_time = time.time()\n#     for epoch in (range(EPOCHS)):\n#         model.train()\n#         train_loss = 0\n#         n_batches = 0\n    \n#         for batch_idx, (images, tgt_boxes, tgt_labels) in enumerate(\n#             tqdm(train_loader, desc=f\"[Train] Epoch {epoch+1}/{EPOCHS}\")\n#         ): # lấy ảnh từ data_loader\n#             images = images.to(device) # Chuyển sang GPU để train\n#             tgt_boxes = [b.to(device) for b in tgt_boxes] # Lấy danh sách box\n#             tgt_labels = [l.to(device) for l in tgt_labels] # Lấy danh sách nhãn\n    \n#             # Cần debug thêm, lấy box với nhãn có đúng chưa.\n#             optimizer.zero_grad() # reset gradient cũ\n#             with torch.amp.autocast('cuda'):\n#                 preds = model(images)\n#                 loss, loss_ce, loss_bbox, loss_iou = detr_loss(\n#                     preds['pred_logits'], preds['pred_boxes'], tgt_labels, tgt_boxes\n#                 )\n    \n#             scaler.scale(loss).backward()\n#             scaler.step(optimizer)\n#             scaler.update()\n    \n#             train_loss += loss.item()\n#             n_batches += 1\n#             # DEBUG: in chỉ số sau mỗi 50 batch\n#             if (batch_idx + 1) % DEBUG_EVERY == 0:\n#                 if len(tgt_boxes[0]) > 0:\n#                     debug_show_img_with_boxes(images[0], tgt_boxes[0], preds['pred_boxes'][0])\n#                 probs = preds['pred_logits'][0].softmax(-1)   # ảnh đầu tiên trong batch\n#                 scores, labels = probs.max(-1)\n    \n#                 num_bg = (labels == 0).sum().item()\n#                 num_obj = (labels == 1).sum().item()\n#                 with torch.no_grad():\n#                     pred_boxes_xyxy = box_cxcywh_to_xyxy(preds['pred_boxes'][0])\n#                     if len(tgt_boxes[0]) > 0:\n#                         gt_boxes_xyxy = box_cxcywh_to_xyxy(tgt_boxes[0])\n#                         ious = box_iou(pred_boxes_xyxy, gt_boxes_xyxy)\n#                         avg_iou = ious.max(1)[0].mean().item()\n#                     else:\n#                         avg_iou = 0.0\n#                 if LOGING == True:\n#                     print(f\"[DEBUG] Epoch {epoch+1} Batch {batch_idx+1}: \"\n#                           f\"Loss={loss.item():.4f} | Max score={scores.max().item():.3f} | \"\n#                           f\"Pred>0.3: {(scores>0.3).sum().item()} | Bg={num_bg} | Obj={num_obj} | IoU≈{avg_iou:.3f}\")\n    \n#         # valuate sau mỗi epoch\n#         prec, rec, f1, map50 = evaluate_map50(model, val_loader, device)\n#         if LOGING == True:\n#             print(f\"[Epoch {epoch+1}] Loss: {train_loss/n_batches:.4f} | P: {prec:.3f} R: {rec:.3f} F1: {f1:.3f} mAP50: {map50:.3f}\")\n    \n#         if map50 > best_map50:\n#             best_map50 = map50\n#             torch.save(model.state_dict(), f\"/kaggle/working/best_swin_MLP_detr_{best_map50:.3f}_{epoch}.pth\")\n#             print(f\"===> Saved best model (mAP50={best_map50:.3f})\")\n    \n#     end_time = time.time()\n#     print(f\"⏳ Total train time: {(end_time-start_time)/60:.2f} min\")\n#     model.load_state_dict(torch.load(\"best_vit_detr.pth\"))\n#     model.to(device)\n#     model.eval()\n#     benchmark_model(model, input_size=(3, 576, 1024))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:45.910070Z","iopub.execute_input":"2025-11-20T07:15:45.910355Z","iopub.status.idle":"2025-11-20T07:15:45.915286Z","shell.execute_reply.started":"2025-11-20T07:15:45.910328Z","shell.execute_reply":"2025-11-20T07:15:45.914500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nfrom math import isfinite\nfrom tqdm import tqdm\nimport torch, time, gc\ndef train(\n    name,\n    device,\n    model,\n    optimizer,\n    EPOCHS,\n    DEBUG_EVERY,\n    micro_bs,\n    accum_steps,\n    LOGING=True,\n    checkpoint_every=None,        # ví dụ: 5 -> mỗi 5 epoch lưu 1 checkpoint\n    checkpoint_dir=\"checkpoints\", # thư mục lưu checkpoint\n    metrics_path=None,            # file log metric, mặc định /kaggle/working/{name}_metrics.json\n    start_epoch=0,                 # dùng khi train tiếp từ checkpoint\n    checkpoint=None\n):\n    print(\"Train with micro_bs:\", micro_bs)\n    print(\"Train with accum_steps:\", accum_steps)\n    train_loader = DataLoader(train_dataset, batch_size=micro_bs, shuffle=True,\n                          collate_fn=collate_fn, num_workers=4, pin_memory=True)\n    val_loader = DataLoader(val_dataset, batch_size=micro_bs, shuffle=False,\n                            collate_fn=collate_fn, num_workers=4, pin_memory=True)\n\n    # Giảm phân mảnh VRAM (tùy chọn)\n    gc.collect()\n    if torch.cuda.is_available():\n        torch.cuda.empty_cache()\n\n    \n\n    scaler = torch.amp.GradScaler('cuda')\n    best_map50 = -1.0\n    # ==== chuẩn bị checkpoint + metrics ====\n    os.makedirs(checkpoint_dir, exist_ok=True)\n    if metrics_path is None:\n        metrics_path = f\"/kaggle/working/{name}_metrics.json\"\n    metrics_history = []  # list lưu tất cả metric theo epoch\n    if checkpoint :\n        ckpt = torch.load(f\"checkpoints/{checkpoint}\", map_location=device)\n        model.load_state_dict(ckpt[\"model_state_dict\"])\n        optimizer.load_state_dict(ckpt[\"optimizer_state_dict\"])\n        scaler = torch.amp.GradScaler('cuda')\n        scaler.load_state_dict(ckpt[\"scaler_state_dict\"])\n        start_epoch = ckpt[\"epoch\"]  # lần train tiếp sẽ bắt đầu từ epoch này\n        metrics_history = ckpt.get(\"metrics_history\", [])\n    start_time = time.time()\n\n    end_epoch = start_epoch + EPOCHS\n    for epoch in range(start_epoch, end_epoch):\n        print(epoch)\n        model.train()\n        train_loss = 0.0\n        n_batches = 0\n        total_train_images = 0 \n        optimizer.zero_grad(set_to_none=True)\n        num_batches = len(train_loader)\n        for batch_idx, (images, tgt_boxes, tgt_labels) in enumerate(\n            tqdm(train_loader, desc=f\"[Train] Epoch {epoch+1}/{EPOCHS}\")\n        ):\n            # \n            # === Move to device ===\n            images = images.to(device, non_blocking=True)\n            tgt_boxes = [b.to(device, non_blocking=True) for b in tgt_boxes]\n            tgt_labels = [l.to(device, non_blocking=True) for l in tgt_labels]\n\n            # === Forward (AMP) ===\n            with torch.amp.autocast('cuda'):\n                preds = model(images)\n                loss, loss_ce, loss_bbox, loss_iou = detr_loss(\n                    preds['pred_logits'], preds['pred_boxes'], tgt_labels, tgt_boxes\n                )\n                # Chia loss để tổng gradient sau 2 lần có độ lớn y như batch=32\n                loss_scaled = loss / accum_steps\n\n            # === Backward (tích lũy gradient) ===\n            scaler.scale(loss_scaled).backward()\n\n            # == Optional: chặn NaN/Inf sớm (an toàn) ==\n            if not isfinite(loss.item()):\n                print(f\"[WARN] Non-finite loss tại batch {batch_idx+1}: {loss.item()}. Reset grad.\")\n                optimizer.zero_grad(set_to_none=True)\n                continue\n\n            # === Mỗi accum_steps mini-batch mới update 1 lần ===\n            if (batch_idx + 1) % accum_steps == 0 or (batch_idx + 1) == num_batches:\n                scaler.step(optimizer)\n                scaler.update()\n                optimizer.zero_grad(set_to_none=True)\n\n            # === Logging ===\n            bs = images.size(0)             # batch size thực tế\n            train_loss += loss.item() * bs\n            n_batches += 1\n            total_train_images += bs\n\n            if (batch_idx + 1) % DEBUG_EVERY == 0:\n                # (giữ nguyên khối debug của bạn)\n                if len(tgt_boxes[0]) > 0:\n                    debug_show_img_with_boxes(images[0], tgt_boxes[0], preds['pred_boxes'][0])\n                probs = preds['pred_logits'][0].softmax(-1)\n                scores, labels = probs.max(-1)\n\n                num_bg = (labels == 0).sum().item()\n                num_obj = (labels == 1).sum().item()\n\n                with torch.no_grad():\n                    pred_boxes_xyxy = box_cxcywh_to_xyxy(preds['pred_boxes'][0])\n                    if len(tgt_boxes[0]) > 0:\n                        gt_boxes_xyxy = box_cxcywh_to_xyxy(tgt_boxes[0])\n                        ious = box_iou(pred_boxes_xyxy, gt_boxes_xyxy)\n                        avg_iou = ious.max(1)[0].mean().item()\n                    else:\n                        avg_iou = 0.0\n\n                if LOGING:\n                    print(\n                        f\"[DEBUG] Epoch {epoch+1} Batch {batch_idx+1}: \"\n                        f\"Loss={loss.item():.4f} | Max score={scores.max().item():.3f} | \"\n                        f\"Pred>0.3: {(scores>0.3).sum().item()} | Bg={num_bg} | Obj={num_obj} | IoU≈{avg_iou:.3f}\"\n                    )\n        current_lr = optimizer.param_groups[0]['lr'] if optimizer.param_groups else None\n\n        avg_train_loss = train_loss / max(1, total_train_images)\n        # === Evaluate sau mỗi epoch ===\n        # ========== VAL LOSS LOOP (để lấy val_loss) ==========\n        model.eval()\n        val_loss = 0.0\n        val_images = 0\n        val_batches = 0\n        with torch.no_grad():\n            for images, tgt_boxes, tgt_labels in val_loader:\n                images = images.to(device, non_blocking=True)\n                tgt_boxes = [b.to(device, non_blocking=True) for b in tgt_boxes]\n                tgt_labels = [l.to(device, non_blocking=True) for l in tgt_labels]\n\n                with torch.amp.autocast('cuda'):\n                    preds = model(images)\n                    loss, _, _, _ = detr_loss(\n                        preds['pred_logits'], preds['pred_boxes'], tgt_labels, tgt_boxes\n                    )\n                bs = images.size(0)\n                val_loss += loss.item() * bs   # tổng loss theo số ảnh\n                val_images += bs               # đếm ảnh\n\n        avg_val_loss = val_loss / max(1, val_images)\n        prec, rec, f1, map50 = evaluate_map50(model, val_loader, device)\n        if LOGING:\n            print(\n                f\"[Epoch {epoch+1}] Loss: {train_loss/max(1,n_batches):.4f} | \"\n                f\"P: {prec:.3f} R: {rec:.3f} F1: {f1:.3f} mAP50: {map50:.3f}\"\n            )\n        metrics_history.append({\n            \"epoch\": epoch + 1,          # đánh số epoch 1-based cho dễ nhìn\n            \"lr\": current_lr,\n            \"train_loss\": avg_train_loss,\n            \"val_loss\": avg_val_loss,\n            \"map50\": map50,\n        })\n        # mỗi epoch ghi đè file metrics (JSON)\n        try:\n            with open(metrics_path, \"w\") as f:\n                json.dump(metrics_history, f, indent=2)\n        except Exception as e:\n            print(f\"[WARN] Không ghi được metrics vào {metrics_path}: {e}\")\n            \n        # Vẫn lưu best model theo mAP50 như cũ (không dừng sớm nữa)\n        if map50 > best_map50:\n            best_map50 = map50\n            tick_time = time.time()\n            path = f\"/kaggle/working/{name}_{best_map50:.3f}_{epoch}_{(tick_time-start_time)/60:.2f}min.pth\"\n            torch.save(model.state_dict(), path)\n            print(f\"===> Saved best model (mAP50={best_map50:.3f}) -> {path}\")\n        # ========== Lưu CHECKPOINT định kỳ ==========\n        if checkpoint_every is not None and checkpoint_every > 0:\n            # nếu epoch+1 chia hết cho checkpoint_every thì lưu\n            if ((epoch + 1) % checkpoint_every) == 0 or (epoch + 1) == end_epoch:\n                ckpt_path = os.path.join(checkpoint_dir, f\"checkpoint_{epoch+1}.pth\")\n                ckpt = {\n                    \"epoch\": epoch + 1,                     # epoch đã train xong\n                    \"model_state_dict\": model.state_dict(),\n                    \"optimizer_state_dict\": optimizer.state_dict(),\n                    \"scaler_state_dict\": scaler.state_dict(),\n                    \"best_map50\": best_map50,\n                    \"last_train_loss\": avg_train_loss,\n                    \"last_map50\": map50,\n                    \"name\": name,\n                    \"metrics_history\": metrics_history\n                }\n                try:\n                    torch.save(ckpt, ckpt_path)\n                    print(f\"[Checkpoint] Saved: {ckpt_path}\")\n                except Exception as e:\n                    print(f\"[WARN] Không lưu được checkpoint {ckpt_path}: {e}\")\n\n    end_time = time.time()\n    print(f\"⏳ Total train time: {(end_time-start_time)/60:.2f} min\")\n\n\n    # model.load_state_dict(torch.load(\"best_vit_detr.pth\"))\n    # model.to(device).eval()\n    # benchmark_model(model, input_size=(3, 576, 1024))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:45.916036Z","iopub.execute_input":"2025-11-20T07:15:45.916226Z","iopub.status.idle":"2025-11-20T07:15:45.941416Z","shell.execute_reply.started":"2025-11-20T07:15:45.916207Z","shell.execute_reply":"2025-11-20T07:15:45.940748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cp /kaggle/input/deit-500-16-2-500-checkpoint-480/DeiT_0.226_477_63.58min.pth /kaggle/working/\n!cp /kaggle/input/deit-500-16-2-500-checkpoint-480/DeiT_metrics.json /kaggle/working/\n!cp /kaggle/input/deit-500-16-2-500-checkpoint-480/checkpoint_480.pth /kaggle/working/checkpoints/ckpoint.pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T08:32:29.486776Z","iopub.execute_input":"2025-11-20T08:32:29.487187Z","iopub.status.idle":"2025-11-20T08:32:32.352194Z","shell.execute_reply.started":"2025-11-20T08:32:29.487144Z","shell.execute_reply":"2025-11-20T08:32:32.351141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls -lh /kaggle/working\n!ls -lh /kaggle/working/checkpoints","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T07:15:58.255612Z","iopub.execute_input":"2025-11-20T07:15:58.256216Z","iopub.status.idle":"2025-11-20T07:15:58.556933Z","shell.execute_reply.started":"2025-11-20T07:15:58.256189Z","shell.execute_reply":"2025-11-20T07:15:58.555894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_with(name,EPOCHS,batchsize, stepbatchsize, DEBUG_EVERY = 1,checkpoint=None,backbone_lr = 1e-5 ,head_lr =1e-5 ):\n    device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n    print(\"Using:\", device)\n    backbone = None\n    if name == \"CVT\":\n        backbone = CVTBackboneFlexible(\n            model_name='vit_tiny_patch16_224',\n            pretrained=True,\n            use_pos_embed=False,     \n            add_2d_pos_embed=True   \n        )\n    elif name == \"Swin\":\n        backbone = SwinBackboneFlexible('swin_tiny_patch4_window7_224', pretrained=True)\n    elif name == \"DeiT\":\n        backbone = DeiTBackboneFlexible('deit_tiny_patch16_224', pretrained=True, img_size=(576, 1024))\n    elif name == \"ViT\":\n        backbone = ViTBackboneFlexible('vit_tiny_patch16_224', pretrained=True)\n    detr_head = None\n    optimizer = None\n    if name == \"Swin\":\n        detr_head = DETRHead(d_model=backbone.embed_dim, num_queries=10, num_classes=2, num_decoder_layers=3,nhead=12, typeBBox = \"MLP\")\n    else:\n        detr_head = DETRHead(d_model=backbone.embed_dim, num_queries=15, num_classes=2, num_decoder_layers=3,nhead=12, typeBBox = \"MLP\")\n\n    model = ViT_DETR(backbone, detr_head).to(device)\n    if name == \"Swin\":\n        optimizer = torch.optim.AdamW([\n            {\"params\": model.backbone.parameters(), \"lr\": backbone_lr},\n            {\"params\": model.detr_head.parameters(), \"lr\": head_lr}  # trước 2e-4\n        ])\n\n    else:\n        optimizer = torch.optim.AdamW([\n            {\"params\": model.backbone.parameters(), \"lr\": backbone_lr},\n            {\"params\": model.detr_head.parameters(), \"lr\": head_lr}  # trước 2e-4\n        ])\n        # for p in model.backbone.parameters(): \n        #     p.requires_grad=False\n\n    # optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4)\n    # {\"params\": model.backbone.parameters(), \"lr\": 5e-5},\n    start_epoch = 0\n\n    train(\n        name,\n        device=device,\n        model=model,\n        optimizer=optimizer,\n        EPOCHS=EPOCHS,\n        DEBUG_EVERY=DEBUG_EVERY,\n        micro_bs=batchsize,\n        accum_steps=stepbatchsize,\n        LOGING=False,\n        checkpoint_every=5,\n        checkpoint_dir=\"checkpoints\",\n        start_epoch=start_epoch,\n        checkpoint=checkpoint\n    )\n\ntrain_with(\"DeiT\",500,16, 2,500,checkpoint= \"ckpoint.pth\",backbone_lr = 1e-5 ,head_lr =1e-5 )\n","metadata":{"scrolled":true,"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T08:32:37.984454Z","iopub.execute_input":"2025-11-20T08:32:37.985037Z","iopub.status.idle":"2025-11-20T10:11:08.138113Z","shell.execute_reply.started":"2025-11-20T08:32:37.984991Z","shell.execute_reply":"2025-11-20T10:11:08.137037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!mv /kaggle/working/checkpoints/checkpoint_500.pth /kaggle/working/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:11:20.974276Z","iopub.execute_input":"2025-11-20T10:11:20.975065Z","iopub.status.idle":"2025-11-20T10:11:21.140387Z","shell.execute_reply.started":"2025-11-20T10:11:20.975035Z","shell.execute_reply":"2025-11-20T10:11:21.139632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\nimport matplotlib.pyplot as plt\n\nmetrics_path = \"/kaggle/working/DeiT_metrics.json\"\n\n# Load metrics\nwith open(metrics_path, \"r\") as f:\n    metrics = json.load(f)\n\nepochs = [m[\"epoch\"] for m in metrics]\ntrain_loss = [m.get(\"train_loss\", None) for m in metrics]\nval_loss = [m.get(\"val_loss\", None) for m in metrics]   # m.get để tránh lỗi nếu chưa có\n\n# === Vẽ biểu đồ gộp ===\nplt.figure(figsize=(9, 5))\n\nplt.plot(epochs, train_loss, marker=\"o\", label=\"Train Loss\")\nplt.plot(epochs, val_loss, marker=\"s\", label=\"Val Loss\")\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Train & Val Loss vs Epoch\")\nplt.grid(True)\nplt.legend()\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:12:20.061400Z","iopub.execute_input":"2025-11-20T10:12:20.062215Z","iopub.status.idle":"2025-11-20T10:12:20.300742Z","shell.execute_reply.started":"2025-11-20T10:12:20.062183Z","shell.execute_reply":"2025-11-20T10:12:20.300097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\n\nmetrics_path = \"/kaggle/working/DeiT_metrics.json\"\n\nwith open(metrics_path, \"r\") as f:\n    metrics = json.load(f)\n\nepochs = [m[\"epoch\"] for m in metrics]\nval_loss = [m[\"val_loss\"] for m in metrics]\n\n# === Vẽ biểu đồ ===\nplt.figure(figsize=(8, 5))\nplt.plot(epochs, val_loss, marker=\"o\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Val Loss\")\nplt.title(\"Val Loss vs Epoch\")\nplt.grid(True)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:13:28.906577Z","iopub.execute_input":"2025-11-20T10:13:28.907189Z","iopub.status.idle":"2025-11-20T10:13:29.072699Z","shell.execute_reply.started":"2025-11-20T10:13:28.907164Z","shell.execute_reply":"2025-11-20T10:13:29.071969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n\n# ==== 1. Tạo lại backbone y hệt lúc train ====\nbackbone = DeiTBackboneFlexible(\n    'deit_tiny_patch16_224',\n    pretrained=False,              # TEST -> không load pretrained lại\n    img_size=(576, 1024)\n)\n\n# ==== 2. Tạo lại DETR head ====\ndetr_head = DETRHead(\n    d_model=backbone.embed_dim,\n    num_queries=15,\n    num_classes=2,\n    num_decoder_layers=3,\n    nhead=12,\n    typeBBox=\"MLP\"\n)\n\n# ==== 3. Tạo lại model wrapper ====\nmodel = ViT_DETR(backbone, detr_head).to(device)\n\n# ==== 4. Load weights ====\nstate_dict = torch.load(\"/kaggle/working/DeiT_0.230_494_73.38min.pth\", map_location=device)\nmodel.load_state_dict(state_dict)\n\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:16:55.966166Z","iopub.execute_input":"2025-11-20T10:16:55.966479Z","iopub.status.idle":"2025-11-20T10:16:56.210189Z","shell.execute_reply.started":"2025-11-20T10:16:55.966458Z","shell.execute_reply":"2025-11-20T10:16:56.209424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# model.load_state_dict(torch.load(\"/kaggle/working/DeiT_0.230_494_73.38min.pth\"))\n# model.to(device)\n# model.eval()\n\ntest_dataset = DFDetectionDataset(df_to_test, transform=transform)\ntest_loader = DataLoader(test_dataset, batch_size=16, shuffle=False, \n                         collate_fn=collate_fn, num_workers=2, pin_memory=True)\n\nprecision, recall, f1, map50 = evaluate_map50(model, test_loader, device)\n\nprint(\"TEST METRICS\")\nprint(f\"Precision: {precision:.3f}\")\nprint(f\"Recall:    {recall:.3f}\")\nprint(f\"F1 Score:  {f1:.3f}\")\nprint(f\"mAP@50:    {map50:.3f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:17:27.555452Z","iopub.execute_input":"2025-11-20T10:17:27.556058Z","iopub.status.idle":"2025-11-20T10:17:53.293113Z","shell.execute_reply.started":"2025-11-20T10:17:27.556035Z","shell.execute_reply":"2025-11-20T10:17:53.291663Z"}},"outputs":[],"execution_count":null}]}