{"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":24800,"datasetId":1042002,"databundleVersionId":1831594,"isSourceIdPinned":false},{"sourceType":"datasetVersion","sourceId":16234587,"datasetId":10409620,"databundleVersionId":17216720},{"sourceType":"datasetVersion","sourceId":1810939,"datasetId":1075804,"databundleVersionId":1848423}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Bài Tập Lớn - Nhận Dạng Bệnh Từ Ảnh X-Quang Ngực\n\n## Đề bài\nSử dụng dataset VinDr-CXR (https://vindr.ai/cxr) chứa hơn 18,000 ảnh X-quang ngực \nđược thu thập từ 2 bệnh viện lớn tại Việt Nam (2018-2020) để xây dựng mô hình \nhuấn luyện và nhận dạng các trường hợp bệnh được chụp XR.\n\n## Yêu cầu\n1. Sử dụng các mô hình đã học để xây dựng mô hình huấn luyện\n2. Nhận dạng các trường hợp bệnh từ ảnh X-quang\n3. Kết quả phải được cải tiến và so sánh với các nghiên cứu trước đó\n\n## Dataset\n- **VinDr-CXR**: 18,000 ảnh DICOM, 14 loại bệnh, 17 bác sĩ label\n- **MIMIC-CXR**: https://mimic.mit.edu/ (dùng để pretrain hoặc so sánh)\n\n## Các bước thực hiện\n- [ ] Bước 1: Khám phá data (EDA)\n- [ ] Bước 2: Chuẩn bị data\n- [ ] Bước 3: Xây dựng và train model\n- [ ] Bước 4: Đánh giá và so sánh với nghiên cứu trước\n- [ ] Bước 5: Submit kết quả","metadata":{}},{"cell_type":"markdown","source":"1.Khám phá data (EDA)","metadata":{}},{"cell_type":"markdown","source":"In ra danh sách file/folder trong dataset. Kết quả thấy có: train/, test/, train.csv, sample_submission.csv","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\n\nbase_path = \"/kaggle/input/competitions/vinbigdata-chest-xray-abnormalities-detection\"\n\n# Xem cấu trúc thư mục\nfor item in os.listdir(base_path):\n    print(item)\n\nprint(\"\\n--- Train CSV ---\")\ndf = pd.read_csv(f\"{base_path}/train.csv\")\nprint(df.shape)\nprint(df.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:01:39.206484Z","iopub.execute_input":"2026-05-12T20:01:39.207187Z","iopub.status.idle":"2026-05-12T20:01:39.307411Z","shell.execute_reply.started":"2026-05-12T20:01:39.207146Z","shell.execute_reply":"2026-05-12T20:01:39.306556Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Đọc file train.csv CSV chứa nhãn, xem có bao nhiêu ảnh(image_id), bao nhiêu bác sĩ(rad_id), phân bố các loại bệnh(class_name)\n=>Kết quả : \ngồm 67,914 dòng, 8 cột ,\ngồm 15000 ảnh train , \ngồm 17 bác sĩ xem kết quả (radiologist) ","metadata":{}},{"cell_type":"code","source":"# Xem thống kê tổng quan\nprint(\"=== Số ảnh unique ===\")\nprint(df['image_id'].nunique())\n\nprint(\"\\n=== Phân bố các class ===\")\nprint(df['class_name'].value_counts())\n\nprint(\"\\n=== Số radiologist ===\")\nprint(df['rad_id'].nunique())\n\nprint(\"\\n=== Xem thư mục train ===\")\ntrain_imgs = os.listdir(f\"{base_path}/train\")\nprint(f\"Số ảnh train: {len(train_imgs)}\")\nprint(\"Ví dụ:\", train_imgs[:3])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:01:42.896582Z","iopub.execute_input":"2026-05-12T20:01:42.896942Z","iopub.status.idle":"2026-05-12T20:01:42.932295Z","shell.execute_reply.started":"2026-05-12T20:01:42.896911Z","shell.execute_reply":"2026-05-12T20:01:42.931373Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Class nhiều nhất: No finding (31,818)\n\nít nhất: Pneumothorax (226)\n\n=> mất cân bằng dữ liệu nghiêm trọng ","metadata":{}},{"cell_type":"markdown","source":"Lấy ảnh đầu tiên trong thư mục train, đọc file DICOM, convert thành ma trận số để vẽ","metadata":{}},{"cell_type":"code","source":"import pydicom\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nimport numpy as np\n\n# Lấy ảnh đầu tiên trong thư mục train, đọc file DICOM, convert thành ma trận số để vẽ\nsample_id = train_imgs[0].replace('.dicom', '')\ndicom_path = f\"{base_path}/train/{train_imgs[0]}\"\ndicom = pydicom.dcmread(dicom_path)\n\n\nimg = dicom.pixel_array\n\n# Tìm trong CSV các dòng có cùng image_id với ảnh đang xem → lấy tọa độ bounding box và tên bệnh\nsample_df = df[df['image_id'] == sample_id]\n\n# Vẽ ảnh + bounding box\nfig, ax = plt.subplots(1, 1, figsize=(8, 8))\n#Vẽ ảnh X-quang, sau đó vẽ hộp vàng quanh vùng bệnh và ghi tên bệnh lên\nax.imshow(img, cmap='gray')\n\nfor _, row in sample_df.iterrows():\n    if not np.isnan(row['x_min']):\n        rect = patches.Rectangle(\n            (row['x_min'], row['y_min']),\n            row['x_max'] - row['x_min'],\n            row['y_max'] - row['y_min'],\n            linewidth=2, edgecolor='yellow', facecolor='none'\n        )\n        ax.add_patch(rect)\n        ax.text(row['x_min'], row['y_min']-10, \n                row['class_name'], color='yellow', fontsize=10)\n\nax.set_title(f\"ID: {sample_id}\\n{sample_df['class_name'].values}\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:01:46.252951Z","iopub.execute_input":"2026-05-12T20:01:46.253271Z","iopub.status.idle":"2026-05-12T20:01:51.193287Z","shell.execute_reply.started":"2026-05-12T20:01:46.253242Z","shell.execute_reply":"2026-05-12T20:01:51.192536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tìm ảnh có bounding box (không phải No finding)\nhas_bbox = df[df['class_name'] != 'No finding'].iloc[0]\nsample_id = has_bbox['image_id']\ndicom_path = f\"{base_path}/train/{sample_id}.dicom\"\ndicom = pydicom.dcmread(dicom_path)\nimg = dicom.pixel_array\n\nsample_df = df[df['image_id'] == sample_id]\n\nfig, ax = plt.subplots(1, 1, figsize=(8, 8))\nax.imshow(img, cmap='gray')\n\nfor _, row in sample_df.iterrows():\n    if not np.isnan(row['x_min']):\n        rect = patches.Rectangle(\n            (row['x_min'], row['y_min']),\n            row['x_max'] - row['x_min'],\n            row['y_max'] - row['y_min'],\n            linewidth=2, edgecolor='yellow', facecolor='none'\n        )\n        ax.add_patch(rect)\n        ax.text(row['x_min'], row['y_min']-10,\n                f\"{row['class_name']} ({row['rad_id']})\",\n                color='yellow', fontsize=9)\n\nax.set_title(f\"ID: {sample_id}\\nFindings: {sample_df['class_name'].unique()}\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:01:55.744774Z","iopub.execute_input":"2026-05-12T20:01:55.745734Z","iopub.status.idle":"2026-05-12T20:01:58.82156Z","shell.execute_reply.started":"2026-05-12T20:01:55.745675Z","shell.execute_reply":"2026-05-12T20:01:58.820763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rad_per_image = df.groupby('image_id')['rad_id'].nunique()\nprint(rad_per_image.value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:02.610665Z","iopub.execute_input":"2026-05-12T20:02:02.611032Z","iopub.status.idle":"2026-05-12T20:02:02.645538Z","shell.execute_reply.started":"2026-05-12T20:02:02.611002Z","shell.execute_reply":"2026-05-12T20:02:02.644842Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"✅ Load dataset + đọc CSV\n\n\n✅ Xem thống kê (15,000 ảnh, 17 bác sĩ, 14 classes)\n\n\n✅ Phát hiện mất cân bằng data (No finding vs Pneumothorax)\n\n\n✅ Visualize ảnh không có bệnh (No finding)\n\n\n✅ Visualize ảnh có bệnh + bounding box\n\n\n✅ Kiểm tra số bác sĩ mỗi ảnh (đúng 3 bác sĩ/ảnh)","metadata":{}},{"cell_type":"code","source":"import os\n\n# Tìm đường dẫn dataset PNG\nfor item in os.listdir('/kaggle/input/datasets/xhlulu/vinbigdata-chest-xray-resized-png-256x256'):\n    print(item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:07.35664Z","iopub.execute_input":"2026-05-12T20:02:07.357756Z","iopub.status.idle":"2026-05-12T20:02:07.365348Z","shell.execute_reply.started":"2026-05-12T20:02:07.357677Z","shell.execute_reply":"2026-05-12T20:02:07.363928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"png_base = '/kaggle/input/datasets/xhlulu/vinbigdata-chest-xray-resized-png-256x256'\n\n# Xem thử\ntrain_imgs_png = os.listdir(f'{png_base}/train')\nprint(f\"Số ảnh train: {len(train_imgs_png)}\")\nprint(f\"Ví dụ: {train_imgs_png[:3]}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:09.994974Z","iopub.execute_input":"2026-05-12T20:02:09.995706Z","iopub.status.idle":"2026-05-12T20:02:10.006951Z","shell.execute_reply.started":"2026-05-12T20:02:09.995671Z","shell.execute_reply":"2026-05-12T20:02:10.005984Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_meta = pd.read_csv(f\"{png_base}/train_meta.csv\")\nprint(df_meta.shape)\nprint(df_meta.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:12.24312Z","iopub.execute_input":"2026-05-12T20:02:12.243824Z","iopub.status.idle":"2026-05-12T20:02:12.267112Z","shell.execute_reply.started":"2026-05-12T20:02:12.243775Z","shell.execute_reply":"2026-05-12T20:02:12.266186Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Bước 2 - Chuẩn bị Data\n\n### Mục tiêu\nChuẩn bị data sạch và đúng định dạng để đưa vào train model, gồm 3 việc:\n\n### Việc 1: Ảnh PNG ✅\n- Sử dụng dataset PNG 256x256 có sẵn trên Kaggle (xhlulu/vinbigdata-chest-xray-resized-png-256x256)\n- Không cần convert từ DICOM\n\n### Việc 2: Scale Bounding Box 🔲\n- Bounding box trong train.csv được tính trên ảnh gốc (khoảng 3000x3000)\n- Ảnh PNG đã resize về 256x256\n- Cần scale lại tọa độ box cho khớp với ảnh PNG\n- Sử dụng train_meta.csv để biết kích thước ảnh gốc\n\n### Việc 3: Merge Bounding Box 🔲\n- Mỗi ảnh có 3 bác sĩ label → 3 bounding box hơi lệch nhau\n- Cần gộp lại thành 1 box đại diện\n- Sử dụng kỹ thuật WBF (Weighted Boxes Fusion)\n\n### Việc 4: Chia Train/Validation 🔲\n- Chia 15,000 ảnh thành:\n  - 80% train = 12,000 ảnh\n  - 20% validation = 3,000 ảnh\n- Dùng để đánh giá model trong lúc train","metadata":{}},{"cell_type":"markdown","source":"Đọc từng file .dicom trong thư mục train/, convert sang ảnh PNG và lưu vào thư mục mới để model có thể đọc được.\n\nđã có file vinbigdata-chest-xray-resized-png-256x256","metadata":{}},{"cell_type":"code","source":"# Việc 2: Scale bounding box từ ảnh gốc → 256x256\n\n# Gộp train.csv với train_meta.csv để biết kích thước ảnh gốc\ndf_scaled = df.merge(df_meta, on='image_id', how='left')\n\n# Scale tọa độ về 256x256\ndf_scaled['x_min'] = df_scaled['x_min'] * (256 / df_scaled['dim1'])\ndf_scaled['y_min'] = df_scaled['y_min'] * (256 / df_scaled['dim0'])\ndf_scaled['x_max'] = df_scaled['x_max'] * (256 / df_scaled['dim1'])\ndf_scaled['y_max'] = df_scaled['y_max'] * (256 / df_scaled['dim0'])\n\n# So sánh trước và sau scale\nprint(\"=== TRƯỚC khi scale ===\")\nprint(df[df['class_name'] != 'No finding'][['class_name','x_min','y_min','x_max','y_max']].head(3))\n\nprint(\"\\n=== SAU khi scale ===\")\nprint(df_scaled[df_scaled['class_name'] != 'No finding'][['class_name','x_min','y_min','x_max','y_max']].head(3))\n\nprint(f\"\\nSố dòng: {df_scaled.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:26.540649Z","iopub.execute_input":"2026-05-12T20:02:26.541668Z","iopub.status.idle":"2026-05-12T20:02:26.612613Z","shell.execute_reply.started":"2026-05-12T20:02:26.541611Z","shell.execute_reply":"2026-05-12T20:02:26.611697Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_scaled.to_csv(\"train_scaled_bbox.csv\", index=False)\nprint(\"Đã lưu train_scaled_bbox.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:30.355455Z","iopub.execute_input":"2026-05-12T20:02:30.35584Z","iopub.status.idle":"2026-05-12T20:02:30.844865Z","shell.execute_reply.started":"2026-05-12T20:02:30.355784Z","shell.execute_reply":"2026-05-12T20:02:30.844036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_scaled.to_csv(\"train_scaled_bbox.csv\", index=False)\nprint(\"Saved!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T19:54:35.758203Z","iopub.execute_input":"2026-05-12T19:54:35.75867Z","iopub.status.idle":"2026-05-12T19:54:36.254088Z","shell.execute_reply.started":"2026-05-12T19:54:35.758638Z","shell.execute_reply":"2026-05-12T19:54:36.253086Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"BƯỚC 3 – MERGE BOUNDING BOX (WBF)\n🎯 Mục tiêu\n\nMỗi ảnh trong VinDr được 3 bác sĩ label độc lập\n→ cùng 1 bệnh nhưng có 3 box hơi lệch nhau\n\nNếu giữ nguyên:\n\nModel sẽ học nhiễu\nmAP giảm mạnh ❌\n\n👉 Research VinDr luôn làm bước này:\ngộp 3 box → 1 box chuẩn bằng WBF\n\nSau bước này bạn sẽ tạo file mới:\n\ntrain_wbf.csv","metadata":{}},{"cell_type":"markdown","source":"Thuật toán Weighted Boxes Fusion không có sẵn trong Python → cần cài thư viện ensemble-boxes.","metadata":{}},{"cell_type":"code","source":"!pip install ensemble-boxes -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:43.420849Z","iopub.execute_input":"2026-05-12T20:02:43.421201Z","iopub.status.idle":"2026-05-12T20:02:47.628461Z","shell.execute_reply.started":"2026-05-12T20:02:43.421172Z","shell.execute_reply":"2026-05-12T20:02:47.627229Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Load file train_scaled_bbox.csv (kết quả bước 2).\nFile này chứa bbox đã được scale về ảnh 256×256.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom ensemble_boxes import weighted_boxes_fusion\nfrom tqdm import tqdm\n\ndf = pd.read_csv(\"/kaggle/working/train_scaled_bbox.csv\")\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:50.689369Z","iopub.execute_input":"2026-05-12T20:02:50.689802Z","iopub.status.idle":"2026-05-12T20:02:50.817186Z","shell.execute_reply.started":"2026-05-12T20:02:50.689698Z","shell.execute_reply":"2026-05-12T20:02:50.816313Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"WBF chỉ hiểu label dạng số, không hiểu text.\n→ phải chuyển class_name → class_id.","metadata":{}},{"cell_type":"code","source":"labels = df['class_name'].unique()\n\nlabel2id = {name:i for i,name in enumerate(labels)}\nid2label = {v:k for k,v in label2id.items()}\n\ndf['class_id'] = df['class_name'].map(label2id)\n\nprint(\"Số class:\", len(label2id))\nprint(\"\\nMapping class → id:\")\nfor k,v in label2id.items():\n    print(f\"{k} → {v}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:55.52571Z","iopub.execute_input":"2026-05-12T20:02:55.526286Z","iopub.status.idle":"2026-05-12T20:02:55.543525Z","shell.execute_reply.started":"2026-05-12T20:02:55.52625Z","shell.execute_reply":"2026-05-12T20:02:55.542323Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Loại bỏ ảnh \"No finding\"\n🎯 Mục đích\n\n\"No finding\" = ảnh không có bbox\n→ WBF chỉ dùng cho ảnh có bbox.","metadata":{}},{"cell_type":"code","source":"df_box = df[df['class_name'] != 'No finding'].copy()\nprint(\"Số bbox cần merge:\", len(df_box))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:02:59.831885Z","iopub.execute_input":"2026-05-12T20:02:59.83238Z","iopub.status.idle":"2026-05-12T20:02:59.852157Z","shell.execute_reply.started":"2026-05-12T20:02:59.832345Z","shell.execute_reply":"2026-05-12T20:02:59.851098Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Chuẩn hóa bbox về [0 → 1]\n🎯 Mục đích\n\nThuật toán WBF yêu cầu tọa độ bbox nằm trong khoảng 0 → 1\n→ phải chia cho kích thước ảnh (256).","metadata":{}},{"cell_type":"code","source":"df_box['x_min'] /= 256\ndf_box['y_min'] /= 256\ndf_box['x_max'] /= 256\ndf_box['y_max'] /= 256","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:03:04.209896Z","iopub.execute_input":"2026-05-12T20:03:04.210783Z","iopub.status.idle":"2026-05-12T20:03:04.218654Z","shell.execute_reply.started":"2026-05-12T20:03:04.210699Z","shell.execute_reply":"2026-05-12T20:03:04.217694Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Áp dụng thuật toán WBF (PHẦN QUAN TRỌNG NHẤT)\n🧠 Mục đích\n\nGộp bounding box của 3 bác sĩ → 1 bounding box duy nhất cho mỗi bệnh trong mỗi ảnh.","metadata":{}},{"cell_type":"code","source":"results = []\n\nfor image_id, group in tqdm(df_box.groupby(\"image_id\")):\n    \n    boxes_list = []\n    scores_list = []\n    labels_list = []\n\n    # mỗi group rad_id = 1 bác sĩ\n    for rad_id, g in group.groupby(\"rad_id\"):\n        boxes = g[['x_min','y_min','x_max','y_max']].values\n        scores = np.ones(len(boxes))   # VinDr không có confidence → gán = 1\n        labels = g['class_id'].values\n        \n        boxes_list.append(boxes)\n        scores_list.append(scores)\n        labels_list.append(labels)\n\n    boxes, scores, labels = weighted_boxes_fusion(\n        boxes_list, scores_list, labels_list,\n        iou_thr=0.5, skip_box_thr=0.0\n    )\n\n    for b, s, l in zip(boxes, scores, labels):\n        results.append([image_id, id2label[int(round(l))], b[0], b[1], b[2], b[3]])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:03:08.110043Z","iopub.execute_input":"2026-05-12T20:03:08.110551Z","iopub.status.idle":"2026-05-12T20:03:20.894136Z","shell.execute_reply.started":"2026-05-12T20:03:08.110515Z","shell.execute_reply":"2026-05-12T20:03:20.893255Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Tạo DataFrame từ kết quả WBF\n🧠 Mục đích\n\nChuyển kết quả WBF thành bảng dữ liệu.","metadata":{}},{"cell_type":"code","source":"df_wbf = pd.DataFrame(results, columns=[\n    \"image_id\",\"class_name\",\"x_min\",\"y_min\",\"x_max\",\"y_max\"\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:03:33.18318Z","iopub.execute_input":"2026-05-12T20:03:33.183897Z","iopub.status.idle":"2026-05-12T20:03:33.211954Z","shell.execute_reply.started":"2026-05-12T20:03:33.183853Z","shell.execute_reply":"2026-05-12T20:03:33.210981Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Convert bbox từ [0–1] → pixel 256\n🧠 Mục đích\n\nĐưa tọa độ bbox về đơn vị pixel để dùng cho YOLO.","metadata":{}},{"cell_type":"markdown","source":"Lưu dataset sau khi merge\n🧠 Mục đích\n\nTạo dataset cuối cùng dùng để train model.","metadata":{}},{"cell_type":"code","source":"df_wbf.to_csv(\"train_wbf.csv\", index=False)\n\nprint(\"Hoàn thành WBF ✅\")\nprint(\"Kích thước dataset:\", df_wbf.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:03:37.990882Z","iopub.execute_input":"2026-05-12T20:03:37.991224Z","iopub.status.idle":"2026-05-12T20:03:38.202829Z","shell.execute_reply.started":"2026-05-12T20:03:37.991193Z","shell.execute_reply":"2026-05-12T20:03:38.201796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf_check = pd.read_csv(\"/kaggle/working/train_wbf.csv\")\nprint(df_check[['x_min','y_min','x_max','y_max']].head(5))\nprint(\"\\nMin:\", df_check[['x_min','x_max']].min().min())\nprint(\"Max:\", df_check[['x_min','x_max']].max().max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:03:50.065497Z","iopub.execute_input":"2026-05-12T20:03:50.0663Z","iopub.status.idle":"2026-05-12T20:03:50.115255Z","shell.execute_reply.started":"2026-05-12T20:03:50.066262Z","shell.execute_reply":"2026-05-12T20:03:50.114403Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"BƯỚC 3 – Chuẩn bị dataset cho YOLO","metadata":{}},{"cell_type":"code","source":"# =========================================================\n# TASK 1 — Split dataset thành Train/Validation (80/20)\n# Input : train_wbf.csv\n# Output: train_images.txt, val_images.txt\n# =========================================================\n\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\n\n# 1️⃣ Load file bbox sau WBF\ndf = pd.read_csv(\"/kaggle/working/train_wbf.csv\")\n\n# 2️⃣ Lấy danh sách image_id duy nhất (15,000 ảnh)\nimage_ids = df['image_id'].unique()\nprint(\"Tổng số ảnh:\", len(image_ids))\n\n# 3️⃣ Chia train / validation (80/20)\ntrain_ids, val_ids = train_test_split(\n    image_ids,\n    test_size=0.2,\n    random_state=42\n)\n\nprint(\"Train images:\", len(train_ids))\nprint(\"Val images:\", len(val_ids))\n\n# 4️⃣ Lưu danh sách ảnh ra file txt\npd.Series(train_ids).to_csv(\"/kaggle/working/train_images.txt\", index=False, header=False)\npd.Series(val_ids).to_csv(\"/kaggle/working/val_images.txt\", index=False, header=False)\n\nprint(\"✅ Đã tạo train_images.txt và val_images.txt\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:04:07.083102Z","iopub.execute_input":"2026-05-12T20:04:07.083482Z","iopub.status.idle":"2026-05-12T20:04:07.138982Z","shell.execute_reply.started":"2026-05-12T20:04:07.083449Z","shell.execute_reply":"2026-05-12T20:04:07.138044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# TASK 2 — CONVERT BOUNDING BOX (WBF) → YOLO FORMAT\n# =========================================================\n# 🎯 Mục tiêu:\n# - Chuyển bbox từ WBF (pixel 256x256)\n#   sang format YOLO:\n#   <class_id> <x_center> <y_center> <width> <height>\n#\n# - Normalize về [0 → 1]\n# - Loại bỏ class \"No finding\"\n# =========================================================\n\nimport pandas as pd\nimport os\n\n# =========================================================\n# 1️⃣ Load dữ liệu sau WBF\n# =========================================================\ndf = pd.read_csv(\"/kaggle/working/train_wbf.csv\")\n\nIMG_SIZE = 256  # ảnh đã resize về 256x256\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:04:17.91581Z","iopub.execute_input":"2026-05-12T20:04:17.916935Z","iopub.status.idle":"2026-05-12T20:04:17.958882Z","shell.execute_reply.started":"2026-05-12T20:04:17.916897Z","shell.execute_reply":"2026-05-12T20:04:17.958069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 27 - sửa lại thành:\nclasses = {\n    \"Cardiomegaly\": 0,\n    \"Aortic enlargement\": 1,\n    \"Pleural thickening\": 2,\n    \"ILD\": 3,\n    \"Nodule/Mass\": 4,\n    \"Pulmonary fibrosis\": 5,\n    \"Lung Opacity\": 6,\n    \"Atelectasis\": 7,\n    \"Other lesion\": 8,\n    \"Infiltration\": 9,\n    \"Pleural effusion\": 10,\n    \"Calcification\": 11,\n    \"Consolidation\": 12,\n    \"Pneumothorax\": 13\n}\n\nlabel2id = classes\nid2label = {v: k for k, v in classes.items()}\ndf['class_id'] = df['class_name'].map(label2id)\n\nprint(\"Số class:\", len(label2id))\nfor k, v in label2id.items():\n    print(f\"{k} → {v}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:04:27.466966Z","iopub.execute_input":"2026-05-12T20:04:27.4679Z","iopub.status.idle":"2026-05-12T20:04:27.477695Z","shell.execute_reply.started":"2026-05-12T20:04:27.467863Z","shell.execute_reply":"2026-05-12T20:04:27.476669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def convert_bbox(xmin, ymin, xmax, ymax):\n    x_center = (xmin + xmax) / 2.0   # bỏ / IMG_SIZE\n    y_center = (ymin + ymax) / 2.0   # bỏ / IMG_SIZE\n    w = xmax - xmin                   # bỏ / IMG_SIZE\n    h = ymax - ymin                   # bỏ / IMG_SIZE\n    return x_center, y_center, w, h","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:04:38.255909Z","iopub.execute_input":"2026-05-12T20:04:38.256259Z","iopub.status.idle":"2026-05-12T20:04:38.261476Z","shell.execute_reply.started":"2026-05-12T20:04:38.256228Z","shell.execute_reply":"2026-05-12T20:04:38.260463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 4️⃣ Tạo file label YOLO cho từng ảnh\n# =========================================================\ndef create_yolo_labels(df, image_list, output_dir):\n\n    os.makedirs(output_dir, exist_ok=True)\n\n    for img_id in image_list:\n\n        img_df = df[df['image_id'] == img_id]\n\n        lines = []\n\n        for _, row in img_df.iterrows():\n\n            class_name = row['class_name']\n\n            # ❌ Bỏ class \"No finding\" (không dùng trong detection)\n            if class_name == \"No finding\":\n                continue\n\n            # nếu class không hợp lệ thì skip\n            if class_name not in classes:\n                continue\n\n            class_id = classes[class_name]\n\n            # =====================================================\n            # 5️⃣ Convert bbox sang YOLO format\n            # =====================================================\n            x_center, y_center, w, h = convert_bbox(\n                row['x_min'],\n                row['y_min'],\n                row['x_max'],\n                row['y_max']\n            )\n\n            # =====================================================\n            # 6️⃣ Clamp giá trị để tránh lỗi WBF vượt biên\n            # =====================================================\n            x_center = max(0, min(1, x_center))\n            y_center = max(0, min(1, y_center))\n            w = max(0, min(1, w))\n            h = max(0, min(1, h))\n\n            # format YOLO: class x y w h\n            lines.append(f\"{class_id} {x_center} {y_center} {w} {h}\")\n\n        # =====================================================\n        # 7️⃣ Lưu file label cho từng ảnh\n        # =====================================================\n        with open(f\"{output_dir}/{img_id}.txt\", \"w\") as f:\n            f.write(\"\\n\".join(lines))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:04:42.684817Z","iopub.execute_input":"2026-05-12T20:04:42.685307Z","iopub.status.idle":"2026-05-12T20:04:42.693803Z","shell.execute_reply.started":"2026-05-12T20:04:42.685273Z","shell.execute_reply":"2026-05-12T20:04:42.692706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 8️⃣ Load danh sách train / val\n# =========================================================\ntrain_ids = open(\"/kaggle/working/train_images.txt\").read().splitlines()\nval_ids = open(\"/kaggle/working/val_images.txt\").read().splitlines()\n\nprint(\"Train images:\", len(train_ids))\nprint(\"Val images:\", len(val_ids))\n\n# =========================================================\n# 9️⃣ RUN TASK 2\n# =========================================================\ncreate_yolo_labels(\n    df,\n    train_ids,\n    \"/kaggle/working/dataset_yolo/labels/train\"\n)\n\ncreate_yolo_labels(\n    df,\n    val_ids,\n    \"/kaggle/working/dataset_yolo/labels/val\"\n)\n\nprint(\"✅ HOÀN THÀNH TASK 2 - YOLO LABELS READY\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:04:47.061799Z","iopub.execute_input":"2026-05-12T20:04:47.062396Z","iopub.status.idle":"2026-05-12T20:05:00.313144Z","shell.execute_reply.started":"2026-05-12T20:04:47.062362Z","shell.execute_reply":"2026-05-12T20:05:00.312011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#TASK 3\nimport os\nimport shutil\nfrom tqdm import tqdm\n\n# 1️⃣ Định nghĩa đường dẫn\nSOURCE_IMG_DIR = \"/kaggle/input/datasets/xhlulu/vinbigdata-chest-xray-resized-png-256x256/train\" # Thư mục gốc chứa 15,000 ảnh\nTRAIN_IMG_TARGET = \"/kaggle/working/dataset_yolo/images/train\"\nVAL_IMG_TARGET = \"/kaggle/working/dataset_yolo/images/val\"\n\n# 2️⃣ Tạo thư mục\nos.makedirs(TRAIN_IMG_TARGET, exist_ok=True)\nos.makedirs(VAL_IMG_TARGET, exist_ok=True)\n\n# 3️⃣ Đọc danh sách file nhãn đã tạo (để biết cần copy ảnh nào)\ntrain_label_files = os.listdir(\"/kaggle/working/dataset_yolo/labels/train\")\nval_label_files = os.listdir(\"/kaggle/working/dataset_yolo/labels/val\")\n\nprint(\"Đang copy ảnh vào tập Train...\")\nfor label_file in tqdm(train_label_files):\n    img_id = label_file.replace(\".txt\", \".png\")\n    src = os.path.join(SOURCE_IMG_DIR, img_id)\n    dst = os.path.join(TRAIN_IMG_TARGET, img_id)\n    if os.path.exists(src):\n        shutil.copy(src, dst)\n\nprint(\"Đang copy ảnh vào tập Val...\")\nfor label_file in tqdm(val_label_files):\n    img_id = label_file.replace(\".txt\", \".png\")\n    src = os.path.join(SOURCE_IMG_DIR, img_id)\n    dst = os.path.join(VAL_IMG_TARGET, img_id)\n    if os.path.exists(src):\n        shutil.copy(src, dst)\n\nprint(\"✅ TẤT CẢ ĐÃ SẴN SÀNG ĐỂ TRAIN!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:05:08.190982Z","iopub.execute_input":"2026-05-12T20:05:08.191486Z","iopub.status.idle":"2026-05-12T20:05:17.991797Z","shell.execute_reply.started":"2026-05-12T20:05:08.191451Z","shell.execute_reply":"2026-05-12T20:05:17.990915Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import yaml\n\n# Khai báo cấu hình\ndata_config = {\n    'path': '/kaggle/working/dataset_yolo', # Thư mục gốc chứa images và labels\n    'train': 'images/train',                # Đường dẫn tương đối từ path đến ảnh train\n    'val': 'images/val',                    # Đường dẫn tương đối từ path đến ảnh val\n    'nc': 14,                               # 14 loại bệnh (không tính \"No finding\")\n    'names': [\n        \"Cardiomegaly\", \"Aortic enlargement\", \"Pleural thickening\", \"ILD\",\n        \"Nodule/Mass\", \"Pulmonary fibrosis\", \"Lung Opacity\", \"Atelectasis\",\n        \"Other lesion\", \"Infiltration\", \"Pleural effusion\", \"Calcification\",\n        \"Consolidation\", \"Pneumothorax\"\n    ]\n}\n\n# Lưu file yaml vào ổ cứng\nwith open('/kaggle/working/vinbigdata.yaml', 'w') as f:\n    yaml.dump(data_config, f, default_flow_style=False)\n\nprint(\"✅ Đã tạo xong file vinbigdata.yaml\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:05:22.427874Z","iopub.execute_input":"2026-05-12T20:05:22.428764Z","iopub.status.idle":"2026-05-12T20:05:22.437437Z","shell.execute_reply.started":"2026-05-12T20:05:22.42871Z","shell.execute_reply":"2026-05-12T20:05:22.436096Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"BƯỚC 3: HUẤN LUYỆN MÔ HÌNH (TRAIN MODEL)\n1. Giới thiệu giải pháp\nTrong dự án này, chúng ta lựa chọn mô hình YOLOv8 (You Only Look Once) phiên bản từ Ultralytics.\nLý do chọn YOLOv8: Đây là mô hình State-of-the-art (hiện đại nhất) trong bài toán Object Detection. Nó có sự cân bằng cực tốt giữa tốc độ xử lý và độ chính xác, phù hợp để chạy trên hạ tầng của Kaggle.\nPhiên bản: Sử dụng yolov8s.pt (phiên bản Small). Đây là bản đủ mạnh để học được 14 loại bệnh phổi nhưng vẫn đủ nhẹ để hoàn thành huấn luyện trong thời gian giới hạn của GPU Kaggle.\n2. Các tham số huấn luyện (Hyperparameters)\nĐể đạt kết quả tốt, chúng ta cấu hình các tham số sau:\ndata: Đường dẫn đến file vinbigdata.yaml (nơi chứa thông tin dataset).\nepochs: 30 - 50 (Số lần model học lại toàn bộ dữ liệu. Ban đầu nên thử 30 để kiểm tra độ hội tụ).\nimgsz: 256 (Kích thước ảnh đầu vào, phải khớp với ảnh PNG đã chuẩn bị).\nbatch: 16 (Số lượng ảnh máy tính xử lý cùng một lúc).\ndevice: 0 (Chỉ định sử dụng GPU để tăng tốc độ huấn luyện gấp hàng chục lần so với CPU).\n3. Triển khai code huấn luyện\n","metadata":{}},{"cell_type":"code","source":"# 1️⃣ Cài đặt thư viện Ultralytics\n!pip install ultralytics -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T19:57:34.457293Z","iopub.execute_input":"2026-05-12T19:57:34.457815Z","iopub.status.idle":"2026-05-12T19:57:38.6567Z","shell.execute_reply.started":"2026-05-12T19:57:34.457779Z","shell.execute_reply":"2026-05-12T19:57:38.655577Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from ultralytics import YOLO\nimport os\nimport matplotlib.pyplot as plt\nimport cv2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:05:35.808524Z","iopub.execute_input":"2026-05-12T20:05:35.809385Z","iopub.status.idle":"2026-05-12T20:05:35.813877Z","shell.execute_reply.started":"2026-05-12T20:05:35.809349Z","shell.execute_reply":"2026-05-12T20:05:35.812927Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nlabel_dir = \"/kaggle/working/dataset_yolo/labels/train\"\nfiles = os.listdir(label_dir)\n\n# In 3 file đầu\nfor f in files[:3]:\n    path = os.path.join(label_dir, f)\n    with open(path) as fp:\n        content = fp.read()\n    print(f\"=== {f} ===\")\n    print(content)\n    print()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:05:50.845795Z","iopub.execute_input":"2026-05-12T20:05:50.846152Z","iopub.status.idle":"2026-05-12T20:05:50.856546Z","shell.execute_reply.started":"2026-05-12T20:05:50.846109Z","shell.execute_reply":"2026-05-12T20:05:50.855303Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install -U ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:06:18.77155Z","iopub.execute_input":"2026-05-12T20:06:18.772271Z","iopub.status.idle":"2026-05-12T20:06:23.397267Z","shell.execute_reply.started":"2026-05-12T20:06:18.772237Z","shell.execute_reply":"2026-05-12T20:06:23.396218Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from ultralytics import YOLO\n\nmodel = YOLO('yolov8n.pt')  \n\nresults = model.train(\n    data='/kaggle/working/vinbigdata.yaml',\n\n    epochs=120,\n    imgsz=640,\n    batch=16,\n    device=0,\n\n    cache=True,\n    amp=True,\n    workers=8,\n\n    mosaic=0.7,\n    mixup=0.15,\n    hsv_h=0.01,\n    hsv_s=0.5,\n    hsv_v=0.3,\n    fliplr=0.5,\n    flipud=0.0,\n\n    lr0=0.005,\n    lrf=0.01,\n\n    freeze=10,\n    patience=30,\n\n    name='fast_but_better_v3'\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:06:33.519291Z","iopub.execute_input":"2026-05-12T20:06:33.520221Z","iopub.status.idle":"2026-05-12T21:17:37.359289Z","shell.execute_reply.started":"2026-05-12T20:06:33.520177Z","shell.execute_reply":"2026-05-12T21:17:37.358259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip uninstall ray -y -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T15:14:12.944164Z","iopub.execute_input":"2026-05-12T15:14:12.945103Z","iopub.status.idle":"2026-05-12T15:14:17.297383Z","shell.execute_reply.started":"2026-05-12T15:14:12.945064Z","shell.execute_reply":"2026-05-12T15:14:17.296352Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\nimport os\n\n# Xóa toàn bộ thư mục dataset và runs\nfor folder in [\n    \"/kaggle/working/dataset_yolo\",\n    \"/kaggle/working/runs\",\n    \"/kaggle/working/train_png\"\n]:\n    if os.path.exists(folder):\n        shutil.rmtree(folder)\n        print(f\"✅ Đã xóa: {folder}\")\n\n# Xóa các file CSV\nfor f in [\"train_scaled_bbox.csv\", \"train_wbf.csv\", \"train_images.txt\", \"val_images.txt\"]:\n    path = f\"/kaggle/working/{f}\"\n    if os.path.exists(path):\n        os.remove(path)\n        print(f\"✅ Đã xóa: {f}\")\n\nprint(\"\\n🧹 Sạch rồi, chạy lại từ đầu đi!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T20:01:13.754938Z","iopub.execute_input":"2026-05-12T20:01:13.755481Z","iopub.status.idle":"2026-05-12T20:01:14.003454Z","shell.execute_reply.started":"2026-05-12T20:01:13.755436Z","shell.execute_reply":"2026-05-12T20:01:14.002316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir('/kaggle/input'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T23:52:49.237511Z","iopub.execute_input":"2026-05-12T23:52:49.238285Z","iopub.status.idle":"2026-05-12T23:52:49.242919Z","shell.execute_reply.started":"2026-05-12T23:52:49.238253Z","shell.execute_reply":"2026-05-12T23:52:49.242169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"INPUT:\", os.listdir('/kaggle/input'))\nprint(\"DATASETS:\", os.listdir('/kaggle/input/datasets'))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T23:54:08.877052Z","iopub.execute_input":"2026-05-12T23:54:08.877609Z","iopub.status.idle":"2026-05-12T23:54:08.882673Z","shell.execute_reply.started":"2026-05-12T23:54:08.877578Z","shell.execute_reply":"2026-05-12T23:54:08.881979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nfrom IPython.display import Image, display\nimport os\n\npath = '/kaggle/input/datasets/hngbnhhong/model1/results.csv'\ndf = pd.read_csv(path)\n\nfolder = '/kaggle/input/datasets/hngbnhhong/model1'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T23:54:39.410075Z","iopub.execute_input":"2026-05-12T23:54:39.410941Z","iopub.status.idle":"2026-05-12T23:54:39.418494Z","shell.execute_reply.started":"2026-05-12T23:54:39.410897Z","shell.execute_reply":"2026-05-12T23:54:39.417872Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure()\n\nplt.plot(df['epoch'], df['train/box_loss'], label='train box')\nplt.plot(df['epoch'], df['val/box_loss'], label='val box')\n\nplt.plot(df['epoch'], df['train/cls_loss'], label='train cls')\nplt.plot(df['epoch'], df['val/cls_loss'], label='val cls')\n\nplt.plot(df['epoch'], df['train/dfl_loss'], label='train dfl')\nplt.plot(df['epoch'], df['val/dfl_loss'], label='val dfl')\n\nplt.title(\"Loss Curves\")\nplt.legend()\n\nplt.savefig(\"/kaggle/working/loss_curves.png\")  # 👈 lưu file ở đây\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T23:54:45.628783Z","iopub.execute_input":"2026-05-12T23:54:45.629121Z","iopub.status.idle":"2026-05-12T23:54:45.962239Z","shell.execute_reply.started":"2026-05-12T23:54:45.629097Z","shell.execute_reply":"2026-05-12T23:54:45.961576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure()\n\nplt.plot(df['epoch'], df['metrics/precision(B)'], label='Precision')\nplt.plot(df['epoch'], df['metrics/recall(B)'], label='Recall')\n\nplt.title(\"Precision vs Recall\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T23:54:50.307335Z","iopub.execute_input":"2026-05-12T23:54:50.307597Z","iopub.status.idle":"2026-05-12T23:54:50.443871Z","shell.execute_reply.started":"2026-05-12T23:54:50.307576Z","shell.execute_reply":"2026-05-12T23:54:50.44317Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure()\n\nplt.plot(df['epoch'], df['metrics/mAP50(B)'], label='mAP50')\nplt.plot(df['epoch'], df['metrics/mAP50-95(B)'], label='mAP50-95')\n\nplt.title(\"mAP Curve\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T23:54:54.83978Z","iopub.execute_input":"2026-05-12T23:54:54.840497Z","iopub.status.idle":"2026-05-12T23:54:55.025319Z","shell.execute_reply.started":"2026-05-12T23:54:54.840469Z","shell.execute_reply":"2026-05-12T23:54:55.02465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"classes = [\n\"Cardiomegaly\",\"Aortic enlargement\",\"Pleural thickening\",\"ILD\",\n\"Nodule/Mass\",\"Pulmonary fibrosis\",\"Lung Opacity\",\"Atelectasis\",\n\"Other lesion\",\"Infiltration\",\"Pleural effusion\",\"Calcification\",\n\"Consolidation\",\"Pneumothorax\"\n]\n\nmap50 = [\n0.914,0.898,0.247,0.325,\n0.298,0.27,0.294,0.234,\n0.124,0.375,0.448,0.188,\n0.312,0.317\n]\n\nplt.figure()\n\nplt.bar(classes, map50)\nplt.xticks(rotation=90)\nplt.title(\"Per-class mAP@0.5\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T23:55:06.355836Z","iopub.execute_input":"2026-05-12T23:55:06.356628Z","iopub.status.idle":"2026-05-12T23:55:06.506442Z","shell.execute_reply.started":"2026-05-12T23:55:06.356595Z","shell.execute_reply":"2026-05-12T23:55:06.505848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\nshutil.make_archive(\n    '/kaggle/working/fast_but_better-5',  # tên file zip\n    'zip',\n    '/kaggle/working/runs/detect/fast_but_better-5'\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-12T17:20:01.490139Z","iopub.execute_input":"2026-05-12T17:20:01.491196Z","iopub.status.idle":"2026-05-12T17:20:02.598157Z","shell.execute_reply.started":"2026-05-12T17:20:01.491152Z","shell.execute_reply":"2026-05-12T17:20:02.597357Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"So sánh với các nghiên cứu trước đó \n\n## Bước 4: So sánh với nghiên cứu trước\n\n### Các nghiên cứu tham khảo\n\n| Nghiên cứu | Model | mAP50 | Năm |\n|---|---|---|---|\n| CXR-RefineDet | RefineDet + RRNet | 16.9% | 2022 |\n| Kaggle Winner | Ensemble | 32.4% | 2021 |\n| YOLO-CXR (Hao) | YOLOv8 cải tiến | 33.8% | 2024 |\n| YOLOv11-MFF | YOLOv11 + fusion | 41.5% | 2025 |\n| **Model của chúng tôi** | **YOLOv8** | **31.8%** | **2025** |\n\n### Nhận xét\n- Vượt qua CXR-RefineDet 2022 (31.8% > 16.9%)\n- Gần bằng Kaggle Winner 2021 (31.8% vs 32.4%)\n- Chưa bằng SOTA 2024-2025\n\n### Tài liệu tham khảo\n1. CXR-RefineDet (2022): https://pmc.ncbi.nlm.nih.gov/articles/PMC8759881/\n2. Kaggle Competition: https://www.kaggle.com/competitions/vinbigdata-chest-xray-abnormalities-detection/leaderboard\n3. YOLOv11-MFF (2025): https://www.ncbi.nlm.nih.gov/pmc/articles/PMC12551852/\n4. VinDr-CXR Paper: https://pmc.ncbi.nlm.nih.gov/articles/PMC9300612/","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}