Το σύνολο δεδομένων που χρησιμοποιείται είναι το
BreastCancer, το οποίο προέρχεται από το πακέτο
mlbench και περιλαμβάνει δεδομένα από το Πανεπιστήμιο του
Wisconsin.
Μέγεθος: Αποτελείται από 699 παρατηρήσεις (βιοψίες).
Χαρακτηριστικά: Περιλαμβάνει 9 κυτταρολογικά χαρακτηριστικά (όπως το πάχος της μάζας, η ομοιομορφία του μεγέθους των κυττάρων, η προσκόλληση οριακών κυττάρων κ.α.).
Κλίμακα: Κάθε χαρακτηριστικό βαθμολογείται σε μια κλίμακα από 1 έως 10.
Target Variable (Class): Είναι μια δυαδική μεταβλητή που χωρίζει τα δείγματα σε:
Benign (Καλοήθης όγκος)
Malignant (Κακοήθης όγκος)
Ως Data Scientists σε ένα ογκολογικό κέντρο, ο στόχος μας είναι να αναπτύξουμε ένα Decision Support Tool για τους παθολόγους.
Οι κύριες προτεραιότητες του μοντέλου είναι:
Υψηλή Ακρίβεια (Accuracy): Η σωστή κατηγοριοποίηση των όγκων.
Υψηλή Ευαισθησία (Sensitivity): Είναι κρίσιμο να μη χάσουμε καμία κακοήθη περίπτωση (false negative), καθώς αυτό θα σήμαινε καθυστέρηση στη θεραπεία ενός ασθενή.
Ερμηνευσιμότητα: Να κατανοήσουμε ποια κυτταρολογικά χαρακτηριστικά είναι οι σημαντικότεροι δείκτες κακοήθειας.
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
##
## Attaching package: 'randomForest'
##
## The following object is masked from 'package:dplyr':
##
## combine
##
## The following object is masked from 'package:ggplot2':
##
## margin
## Loading required package: lattice
##
## Attaching package: 'caret'
##
## The following object is masked from 'package:purrr':
##
## lift
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
##
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
set.seed(42)
# --- Φόρτωση δεδομένων ---
data("BreastCancer", package = "mlbench")
bc <- BreastCancer
# Καθαρισμός: αφαίρεση ID, χειρισμός missing, μετατροπή σε numeric
bc$Id <- NULL
bc <- na.omit(bc)
bc[, 1:9] <- lapply(bc[, 1:9], function(x) as.numeric(as.character(x)))
str(bc)## 'data.frame': 683 obs. of 10 variables:
## $ Cl.thickness : num 5 5 3 6 4 8 1 2 2 4 ...
## $ Cell.size : num 1 4 1 8 1 10 1 1 1 2 ...
## $ Cell.shape : num 1 4 1 8 1 10 1 2 1 1 ...
## $ Marg.adhesion : num 1 5 1 1 3 8 1 1 1 1 ...
## $ Epith.c.size : num 2 7 2 3 2 7 2 2 2 2 ...
## $ Bare.nuclei : num 1 10 2 4 1 10 10 1 1 1 ...
## $ Bl.cromatin : num 3 3 3 3 3 9 3 3 1 2 ...
## $ Normal.nucleoli: num 1 2 1 7 1 7 1 1 1 1 ...
## $ Mitoses : num 1 1 1 1 1 1 1 1 5 1 ...
## $ Class : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...
## - attr(*, "na.action")= 'omit' Named int [1:16] 24 41 140 146 159 165 236 250 276 293 ...
## ..- attr(*, "names")= chr [1:16] "24" "41" "140" "146" ...
##
## benign malignant
## 444 239
Χρησιμοποιούμε τη συνάρτηση createDataPartition για να
διασφαλίσουμε ότι το ποσοστό των κακοήθων όγκων παραμένει σταθερό και
στα δύο δείγματα.
set.seed(42) # Για αναπαραγωγιμότητα του διαχωρισμού
# Δημιουργία δεικτών για το split
train_idx <- createDataPartition(bc$Class, p = 0.7, list = FALSE)
# Δημιουργία των συνόλων εκπαίδευσης και ελέγχου
train_set <- bc[train_idx, ]
test_set <- bc[-train_idx, ]
# Έλεγχος αναλογίας κλάσεων (επαλήθευση stratified split)
cat("Αναλογία στο Train Set:\n")## Αναλογία στο Train Set:
##
## benign malignant
## 0.6492693 0.3507307
##
## Αναλογία στο Test Set:
##
## benign malignant
## 0.6519608 0.3480392
Εκπαιδεύουμε το μοντέλο Random Forest με 500
δέντρα. Η παράμετρος importance = TRUE επιτρέπει
την καταγραφή της συνεισφοράς κάθε μεταβλητής στην τελική πρόβλεψη.
set.seed(42)
rf_model <- randomForest(
Class ~ .,
data = train_set,
ntree = 500,
importance = TRUE
)
print(rf_model)##
## Call:
## randomForest(formula = Class ~ ., data = train_set, 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
Το μοντέλο πέτυχε OOB error 3.76%.
Από το Confusion Matrix προκύπτει ότι η ακρίβεια είναι υψηλή και στις δύο κλάσεις, με ελαφρώς μεγαλύτερο σφάλμα στην αναγνώριση των κακοήθων όγκων (~4.8%).
Ελέγχουμε την απόδοση του μοντέλου σε άγνωστα δεδομένα (Test Set) για να υπολογίσουμε την τελική ακρίβεια και την ευαισθησία του.
# Προβλέψεις
rf_pred <- predict(rf_model, test_set)
rf_prob <- predict(rf_model, test_set, type = "prob")[, "malignant"]
# Μετρικές
rf_cm <- confusionMatrix(rf_pred, test_set$Class, positive = "malignant")
print(rf_cm)## 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
##
## Setting direction: controls < cases
##
## Random Forest AUC: 0.9985
Το μοντέλο πέτυχε Accuracy 98.04% στο test set.
Ιδιαίτερα σημαντική είναι η Sensitivity (97.18%), καθώς δείχνει ότι το μοντέλο εντοπίζει σχεδόν όλες τις κακοήθεις περιπτώσεις, έχοντας μόλις 2 “αστοχίες” (False Negatives).
Το AUC (0.9985) επιβεβαιώνει την κορυφαία διαχωριστική ικανότητα του αλγορίθμου.
Σε αυτό το βήμα, εξετάζουμε ποια κυτταρολογικά χαρακτηριστικά έχουν τη μεγαλύτερη βαρύτητα για την πρόβλεψη της κακοήθειας.
## benign malignant MeanDecreaseAccuracy MeanDecreaseGini
## Cl.thickness 12.702656 19.830096 19.363837 11.845927
## Cell.size 11.605774 16.330563 19.826188 60.344346
## Cell.shape 10.417048 20.502944 21.800283 53.561147
## Marg.adhesion 6.228395 12.425305 13.009741 5.977215
## Epith.c.size 10.510480 3.579421 11.128863 11.709247
## Bare.nuclei 20.271034 19.036320 26.035566 34.154820
## Bl.cromatin 6.086543 14.621620 14.691182 14.587449
## Normal.nucleoli 11.849778 11.676750 15.229482 23.815034
## Mitoses 4.984157 0.839361 4.608274 1.550928
Βάσει του δείκτη MeanDecreaseGini, τα τρία σημαντικότερα χαρακτηριστικά για την πρόβλεψη της κακοήθειας είναι τα:
Αυτό υποδεικνύει ότι το μέγεθος, το σχήμα των κυττάρων και η κατάσταση των πυρήνων αποτελούν τους κρισιμότερους παράγοντες για τη διάγνωση.
98.04%
Όχι απαραίτητα. Σε ιατρικά θέματα, η Ευαισθησία (Sensitivity) είναι συχνά πιο σημαντική από το Accuracy, καθώς ένα False Negative (παράβλεψη κακοήθειας) μπορεί να είναι μοιραίο. Στη δική μας περίπτωση, το Sensitivity είναι 97.18%, που είναι εξαιρετικό αλλά πάντα επιδιώκουμε το 100%.
Προετοιμάζουμε τα δεδομένα μετατρέποντας τα features σε αριθμητικό matrix και την κλάση στόχο σε τιμές 0 (benign) και 1 (malignant).
# Μετατροπή των features σε matrix
train_x <- as.matrix(train_set[, 1:9])
test_x <- as.matrix(test_set[, 1:9])
# Μετατροπή του target σε numeric (0 για benign, 1 για malignant)
train_y <- ifelse(train_set$Class == "malignant", 1, 0)
test_y <- ifelse(test_set$Class == "malignant", 1, 0)
# Δημιουργία των DMatrix αντικειμένων (optimized για XGBoost)
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)Εκπαιδεύουμε το μοντέλο XGBoost. Χρησιμοποιούμε
eta = 0.1 (ρυθμός μάθησης) και
early_stopping_rounds = 20, ώστε η εκπαίδευση να σταματήσει
αυτόματα αν το μοντέλο πάψει να βελτιώνεται στο test set, αποφεύγοντας
το overfitting.
set.seed(42)
xgb_model <- xgb.train(
params = list(
objective = "binary:logistic",
eta = 0.1,
max_depth = 4,
eval_metric = "auc"
),
data = dtrain,
nrounds = 500,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 20,
print_every_n = 10,
verbose = 1
)## Warning in throw_err_or_depr_msg("Parameter '", match_old, "' has been renamed
## to '", : Parameter 'watchlist' has been renamed to 'evals'. This warning will
## become an error in a future version.
## Multiple eval metrics are present. Will use test_auc for early stopping.
## Will train until test_auc hasn't improved in 20 rounds.
##
## [1] train-auc:0.989148 test-auc:0.987557
## [11] train-auc:0.995856 test-auc:0.994599
## [21] train-auc:0.998096 test-auc:0.996505
## [31] train-auc:0.999177 test-auc:0.996294
## [41] train-auc:0.999541 test-auc:0.996399
## Stopping. Best iteration:
## [45] train-auc:0.999675 test-auc:0.996611
##
## [45] train-auc:0.999675 test-auc:0.996611
ο XGBoost σταμάτησε στην 45η επανάληψη (Best iteration), καθώς η διαδικασία του early stopping εντόπισε ότι η απόδοση στο test set σταμάτησε να βελτιώνεται.
Το test-auc (0.9966) είναι εξαιρετικά υψηλό, υποδηλώνοντας ότι το μοντέλο έχει πολύ καλή γενίκευση.
Συγκρίνουμε τις επιδόσεις των δύο αλγορίθμων στο test set χρησιμοποιώντας τις βασικές μετρικές αξιολόγησης.
# Προβλέψεις XGBoost (πιθανότητες)
xgb_prob <- predict(xgb_model, dtest)
# Μετατροπή σε κλάσεις (κατώφλι 0.5)
xgb_pred <- factor(ifelse(xgb_prob > 0.5, "malignant", "benign"),
levels = c("benign", "malignant"))
# Confusion Matrix για XGBoost
xgb_cm <- confusionMatrix(xgb_pred, test_set$Class, positive = "malignant")
# Συγκεντρωτικός Πίνακας
comparison <- data.frame(
Metric = c("Accuracy", "Sensitivity", "Specificity", "AUC"),
RandomForest = c(rf_cm$overall["Accuracy"],
rf_cm$byClass["Sensitivity"],
rf_cm$byClass["Specificity"],
auc(rf_roc)),
XGBoost = c(xgb_cm$overall["Accuracy"],
xgb_cm$byClass["Sensitivity"],
xgb_cm$byClass["Specificity"],
auc(roc(test_set$Class, xgb_prob, quiet = TRUE)))
)
print(comparison)## Metric RandomForest XGBoost
## Accuracy Accuracy 0.9803922 0.9754902
## Sensitivity Sensitivity 0.9718310 0.9577465
## Specificity Specificity 0.9849624 0.9849624
## AUC 0.9985174 0.9966112
Συγκρίνοντας τα δύο μοντέλα, το Random Forest παρουσιάζει οριακά καλύτερη απόδοση με Accuracy 98.04% έναντι 97.55% του XGBoost.
Πιο σημαντικό είναι ότι το Random Forest πέτυχε υψηλότερη Sensitivity (97.18% vs 95.77%), γεγονός που το καθιστά προτιμότερο στο ιατρικό αυτό πλαίσιο, καθώς εντοπίζει περισσότερες περιπτώσεις κακοήθειας.
Σε αυτό το βήμα εξετάζουμε την επίδραση του ρυθμού μάθησης (learning rate) στην ταχύτητα σύγκλισης του μοντέλου. Δοκιμάζουμε μια πολύ χαμηλή τιμή (0.01) και μια υψηλότερη (0.3).
# 1. Εκπαίδευση με eta = 0.01 (Πολύ αργή μάθηση)
set.seed(42)
xgb_low <- xgb.train(
params = list(objective = "binary:logistic", eta = 0.01, eval_metric = "auc"),
data = dtrain,
nrounds = 500,
evals = list(test = dtest),
early_stopping_rounds = 20,
verbose = 0
)
# 2. Εκπαίδευση με eta = 0.3 (Γρήγορη μάθηση)
set.seed(42)
xgb_high <- xgb.train(
params = list(objective = "binary:logistic", eta = 0.3, eval_metric = "auc"),
data = dtrain,
nrounds = 500,
evals = list(test = dtest),
early_stopping_rounds = 20,
verbose = 0
)
# Εξαγωγή του αριθμού των γύρων που εκτελέστηκαν
rounds_low <- nrow(xgb_low$evaluation_log)
rounds_high <- nrow(xgb_high$evaluation_log)
# Εκτύπωση αποτελεσμάτων
cat("Αριθμός γύρων για eta=0.01:", rounds_low, "\n")## Αριθμός γύρων για eta=0.01:
## Αριθμός γύρων για eta=0.3 :
Για eta = 0.01: Ο αλγόριθμος χρειάστηκε τον μέγιστο αριθμό επαναλήψεων (500 γύρους), καθώς ο χαμηλός ρυθμός μάθησης επιβάλλει πολύ μικρές και συντηρητικές διορθώσεις σε κάθε δέντρο. Αυτό προσφέρει σταθερότητα αλλά απαιτεί μεγάλο χρόνο εκπαίδευσης.
Για eta = 0.3: Ο αλγόριθμος συνέκλινε πολύ ταχύτερα (σε λιγότερους από 50 γύρους), καθώς οι μεγάλες διορθώσεις οδηγούν σε γρήγορη ελαχιστοποίηση του σφάλματος. Ωστόσο, ο κίνδυνος για overfitting είναι αυξημένος, γι’ αυτό και το early stopping σταμάτησε την εκπαίδευση μόλις η απόδοση στο test set σταμάτησε να βελτιώνεται.
Στο τελευταίο αυτό βήμα, προχωράμε σε μια οπτική σύγκριση της απόδοσης των δύο μοντέλων. Σχεδιάζουμε τις καμπύλες ROC και υπολογίζουμε το AUC (Area Under the Curve) για το καθένα.
Η σύγκριση αυτή μας επιτρέπει να δούμε ποιος αλγόριθμος έχει την καλύτερη διαχωριστική ικανότητα μεταξύ καλοήθων και κακοήθων δειγμάτων σε όλο το εύρος των πιθανών κατωφλίων (thresholds).
# Δημιουργία ROC αντικειμένου για το XGBoost
xgb_roc <- roc(test_set$Class, xgb_prob, quiet = TRUE)
# Σχεδίαση της καμπύλης του Random Forest (Μπλε)
plot(rf_roc, col = "royalblue", lwd = 3, main = "ROC Comparison: RF vs XGBoost")
# Προσθήκη της καμπύλης του XGBoost (Πορτοκαλί)
plot(xgb_roc, col = "darkorange", lwd = 3, add = TRUE)
# Προσθήκη Legend
legend("bottomright",
legend = c(paste0("Random Forest (AUC = ", round(auc(rf_roc), 3), ")"),
paste0("XGBoost (AUC = ", round(auc(xgb_roc), 3), ")")),
col = c("royalblue", "darkorange"), lwd = 3)Η οπτική σύγκριση των καμπυλών ROC επιβεβαιώνει την κορυφαία απόδοση και των δύο αλγορίθμων, με το Random Forest (AUC = 0.999) να υπερτερεί οριακά του XGBoost (AUC = 0.997).
Η εγγύτητα των καμπυλών στην πάνω αριστερή γωνία υποδηλώνει ότι και τα δύο μοντέλα επιτυγχάνουν πολύ υψηλή ευαισθησία (Sensitivity) διατηρώντας ταυτόχρονα εξαιρετικά χαμηλά ποσοστά ψευδώς θετικών αποτελεσμάτων.
Νικητής αναδείχθηκε το Random Forest, αν και η διαφορά ήταν οριακή.
Συγκεκριμένα, το Random Forest πέτυχε Accuracy 98.04% έναντι 97.55% του XGBoost (διαφορά 0.49%). Στο AUC, η διαφορά ήταν ακόμα μικρότερη (0.999 έναντι 0.997), επιβεβαιώνοντας ότι και τα δύο μοντέλα είναι εξαιρετικά αξιόπιστα για το συγκεκριμένο πρόβλημα.
Προκαλεί εντύπωση το γεγονός ότι το Random Forest, ένας πιο “απλός” αλγόριθμος σε σχέση με το XGBoost, κατάφερε να υπερτερήσει.
Συνήθως, το XGBoost θεωρείται πιο ισχυρό σε μεγάλα και περίπλοκα σύνολα δεδομένων. Στην προκειμένη περίπτωση, φαίνεται πως η φύση των κυτταρολογικών χαρακτηριστικών ευνοεί τη δομή του Random Forest, ενώ το μέγεθος του δείγματος ήταν επαρκές για να “μάθει” το μοντέλο χωρίς να χρειαστεί την επιπλέον πολυπλοκότητα του Gradient Boosting.
Η δομή της εκπαίδευσης άλλαξε ριζικά ανάλογα με το learning rate:
Με το πολύ χαμηλό eta (0.01), το μοντέλο ήταν εξαιρετικά “συντηρητικό” και χρειάστηκε το μέγιστο πλήθος γύρων (500), καθώς η βελτίωση σε κάθε βήμα ήταν απειροελάχιστη.
Με το ενδιάμεσο eta (0.1), είχαμε μια ισορροπημένη σύγκλιση (σταμάτησε στις 45 επαναλήψεις).
Με το υψηλό eta (0.3), η εκπαίδευση ολοκληρώθηκε πολύ γρήγορα, καθώς ο αλγόριθμος έκανε μεγάλα βήματα βελτιστοποίησης. Ωστόσο, υπάρχει ο κίνδυνος το μοντέλο να “προσπεράσει” την ιδανική λύση (overshooting) αν το βήμα είναι υπερβολικά μεγάλο.