Đánh giá mô hình phân loại nhị phân bằng R

Confusion Matrix, ROC/AUC, AIC, BIC và Pseudo R-square

1 1. Mục tiêu phân tích

Bài thực hành này sử dụng bộ dữ liệu Breast Cancer Wisconsin Diagnostic - WDBC. Đây là bộ dữ liệu phân loại nhị phân, tức là biến kết quả chỉ có hai nhóm:

  • Benign: u lành tính.
  • Malignant: u ác tính.

Trong bài này, ta đặt Malignantlớp dương. Điều này rất quan trọng vì các chỉ số như Sensitivity/RecallSpecificity luôn phụ thuộc vào việc ta chọn nhóm nào là lớp dương.

Mục tiêu phân tích gồm ba phần lớn:

  1. Xây dựng mô hình phân loại bằng hồi quy logistic.
  2. Đánh giá khả năng dự báo bằng Confusion Matrix, Accuracy, Sensitivity, Specificity, ROC CurveAUC.
  3. So sánh, lựa chọn mô hình bằng AIC, BICPseudo R-square.

1.1 Giải thích bản chất

Ta không chỉ cần biết mô hình dự đoán đúng hay sai. Ta cần biết mô hình sai theo kiểu nào.

Ví dụ trong bối cảnh y tế, nếu bệnh nhân thật sự bị u ác tính nhưng mô hình dự đoán là u lành tính thì đó là lỗi nghiêm trọng. Vì vậy, ngoài Accuracy, ta bắt buộc phải xem thêm Sensitivity/Recall.


2 2. Cài và nạp package

packages <- c("readr", "dplyr", "caret", "pROC", "pscl", "MASS", "ggplot2")
new_packages <- packages[!(packages %in% rownames(installed.packages()))]
if (length(new_packages) > 0) install.packages(new_packages)

library(readr)
library(dplyr)
library(caret)
library(pROC)
library(pscl)
library(MASS)
library(ggplot2)

2.1 Giải thích

Đoạn code này dùng để kiểm tra xem máy đã có đủ package cần thiết chưa. Nếu thiếu package nào thì R sẽ tự cài package đó.

Ý nghĩa từng package:

  • readr: đọc dữ liệu từ đường link online.
  • dplyr: xử lý, biến đổi dữ liệu.
  • caret: chia dữ liệu train/test và tính ma trận nhầm lẫn.
  • pROC: vẽ ROC Curve và tính AUC.
  • pscl: tính Pseudo R-square cho mô hình logistic.
  • MASS: dùng hàm stepAIC() để lựa chọn biến theo AIC hoặc BIC.
  • ggplot2: hỗ trợ vẽ biểu đồ nếu cần mở rộng.

2.2 Vì sao cần làm bước này?

Trong R, mỗi nhóm công việc thường cần một package riêng. Nếu không nạp package, các hàm như read_csv(), confusionMatrix(), roc() hoặc stepAIC() sẽ không chạy được.


3 3. Đọc dữ liệu online

url <- "https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data"

feature_base <- c(
  "radius", "texture", "perimeter", "area", "smoothness",
  "compactness", "concavity", "concave_points", "symmetry",
  "fractal_dimension"
)

var_names <- c(
  "id", "diagnosis",
  paste0(feature_base, "_mean"),
  paste0(feature_base, "_se"),
  paste0(feature_base, "_worst")
)

data_raw <- read_csv(url, col_names = var_names, show_col_types = FALSE)

data <- data_raw %>%
  mutate(
    diagnosis = factor(
      ifelse(diagnosis == "M", "Malignant", "Benign"),
      levels = c("Benign", "Malignant")
    ),
    y = ifelse(diagnosis == "Malignant", 1, 0)
  )

dim(data_raw)
[1] 569  32
table(data$diagnosis)

   Benign Malignant 
      357       212 

3.1 Giải thích

Bước này đọc dữ liệu trực tiếp từ đường link online. Bộ dữ liệu gốc không có tên cột, nên ta phải tự đặt tên cột bằng var_names.

Trong dữ liệu gốc:

  • M nghĩa là Malignant, tức u ác tính.
  • B nghĩa là Benign, tức u lành tính.

Ta chuyển M thành Malignant, B thành Benign để kết quả dễ đọc hơn.

Sau đó, ta tạo thêm biến y:

  • y = 1 nếu là Malignant.
  • y = 0 nếu là Benign.

3.2 Vì sao cần tạo biến y?

Mô hình hồi quy logistic trong R có thể chạy với biến nhị phân dạng 0/1. Vì vậy, ta mã hóa biến chẩn đoán thành y để mô hình hiểu rằng đây là bài toán dự báo xác suất xảy ra biến cố.

Ở đây, biến cố là:

Bệnh nhân thuộc nhóm u ác tính.

Nói cách khác, mô hình sẽ ước lượng xác suất:

[ P(y = 1) = P() ]

3.3 Cách đọc kết quả

Lệnh:

id="u08ij3"
dim(data_raw)

cho biết dữ liệu có bao nhiêu dòng và bao nhiêu cột.

Lệnh:

id="xovput"
table(data$diagnosis)

cho biết số quan sát thuộc nhóm Benign và số quan sát thuộc nhóm Malignant.

Nếu hai nhóm quá lệch nhau, ví dụ 95% là Benign và 5% là Malignant, thì Accuracy có thể gây hiểu nhầm. Khi đó ta càng cần xem thêm SensitivitySpecificity.


4 4. Chọn biến đầu vào và chia train/test

predictor_vars <- paste0(feature_base, "_mean")

set.seed(123)
train_index <- createDataPartition(data$diagnosis, p = 0.70, list = FALSE)
train <- data[train_index, ]
test  <- data[-train_index, ]

nrow(train)
[1] 399
nrow(test)
[1] 170
prop.table(table(train$diagnosis))

   Benign Malignant 
0.6265664 0.3734336 
prop.table(table(test$diagnosis))

   Benign Malignant 
0.6294118 0.3705882 

4.1 Giải thích

Trong bộ dữ liệu WDBC, mỗi đặc trưng có ba nhóm đo lường:

  • _mean: giá trị trung bình.
  • _se: sai số chuẩn.
  • _worst: giá trị nghiêm trọng nhất hoặc lớn nhất.

Ở đây ta chọn nhóm biến _mean để mô hình vừa đủ mạnh nhưng vẫn dễ giải thích. Nếu đưa cả 30 biến vào ngay từ đầu, mô hình có thể phức tạp, khó diễn giải và dễ gặp vấn đề tương quan mạnh giữa các biến.

Dòng:

id="jw3if2"
set.seed(123)

giúp kết quả chia dữ liệu có thể lặp lại. Nếu không đặt set.seed(), mỗi lần chạy R có thể chia train/test khác nhau, làm kết quả thay đổi.

Ta chia dữ liệu thành:

  • train: 70% dữ liệu dùng để huấn luyện mô hình.
  • test: 30% dữ liệu dùng để kiểm tra mô hình.

4.2 Vì sao phải chia train/test?

Nếu ta huấn luyện và đánh giá mô hình trên cùng một tập dữ liệu, kết quả thường quá lạc quan. Mô hình có thể học rất tốt dữ liệu đã thấy, nhưng chưa chắc dự đoán tốt dữ liệu mới.

Vì vậy:

  • Tập train dùng để học quy luật.
  • Tập test dùng để kiểm tra khả năng dự báo trên dữ liệu mới.

4.3 Cách đọc kết quả

Hai dòng:

id="8yj8ud"
prop.table(table(train$diagnosis))
prop.table(table(test$diagnosis))

cho biết tỷ lệ BenignMalignant trong tập train và test.

Nếu tỷ lệ hai nhóm trong train và test gần giống nhau thì cách chia dữ liệu là hợp lý. Hàm createDataPartition() của caret giúp giữ tỷ lệ nhóm tương đối cân bằng giữa train và test.


5 5. Chuẩn hóa dữ liệu

center_values <- sapply(train[predictor_vars], mean)
scale_values  <- sapply(train[predictor_vars], sd)

train_scaled <- train
test_scaled  <- test

train_scaled[predictor_vars] <- as.data.frame(
  scale(train[predictor_vars], center = center_values, scale = scale_values)
)

test_scaled[predictor_vars] <- as.data.frame(
  scale(test[predictor_vars], center = center_values, scale = scale_values)
)

train_model <- train_scaled %>% dplyr::select(y, all_of(predictor_vars))
test_model  <- test_scaled %>% dplyr::select(y, diagnosis, all_of(predictor_vars))

summary(train_model)
       y           radius_mean       texture_mean     perimeter_mean   
 Min.   :0.0000   Min.   :-2.0283   Min.   :-2.0178   Min.   :-1.9854  
 1st Qu.:0.0000   1st Qu.:-0.6949   1st Qu.:-0.7204   1st Qu.:-0.6934  
 Median :0.0000   Median :-0.2105   Median :-0.1176   Median :-0.2188  
 Mean   :0.3734   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
 3rd Qu.:1.0000   3rd Qu.: 0.5053   3rd Qu.: 0.6259   3rd Qu.: 0.5240  
 Max.   :1.0000   Max.   : 3.9738   Max.   : 4.7998   Max.   : 3.9862  
   area_mean       smoothness_mean     compactness_mean  concavity_mean   
 Min.   :-1.4604   Min.   :-2.231652   Min.   :-1.4677   Min.   :-1.1183  
 1st Qu.:-0.6780   1st Qu.:-0.703782   1st Qu.:-0.7650   1st Qu.:-0.7521  
 Median :-0.2932   Median :-0.007494   Median :-0.2287   Median :-0.3698  
 Mean   : 0.0000   Mean   : 0.000000   Mean   : 0.0000   Mean   : 0.0000  
 3rd Qu.: 0.3915   3rd Qu.: 0.577415   3rd Qu.: 0.4922   3rd Qu.: 0.5594  
 Max.   : 5.2847   Max.   : 4.540585   Max.   : 4.5479   Max.   : 4.3309  
 concave_points_mean symmetry_mean     fractal_dimension_mean
 Min.   :-1.2589     Min.   :-2.6782   Min.   :-1.8074       
 1st Qu.:-0.7359     1st Qu.:-0.6878   1st Qu.:-0.6932       
 Median :-0.4186     Median :-0.0695   Median :-0.1603       
 Mean   : 0.0000     Mean   : 0.0000   Mean   : 0.0000       
 3rd Qu.: 0.6855     3rd Qu.: 0.5310   3rd Qu.: 0.5013       
 Max.   : 3.9226     Max.   : 4.3782   Max.   : 4.9893       

5.1 Giải thích

Chuẩn hóa dữ liệu nghĩa là đưa các biến định lượng về cùng một thang đo. Sau chuẩn hóa, mỗi biến thường có:

  • Trung bình xấp xỉ 0.
  • Độ lệch chuẩn xấp xỉ 1.

Công thức chuẩn hóa là:

[ z = ]

Trong đó:

  • (x): giá trị ban đầu.
  • ({x}): trung bình của biến.
  • (s): độ lệch chuẩn của biến.
  • (z): giá trị sau chuẩn hóa.

5.2 Vì sao cần chuẩn hóa?

Các biến trong dữ liệu có đơn vị và độ lớn rất khác nhau. Ví dụ:

  • area_mean có thể có giá trị rất lớn.
  • smoothness_mean có thể có giá trị rất nhỏ.

Nếu không chuẩn hóa, các biến có thang đo lớn dễ gây ảnh hưởng mạnh trong quá trình ước lượng, đặc biệt khi ta so sánh hệ số hoặc lựa chọn biến.

5.3 Vì sao dùng trung bình và độ lệch chuẩn của tập train để chuẩn hóa cả test?

Đây là điểm rất quan trọng.

Ta chỉ được phép học thông tin từ tập train. Tập test phải được xem như dữ liệu mới, chưa từng biết trước.

Vì vậy:

  • Trung bình và độ lệch chuẩn được tính từ train.
  • Sau đó áp dụng chính các giá trị đó để chuẩn hóa test.

Nếu ta tính trung bình và độ lệch chuẩn từ toàn bộ dữ liệu trước khi chia train/test, mô hình đã vô tình nhìn thấy thông tin từ tập test. Đây gọi là data leakage, tức rò rỉ dữ liệu.

5.4 Cách đọc kết quả

Lệnh:

id="h2v0r1"
summary(train_model)

cho ta xem phân bố của các biến sau chuẩn hóa. Các biến đầu vào thường sẽ có giá trị quanh 0. Nếu có biến nào quá bất thường, ta cần kiểm tra lại dữ liệu.


6 6. Xây dựng mô hình hồi quy logistic và lựa chọn biến

model_full <- glm(y ~ ., data = train_model, family = binomial(link = "logit"))

model_aic <- MASS::stepAIC(model_full, direction = "both", trace = FALSE)

model_bic <- MASS::stepAIC(
  model_full,
  direction = "both",
  k = log(nrow(train_model)),
  trace = FALSE
)

formula(model_full)
y ~ radius_mean + texture_mean + perimeter_mean + area_mean + 
    smoothness_mean + compactness_mean + concavity_mean + concave_points_mean + 
    symmetry_mean + fractal_dimension_mean
formula(model_aic)
y ~ texture_mean + perimeter_mean + area_mean + smoothness_mean + 
    concavity_mean + symmetry_mean
formula(model_bic)
y ~ texture_mean + area_mean + smoothness_mean + concavity_mean + 
    symmetry_mean

6.1 Giải thích

Ở bước này, ta xây dựng ba mô hình:

  1. model_full: mô hình đầy đủ, sử dụng tất cả biến _mean đã chọn.
  2. model_aic: mô hình được chọn biến theo tiêu chuẩn AIC.
  3. model_bic: mô hình được chọn biến theo tiêu chuẩn BIC.

Mô hình hồi quy logistic được dùng vì biến phụ thuộc y chỉ có hai giá trị: 0 và 1.

Mô hình logistic không dự báo trực tiếp nhãn Benign hay Malignant. Nó dự báo xác suất bệnh nhân thuộc nhóm Malignant:

[ P(y = 1) ]

Sau đó, ta dùng một ngưỡng phân loại, thường là 0.5:

  • Nếu xác suất ≥ 0.5, dự đoán là Malignant.
  • Nếu xác suất < 0.5, dự đoán là Benign.

6.2 Bản chất của hồi quy logistic

Hồi quy tuyến tính thông thường có thể cho kết quả nhỏ hơn 0 hoặc lớn hơn 1, nên không phù hợp để dự báo xác suất.

Hồi quy logistic dùng hàm logit:

[ () = _0 + _1X_1 + _2X_2 + … + _kX_k ]

Trong đó:

  • (p): xác suất thuộc nhóm Malignant.
  • (): odds, tức tỷ số giữa khả năng xảy ra và không xảy ra biến cố.
  • (_j): hệ số của biến độc lập.

Nếu hệ số (_j > 0), khi biến đó tăng thì xác suất thuộc nhóm Malignant có xu hướng tăng.

Nếu hệ số (_j < 0), khi biến đó tăng thì xác suất thuộc nhóm Malignant có xu hướng giảm.

6.3 Vì sao cần lựa chọn biến?

Không phải cứ đưa nhiều biến vào mô hình là tốt. Mô hình quá nhiều biến có thể:

  • Khó giải thích.
  • Dễ bị nhiễu.
  • Dự báo kém trên dữ liệu mới.
  • Có thể bị đa cộng tuyến nếu các biến liên hệ mạnh với nhau.

Vì vậy, ta dùng AIC và BIC để tìm mô hình cân bằng giữa:

  • Độ phù hợp với dữ liệu.
  • Độ đơn giản của mô hình.

6.4 Cách đọc kết quả

Ba dòng:

id="bbv26i"
formula(model_full)
formula(model_aic)
formula(model_bic)

cho biết mỗi mô hình giữ lại những biến nào.

Nếu model_bic có ít biến hơn model_aic, điều đó là bình thường vì BIC phạt mô hình phức tạp mạnh hơn AIC.


7 7. AIC, BIC và Pseudo R-square

pseudo_r2_table <- function(model) {
  r2 <- pscl::pR2(model)
  tibble(
    McFadden = unname(r2["McFadden"]),
    Cox_Snell = unname(r2["r2ML"]),
    Nagelkerke = unname(r2["r2CU"])
  )
}

model_summary <- bind_rows(
  tibble(
    Model = "Full model",
    AIC = AIC(model_full),
    BIC = BIC(model_full),
    Variables = paste(attr(terms(model_full), "term.labels"), collapse = ", ")
  ) %>% bind_cols(pseudo_r2_table(model_full)),
  tibble(
    Model = "Stepwise AIC",
    AIC = AIC(model_aic),
    BIC = BIC(model_aic),
    Variables = paste(attr(terms(model_aic), "term.labels"), collapse = ", ")
  ) %>% bind_cols(pseudo_r2_table(model_aic)),
  tibble(
    Model = "Stepwise BIC",
    AIC = AIC(model_bic),
    BIC = BIC(model_bic),
    Variables = paste(attr(terms(model_bic), "term.labels"), collapse = ", ")
  ) %>% bind_cols(pseudo_r2_table(model_bic))
)
fitting null model for pseudo-r2
fitting null model for pseudo-r2
fitting null model for pseudo-r2
model_summary
# A tibble: 3 × 7
  Model          AIC   BIC Variables               McFadden Cox_Snell Nagelkerke
  <chr>        <dbl> <dbl> <chr>                      <dbl>     <dbl>      <dbl>
1 Full model    119.  163. radius_mean, texture_m…    0.816     0.660      0.900
2 Stepwise AIC  113.  141. texture_mean, perimete…    0.812     0.658      0.897
3 Stepwise BIC  114.  138. texture_mean, area_mea…    0.806     0.655      0.893

7.1 Giải thích

Bảng model_summary dùng để so sánh ba mô hình theo các tiêu chí:

  • AIC.
  • BIC.
  • McFadden Pseudo R-square.
  • Cox & Snell Pseudo R-square.
  • Nagelkerke Pseudo R-square.
  • Danh sách biến được giữ lại trong mô hình.

7.2 Bản chất của AIC và BIC

AIC và BIC đều dùng để so sánh mô hình. Cả hai đều có cùng tư tưởng:

Mô hình tốt là mô hình vừa khớp dữ liệu tốt, vừa không quá phức tạp.

AIC có dạng tổng quát:

[ AIC = -2(L) + 2k ]

BIC có dạng tổng quát:

[ BIC = -2(L) + k(n) ]

Trong đó:

  • (L): likelihood, tức mức độ mô hình phù hợp với dữ liệu.
  • (k): số tham số trong mô hình.
  • (n): số quan sát.

AIC và BIC càng thấp càng tốt.

BIC thường phạt số biến mạnh hơn AIC, đặc biệt khi cỡ mẫu lớn. Vì vậy, BIC thường chọn mô hình gọn hơn.

7.3 Bản chất của Pseudo R-square

Trong hồi quy tuyến tính, R-square cho biết mô hình giải thích được bao nhiêu phần trăm biến thiên của biến phụ thuộc.

Nhưng trong hồi quy logistic, biến phụ thuộc chỉ là 0/1, nên không dùng R-square truyền thống theo cùng một nghĩa được. Vì vậy ta dùng Pseudo R-square.

Các chỉ số trong bảng gồm:

  • McFadden: thường được dùng nhiều trong logistic regression. Giá trị càng cao càng tốt.
  • Cox_Snell: đo mức cải thiện của mô hình so với mô hình rỗng.
  • Nagelkerke: phiên bản điều chỉnh của Cox & Snell để dễ diễn giải hơn vì có thể tiến gần 1.

7.4 Cách đọc kết quả

Khi bảng hiện ra, ta đọc như sau:

  1. Mô hình nào có AIC thấp nhất thì tốt nhất theo tiêu chuẩn AIC.
  2. Mô hình nào có BIC thấp nhất thì tốt nhất theo tiêu chuẩn BIC.
  3. Mô hình nào có Pseudo R-square cao hơn thì cải thiện tốt hơn so với mô hình rỗng.
  4. Nếu hai mô hình có kết quả gần nhau, nên ưu tiên mô hình ít biến hơn để dễ giải thích.

8 8. Tự động xác định mô hình tốt theo AIC và BIC

best_by_aic <- model_summary %>% dplyr::slice_min(AIC, n = 1)
best_by_bic <- model_summary %>% dplyr::slice_min(BIC, n = 1)

best_by_aic
# A tibble: 1 × 7
  Model          AIC   BIC Variables               McFadden Cox_Snell Nagelkerke
  <chr>        <dbl> <dbl> <chr>                      <dbl>     <dbl>      <dbl>
1 Stepwise AIC  113.  141. texture_mean, perimete…    0.812     0.658      0.897
best_by_bic
# A tibble: 1 × 7
  Model          AIC   BIC Variables               McFadden Cox_Snell Nagelkerke
  <chr>        <dbl> <dbl> <chr>                      <dbl>     <dbl>      <dbl>
1 Stepwise BIC  114.  138. texture_mean, area_mea…    0.806     0.655      0.893

8.1 Giải thích

Đoạn code này tự động tìm mô hình có AIC thấp nhất và mô hình có BIC thấp nhất.

8.2 Cách đọc kết quả

Nếu best_by_aicStepwise AIC, nghĩa là mô hình được chọn biến theo AIC đang cân bằng tốt nhất giữa độ khớp và độ phức tạp theo tiêu chuẩn AIC.

Nếu best_by_bicStepwise BIC, nghĩa là mô hình được chọn biến theo BIC đang được ưu tiên theo hướng gọn hơn.

Trong nhiều bài thực hành, ta thường chọn mô hình theo BIC nếu muốn mô hình dễ giải thích, ít biến hơn. Ta chọn theo AIC nếu muốn ưu tiên khả năng dự báo và chấp nhận mô hình có thể nhiều biến hơn.


9 9. Ma trận nhầm lẫn, Accuracy, Sensitivity, Specificity và AUC

evaluate_model <- function(model, test_data, cutoff = 0.50) {
  prob <- predict(model, newdata = test_data, type = "response")

  pred_class <- factor(
    ifelse(prob >= cutoff, "Malignant", "Benign"),
    levels = c("Benign", "Malignant")
  )

  truth <- factor(
    ifelse(test_data$y == 1, "Malignant", "Benign"),
    levels = c("Benign", "Malignant")
  )

  cm <- caret::confusionMatrix(
    data = pred_class,
    reference = truth,
    positive = "Malignant"
  )

  roc_obj <- pROC::roc(
    response = truth,
    predictor = prob,
    levels = c("Benign", "Malignant"),
    direction = "<"
  )

  metrics <- tibble(
    Accuracy = unname(cm$overall["Accuracy"]),
    Sensitivity_Recall = unname(cm$byClass["Sensitivity"]),
    Specificity = unname(cm$byClass["Specificity"]),
    AUC = as.numeric(pROC::auc(roc_obj))
  )

  list(
    confusion_matrix = cm$table,
    metrics = metrics,
    roc = roc_obj,
    probability = prob,
    prediction = pred_class,
    truth = truth
  )
}

eval_full <- evaluate_model(model_full, test_model)
eval_aic  <- evaluate_model(model_aic, test_model)
eval_bic  <- evaluate_model(model_bic, test_model)

eval_full$confusion_matrix
           Reference
Prediction  Benign Malignant
  Benign       101         4
  Malignant      6        59
eval_full$metrics
# A tibble: 1 × 4
  Accuracy Sensitivity_Recall Specificity   AUC
     <dbl>              <dbl>       <dbl> <dbl>
1    0.941              0.937       0.944 0.979
eval_aic$confusion_matrix
           Reference
Prediction  Benign Malignant
  Benign       100         6
  Malignant      7        57
eval_aic$metrics
# A tibble: 1 × 4
  Accuracy Sensitivity_Recall Specificity   AUC
     <dbl>              <dbl>       <dbl> <dbl>
1    0.924              0.905       0.935 0.976
eval_bic$confusion_matrix
           Reference
Prediction  Benign Malignant
  Benign       101         6
  Malignant      6        57
eval_bic$metrics
# A tibble: 1 × 4
  Accuracy Sensitivity_Recall Specificity   AUC
     <dbl>              <dbl>       <dbl> <dbl>
1    0.929              0.905       0.944 0.977

9.1 Giải thích

Hàm evaluate_model() dùng để đánh giá một mô hình trên tập test.

Bên trong hàm có các bước:

  1. Dự báo xác suất bằng predict(..., type = "response").
  2. Chuyển xác suất thành nhãn dự báo bằng ngưỡng 0.5.
  3. So sánh nhãn dự báo với nhãn thực tế.
  4. Tạo ma trận nhầm lẫn.
  5. Tính Accuracy, Sensitivity, Specificity.
  6. Tạo ROC object và tính AUC.

9.2 Bản chất của xác suất dự báo

Dòng:

id="fpdu1m"
prob <- predict(model, newdata = test_data, type = "response")

trả về xác suất một quan sát thuộc nhóm Malignant.

Ví dụ:

  • Nếu prob = 0.90, mô hình cho rằng xác suất u ác tính là 90%.
  • Nếu prob = 0.20, mô hình cho rằng xác suất u ác tính là 20%.

Sau đó ta dùng ngưỡng 0.5:

  • prob >= 0.5: dự đoán Malignant.
  • prob < 0.5: dự đoán Benign.

9.3 Bản chất của Confusion Matrix

Ma trận nhầm lẫn so sánh giữa:

  • Nhãn mô hình dự đoán.
  • Nhãn thực tế.

Vì ta chọn Malignant là lớp dương, nên có bốn trường hợp:

Trường hợp Ý nghĩa
True Positive, TP Thực tế là Malignant, mô hình dự đoán đúng là Malignant
False Negative, FN Thực tế là Malignant, nhưng mô hình dự đoán sai là Benign
True Negative, TN Thực tế là Benign, mô hình dự đoán đúng là Benign
False Positive, FP Thực tế là Benign, nhưng mô hình dự đoán sai là Malignant

Trong bài toán y tế, False Negative thường rất nguy hiểm vì mô hình bỏ sót ca ác tính.

9.4 Công thức các chỉ số

Accuracy:

[ Accuracy = ]

Accuracy cho biết tỷ lệ dự đoán đúng trên toàn bộ tập test.

Sensitivity/Recall:

[ Sensitivity = Recall = ]

Sensitivity cho biết trong tất cả ca thật sự ác tính, mô hình phát hiện đúng được bao nhiêu ca.

Specificity:

[ Specificity = ]

Specificity cho biết trong tất cả ca thật sự lành tính, mô hình nhận diện đúng được bao nhiêu ca.

9.5 Cách đọc kết quả

Nếu kết quả cho thấy:

  • Accuracy cao: mô hình dự đoán đúng nhiều quan sát nói chung.
  • Sensitivity_Recall cao: mô hình ít bỏ sót ca Malignant.
  • Specificity cao: mô hình ít báo nhầm ca Benign thành Malignant.
  • AUC cao: mô hình phân biệt tốt giữa hai nhóm.

Trong bối cảnh y tế, ta thường ưu tiên Sensitivity/Recall hơn Accuracy, vì bỏ sót u ác tính có thể nguy hiểm hơn việc cảnh báo nhầm.


10 10. So sánh chỉ số đánh giá trên tập test

eval_table <- bind_rows(
  tibble(Model = "Full model") %>% bind_cols(eval_full$metrics),
  tibble(Model = "Stepwise AIC") %>% bind_cols(eval_aic$metrics),
  tibble(Model = "Stepwise BIC") %>% bind_cols(eval_bic$metrics)
)

eval_table
# A tibble: 3 × 5
  Model        Accuracy Sensitivity_Recall Specificity   AUC
  <chr>           <dbl>              <dbl>       <dbl> <dbl>
1 Full model      0.941              0.937       0.944 0.979
2 Stepwise AIC    0.924              0.905       0.935 0.976
3 Stepwise BIC    0.929              0.905       0.944 0.977

10.1 Giải thích

Bảng eval_table gom các chỉ số đánh giá của cả ba mô hình vào cùng một bảng để dễ so sánh.

10.2 Cách đọc kết quả

Khi chọn mô hình cuối cùng, không nên chỉ nhìn một chỉ số duy nhất. Ta nên đọc theo thứ tự:

  1. Mô hình có Sensitivity_Recall có đủ cao không?
  2. Mô hình có AUC cao không?
  3. Mô hình có AccuracySpecificity ổn không?
  4. Mô hình có quá nhiều biến không?
  5. AIC/BIC của mô hình có tốt không?

Nếu một mô hình có AUC rất cao nhưng giữ quá nhiều biến, còn mô hình khác có AUC gần tương đương nhưng ít biến hơn, ta thường ưu tiên mô hình ít biến hơn.


11 11. ROC Curve

plot(eval_bic$roc, main = "ROC Curve - Logistic Regression, Stepwise BIC")
auc_value <- as.numeric(pROC::auc(eval_bic$roc))
legend("bottomright", legend = paste("AUC =", round(auc_value, 4)), bty = "n")

11.1 Giải thích

ROC Curve là đường cong thể hiện khả năng phân biệt hai nhóm của mô hình khi thay đổi ngưỡng phân loại.

Ở các phần trước, ta dùng ngưỡng 0.5. Nhưng thực tế, ta có thể thay đổi ngưỡng này:

  • Ngưỡng thấp hơn, ví dụ 0.3: mô hình dễ dự đoán Malignant hơn, Sensitivity có thể tăng nhưng False Positive cũng có thể tăng.
  • Ngưỡng cao hơn, ví dụ 0.7: mô hình thận trọng hơn khi dự đoán Malignant, Specificity có thể tăng nhưng Sensitivity có thể giảm.

ROC Curve cho ta cái nhìn tổng quát về mô hình ở nhiều ngưỡng khác nhau, không chỉ riêng ngưỡng 0.5.

11.2 Bản chất của AUC

AUC là diện tích dưới đường ROC.

Có thể hiểu đơn giản:

AUC đo khả năng mô hình xếp một ca ác tính có xác suất cao hơn một ca lành tính.

Cách diễn giải thường dùng:

AUC Ý nghĩa tương đối
0.5 Mô hình gần như đoán ngẫu nhiên
0.6 - 0.7 Khả năng phân biệt yếu
0.7 - 0.8 Khả năng phân biệt chấp nhận được
0.8 - 0.9 Khả năng phân biệt tốt
> 0.9 Khả năng phân biệt rất tốt

11.3 Cách đọc kết quả

Nếu đường ROC càng áp sát góc trên bên trái thì mô hình càng tốt.

Nếu AUC càng gần 1 thì mô hình càng có khả năng phân biệt tốt giữa MalignantBenign.


12 12. Kiểm tra tác động của việc thay đổi ngưỡng phân loại

eval_bic_cutoff_03 <- evaluate_model(model_bic, test_model, cutoff = 0.30)
eval_bic_cutoff_05 <- evaluate_model(model_bic, test_model, cutoff = 0.50)
eval_bic_cutoff_07 <- evaluate_model(model_bic, test_model, cutoff = 0.70)

cutoff_table <- bind_rows(
  tibble(Cutoff = 0.30) %>% bind_cols(eval_bic_cutoff_03$metrics),
  tibble(Cutoff = 0.50) %>% bind_cols(eval_bic_cutoff_05$metrics),
  tibble(Cutoff = 0.70) %>% bind_cols(eval_bic_cutoff_07$metrics)
)

cutoff_table
# A tibble: 3 × 5
  Cutoff Accuracy Sensitivity_Recall Specificity   AUC
   <dbl>    <dbl>              <dbl>       <dbl> <dbl>
1    0.3    0.906              0.952       0.879 0.977
2    0.5    0.929              0.905       0.944 0.977
3    0.7    0.929              0.857       0.972 0.977

12.1 Giải thích

Đây là bước mở rộng rất quan trọng để hiểu bản chất phân loại.

Mô hình logistic trả về xác suất, còn nhãn phân loại phụ thuộc vào ngưỡng. Vì vậy, cùng một mô hình nhưng nếu đổi ngưỡng, Confusion Matrix có thể thay đổi.

12.2 Cách đọc kết quả

Nếu giảm cutoff từ 0.5 xuống 0.3:

  • Mô hình dễ dự đoán Malignant hơn.
  • Sensitivity thường tăng.
  • Specificity có thể giảm.

Nếu tăng cutoff từ 0.5 lên 0.7:

  • Mô hình khó dự đoán Malignant hơn.
  • Specificity thường tăng.
  • Sensitivity có thể giảm.

Trong bài toán y tế, nếu mục tiêu là không bỏ sót ca ác tính, ta có thể cân nhắc dùng ngưỡng thấp hơn 0.5. Tuy nhiên, điều này có thể làm tăng số ca báo động giả.


13 13. Gợi ý viết kết luận cuối bài

Sau khi chạy toàn bộ code, có thể viết kết luận theo mẫu sau. Cần thay các phần trong ngoặc vuông bằng kết quả thực tế sau khi chạy R.

Bộ dữ liệu WDBC được sử dụng để xây dựng mô hình phân loại nhị phân giữa hai nhóm u lành tính và u ác tính. Mô hình hồi quy logistic được xây dựng trên tập huấn luyện 70% và đánh giá trên tập kiểm tra 30%.

Kết quả so sánh AIC và BIC cho thấy mô hình [tên mô hình] có chỉ số [AIC/BIC] thấp nhất, cho thấy mô hình này đạt sự cân bằng tốt giữa độ phù hợp và độ phức tạp. Các chỉ số Pseudo R-square cũng cho thấy mô hình cải thiện rõ rệt so với mô hình rỗng.

Trên tập test, mô hình đạt Accuracy = [giá trị], Sensitivity/Recall = [giá trị], Specificity = [giá trị] và AUC = [giá trị]. Vì Malignant được chọn là lớp dương, Sensitivity/Recall phản ánh khả năng phát hiện đúng các ca u ác tính. Đây là chỉ số đặc biệt quan trọng trong bối cảnh y tế vì bỏ sót ca ác tính có thể gây hậu quả nghiêm trọng.

Nhìn chung, mô hình có khả năng phân biệt tốt giữa hai nhóm BenignMalignant. Nếu ưu tiên diễn giải và mô hình gọn, có thể chọn mô hình theo BIC. Nếu ưu tiên khả năng dự báo và chấp nhận nhiều biến hơn, có thể cân nhắc mô hình theo AIC.


14 14. Tóm tắt bản chất các chỉ số

Nhóm chỉ số Chỉ số Ý nghĩa Cách đánh giá
Confusion Matrix Accuracy Tỷ lệ dự đoán đúng toàn bộ Càng cao càng tốt, nhưng dễ gây hiểu nhầm nếu lệch lớp
Confusion Matrix Sensitivity/Recall Tỷ lệ phát hiện đúng ca Malignant Rất quan trọng nếu sợ bỏ sót ca bệnh
Confusion Matrix Specificity Tỷ lệ nhận diện đúng ca Benign Quan trọng nếu muốn giảm báo động giả
ROC/AUC ROC Curve Đánh giá mô hình ở nhiều ngưỡng phân loại Đường càng gần góc trên trái càng tốt
ROC/AUC AUC Khả năng phân biệt hai nhóm Càng gần 1 càng tốt
Model Selection AIC So sánh mô hình, có phạt số biến Càng thấp càng tốt
Model Selection BIC So sánh mô hình, phạt số biến mạnh hơn AIC Càng thấp càng tốt
Pseudo R-square McFadden, Cox & Snell, Nagelkerke Mức cải thiện so với mô hình rỗng Càng cao càng tốt, nhưng không hiểu giống hệt R-square tuyến tính