{"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":10338,"databundleVersionId":862042},{"sourceType":"datasetVersion","sourceId":1426603,"datasetId":835414,"databundleVersionId":1459942},{"sourceType":"datasetVersion","sourceId":951996,"datasetId":516716,"databundleVersionId":979875}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install pydicom","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-09T16:30:07.769348Z","iopub.execute_input":"2026-05-09T16:30:07.769611Z","iopub.status.idle":"2026-05-09T16:30:12.076560Z","shell.execute_reply.started":"2026-05-09T16:30:07.769576Z","shell.execute_reply":"2026-05-09T16:30:12.075668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom glob import glob\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport pydicom\nimport cv2\nimport numpy as np\nfrom PIL import Image\nfrom tqdm import tqdm\n\n# ==========================================\n# 1. KHỞI TẠO VÀ GỘP DATASET (FULL DATA)\n# ==========================================\nbase_path = '/kaggle/input'\n\n# Đường dẫn dữ liệu\nrsna_images = glob(os.path.join(base_path, 'competitions/rsna-pneumonia-detection-challenge/stage_2_train_images/*.dcm'))\npediatric_normal = glob(os.path.join(base_path, 'datasets/tolgadincer/labeled-chest-xray-images/chest_xray/train/NORMAL/*.jpeg'))\npediatric_pneu = glob(os.path.join(base_path, 'datasets/tolgadincer/labeled-chest-xray-images/chest_xray/train/PNEUMONIA/*.jpeg'))\nindiana_images = glob(os.path.join(base_path, 'datasets/raddar/chest-xrays-indiana-university/images/images_normalized/*.png'))\n\ndata = []\n# Nạp 100% dữ liệu (Không giới hạn)\nfor p in pediatric_normal: data.append({'path': p, 'label': 0})\nfor p in pediatric_pneu: data.append({'path': p, 'label': 1})\nfor p in rsna_images: data.append({'path': p, 'label': 1}) \nfor p in indiana_images: data.append({'path': p, 'label': 0})\n\ndf = pd.DataFrame(data)\nprint(f\"Tổng số lượng ảnh đưa vào huấn luyện THỰC TẾ: {len(df)}\")\n\n# ==========================================\n# 2. TIỀN XỬ LÝ (RESIZE & NORMALIZE)\n# ==========================================\nclass CXRDataset(Dataset):\n    def __init__(self, dataframe, transform=None):\n        self.df = dataframe\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_path = self.df.iloc[idx]['path']\n        label = self.df.iloc[idx]['label']\n\n        try:\n            if img_path.endswith('.dcm'):\n                dicom = pydicom.dcmread(img_path)\n                img = dicom.pixel_array\n                img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)\n                img = Image.fromarray(img).convert('RGB')\n            else:\n                img = Image.open(img_path).convert('RGB')\n                \n            if self.transform:\n                img = self.transform(img)\n                \n            return img, torch.tensor(label, dtype=torch.long)\n        \n        except Exception as e:\n            return torch.zeros((3, 256, 256)), torch.tensor(label, dtype=torch.long)\n\ntransform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# Batch size giữ ở mức 32 để tránh lỗi tràn bộ nhớ RAM của GPU (Out of Memory)\ndataset = CXRDataset(df, transform=transform)\ndataloader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=2)\n\n# ==========================================\n# 3. KIẾN TRÚC MÔ HÌNH (DENSENET-121)\n# ==========================================\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Đang sử dụng thiết bị: {device}\")\n\nmodel = models.densenet121(weights=models.DenseNet121_Weights.DEFAULT)\nnum_ftrs = model.classifier.in_features\nmodel.classifier = nn.Linear(num_ftrs, 2)\nmodel = model.to(device)\n\n# ==========================================\n# 4. HÀM HUẤN LUYỆN\n# ==========================================\ncriterion = nn.CrossEntropyLoss()\n# Giảm learning rate một chút để mô hình hội tụ sâu hơn và ổn định hơn trên lượng data lớn\noptimizer = torch.optim.Adam(model.parameters(), lr=0.0005) \n\nnum_epochs = 10 # Train thật 10 vòng\n\nprint(\"BẮT ĐẦU HUẤN LUYỆN TOÀN TẬP...\")\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n    \n    progress_bar = tqdm(dataloader, desc=f\"Epoch {epoch+1}/{num_epochs}\")\n    \n    for inputs, labels in progress_bar:\n        inputs, labels = inputs.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n        _, predicted = torch.max(outputs.data, 1)\n        total += labels.size(0)\n        correct += (predicted == labels).sum().item()\n        \n        # Hiển thị độ chính xác (Accuracy) realtime\n        progress_bar.set_postfix(loss=loss.item(), acc=100.*correct/total)\n    \n    # Tính Loss và Accuracy trung bình của Epoch\n    epoch_loss = running_loss / len(dataloader)\n    epoch_acc = 100. * correct / total\n    print(f\"\\n=> Kết thúc Epoch {epoch+1}: Loss = {epoch_loss:.4f}, Accuracy = {epoch_acc:.2f}%\\n\")\n    \n    # LƯU DỰ PHÒNG SAU MỖI EPOCH\n    torch.save(model.state_dict(), f'cxr_pretrained_epoch_{epoch+1}.pth')\n\n# ==========================================\n# 5. LƯU KẾT QUẢ CUỐI CÙNG\n# ==========================================\ntorch.save(model.state_dict(), 'cxr_pretrained.pth')\nprint(\"HOÀN THÀNH! Đã lưu thành công mô hình cuối cùng vào file 'cxr_pretrained.pth'!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-09T16:41:41.720539Z","iopub.execute_input":"2026-05-09T16:41:41.721545Z","iopub.status.idle":"2026-05-09T18:23:01.851902Z","shell.execute_reply.started":"2026-05-09T16:41:41.721495Z","shell.execute_reply":"2026-05-09T18:23:01.850930Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}