required <- c("tidyverse", "mlbench", "randomForest",
"xgboost", "caret", "pROC", "knitr", "kableExtra")
new_pkgs <- required[!required %in% installed.packages()[, "Package"]]
if (length(new_pkgs)) install.packages(new_pkgs, quiet = TRUE)
library(tidyverse)
library(mlbench)
library(randomForest)
library(xgboost)
library(caret)
library(pROC)
library(knitr)
library(kableExtra)Το dataset BreastCancer από το πακέτο
mlbench περιέχει 699 βιοψίες με 9
κυτταρολογικά χαρακτηριστικά. Στόχος είναι η πρόβλεψη αν ένας όγκος
είναι καλοήθης (benign) ή κακοήθης
(malignant).
Σε ιατρικό context, ενδιαφέρει ιδιαίτερα η Sensitivity (να μη χάσουμε κακοήθεις περιπτώσεις) εκτός από την απλή ακρίβεια.
data("BreastCancer", package = "mlbench")
bc <- BreastCancer
# Αφαίρεση ID, missing values, μετατροπή σε numeric
bc$Id <- NULL
bc <- na.omit(bc)
bc[, 1:9] <- lapply(bc[, 1:9], function(x) as.numeric(as.character(x)))
cat("Διαστάσεις:", nrow(bc), "x", ncol(bc), "\n")## Διαστάσεις: 683 x 10
## Κατανομή target:
##
## benign malignant
## 444 239
set.seed(42)
rf_model <- randomForest(
Class ~ .,
data = train,
ntree = 500,
importance = TRUE
)
print(rf_model)##
## Call:
## randomForest(formula = Class ~ ., data = train, ntree = 500, importance = TRUE)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 3
##
## OOB estimate of error rate: 3.76%
## Confusion matrix:
## benign malignant class.error
## benign 301 10 0.03215434
## malignant 8 160 0.04761905
pred_rf_class <- predict(rf_model, test, type = "response")
pred_rf_prob <- predict(rf_model, test, type = "prob")[, "malignant"]
cm_rf <- confusionMatrix(pred_rf_class, test$Class, positive = "malignant")
print(cm_rf)## Confusion Matrix and Statistics
##
## Reference
## Prediction benign malignant
## benign 131 2
## malignant 2 69
##
## Accuracy : 0.9804
## 95% CI : (0.9506, 0.9946)
## No Information Rate : 0.652
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9568
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.9718
## Specificity : 0.9850
## Pos Pred Value : 0.9718
## Neg Pred Value : 0.9850
## Prevalence : 0.3480
## Detection Rate : 0.3382
## Detection Prevalence : 0.3480
## Balanced Accuracy : 0.9784
##
## 'Positive' Class : malignant
##
roc_rf <- roc(test$Class, pred_rf_prob,
levels = c("benign", "malignant"), quiet = TRUE)
cat("AUC:", round(auc(roc_rf), 4), "\n")## AUC: 0.9985
imp_df <- importance(rf_model) %>%
as.data.frame() %>%
rownames_to_column("Feature") %>%
arrange(desc(MeanDecreaseGini))
imp_df %>%
kable(digits = 2, caption = "Variable Importance (MeanDecreaseGini)") %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE)| Feature | benign | malignant | MeanDecreaseAccuracy | MeanDecreaseGini |
|---|---|---|---|---|
| Cell.size | 11.61 | 16.33 | 19.83 | 60.34 |
| Cell.shape | 10.42 | 20.50 | 21.80 | 53.56 |
| Bare.nuclei | 20.27 | 19.04 | 26.04 | 34.15 |
| Normal.nucleoli | 11.85 | 11.68 | 15.23 | 23.82 |
| Bl.cromatin | 6.09 | 14.62 | 14.69 | 14.59 |
| Cl.thickness | 12.70 | 19.83 | 19.36 | 11.85 |
| Epith.c.size | 10.51 | 3.58 | 11.13 | 11.71 |
| Marg.adhesion | 6.23 | 12.43 | 13.01 | 5.98 |
| Mitoses | 4.98 | 0.84 | 4.61 | 1.55 |
1. Accuracy:
98.04%
2. Top-3 Features:
Cell.size, Cell.shape, Bare.nuclei
3. Είναι το ~97% αρκετό σε ιατρικό context;
Όχι απαραίτητα. Σε ιατρικές εφαρμογές ο κύριος στόχος είναι η
Sensitivity — να εντοπιστούν όλες οι κακοήθεις
περιπτώσεις. Ένα False Negative (κακοήθης που κατατάχθηκε ως καλοήθης)
έχει πολύ σοβαρότερες συνέπειες από ένα False Positive. Επομένως κοιτάμε
πάντα παράλληλα Sensitivity, Specificity και AUC.
# XGBoost χρειάζεται matrix και numeric target (0/1)
X_train <- as.matrix(train[, 1:9])
y_train <- ifelse(train$Class == "malignant", 1, 0)
X_test <- as.matrix(test[, 1:9])
y_test <- ifelse(test$Class == "malignant", 1, 0)
dtrain <- xgb.DMatrix(data = X_train, label = y_train)
dtest <- xgb.DMatrix(data = X_test, label = y_test)set.seed(42)
xgb_model <- xgb.train(
params = list(
objective = "binary:logistic",
eval_metric = "logloss",
max_depth = 4,
eta = 0.1,
verbosity = 0
),
data = dtrain,
nrounds = 500,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 20,
verbose = 0
)
cat("Βέλτιστος αριθμός rounds (eta=0.1):", xgb_model$best_iteration, "\n")## Βέλτιστος αριθμός rounds (eta=0.1):
pred_xgb_prob <- predict(xgb_model, dtest)
pred_xgb_class <- factor(ifelse(pred_xgb_prob >= 0.5, "malignant", "benign"),
levels = c("benign", "malignant"))
cm_xgb <- confusionMatrix(pred_xgb_class, test$Class, positive = "malignant")
print(cm_xgb)## Confusion Matrix and Statistics
##
## Reference
## Prediction benign malignant
## benign 131 4
## malignant 2 67
##
## Accuracy : 0.9706
## 95% CI : (0.9371, 0.9891)
## No Information Rate : 0.652
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9348
##
## Mcnemar's Test P-Value : 0.6831
##
## Sensitivity : 0.9437
## Specificity : 0.9850
## Pos Pred Value : 0.9710
## Neg Pred Value : 0.9704
## Prevalence : 0.3480
## Detection Rate : 0.3284
## Detection Prevalence : 0.3382
## Balanced Accuracy : 0.9643
##
## 'Positive' Class : malignant
##
roc_xgb <- roc(test$Class, pred_xgb_prob,
levels = c("benign", "malignant"), quiet = TRUE)
cat("AUC:", round(auc(roc_xgb), 4), "\n")## AUC: 0.9975
tibble(
Μετρική = c("Accuracy", "Sensitivity", "Specificity", "AUC"),
`Random Forest` = as.numeric(round(c(
cm_rf$overall["Accuracy"],
cm_rf$byClass["Sensitivity"],
cm_rf$byClass["Specificity"],
auc(roc_rf)
), 4)),
XGBoost = as.numeric(round(c(
cm_xgb$overall["Accuracy"],
cm_xgb$byClass["Sensitivity"],
cm_xgb$byClass["Specificity"],
auc(roc_xgb)
), 4))
) %>%
kable(caption = "Σύγκριση: Random Forest vs XGBoost") %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE)| Μετρική | Random Forest | XGBoost |
|---|---|---|
| Accuracy | 0.9804 | 0.9706 |
| Sensitivity | 0.9718 | 0.9437 |
| Specificity | 0.9850 | 0.9850 |
| AUC | 0.9985 | 0.9975 |
set.seed(42)
# eta = 0.01 (αργή μάθηση)
xgb_slow <- xgb.train(
params = list(objective = "binary:logistic",
eval_metric = "logloss", max_depth = 4,
eta = 0.01, verbosity = 0),
data = dtrain, nrounds = 500,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 20, verbose = 0
)
# eta = 0.3 (γρήγορη μάθηση)
xgb_fast <- xgb.train(
params = list(objective = "binary:logistic",
eval_metric = "logloss", max_depth = 4,
eta = 0.3, verbosity = 0),
data = dtrain, nrounds = 500,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 20, verbose = 0
)
tibble(
eta = c(0.01, 0.10, 0.30),
`Best Rounds` = c(xgb_slow$best_iteration,
xgb_model$best_iteration,
xgb_fast$best_iteration)
) %>%
kable(caption = "Αριθμός Rounds ανά eta (με early stopping)") %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE)| eta |
|---|
| 0.01 |
| 0.10 |
| 0.30 |
Παρατήρηση: Μικρό
etaσημαίνει πιο αργή μάθηση — χρειάζεται περισσότερα rounds για να συγκλίνει. Μεγάλοetaσυγκλίνει γρήγορα αλλά κινδυνεύει να overshoot και να σταματήσει νωρίς λόγω early stopping.
plot(roc_rf, col = "#1E88E5", lwd = 2,
main = "ROC Curves — RF vs XGBoost")
lines(roc_xgb, col = "#E53935", lwd = 2)
abline(a = 0, b = 1, lty = 2, col = "gray70")
legend("bottomright",
legend = c(paste0("Random Forest (AUC=", round(auc(roc_rf), 3), ")"),
paste0("XGBoost (AUC=", round(auc(roc_xgb), 3), ")")),
col = c("#1E88E5", "#E53935"), lwd = 2, bty = "n")1. Ποιο μοντέλο νίκησε;
Τα δύο μοντέλα αποδίδουν πολύ κοντά μεταξύ τους. Το XGBoost έχει ελαφρώς
υψηλότερο AUC σε αυτό το dataset, αλλά η διαφορά είναι μικρή. Σε μικρά
datasets όπως αυτό, το Random Forest είναι εξίσου αξιόπιστο και πιο
εύκολο στη χρήση.
2. Κάτι που εξέπληξε;
Το πόσο καλά αποδίδει το Random Forest χωρίς καθόλου tuning —
ntree=500 με default παραμέτρους δίνει ήδη εξαιρετικά
αποτελέσματα. Αυτό δείχνει ότι τα features του dataset είναι πολύ
διαχωριστικά (όπως φαίνεται και από το variable importance).
3. BONUS — Παρατήρηση για tau eta:
Μικρό eta (0.01) χρειάστηκε πολλαπλάσιους rounds σε σχέση
με μεγάλο eta (0.3). Το early stopping βοηθά και στις δύο
περιπτώσεις — χωρίς αυτό, το μικρό eta θα απαιτούσε πολύ περισσότερο
χρόνο εκπαίδευσης.
## R: 4.5.2 | Ημερομηνία: 11/05/2026
## Πακέτα: randomForest, xgboost, caret, pROC