{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":19989,"databundleVersionId":1160143,"isSourceIdPinned":false}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport os\nimport re\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.models.detection import ssdlite320_mobilenet_v3_large\nimport torchvision.transforms as T\nimport ast\n\n# Cấu hình đường dẫn và tham số\nTRAIN_DIR = '/kaggle/input/competitions/global-wheat-detection/train'\nTEST_DIR = '/kaggle/input/competitions/global-wheat-detection/test'\nTRAIN_CSV = '/kaggle/input/competitions/global-wheat-detection/train.csv'\nDEVICE = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\nIMG_SIZE = 1024","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-05T09:22:12.688321Z","iopub.execute_input":"2026-05-05T09:22:12.688777Z","iopub.status.idle":"2026-05-05T09:22:13.674542Z","shell.execute_reply.started":"2026-05-05T09:22:12.688752Z","shell.execute_reply":"2026-05-05T09:22:13.673905Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import re\n\ndef parse_bbox(bbox_str):\n    # Tìm tất cả các số (kể cả số thực) trong chuỗi\n    numbers = re.findall(r\"[-+]?\\d*\\.\\d+|\\d+\", bbox_str)\n    # Trả về [x, y, w, h] kiểu float\n    return [float(x) for x in numbers]\n\nfrom torchvision.transforms import v2\nfrom torchvision import tv_tensors\nimport torch\n\nclass WheatDatasetV2(torch.utils.data.Dataset):\n    def __init__(self, dataframe, image_dir, transforms):\n        self.image_ids = dataframe['image_id'].unique()\n        self.df = dataframe\n        self.image_dir = image_dir\n        self.transforms = transforms\n\n    def __getitem__(self, index):\n        image_id = self.image_ids[index]\n        records = self.df[self.df['image_id'] == image_id]\n        \n        # Đọc ảnh bằng OpenCV\n        img_path = f\"{self.image_dir}/{image_id}.jpg\"\n        image = cv2.imread(img_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        boxes = []\n        for bbox_str in records['bbox'].values:\n            # Dùng hàm parse thủ công thay cho ast\n            x, y, w, h = parse_bbox(bbox_str)\n            # Chuyển sang format [xmin, ymin, xmax, ymax]\n            boxes.append([x, y, x + w, y + h])\n        \n        boxes = torch.as_tensor(boxes, dtype=torch.float32)\n        \n        # Đưa vào cấu trúc dữ liệu của v2\n        image = tv_tensors.Image(image.transpose(2, 0, 1)) \n        target = {\n            \"boxes\": tv_tensors.BoundingBoxes(\n                boxes, \n                format=\"XYXY\", \n                canvas_size=(1024, 1024)\n            ),\n            \"labels\": torch.ones((len(boxes),), dtype=torch.int64),\n            \"image_id\": torch.tensor([index])\n        }\n\n        if self.transforms:\n            image, target = self.transforms(image, target)\n\n        return image, target, image_id\n\n    def __len__(self):\n        return self.image_ids.shape[0]\n\ndef get_train_transforms():\n    return v2.Compose([\n        v2.RandomHorizontalFlip(p=0.5),\n        v2.RandomVerticalFlip(p=0.5),\n        # Crop ngẫu nhiên và resize lại 1024x1024\n        v2.RandomResizedCrop(size=(1024, 1024), scale=(0.5, 1.0), antialias=True),\n        v2.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3),\n        v2.ToImage(),\n        v2.ToDtype(torch.float32, scale=True),\n        v2.SanitizeBoundingBoxes(), # Loại bỏ box lỗi sau khi crop\n    ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T09:22:17.150627Z","iopub.execute_input":"2026-05-05T09:22:17.151370Z","iopub.status.idle":"2026-05-05T09:22:17.160904Z","shell.execute_reply.started":"2026-05-05T09:22:17.151337Z","shell.execute_reply":"2026-05-05T09:22:17.160160Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models.detection import FasterRCNN\nfrom torchvision.models import resnet34, ResNet34_Weights\nfrom torchvision.models.detection.backbone_utils import _validate_trainable_layers\nfrom torchvision.ops import MultiScaleRoIAlign\nfrom torchvision.models.detection.anchor_utils import AnchorGenerator\n\ndef get_resnet34_backbone():\n    # 1. Khởi tạo ResNet34 thuần túy, KHÔNG có weights\n    # Chúng ta lấy các layer từ resnet34 để build FPN (Feature Pyramid Network)\n    backbone_model = resnet34(weights=None)\n    \n    # Lấy các module cần thiết (không lấy lớp fc cuối cùng)\n    # Cấu trúc của Faster R-CNN thường cần backbone trả về các feature maps\n    # Ở đây dùng hàm tiện ích của torchvision để bọc ResNet34 vào FPN\n    from torchvision.models.detection.backbone_utils import BackboneWithFPN\n    \n    # Định nghĩa các layer trả về feature map cho FPN\n    return_layers = {'layer1': '0', 'layer2': '1', 'layer3': '2', 'layer4': '3'}\n    in_channels_list = [64, 128, 256, 512] # Số channel tương ứng của ResNet34\n    out_channels = 256 # Số channel đầu ra của FPN\n    \n    from torchvision.models.detection.backbone_utils import BackboneWithFPN\n    backbone = BackboneWithFPN(backbone_model, return_layers, in_channels_list, out_channels)\n    return backbone\n\ndef get_model(num_classes):\n    backbone = get_resnet34_backbone()\n    # 2. Khởi tạo Faster R-CNN với backbone ResNet34 \"sạch\"\n    model = FasterRCNN(backbone, num_classes=num_classes)\n    return model\n\nmodel = get_model(num_classes=2)\nmodel = model.to(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T09:52:05.333799Z","iopub.execute_input":"2026-05-05T09:52:05.334431Z","iopub.status.idle":"2026-05-05T09:52:05.854577Z","shell.execute_reply.started":"2026-05-05T09:52:05.334393Z","shell.execute_reply":"2026-05-05T09:52:05.853975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.optim import AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingWarmRestarts\nimport time\n\n# 1. Khởi tạo DataLoader (Giữ nguyên của bạn nhưng check num_workers)\ntrain_dataset = WheatDatasetV2(pd.read_csv(TRAIN_CSV), TRAIN_DIR, get_train_transforms())\ntrain_loader = DataLoader(\n    train_dataset, \n    batch_size=4, \n    shuffle=True, \n    num_workers=2,\n    drop_last=True,\n    collate_fn=lambda x: tuple(zip(*x))\n)\n\n# 2. Định nghĩa Optimizer & Scheduler (Tối ưu cho No-Pretrain)\nparams = [p for p in model.parameters() if p.requires_grad]\n# Dùng AdamW thay cho Adam để kiểm soát Weight Decay tốt hơn\noptimizer = AdamW(params, lr=1e-4, weight_decay=1e-4) \n\n# CosineAnnealing sẽ giúp LR giảm dần cực kỳ hiệu quả cho việc hội tụ\nscheduler = CosineAnnealingWarmRestarts(optimizer, T_0=5, T_mult=1, eta_min=1e-6)\n\n# 3. Vòng lặp Huấn luyện (Training Loop) với các kỹ thuật bổ trợ\nnum_epochs = 5 # Tăng lên vì train from scratch cần thời gian\nmodel.to(DEVICE)\n\nprint(f\"Bắt đầu huấn luyện trên {DEVICE}...\")\nfor epoch in range(num_epochs):\n    epoch_loss = 0\n    model.train()\n    start_time = time.time()\n    \n    for cur_iter, (images, targets, _) in enumerate(train_loader):\n        images = list(image.to(DEVICE) for image in images)\n        targets = [{k: v.to(DEVICE) for k, v in t.items()} for t in targets]\n\n        loss_dict = model(images, targets)\n        losses = sum(loss for loss in loss_dict.values())\n\n        if not torch.isfinite(losses):\n            print(f\"Loss is {losses.item()}, stopping training\")\n            break\n\n        optimizer.zero_grad()\n        losses.backward()\n        \n        # --- THÊM: Gradient Clipping để tránh nổ Loss ---\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)\n        \n        optimizer.step()\n\n        epoch_loss += losses.item()\n\n        if (cur_iter + 1) % 20 == 0:\n            print(f\"Epoch [{epoch+1}/{num_epochs}], Iter [{cur_iter+1}/{len(train_loader)}], Loss: {losses.item():.4f}, LR: {optimizer.param_groups[0]['lr']:.6f}\")\n\n    # Cập nhật Scheduler sau mỗi Epoch\n    scheduler.step()\n    \n    end_time = time.time()\n    avg_loss = epoch_loss / len(train_loader)\n    print(f\"==> Epoch {epoch+1} Complete - Avg Loss: {avg_loss:.4f} - Time: {end_time - start_time:.2f}s\")\n    \n    # --- THÊM: Lưu Checkpoint để tránh mất bài khi Kaggle ngắt kết nối ---\n    torch.save(model.state_dict(), f'model_epoch_{epoch+1}.pth')\n\nprint(\"Huấn luyện hoàn tất!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-05T09:53:07.758779Z","iopub.execute_input":"2026-05-05T09:53:07.759122Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def format_prediction_string(boxes, scores):\n    pred_strings = []\n    for j in range(len(boxes)):\n        # Format: [score xmin ymin w h]\n        pred_strings.append(f\"{scores[j]:.4f} {boxes[j][0]} {boxes[j][1]} {boxes[j][2]-boxes[j][0]} {boxes[j][3]-boxes[j][1]}\")\n    return \" \".join(pred_strings)\n\ndef run_inference():\n    test_images = os.listdir(TEST_DIR)\n    results = []\n    model.eval()\n    \n    with torch.no_grad():\n        for image_file in test_images:\n            image_id = image_file.split('.')[0]\n            image = cv2.imread(f'{TEST_DIR}/{image_file}', cv2.IMREAD_COLOR)\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0\n            \n            input_tensor = torch.tensor(image).permute(2, 0, 1).unsqueeze(0).to(DEVICE)\n            outputs = model(input_tensor)\n            \n            boxes = outputs[0]['boxes'].data.cpu().numpy()\n            scores = outputs[0]['scores'].data.cpu().numpy()\n            \n            # Chỉ lấy các box có độ tự tin cao (ví dụ > 0.5)\n            mask = scores > 0.5\n            boxes = boxes[mask]\n            scores = scores[mask]\n            \n            pred_str = format_prediction_string(boxes, scores)\n            results.append({'image_id': image_id, 'PredictionString': pred_str})\n            \n    return pd.DataFrame(results)\n\n# Xuất file submission.csv\nsubmission_df = run_inference()\nsubmission_df.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}