{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"datasetVersion","sourceId":14710900,"datasetId":9398868,"databundleVersionId":15556611},{"sourceType":"datasetVersion","sourceId":952401,"datasetId":517172,"databundleVersionId":980293},{"sourceType":"datasetVersion","sourceId":14820720,"datasetId":9478123,"databundleVersionId":15677678},{"sourceType":"datasetVersion","sourceId":46865,"datasetId":34835,"databundleVersionId":49246}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<h1>DR_Net13</h1>","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc, mean_squared_error\n\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, optimizers\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping\n\ntry:\n    gpus = tf.config.list_physical_devices('GPU')\n    if gpus:\n        try:\n            for gpu in gpus:\n                tf.config.experimental.set_memory_growth(gpu, True)\n            strategy = tf.distribute.MirroredStrategy()\n        except RuntimeError as e:\n            print(e)\n            strategy = tf.distribute.get_strategy() \n    else:\n        print(\"Không tìm thấy GPU\")\n        strategy = tf.distribute.get_strategy() \nexcept Exception as e:\n    print(f\"Lỗi cấu hình GPU: {e}\")\n    strategy = tf.distribute.get_strategy()\n\nSEED = 42\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nTRAIN_IMG_DIR = '/kaggle/input/datasets/sovitrath/diabetic-retinopathy-224x224-gaussian-filtered/gaussian_filtered_images/gaussian_filtered_images'\nIMG_SIZE = 224\nNUM_CLASSES = 5\nBATCH_SIZE = 32 * strategy.num_replicas_in_sync ","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2026-03-11T06:31:50.623171Z","iopub.status.busy":"2026-03-11T06:31:50.622579Z","iopub.status.idle":"2026-03-11T06:32:08.233486Z","shell.execute_reply":"2026-03-11T06:32:08.232818Z","shell.execute_reply.started":"2026-03-11T06:31:50.623134Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h2>diabetic-retinopathy-224x224-gaussian-filtered</h2>","metadata":{}},{"cell_type":"code","source":"classes = ['No_DR', 'Mild', 'Moderate', 'Severe', 'Proliferate_DR']\nfig, axes = plt.subplots(1, len(classes), figsize=(20, 5))\nfor i, cls in enumerate(classes):\n    class_dir = os.path.join(TRAIN_IMG_DIR, cls)\n    file_names = os.listdir(class_dir)\n    img_name = file_names[0]\n    full_img_path = os.path.join(class_dir, img_name)\n    img = cv2.imread(full_img_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    axes[i].imshow(img)\n    axes[i].set_title(cls)\n    axes[i].axis('off')\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:32:08.235239Z","iopub.status.busy":"2026-03-11T06:32:08.234750Z","iopub.status.idle":"2026-03-11T06:32:09.293010Z","shell.execute_reply":"2026-03-11T06:32:09.292224Z","shell.execute_reply.started":"2026-03-11T06:32:08.235214Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h3>Khử nhiễu - median filter</h3>","metadata":{}},{"cell_type":"code","source":"classes = ['No_DR', 'Mild', 'Moderate', 'Severe', 'Proliferate_DR']\nfig, axes = plt.subplots(1, len(classes), figsize=(20, 5))\nfor i, cls in enumerate(classes):\n    class_dir = os.path.join(TRAIN_IMG_DIR, cls)\n    file_names = os.listdir(class_dir)\n    img_name = file_names[0]\n    full_img_path = os.path.join(class_dir, img_name)\n    img = cv2.imread(full_img_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    img = cv2.medianBlur(img, 3) #Khử nhiễu\n    axes[i].imshow(img)\n    axes[i].set_title(cls)\n    axes[i].axis('off')\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:32:09.294168Z","iopub.status.busy":"2026-03-11T06:32:09.293933Z","iopub.status.idle":"2026-03-11T06:32:09.963218Z","shell.execute_reply":"2026-03-11T06:32:09.962316Z","shell.execute_reply.started":"2026-03-11T06:32:09.294140Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h3>Gamma Correction</h3>","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\ndef apply_gamma_correction(image, gamma=1.2):\n\n    inv_gamma = 1.0 / gamma\n    \n    table = np.array([((i / 255.0) ** inv_gamma) * 255 \n                      for i in np.arange(0, 256)]).astype(\"uint8\")\n    \n    return cv2.LUT(image, table)\n\nimg_path = '/kaggle/input/datasets/sovitrath/diabetic-retinopathy-224x224-gaussian-filtered/gaussian_filtered_images/gaussian_filtered_images/Severe/0104b032c141.png'\nimage = cv2.imread(img_path)\n\nif image is not None:\n\n    gamma_image = apply_gamma_correction(image, gamma=1.2)\n    \n    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    gamma_image_rgb = cv2.cvtColor(gamma_image, cv2.COLOR_BGR2RGB)\n    \n    # 3. Hiển thị so sánh bằng Matplotlib\n    plt.figure(figsize=(12, 6))\n    \n    plt.subplot(1, 2, 1)\n    plt.imshow(image_rgb)\n    plt.title(\"Ảnh gốc\")\n    plt.axis('off')\n    \n    plt.subplot(1, 2, 2)\n    plt.imshow(gamma_image_rgb)\n    plt.title(\"Ảnh sau Gamma Correction\")\n    plt.axis('off')\n    \n    plt.show() ","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:32:09.965300Z","iopub.status.busy":"2026-03-11T06:32:09.965010Z","iopub.status.idle":"2026-03-11T06:32:10.311068Z","shell.execute_reply":"2026-03-11T06:32:10.310228Z","shell.execute_reply.started":"2026-03-11T06:32:09.965276Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_image(image_path, gamma=1.2, kernel_size = 3):\n    \"\"\"\n    1. Khử nhiễu: Median Filter\n    2. Tăng cường: Gamma Correction\n    \"\"\"\n    img = cv2.imread(image_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    # 1. Khử nhiễu \n    img = cv2.medianBlur(img, kernel_size)\n    \n    # 2. Gamma Correction\n    img = apply_gamma_correction(img, gamma) \n    \n    return img\n\npath = '/kaggle/input/datasets/sovitrath/diabetic-retinopathy-224x224-gaussian-filtered/gaussian_filtered_images/gaussian_filtered_images/Severe/03c85870824c.png'\noriginal_img = cv2.imread(path)\noriginal_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB)\n\nplt.figure(figsize=(12, 6))\n\nplt.subplot(1, 2, 1)\nplt.title(\"Original Image\")\nplt.imshow(original_img)\n\nplt.subplot(1, 2, 2)\nplt.title(\"Median + Gamma Correction\")\nplt.imshow(preprocess_image(path))\n\nplt.show()","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:32:10.312845Z","iopub.status.busy":"2026-03-11T06:32:10.312391Z","iopub.status.idle":"2026-03-11T06:32:10.773656Z","shell.execute_reply":"2026-03-11T06:32:10.772895Z","shell.execute_reply.started":"2026-03-11T06:32:10.312811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nfrom pathlib import Path\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nimport albumentations as A\nfrom tensorflow.keras.utils import to_categorical\n\naug_dict = {\n    'rotate_45': A.Rotate(limit=(45, 45), p=1.0),\n    'rotate_90_right': A.Rotate(limit=(90, 90), p=1.0),\n    'rotate_90_left': A.Rotate(limit=(-90, -90), p=1.0),\n    'horizontal_flip': A.HorizontalFlip(p=1.0),\n    'vertical_flip': A.VerticalFlip(p=1.0),\n    'translate': A.Affine(translate_px={\"x\": 30, \"y\": 15}, p=1.0),\n    'horizontal_shear': A.Affine(shear={\"x\": 45}, p=1.0), \n    'vertical_shear': A.Affine(shear={\"y\": 45}, p=1.0)\n}\naug_names = list(aug_dict.keys())\n\ndef apply_random_augmentation(image):\n    random_aug_name = np.random.choice(aug_names)\n    transform = aug_dict[random_aug_name]\n    return transform(image=image)['image']\n\ndef load_and_balance_from_folders(base_dir, target_per_class=1500):\n    base_path = Path(base_dir)\n    \n    classes = ['No_DR', 'Mild', 'Moderate', 'Severe', 'Proliferate_DR']\n    \n    data_X, data_y = [], []\n    \n    for label, cls_name in enumerate(classes):\n        class_dir = base_path / cls_name\n        \n        image_paths = list(class_dir.glob(\"*.png\"))\n        count = len(image_paths)\n        \n        print(f\"\\n--- Xử lý Lớp {cls_name} (Nhãn {label}) - Gốc có {count} ảnh ---\")\n        \n        if count == 0:\n            print(f\"Cảnh báo: Thư mục {cls_name} trống hoặc sai đường dẫn!\")\n            continue\n\n        loaded_originals = []\n        \n        # Bước A: Load ảnh gốc (1500 ảnh)\n        np.random.shuffle(image_paths)\n        selected_paths = image_paths[:min(count, target_per_class)]\n        \n        for img_path in tqdm(selected_paths, desc=\"Loading Originals\"):\n            img = preprocess_image(str(img_path)) \n            loaded_originals.append(img)\n            data_X.append(img)\n            data_y.append(label)\n                \n        # Bước B: Oversampling \n        current_count = len(loaded_originals)\n        needed = target_per_class - current_count\n        \n        if needed > 0:\n            print(f\"Cần sinh thêm {needed} ảnh bằng Augmentation...\")\n            for i in tqdm(range(needed), desc=\"Augmenting\"):\n                # Lấy xoay vòng các ảnh gốc để làm dữ liệu nền tảng\n                orig_img = loaded_originals[i % current_count]\n                \n                # Tạo ra 1 phiên bản biến đổi\n                aug_img = apply_random_augmentation(orig_img)\n                \n                data_X.append(aug_img)\n                data_y.append(label)\n                \n    return np.array(data_X), np.array(data_y)\n\nBASE_DIR = '/kaggle/input/datasets/sovitrath/diabetic-retinopathy-224x224-gaussian-filtered/gaussian_filtered_images/gaussian_filtered_images'\nX_full, y_full = load_and_balance_from_folders(BASE_DIR, target_per_class=1500)\n\n# Chuẩn hóa về [0, 1] và One-hot\nX_full = X_full.astype('float32') / 255.0\ny_full = to_categorical(y_full, 5)\n\nprint(f\"\\nHoàn tất! Kích thước X: {X_full.shape}, y: {y_full.shape}\")","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:32:10.774970Z","iopub.status.busy":"2026-03-11T06:32:10.774732Z","iopub.status.idle":"2026-03-11T06:33:34.389645Z","shell.execute_reply":"2026-03-11T06:33:34.388749Z","shell.execute_reply.started":"2026-03-11T06:32:10.774948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train, X_temp, y_train, y_temp = train_test_split(X_full, y_full, test_size=0.4, random_state=42, stratify=y_full)\n\nX_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp)\n\nprint(f\"Train Set (60%): X={X_train.shape}, y={y_train.shape}\")\nprint(f\"Validation Set (20%): X={X_val.shape}, y={y_val.shape}\")\nprint(f\"Test Set (20%): X={X_test.shape}, y={y_test.shape}\")","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:33:34.391125Z","iopub.status.busy":"2026-03-11T06:33:34.390815Z","iopub.status.idle":"2026-03-11T06:33:36.272988Z","shell.execute_reply":"2026-03-11T06:33:36.272214Z","shell.execute_reply.started":"2026-03-11T06:33:34.391091Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\ndef check_data_distribution(y_train, y_val, y_test):\n    y_train_labels = np.argmax(y_train, axis=1)\n    y_val_labels = np.argmax(y_val, axis=1)\n    y_test_labels = np.argmax(y_test, axis=1)\n    \n    classes, train_counts = np.unique(y_train_labels, return_counts=True)\n    _, val_counts = np.unique(y_val_labels, return_counts=True)\n    _, test_counts = np.unique(y_test_labels, return_counts=True)\n    \n    class_names = ['No_DR (0)', 'Mild (1)', 'Moderate (2)', 'Severe (3)', 'Proliferate_DR (4)']\n    \n    df_stats = pd.DataFrame({\n        'Class': class_names,\n        'Train Set': train_counts,\n        'Validation Set': val_counts,\n        'Test Set': test_counts\n    })\n    \n    df_stats.loc['Total'] = ['Tổng cộng', sum(train_counts), sum(val_counts), sum(test_counts)]\n    \n    print(df_stats.to_string(index=False))\n\ncheck_data_distribution(y_train, y_val, y_test)","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:33:36.274208Z","iopub.status.busy":"2026-03-11T06:33:36.273973Z","iopub.status.idle":"2026-03-11T06:33:36.295533Z","shell.execute_reply":"2026-03-11T06:33:36.294967Z","shell.execute_reply.started":"2026-03-11T06:33:36.274187Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, models, optimizers\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping\n\ndef build_drnet13(input_shape=(224, 224, 3), num_classes=5):\n    model = models.Sequential(name='DRNet13')\n    \n    model.add(layers.Input(shape=input_shape))\n    \n    # Block 1 \n    # 64 filters, 3x3, ReLU \n    model.add(layers.Conv2D(64, (3, 3), strides=1, padding='same', activation='relu', name='Conv_1'))\n    # Pooling: 2x2, stride 2, valid (None) \n    model.add(layers.MaxPooling2D((2, 2), strides=2, padding='valid', name='Pool_1')) # Output: 112x112\n    \n    #Block 2\n    # 128 filters \n    model.add(layers.Conv2D(128, (3, 3), strides=1, padding='same', activation='relu', name='Conv_2'))\n    model.add(layers.MaxPooling2D((2, 2), strides=2, padding='valid', name='Pool_2')) # Output: 56x56\n    \n    # Block 3\n    # 256 filters \n    model.add(layers.Conv2D(256, (3, 3), strides=1, padding='same', activation='relu', name='Conv_3'))\n    model.add(layers.MaxPooling2D((2, 2), strides=2, padding='valid', name='Pool_3')) # Output: 28x28\n    \n    #Normalization Layer \n    model.add(layers.BatchNormalization(name='Norm_Layer'))\n    \n    # Flatten \n    model.add(layers.Flatten(name='Faltten'))\n    \n    #  Dense Block 1 \n    # 1024 nodes \n    model.add(layers.Dense(1024, activation='relu', name='FC_1'))\n    # Dropout 0.5 \n    model.add(layers.Dropout(0.5, name='Dropout_Layer'))\n    \n    #  Dense Block 2 \n    # 512 nodes \n    model.add(layers.Dense(512, activation='relu', name='FC_2'))\n    \n    # Output Layer\n    # 5 classes với Softmax \n    model.add(layers.Dense(num_classes, activation='softmax', name='Output_Layer'))\n    \n    return model\n","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:33:36.296662Z","iopub.status.busy":"2026-03-11T06:33:36.296346Z","iopub.status.idle":"2026-03-11T06:33:36.304819Z","shell.execute_reply":"2026-03-11T06:33:36.304133Z","shell.execute_reply.started":"2026-03-11T06:33:36.296632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with strategy.scope():\n    model = build_drnet13(input_shape=(IMG_SIZE, IMG_SIZE, 3), num_classes=NUM_CLASSES)\n    \n    opt = optimizers.Adam(learning_rate=0.00001) \n    \n    model.compile(optimizer=opt,\n                  loss='categorical_crossentropy',\n                  metrics=['accuracy'])\n\nmodel.summary()\n\ncheckpoint = ModelCheckpoint(\"best_drnet13.keras\", monitor='val_accuracy', verbose=1, save_best_only=True, mode='max')\nearly_stop = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True)\n\nprint(f\"\\nBắt đầu huấn luyện với {strategy.num_replicas_in_sync} GPU...\")\n\nhistory = model.fit(\n    X_train, y_train,\n    epochs=300,                \n    batch_size=32 * strategy.num_replicas_in_sync, \n    validation_data=(X_val, y_val),\n    callbacks=[checkpoint, early_stop],\n    verbose=1\n)","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:33:36.307421Z","iopub.status.busy":"2026-03-11T06:33:36.307058Z","iopub.status.idle":"2026-03-11T06:59:14.343893Z","shell.execute_reply":"2026-03-11T06:59:14.343239Z","shell.execute_reply.started":"2026-03-11T06:33:36.307364Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    balanced_accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    matthews_corrcoef,\n    cohen_kappa_score,\n    precision_recall_fscore_support,\n    confusion_matrix,\n    ConfusionMatrixDisplay,\n    roc_auc_score\n)\n\n\nLABELS = [0, 1, 2, 3, 4]\n\nCLASS_NAMES = [\n    \"No DR\",\n    \"Mild\",\n    \"Moderate\",\n    \"Severe\",\n    \"Proliferative\"\n]\n\n\ndef evaluate_dr_model(\n    y_true,\n    y_pred,\n    y_prob=None,\n    experiment_name=\"DRNet13\",\n    output_prefix=\"drnet13\"\n):\n    \"\"\"\n    Đánh giá mô hình theo cùng một cấu trúc metric\n    dùng cho toàn bộ các notebook DR.\n\n    Specificity được tính theo one-vs-rest:\n        Specificity = TN / (TN + FP)\n    \"\"\"\n\n    y_true = np.asarray(\n        y_true\n    ).reshape(-1).astype(int)\n\n    y_pred = np.asarray(\n        y_pred\n    ).reshape(-1).astype(int)\n\n    if y_prob is not None:\n        y_prob = np.asarray(\n            y_prob,\n            dtype=float\n        )\n\n    if len(y_true) == 0:\n        raise ValueError(\n            \"Tập đánh giá không có mẫu.\"\n        )\n\n    if len(y_true) != len(y_pred):\n        raise ValueError(\n            \"y_true và y_pred không cùng số lượng mẫu.\"\n        )\n\n    # =====================================================\n    # 1. CONFUSION MATRIX\n    # =====================================================\n\n    cm = confusion_matrix(\n        y_true,\n        y_pred,\n        labels=LABELS\n    )\n\n    cm_normalized = confusion_matrix(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        normalize=\"true\"\n    )\n\n    # =====================================================\n    # 2. OVERALL METRICS\n    # =====================================================\n\n    accuracy = accuracy_score(\n        y_true,\n        y_pred\n    )\n\n    balanced_acc = balanced_accuracy_score(\n        y_true,\n        y_pred\n    )\n\n    precision_macro = precision_score(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        average=\"macro\",\n        zero_division=0\n    )\n\n    precision_weighted = precision_score(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        average=\"weighted\",\n        zero_division=0\n    )\n\n    recall_macro = recall_score(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        average=\"macro\",\n        zero_division=0\n    )\n\n    recall_weighted = recall_score(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        average=\"weighted\",\n        zero_division=0\n    )\n\n    f1_macro = f1_score(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        average=\"macro\",\n        zero_division=0\n    )\n\n    f1_weighted = f1_score(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        average=\"weighted\",\n        zero_division=0\n    )\n\n    mcc = matthews_corrcoef(\n        y_true,\n        y_pred\n    )\n\n    qwk = cohen_kappa_score(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        weights=\"quadratic\"\n    )\n\n    within_1_grade_acc = float(\n        np.mean(\n            np.abs(y_true - y_pred) <= 1\n        )\n    )\n\n    # =====================================================\n    # 3. CLASS-WISE METRICS\n    # =====================================================\n\n    (\n        precision_class,\n        recall_class,\n        f1_class,\n        support_class\n    ) = precision_recall_fscore_support(\n        y_true,\n        y_pred,\n        labels=LABELS,\n        average=None,\n        zero_division=0\n    )\n\n    # =====================================================\n    # 4. SPECIFICITY ONE-VS-REST\n    # =====================================================\n\n    specificity_class = []\n\n    for class_index in range(\n        len(LABELS)\n    ):\n        true_positive = int(\n            cm[class_index, class_index]\n        )\n\n        false_negative = int(\n            cm[class_index, :].sum()\n            - true_positive\n        )\n\n        false_positive = int(\n            cm[:, class_index].sum()\n            - true_positive\n        )\n\n        true_negative = int(\n            cm.sum()\n            - true_positive\n            - false_negative\n            - false_positive\n        )\n\n        denominator = (\n            true_negative\n            + false_positive\n        )\n\n        specificity = (\n            true_negative / denominator\n            if denominator > 0\n            else 0.0\n        )\n\n        specificity_class.append(\n            specificity\n        )\n\n    specificity_class = np.asarray(\n        specificity_class,\n        dtype=float\n    )\n\n    specificity_macro = float(\n        np.mean(specificity_class)\n    )\n\n    specificity_weighted = float(\n        np.average(\n            specificity_class,\n            weights=support_class\n        )\n    )\n\n    # =====================================================\n    # 5. ROC-AUC NẾU CÓ XÁC SUẤT\n    # =====================================================\n\n    roc_auc_macro = np.nan\n    roc_auc_weighted = np.nan\n\n    if y_prob is not None:\n        try:\n            y_true_one_hot = np.eye(\n                len(LABELS)\n            )[y_true]\n\n            roc_auc_macro = roc_auc_score(\n                y_true_one_hot,\n                y_prob,\n                average=\"macro\",\n                multi_class=\"ovr\"\n            )\n\n            roc_auc_weighted = roc_auc_score(\n                y_true_one_hot,\n                y_prob,\n                average=\"weighted\",\n                multi_class=\"ovr\"\n            )\n\n        except Exception as error:\n            print(\n                \"Không thể tính ROC-AUC:\",\n                error\n            )\n\n    # =====================================================\n    # 6. IN OVERALL METRICS\n    # =====================================================\n\n    print(\"\\n\" + \"=\" * 60)\n    print(\n        f\"FINAL EVALUATION: \"\n        f\"{experiment_name}\"\n    )\n    print(\"=\" * 60)\n\n    print(\n        f\"{'Accuracy':<22}: \"\n        f\"{accuracy:.4f}\"\n    )\n\n    print(\n        f\"{'BalancedAcc':<22}: \"\n        f\"{balanced_acc:.4f}\"\n    )\n\n    print(\"-\" * 31)\n\n    print(\n        f\"{'Precision Macro':<22}: \"\n        f\"{precision_macro:.4f}\"\n    )\n\n    print(\n        f\"{'Precision Weighted':<22}: \"\n        f\"{precision_weighted:.4f}\"\n    )\n\n    print(\"-\" * 31)\n\n    print(\n        f\"{'Recall Macro':<22}: \"\n        f\"{recall_macro:.4f}\"\n    )\n\n    print(\n        f\"{'Recall Weighted':<22}: \"\n        f\"{recall_weighted:.4f}\"\n    )\n\n    print(\"-\" * 31)\n\n    print(\n        f\"{'Specificity Macro':<22}: \"\n        f\"{specificity_macro:.4f}\"\n    )\n\n    print(\n        f\"{'Specificity Weighted':<22}: \"\n        f\"{specificity_weighted:.4f}\"\n    )\n\n    print(\"-\" * 31)\n\n    print(\n        f\"{'F1-Score Macro':<22}: \"\n        f\"{f1_macro:.4f}\"\n    )\n\n    print(\n        f\"{'F1-Score Weighted':<22}: \"\n        f\"{f1_weighted:.4f}\"\n    )\n\n    print(\"-\" * 31)\n\n    print(\n        f\"{'MCC':<22}: \"\n        f\"{mcc:.4f}\"\n    )\n\n    print(\n        f\"{'QWK':<22}: \"\n        f\"{qwk:.4f}\"\n    )\n\n    print(\n        f\"{'Within-1-Grade Acc':<22}: \"\n        f\"{within_1_grade_acc:.4f}\"\n    )\n\n    if not np.isnan(\n        roc_auc_macro\n    ):\n        print(\"-\" * 31)\n\n        print(\n            f\"{'ROC-AUC Macro OVR':<22}: \"\n            f\"{roc_auc_macro:.4f}\"\n        )\n\n        print(\n            f\"{'ROC-AUC Weighted OVR':<22}: \"\n            f\"{roc_auc_weighted:.4f}\"\n        )\n\n    print(\"=\" * 31)\n\n    # =====================================================\n    # 7. IN CLASS-WISE METRICS\n    # =====================================================\n\n    print(\n        \"\\n--- CLASS-WISE METRICS ---\"\n    )\n\n    header = (\n        f\"{'Class':<15} | \"\n        f\"{'Precision':>9} | \"\n        f\"{'Recall':>6} | \"\n        f\"{'Specificity':>11} | \"\n        f\"{'F1-Score':>8} | \"\n        f\"{'Support':>7}\"\n    )\n\n    print(header)\n    print(\"-\" * len(header))\n\n    for (\n        class_name,\n        precision,\n        recall,\n        specificity,\n        f1,\n        support\n    ) in zip(\n        CLASS_NAMES,\n        precision_class,\n        recall_class,\n        specificity_class,\n        f1_class,\n        support_class\n    ):\n        print(\n            f\"{class_name:<15} | \"\n            f\"{precision:>9.4f} | \"\n            f\"{recall:>6.4f} | \"\n            f\"{specificity:>11.4f} | \"\n            f\"{f1:>8.4f} | \"\n            f\"{int(support):>7d}\"\n        )\n\n    print(\"=\" * len(header))\n\n    # =====================================================\n    # 8. DATAFRAMES VÀ LƯU CSV\n    # =====================================================\n\n    overall_metrics_df = pd.DataFrame(\n        {\n            \"Metric\": [\n                \"Accuracy\",\n                \"BalancedAcc\",\n                \"Precision Macro\",\n                \"Precision Weighted\",\n                \"Recall Macro\",\n                \"Recall Weighted\",\n                \"Specificity Macro\",\n                \"Specificity Weighted\",\n                \"F1-Score Macro\",\n                \"F1-Score Weighted\",\n                \"MCC\",\n                \"QWK\",\n                \"Within-1-Grade Acc\",\n                \"ROC-AUC Macro OVR\",\n                \"ROC-AUC Weighted OVR\"\n            ],\n            \"Value\": [\n                accuracy,\n                balanced_acc,\n                precision_macro,\n                precision_weighted,\n                recall_macro,\n                recall_weighted,\n                specificity_macro,\n                specificity_weighted,\n                f1_macro,\n                f1_weighted,\n                mcc,\n                qwk,\n                within_1_grade_acc,\n                roc_auc_macro,\n                roc_auc_weighted\n            ]\n        }\n    )\n\n    class_metrics_df = pd.DataFrame(\n        {\n            \"Class\": CLASS_NAMES,\n            \"Precision\": precision_class,\n            \"Recall\": recall_class,\n            \"Specificity\": specificity_class,\n            \"F1-Score\": f1_class,\n            \"Support\": support_class.astype(int)\n        }\n    )\n\n    cm_df = pd.DataFrame(\n        cm,\n        index=CLASS_NAMES,\n        columns=CLASS_NAMES\n    )\n\n    cm_df.index.name = \"Actual\"\n    cm_df.columns.name = \"Predicted\"\n\n    cm_normalized_df = pd.DataFrame(\n        cm_normalized,\n        index=CLASS_NAMES,\n        columns=CLASS_NAMES\n    )\n\n    cm_normalized_df.index.name = \"Actual\"\n    cm_normalized_df.columns.name = \"Predicted\"\n\n    output_dir = Path(\n        \"/kaggle/working\"\n    )\n\n    if not output_dir.exists():\n        output_dir = Path(\".\")\n\n    output_dir.mkdir(\n        parents=True,\n        exist_ok=True\n    )\n\n    overall_metrics_df.to_csv(\n        output_dir\n        / f\"{output_prefix}_overall_metrics.csv\",\n        index=False\n    )\n\n    class_metrics_df.to_csv(\n        output_dir\n        / f\"{output_prefix}_class_metrics.csv\",\n        index=False\n    )\n\n    cm_df.to_csv(\n        output_dir\n        / f\"{output_prefix}_confusion_matrix_counts.csv\"\n    )\n\n    cm_normalized_df.to_csv(\n        output_dir\n        / f\"{output_prefix}_confusion_matrix_normalized.csv\"\n    )\n\n    # =====================================================\n    # 9. CONFUSION MATRIX — COUNTS\n    # =====================================================\n\n    print(\n        \"\\n--- CONFUSION MATRIX: COUNTS ---\"\n    )\n\n    print(\n        cm_df\n    )\n\n    fig, ax = plt.subplots(\n        figsize=(8, 7)\n    )\n\n    display_count = ConfusionMatrixDisplay(\n        confusion_matrix=cm,\n        display_labels=CLASS_NAMES\n    )\n\n    display_count.plot(\n        ax=ax,\n        values_format=\"d\"\n    )\n\n    ax.set_title(\n        f\"Confusion Matrix - Counts\\n\"\n        f\"{experiment_name}\"\n    )\n\n    plt.xticks(\n        rotation=45,\n        ha=\"right\"\n    )\n\n    plt.tight_layout()\n    plt.show()\n\n    # =====================================================\n    # 10. CONFUSION MATRIX — NORMALIZED\n    # =====================================================\n\n    print(\n        \"\\n--- CONFUSION MATRIX: \"\n        \"NORMALIZED BY TRUE CLASS ---\"\n    )\n\n    print(\n        cm_normalized_df.round(4)\n    )\n\n    fig, ax = plt.subplots(\n        figsize=(8, 7)\n    )\n\n    display_normalized = ConfusionMatrixDisplay(\n        confusion_matrix=cm_normalized,\n        display_labels=CLASS_NAMES\n    )\n\n    display_normalized.plot(\n        ax=ax,\n        values_format=\".2f\"\n    )\n\n    ax.set_title(\n        f\"Confusion Matrix - Normalized\\n\"\n        f\"{experiment_name}\"\n    )\n\n    plt.xticks(\n        rotation=45,\n        ha=\"right\"\n    )\n\n    plt.tight_layout()\n    plt.show()\n\n    return {\n        \"overall_metrics\": overall_metrics_df,\n        \"class_metrics\": class_metrics_df,\n        \"confusion_matrix_counts\": cm_df,\n        \"confusion_matrix_normalized\": cm_normalized_df\n    }\n\n\n# =========================================================\n# ĐÁNH GIÁ THÍ NGHIỆM 1:\n# DIABETIC RETINOPATHY 224x224 GAUSSIAN FILTERED\n# =========================================================\n\nprint(\n    \"Đang tiến hành dự đoán trên tập Test \"\n    \"Gaussian Filtered...\"\n)\n\ny_pred_probs = model.predict(\n    X_test,\n    verbose=1\n)\n\ny_pred = np.argmax(\n    y_pred_probs,\n    axis=1\n)\n\ny_true = np.argmax(\n    y_test,\n    axis=1\n)\n\ngaussian_filtered_results = evaluate_dr_model(\n    y_true=y_true,\n    y_pred=y_pred,\n    y_prob=y_pred_probs,\n    experiment_name=(\n        \"DRNet13 - Gaussian Filtered Dataset\"\n    ),\n    output_prefix=(\n        \"drnet13_gaussian_filtered\"\n    )\n)","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:59:14.345196Z","iopub.status.busy":"2026-03-11T06:59:14.344921Z","iopub.status.idle":"2026-03-11T06:59:19.472369Z","shell.execute_reply":"2026-03-11T06:59:19.471642Z","shell.execute_reply.started":"2026-03-11T06:59:14.345172Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom sklearn.metrics import roc_curve, auc\nfrom itertools import cycle\n\nfpr = dict()\ntpr = dict()\nroc_auc = dict()\n\nclass_names = {0: \"No DR\", 1: \"Mild\", 2: \"Moderate\", 3: \"Severe\", 4: \"Proliferate DR\"}\nn_classes = len(class_names)\n\nfor i in range(n_classes):\n    fpr[i], tpr[i], _ = roc_curve(y_test[:, i], y_pred_probs[:, i])\n    roc_auc[i] = auc(fpr[i], tpr[i])\n\nplt.figure(figsize=(10, 8))\n\ncolors = cycle(['green', 'magenta', 'orange', 'purple', 'blue'])\n\nfor i, color in zip(range(n_classes), colors):\n    plt.plot(fpr[i], tpr[i], color=color, lw=2,\n             label=f'{class_names[i]} (AUC = {roc_auc[i]:.3f})')\n\nplt.plot([0, 1], [0, 1], 'k--', lw=2, color='red')\n\nplt.xlim([0.0, 1.0])\nplt.ylim([0.0, 1.05])\nplt.xlabel('1 - Specificity (FPR)', fontsize=12, fontweight='bold')\nplt.ylabel('Sensitivity (TPR)', fontsize=12, fontweight='bold')\nplt.title('Receiver Operating Characteristic (ROC) Curve - DRNet13', fontsize=14, fontweight='bold')\n\nplt.legend(loc=\"lower right\", fontsize=11, frameon=True, shadow=True)\nplt.grid(alpha=0.3)\n\n# Hiển thị\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.execute_input":"2026-03-11T06:59:19.473796Z","iopub.status.busy":"2026-03-11T06:59:19.473458Z","iopub.status.idle":"2026-03-11T06:59:19.716916Z","shell.execute_reply":"2026-03-11T06:59:19.716211Z","shell.execute_reply.started":"2026-03-11T06:59:19.473773Z"}},"outputs":[],"execution_count":null}]}